gaash.ai

Technical & Structure · 9 min read · September 25, 2026 · , gaash.ai

Making a hotel website machine-readable, field by field

A hotel website is machine-readable when every fact a traveler asks about is in the HTML as text and the JSON-LD repeats it: one node for the property, as Hotel, BedAndBreakfast, Motel or Resort, with check-in, check-out, pets and amenities, and one HotelRoom or Suite node per room type, with its beds and occupancy. The markup describes the pages. It never replaces them.

Start with the type that describes the property

On schema.org, the types for a lodging business sit under LodgingBusiness, which it defines as a lodging business such as a motel, hotel or inn, and which sits under LocalBusiness. Its more specific types are BedAndBreakfast, Campground, Hostel, Hotel, Motel, Resort and VacationRental. Use the most specific one that is true of the property. An inn that is none of those uses LodgingBusiness itself, which is a complete type and not a fallback to apologize for.

Because every one of them is a LocalBusiness, the fields in Google's local business documentation apply: name and address required, telephone, geo, url and price range recommended. That page also recommends opening hours. For a hotel, the times travelers ask about are the check-in window and the check-out time, which have properties of their own, and the restaurant's and the spa's hours belong on those outlets' own nodes, as one property, several businesses sets out.

In our audit of 9,483 small business websites (40 trades, 90 metros, late August to early September 2026), LocalBusiness schema appeared on roughly a quarter of sites, and a further 13.7% had business schema missing the fields that would make it usable. Those figures cover every trade together; the audit publishes no hotel cut.

The property's node, field by field

Each row below is one property on the hotel's node: what schema.org says it takes, as its own pages define it, and the sentence on your site that the value has to match. The last column is the one that matters. A value with no visible sentence behind it is a claim only the markup makes.

The hotel node's properties, what schema.org says each takes, and the visible text each value must match.
PropertyWhat schema.org says it takesThe text on your site it must match
name, address, telephoneText; a PostalAddress; textThe name on your sign, the address on the contact page, the number labeled as the front desk
checkinTimeDateTime or Time: the earliest someone may check in"Check-in from [time]" on the policies page
checkoutTimeDateTime or Time: the latest someone may check out"Check-out by [time]"
petsAllowedBoolean or Text; more detail can go in a text valueThe pet policy's own sentence: which pets, which rooms, the fee or none
amenityFeatureLocationFeatureSpecification: a name and a value, and optionally hoursAvailableEach amenity as the amenities text states it, including whether it is included or charged
numberOfRoomsNumber or QuantitativeValue, counting rooms but not bathrooms or closetsThe room count, if your pages state one
availableLanguageLanguage or Text, as IETF BCP 47 codes"The front desk speaks [languages]"
smokingAllowedBoolean, on any placeThe smoking policy
starRatingRating: an official rating, from a national association or standards body, with the rating organization as authorThe rating and the body that awarded it, named on the page; leave it out if no official body rated you

Two details in schema.org's definitions change what you write. amenityFeature does not say whether a feature is included in the rate or costs extra, so the charge for parking or breakfast has to be written in the page's text, and in the feature's name if you want the markup to carry it. And hoursAvailable, an OpeningHoursSpecification on the feature, is where breakfast hours go, so "breakfast served [time] to [time]" on the page can be stated once more in the markup rather than lost.

Leave aggregateRating and review off the hotel's node. Google's review snippet rules say not to aggregate reviews or ratings from other websites, and a LocalBusiness that controls the reviews about itself is not eligible for review stars, which Google calls self-serving. Its local business page recommends aggregateRating only for sites that capture reviews about other businesses. Guests' words belong on the room pages as quoted text, which quoting guest reviews on your own pages covers.

Room types as nodes of their own: HotelRoom or Suite

schema.org defines HotelRoom as a single room in a hotel, under Room and Accommodation, and Suite as a class of accommodation whose key feature is more than one room. Give each room type its own node, on that room type's own page, and point it at the hotel with containedInPlace, so the room and the property stay two things joined by one link.

The room node's properties, what schema.org says each takes, and what to write.
PropertyWhat it takesWhat to write
bedBedDetails, BedType or Text; text on its own for a single bed of one typeA BedDetails with numberOfBeds and typeOfBed when there is more than one bed
occupancyQuantitativeValue, in persons including infants; unit code C62maxValue: the number of people your terms let stay in the room, which is how schema.org describes the property (a double room let to one guest, for instance)
floorSizeQuantitativeValue; FTK for square feet, MTK for square metersThe size your room page states, in the unit it states it
floorLevelText, in the building's own numberingThe floor, as the room page gives it
amenityFeatureLocationFeatureSpecification, as on the hotelWhat this room has that others do not: the bathtub, the balcony, the connecting door
numberOfRoomsNumber or QuantitativeValueFor a Suite, how many rooms it has

The room page's own facts, in text, come first; the overview for hotels lists what a room page states. The node repeats them as structured data, in fields a parser can take without reading prose, and it should say nothing the page does not.

A template in which every value comes from a visible page

The graph below has the property's node and one room's node. Every value is a bracket to fill from your own pages; nothing in it is a default to leave in place. Put the hotel's node on the pages that show the hotel's facts and each room's node on its own room page. Google's introduction to structured data recommends JSON-LD and says to mark up only what a visitor can see.

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Hotel",
      "@id": "https://www.[your-domain]/#hotel",
      "name": "[Hotel name, as on the sign]",
      "url": "https://www.[your-domain]/",
      "telephone": "[front desk number]",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "[street]",
        "addressLocality": "[city]",
        "addressRegion": "[state or province]",
        "postalCode": "[postal code]",
        "addressCountry": "[country code]"
      },
      "geo": { "@type": "GeoCoordinates", "latitude": "[latitude]", "longitude": "[longitude]" },
      "checkinTime": "[hh:mm:ss]",
      "checkoutTime": "[hh:mm:ss]",
      "petsAllowed": "[the pet policy's sentence, as the policies page words it]",
      "smokingAllowed": "[true or false, as the policies page states]",
      "availableLanguage": ["[en]", "[fr]"],
      "numberOfRooms": "[n]",
      "priceRange": "[the price range your rates page states]",
      "amenityFeature": [
        {
          "@type": "LocationFeatureSpecification",
          "name": "[Parking on site, included or with its charge]",
          "value": "[true]"
        },
        {
          "@type": "LocationFeatureSpecification",
          "name": "[Breakfast, included or with its charge]",
          "value": "[true]",
          "hoursAvailable": {
            "@type": "OpeningHoursSpecification",
            "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
            "opens": "[hh:mm:ss]",
            "closes": "[hh:mm:ss]"
          }
        }
      ]
    },
    {
      "@type": "HotelRoom",
      "@id": "https://www.[your-domain]/rooms/[room-type]/#room",
      "name": "[Room type name]",
      "url": "https://www.[your-domain]/rooms/[room-type]/",
      "containedInPlace": { "@id": "https://www.[your-domain]/#hotel" },
      "bed": { "@type": "BedDetails", "numberOfBeds": "[n]", "typeOfBed": "[bed type]" },
      "occupancy": { "@type": "QuantitativeValue", "maxValue": "[n]", "unitCode": "C62" },
      "floorSize": { "@type": "QuantitativeValue", "value": "[n]", "unitCode": "[FTK or MTK]" },
      "floorLevel": "[floor]",
      "amenityFeature": [
        { "@type": "LocationFeatureSpecification", "name": "[Bathtub]", "value": "[true]" }
      ]
    }
  ]
}

The template leaves things out on purpose. It has no aggregateRating, for the reason above. It has no starRating, which you add only for an official rating, with the awarding body as its author. And it has no opening hours on the hotel: those belong to the outlets.

Promise yourself nothing from it in search. Google's general structured data guidelines say markup makes a feature possible but does not guarantee that it appears, even when it is correct, and they ask for up-to-date information. Its page on AI features says no special markup is needed for AI Overviews or AI Mode, and lists structured data that matches the visible text among its practices. The markup is a second statement of your facts, and it is only as good as the first.

The booking engine on another host, and what stays on yours

Where the booking engine runs on its provider's domain, its pages sit on another host, and that host's robots.txt governs them, not yours. Google's reading of the robots.txt standard says a file's rules apply only to the host, protocol and port where the file is posted. Whether an AI crawler may fetch the engine's pages is therefore the provider's setting. Nothing in your own file opens them or closes them.

Robots.txt is not the only wall. In the same audit, 7.2% of sites served our crawler a challenge page instead of the site, which stops a fetch as surely as a rule does, and nobody has to write a line for it. The crawler access check reads the robots.txt of whatever host you give it, so run it on your own domain and on the engine's host. It reads the file only, so it cannot tell you whether a challenge page is waiting behind it.

So everything a traveler or an answer needs to know stays on your own pages, as text: the room facts, a starting rate, the policies, what a direct booking includes. The engine is where the booking is made, not where the facts live. Mark up your pages, not the engine's. And do not borrow tourBookingPage to point at the engine: schema.org defines it as a page on how to book a tour of a place, such as a viewing in real estate, not a stay. The landing and the handover to the engine are the subject of direct bookings from AI answers.

Checking that the page and the markup agree

  1. Open a room page's source with scripts turned off and find the room's facts, the starting rate and the policies in the HTML itself. When an assistant fetches a page, usually no scripts run, so what only a script draws is missing from what it receives.
  2. Find the JSON-LD block in the same source and read each value against the visible text on that page, line by line: the check-in time, the pet sentence, the beds, the occupancy.
  3. Run the block through a validator for syntax. A valid block can still be false, as setting up schema.org correctly explains, so step 2 is not optional.
  4. Run the crawler access check on your domain and on the booking engine's host, and read which answer-side crawlers each file allows.
  5. Change the page and the markup on the same day whenever a policy changes: a new check-in time, a new pet fee, breakfast served later on weekends.

Common questions

Should our markup carry the hotel's star rating?

Only an official one. schema.org defines starRating as an official rating for a lodging business, for example from a national association or a standards body, with the rating organization named in its author property. A score a booking platform shows is not that, and neither is an average of guest reviews. If no official body has rated the property, leave starRating out.

Can the markup carry our room rates?

State the rate in the room page's text first; the markup can only repeat what the page says. A nightly rate that moves with the date goes stale in markup as fast as anywhere else, so keep what you mark up to what stays true, such as the price range on the hotel's node, and change it the day the page changes.

Our booking engine's provider says its pages carry their own markup. Is that enough?

It describes the provider's pages, on the provider's host, under the provider's rules for crawlers. Your own pages still need a node for the hotel and one per room type, because they are the pages you control and the ones an answer can cite as yours. Check that the two sets agree on the name, the address and the check-in time.

Do we need an llms.txt or some other file for AI?

Not for Google: its guide to generative AI search says Google Search ignores llms.txt and other AI text files, and its AI features page says no new machine-readable files are needed. Elsewhere, llms.txt is a proposal that no assistant has committed to. The text on your room pages, and the markup that repeats it, are what any reader can use.

Sources

The markup figures are this site's own, from the audit named below, and cover all 40 trades; there is no hotel cut. Every type and property was opened on its schema.org page on September 25, 2026; the rules on reviews, markup and robots.txt are Google's, as its pages state them.

Sources: schema.org, LocalBusiness and its subtypes · Google, local business structured data · Google, introduction to structured data · Google, general structured data guidelines · Google, review snippet · Google, AI features and your website · Google, how Google interprets the robots.txt specification · Google Search Central, optimizing for generative AI search · llmstxt.org, the llms.txt proposal · our audit of 9,483 small-business sites, with its method

All articles