Most independent hotels treat structured data like a plugin checkbox. Flip it on, forget it, hope Google does something nice. Then they wonder why the OTA listing for their own property outranks them, and why ChatGPT describes their “rooftop bar” that closed in 2019.
Schema markup will not fix bad content or thin authority. What it does is remove ambiguity. It tells a crawler — and increasingly, an AI assistant — exactly what your page is, in a format built for machines instead of marketing copy. That precision is the difference between being quoted correctly and being paraphrased into oblivion. If you have ever wondered why your hotel is invisible to ChatGPT, missing or sloppy structured data is often part of the answer.
This is a working reference. Every schema type below includes what it does, when to use it, and a copy-ready JSON-LD block you can adapt. Swap the placeholder values in [square brackets] for your real data, validate, ship.
The ground rules before you touch a single line
A few things that will save you from the most common self-inflicted wounds:
- One primary entity per page. Your homepage is the
Hotel. A room page is aHotelRoom. Do not stack five competing “main” entities on one URL and expect a crawler to guess. - Reference, do not repeat. Give your hotel a stable
@id(a URL fragment works) and point other schemas at it instead of pasting the full hotel object onto every page. - Only mark up what a human can see. If the price, review, or amenity is not visible on that page, do not put it in the JSON-LD. Invisible markup is the fast lane to a manual action.
- JSON-LD, not Microdata. Google explicitly prefers JSON-LD in a script tag. It is cleaner, easier to maintain, and does not tangle your HTML.
- Validate every time. Use Google’s Rich Results Test and the Schema.org validator. “It looked right” is not validation.
Inside the fenced code blocks below, curly braces are correct and required — that is JSON. In your visible page copy and headings, never write them. This article follows the same rule.

Download this study as a one-page PDF
1. Hotel (and its parent, LodgingBusiness)
This is the anchor. Hotel is a subtype of LodgingBusiness, which is itself a subtype of LocalBusiness. Use the most specific type that fits: Hotel for a hotel, BedAndBreakfast, Motel, Resort, or Hostel where accurate. If you run extended-stay units, LodgingBusiness plus clear room typing often reads better — we go deeper on that in the aparthotel and extended-stay marketing guide.
Put this on your homepage. It is the entity everything else references.
{
"@context": "https://schema.org",
"@type": "Hotel",
"@id": "https://www.example-hotel.com/#hotel",
"name": "[The Harborview Hotel]",
"url": "https://www.example-hotel.com/",
"description": "[A 42-room independent boutique hotel on the waterfront in [your city], with a seasonal restaurant and free parking.]",
"image": [
"https://www.example-hotel.com/img/exterior.jpg",
"https://www.example-hotel.com/img/lobby.jpg"
],
"telephone": "+1-207-555-0142",
"email": "[email protected]",
"priceRange": "$$$",
"currenciesAccepted": "USD",
"paymentAccepted": "Cash, Credit Card",
"checkinTime": "15:00",
"checkoutTime": "11:00",
"petsAllowed": true,
"numberOfRooms": 42,
"starRating": {
"@type": "Rating",
"ratingValue": "4"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "[12 Harbor Way]",
"addressLocality": "[Portland]",
"addressRegion": "[ME]",
"postalCode": "[04101]",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "43.6591",
"longitude": "-70.2568"
},
"amenityFeature": [
{
"@type": "LocationFeatureSpecification",
"name": "Free WiFi",
"value": true
},
{
"@type": "LocationFeatureSpecification",
"name": "Free parking",
"value": true
},
{
"@type": "LocationFeatureSpecification",
"name": "Restaurant",
"value": true
}
],
"sameAs": [
"https://www.facebook.com/examplehotel",
"https://www.instagram.com/examplehotel"
]
}
The starRating here is a self-declared or official classification, not a guest review score — do not confuse the two. The amenityFeature array is doing quiet, heavy lifting: it is exactly the structured signal an AI assistant reaches for when a traveler asks “which hotels in [your city] allow pets and have free parking.” That answerability is the whole game we cover across AI visibility, AEO and GEO.
2. HotelRoom (and Offer for pricing)
Each distinct room type deserves its own page and its own HotelRoom markup, tied back to the parent hotel through containedInPlace. Wrap price and availability in an Offer. Only include an Offer price if a comparable price is genuinely visible on the page.
{
"@context": "https://schema.org",
"@type": "HotelRoom",
"name": "[Harbor King Room]",
"description": "[A 320 sq ft room with a king bed, harbor-facing window, and walk-in shower.]",
"bed": {
"@type": "BedDetails",
"typeOfBed": "King",
"numberOfBeds": 1
},
"occupancy": {
"@type": "QuantitativeValue",
"maxValue": 2,
"unitText": "person"
},
"amenityFeature": [
{
"@type": "LocationFeatureSpecification",
"name": "Air conditioning",
"value": true
},
{
"@type": "LocationFeatureSpecification",
"name": "Coffee maker",
"value": true
}
],
"containedInPlace": {
"@type": "Hotel",
"@id": "https://www.example-hotel.com/#hotel"
},
"offers": {
"@type": "Offer",
"price": "249.00",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"url": "https://www.example-hotel.com/rooms/harbor-king",
"priceSpecification": {
"@type": "PriceSpecification",
"price": "249.00",
"priceCurrency": "USD",
"validThrough": "2026-12-31"
}
}
}
Notice the containedInPlace points to the same @id you defined on the homepage. That is the reference-do-not-repeat rule in action. When your own room pages carry clean price and availability markup, you make it far easier for search and AI to surface your direct rate instead of defaulting to the OTA — which is precisely the leverage behind the book-direct math on OTA commission.
3. FAQPage
FAQ schema marks up genuine questions and answers that appear on the page. It is one of the most useful structured data types for AEO because it hands an assistant pre-packaged question-answer pairs. The rule is strict: the questions and answers must be visible on the page, must be real FAQs, and must not be promotional or used to game rich results.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What time is check-in and check-out?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Check-in is from 3:00 PM and check-out is by 11:00 AM. Early check-in and late check-out are subject to availability."
}
},
{
"@type": "Question",
"name": "Is parking free at the hotel?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. On-site self-parking is complimentary for all overnight guests, with no reservation required."
}
},
{
"@type": "Question",
"name": "Do you charge a resort fee?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Our published rate is the rate you pay, plus applicable state and local taxes. We do not add a resort or destination fee."
}
}
]
}
That resort-fee answer is not a throwaway. Fee transparency is a live legal and trust issue, and being explicit about it is both good practice and good content — see our breakdown of the junk-fee law and resort fees. If you want to preempt the tax question travelers actually ask, the hotel occupancy tax by state explainer is the honest answer to link.
4. Review and AggregateRating
This is where hotels get themselves in trouble. Review markup must reflect real, verifiable reviews that a guest can see on the page. Never fabricate ratings, never aggregate scores you cannot show, and never mark up reviews sourced only from a third-party platform as if they were on your own page. Done honestly, it can qualify you for a star rating in results.
{
"@context": "https://schema.org",
"@type": "Hotel",
"@id": "https://www.example-hotel.com/#hotel",
"name": "[The Harborview Hotel]",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "318",
"bestRating": "5",
"worstRating": "1"
},
"review": [
{
"@type": "Review",
"author": {
"@type": "Person",
"name": "[Dana R.]"
},
"datePublished": "2026-05-14",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"reviewBody": "[Spotless room, harbor view was worth every dollar, and the front desk arranged a late checkout without a fuss.]"
}
]
}
Attach aggregateRating to your existing Hotel entity by reusing the same @id rather than declaring a second, conflicting hotel object. If your reviews live mostly on external platforms, the honest move is to feature genuine on-page testimonials and let the third-party profiles do their own work — a workflow we handle inside content and reputation.
5. LocalBusiness fields (the local layer)
Because Hotel inherits from LocalBusiness, you do not need a separate LocalBusiness block competing with your hotel entity. Instead, make sure the local-critical properties live on the hotel: consistent NAP (name, address, phone), openingHoursSpecification, and geo. Consistency between this markup and your Google Business Profile is what earns trust.
{
"@context": "https://schema.org",
"@type": "Hotel",
"@id": "https://www.example-hotel.com/#hotel",
"name": "[The Harborview Hotel]",
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": [
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"
],
"opens": "00:00",
"closes": "23:59"
}
],
"department": {
"@type": "Restaurant",
"name": "[The Harborview Kitchen]",
"servesCuisine": "New England seafood",
"openingHoursSpecification": {
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Wednesday", "Thursday", "Friday", "Saturday"],
"opens": "17:00",
"closes": "22:00"
}
}
}
A front desk is typically staffed around the clock, hence the full-day hours, but your restaurant or bar is a department with its own hours — model it that way so an assistant does not tell a guest the kitchen is open at 2 AM. The NAP in your markup must match your profile exactly; mismatches are a quiet ranking tax. The full workflow lives in the Google Business Profile playbook and our local SEO and GBP service.
6. BreadcrumbList
Breadcrumbs tell search engines your site hierarchy and can produce the breadcrumb trail shown in results instead of a raw URL. Cheap to add, genuinely useful on room and destination pages.
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://www.example-hotel.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Rooms",
"item": "https://www.example-hotel.com/rooms"
},
{
"@type": "ListItem",
"position": 3,
"name": "Harbor King Room",
"item": "https://www.example-hotel.com/rooms/harbor-king"
}
]
}
The name values should match the anchor text a visitor actually sees, and the final crumb should be the current page. This same hierarchy logic is what makes destination and area pages rank — the deep version is in the OTA destination landers mega-guide.
Putting it together without breaking anything
You do not deploy all seven of these on every page. Map them to page types:
| Page type | Primary schema | Supporting schema |
|---|---|---|
| Homepage | Hotel / LodgingBusiness | AggregateRating, LocalBusiness fields |
| Room page | HotelRoom + Offer | BreadcrumbList, containedInPlace ref |
| FAQ / policies page | FAQPage | Hotel ref |
| Reviews page | Review + AggregateRating | Hotel ref |
| Area / destination page | WebPage | BreadcrumbList, Hotel ref |
Three habits keep this healthy over time. First, treat the @id as your single source of truth and reference it everywhere rather than duplicating the hotel object. Second, keep the markup in sync with the visible page — when the restaurant hours change or a room type is retired, the JSON-LD changes the same day. Stale schema is worse than no schema because it teaches AI to describe a hotel that no longer exists, the exact failure mode we describe in the sleeping-giant AI data piece. Third, validate on every deploy.
Clean structured data will not, on its own, lift you past a competitor with stronger content and links. But it is the difference between a crawler understanding your property in one pass and guessing at it — and between an AI assistant quoting your real check-in time or inventing one. In a search landscape where OTAs quietly outrank hotels for their own names, removing every scrap of ambiguity about who you are and what you offer is not optional. It is table stakes. This is the foundation layer of hotel SEO, and it feeds directly into book-direct conversion.
If you would rather not hand-audit seven schema types across every template, that is exactly the kind of unglamorous, high-leverage plumbing we do. Book a call and we will pressure-test your structured data against how real crawlers and assistants read it.