Hotel prices: enable crawler navigation and price tagging

This guide outlines how to tag your website with microdata and navigation attributes so Google's price accuracy crawler can automatically navigate your booking funnel and validate your room rates.


Conceptual crawler flow

The following sequence illustrates how the crawler navigates a typical booking funnel, handling dynamic loading states and identifying critical actions.

🗺️ Quick flow overview

  • 🔗 Phase 1: Google surface — Crawler accesses the Dynamic Landing Page URL (configured on Google based on your template), landing on your site just as a real user would.
  • 🛬 Phase 2: Landing page — Crawler lands on the partner site (data-nav-stage="landing").
  • 🖱️ Phase 3: Navigation — Crawler executes mandatory interactions (clicks, selections) declared using data-nav-* attributes.
  • 🏁 Phase 4: Final page — Crawler reaches the final accessible page (data-nav-stage-final="true").
  • 📊 Phase 5: Price extraction — Crawler extracts the microdata-tagged price and validates total stay cost.

Step 1: landing page (hotel selection or optional upsell page)

The crawler visits the landing page URL (the page where the user lands on the partner site after clicking on Google items). For details on configuring landing page parameters to ensure the correct rate is selected, see variables and conditions.

State A: loading screen

A loading indicator mask is transiently visible. The crawler waits for this nav tag to disappear (max 15 seconds).

<body data-nav-stage="landing">
  <!-- Loading indicator: Crawler waits for this to disappear (max 15s) -->
  <div data-nav-loading-indicator="true">Loading Page...</div>
</body>

State B: page loaded

Once the loading indicator disappears, the page content is loaded. The crawler locates and clicks the critical path element to advance.

<body data-nav-stage="landing">
  <!-- Ignored: Crawler skips non-selected rates -->
  <div class="hotel-result">
    <h3>Grand Hotel - Basic Room</h3>
    <button>View Rates</button>
  </div>

  <!-- critical path: crawler clicks this to advance -->
  <div class="hotel-result selected-rate">
    <h3>Grand Hotel - Deluxe Suite</h3>
    <button data-nav-criticalpath="true"
            data-nav-interactiontype="CLICK"
            data-nav-interactionorder="1">
      View Rates
    </button>
  </div>
</body>

Step 2: optional intermediate page (room or option selection)

The crawler arrives at the rate selection page.

State A: loading screen

A spinner appears while rates are fetched.

<body data-nav-stage="room-selection">
  <div data-nav-loading-indicator="true">Fetching Rates...</div>
</body>

State B: page loaded

After loading finishes, the crawler might interact with multiple elements in sequence before proceeding. Only mandatory interactions (e.g., agreeing to mandatory terms or selecting required configurations based on user selection on Google or min mandatory price path) should be tagged.

<body data-nav-stage="room-selection">
  <!-- Step 1: mandatory interaction (e.g., select bed configuration) -->
  <div class="rate-option">
    <p>Deluxe Suite</p>

    <!-- Note: Either the value selected by the user on Google should be pre-selected,
         or navigation elements must be provided so the crawler can select it. -->
    <div class="bed-selection">
      <button data-nav-criticalpath="true"
              data-nav-interactiontype="CLICK"
              data-nav-interactionorder="1">
        Select Queen Bed
      </button>
      <button>Select King Bed</button>
    </div>
  </div>

  <!-- Step 2: mandatory interaction (e.g., mandatory insurance opt-out or agreement) -->
  <div class="mandatory-notice">
    <label>
      <input type="checkbox"
             data-nav-criticalpath="true"
             data-nav-interactiontype="CLICK"
             data-nav-interactionorder="2">
      I agree to the non-refundable dynamic pricing waiver.
    </label>
  </div>

  <!-- Step 3: Continue to checkout -->
  <button data-nav-criticalpath="true"
          data-nav-interactiontype="CLICK"
          data-nav-interactionorder="3">
    Continue Reserving
  </button>
</body>

Step 3: final crawling page (price validation / booking summary)

The crawler reaches the final accessible page (the final page before user personal or payment information entry). It verifies the data-nav-stage-final flag is true. This signals the crawler to extract the microdata-tagged price after any remaining navigations. Navigation tags are always interacted with first before extracting the price.

Main item scopes should be a cotype of Hotel and LodgingReservation (e.g., itemtype="https://schema.org/Hotel https://schema.org/LodgingReservation"). For backward compatibility, we also accept a parent Hotel scope that contains a child element cotyped as both Offer and LodgingReservation (for example, <div itemprop="makesOffer" itemscope itemtype="https://schema.org/Offer https://schema.org/LodgingReservation">). Only one Hotel and Offer under that Hotel should be tagged on the page.

  • This example shows only the mandatory total price tagging, recommended for simplicity. {Simple Total Price}

    <!-- End of Scope comments identify container boundaries -->
    <body data-nav-stage="checkout" data-nav-stage-final="true">
    
      <!-- Booking Summary Cart Scope (Hotel + LodgingReservation) -->
      <div class="booking-summary" itemscope itemtype="https://schema.org/Hotel https://schema.org/LodgingReservation">
    
        <!-- Hotel Identifier (Can be hidden meta tag) -->
        <meta itemprop="identifier" content="grand-hotel-001" />
    
        <h2 itemprop="name">Grand Hotel</h2>
    
        <!-- Visible Stay Dates & Occupancy (checkinDate / checkoutDate are also acceptable) -->
        <p>
          Check-in: <span itemprop="checkinTime" content="2026-08-10">Aug 10, 2026</span> |
          Check-out: <span itemprop="checkoutTime" content="2026-08-15">Aug 15, 2026</span> |
          Guests: <span itemprop="numAdults">2</span> Adults, <span itemprop="numChildren">1</span> Child
        </p>
    
        <!-- Offer Scope -->
        <div class="cart-details" itemprop="makesOffer" itemscope itemtype="https://schema.org/Offer">
    
          <!-- Compound Price Scope -->
          <div itemprop="priceSpecification" itemscope itemtype="https://schema.org/CompoundPriceSpecification">
    
            <div class="total-line">
              <span>Total Stay (Taxes Included):</span>
              <!-- The crawler extracts this final rate as total price -->
              <span itemprop="price">440.00</span>
              <meta itemprop="priceCurrency" content="USD" />
            </div>
    
          </div> <!-- End of CompoundPriceSpecification Scope -->
        </div> <!-- End of Offer Scope -->
      </div> <!-- End of Hotel/LodgingReservation Scope -->
    
    </body>
    
  • This example shows the total price alongside itemized breakdowns (Base Rate, Fees). {Total Price + Breakdown}

    <!-- End of Scope comments identify container boundaries -->
    <body data-nav-stage="checkout" data-nav-stage-final="true">
    
      <!-- Booking Summary Cart Scope (Hotel + LodgingReservation) -->
      <div class="booking-summary" itemscope itemtype="https://schema.org/Hotel https://schema.org/LodgingReservation">
    
        <!-- Hotel Identifier (Can be hidden meta tag) -->
        <meta itemprop="identifier" content="grand-hotel-001" />
    
        <h2 itemprop="name">Grand Hotel</h2>
    
        <!-- Visible Stay Dates & Occupancy (checkinDate / checkoutDate are also acceptable) -->
        <p>
          Check-in: <span itemprop="checkinTime" content="2026-08-10">Aug 10, 2026</span> |
          Check-out: <span itemprop="checkoutTime" content="2026-08-15">Aug 15, 2026</span> |
          Guests: <span itemprop="numAdults">2</span> Adults, <span itemprop="numChildren">1</span> Child
        </p>
    
        <!-- Offer Scope -->
        <div class="cart-details" itemprop="makesOffer" itemscope itemtype="https://schema.org/Offer">
    
          <!-- Compound Price Scope -->
          <div itemprop="priceSpecification" itemscope itemtype="https://schema.org/CompoundPriceSpecification">
    
            <div class="line-item">
              <span>Base Rate (2 Nights):</span>
              <!-- Unit Price Scope for Base -->
              <span itemprop="priceSpecification" itemscope itemtype="https://schema.org/UnitPriceSpecification">
                <span itemprop="price">400.00</span>
                <meta itemprop="priceCurrency" content="USD" />
              </span> <!-- End of UnitPriceSpecification Scope -->
            </div>
    
            <div class="line-item">
              <span>Resort Fee:</span>
              <!-- Unit Price Scope for Fee -->
              <span itemprop="priceSpecification" itemscope itemtype="https://schema.org/UnitPriceSpecification">
                <span itemprop="price">40.00</span>
                <meta itemprop="priceCurrency" content="USD" />
              </span> <!-- End of UnitPriceSpecification Scope -->
            </div>
    
            <div class="total-line">
              <span>Total Stay:</span>
              <!-- The crawler extracts this final rate as total price -->
              <span itemprop="price">440.00</span>
              <meta itemprop="priceCurrency" content="USD" />
            </div>
    
          </div> <!-- End of CompoundPriceSpecification Scope -->
        </div> <!-- End of Offer Scope -->
      </div> <!-- End of Hotel/LodgingReservation Scope -->
    
    </body>
    

Use custom data-nav-* HTML attributes to annotate interactive elements and declare funnel stages so the crawler can traverse your booking flow.

1.1 Top-of-page stage declaration (data-nav-stage)

Every page in your booking funnel that displays rates should declare its stage, attached to the <body> tag or a primary wrapper tag (such as <div>, <section>, or <main>). This tells the crawler which stage of the booking flow is active and assists with troubleshooting issues later on.

  • data-nav-stage (Recommended): Declares the active funnel stage. While not strictly required, specifying names (for example, "landing", "room-selection", "checkout", "ready-for-payment") helps identify the active stage during validation.

1.2 Final stage indicator (data-nav-stage-final)

On the final page displaying prices prior to user personal or payment information entry, add the final-stage flag to your top-of-page container alongside a stage declaration (for example, data-nav-stage="ready-for-payment").

  • data-nav-stage-final (Required for price validation pages): Set to "true" (data-nav-stage-final="true") on the final page of the booking funnel that crawler can access . This signals that after all required navigation steps are complete, the crawler should read the price from this page.

1.3 Critical path interaction attributes

On intermediate pages, tag the interactive elements (buttons or links) that advance the user along the booking critical path. If multiple actions are required on the same page, specify the execution order. The crawler follows the specified order before attempting to extract the price on the final page.

Attribute Required Value Purpose
data-nav-criticalpath "true" Marks the mandatory button/link the crawler must click to advance.
data-nav-interactiontype "CLICK" Declares the interaction action. CLICK is for buttons/links.
data-nav-interactionorder "1", "2", ... Declares the execution sequence if multiple actions are required on the same page.
data-nav-loading-indicator "true" Marks an element that indicates a loading state (e.g., spinner). The crawler waits for this element to disappear before proceeding.

Minimal navigation example

<button data-nav-criticalpath="true"
        data-nav-interactiontype="CLICK"
        data-nav-interactionorder="1">
  Continue
</button>

2. Price declaration (microdata)

On the final accessible page of your booking funnel (prior to personal or payment information entry), tag the price offering (one price offer) with microdata tags. Only one offer should be tagged on the page, indicating which rate the crawler extracts, corresponding to the rate selected on Google before the user lands on your website. For details on defining landing page parameters, see variables and conditions.

While Google fully supports the complete hierarchy in the hotel price structured data reference

(including itemized base rates, individual taxes, fees, and room details), Google also accepts a simplified structured data tree to make it easier for you to tag hotel prices on your webpages.

The total stay price is mandatory for available rates. If an itinerary or room is unavailable or sold out, declare https://schema.org/SoldOut under makesOffer without a priceSpecification:

<div itemprop="makesOffer" itemscope itemtype="https://schema.org/Offer">
  <meta itemprop="availability" content="https://schema.org/SoldOut"/>
  Sold Out
</div>

For more information, see the availability property specification.

2.1 Simplified structured data tree

To simplify tagging on your booking pages, you only need to declare the root hotel and reservation container, stay dates (YYYY-MM-DD), occupancy, and a single offer containing the total stay price:

Hotel / LodgingReservation (Root Container)
├── identifier                   [Meta/Attribute] Hotel ID
├── name                         [Visible text] Hotel Name
├── checkinTime / checkoutTime   [Visible text with YYYY-MM-DD in content="..."] Stay Dates
├── numAdults / numChildren      [Visible text] Occupancy
└── makesOffer (Offer)
    └── priceSpecification (CompoundPriceSpecification)
        ├── priceCurrency        [ISO Code, such as "USD"] Currency
        └── price                [Visible text] Total Stay Price (Taxes & Fees Included)

2.2 Critical visibility rules

To ensure reliable price extraction, follow these visibility rules when tagging your HTML:

  • Total stay price must be visible: Tag the visible price number directly on the page using itemprop="price". Don't use hidden metadata (<meta>) or attribute values (value="..." or content="...") for the price number.
  • Machine-readable stay dates: Tag visible stay dates on the page as much as possible. If needed, you can use the content="..." attribute on <span> elements to provide a standard YYYY-MM-DD format while displaying the visible date (for example, <span itemprop="checkinTime" content="2026-08-10">August 10, 2026</span>; checkinDate is also acceptable).
  • Non-visible fields: Fields that aren't displayed directly on the page (such as the hotel ID or currency code) can use <meta> tags or content="..." attributes.

3. Examples

The following end-to-end examples show how to tag intermediate funnel steps and final checkout pages with navigation attributes and microdata.

Example 1: intermediate page with clickable button

The following snippet demonstrates an intermediate room selection page (room-selection) with a single critical path action button tagged to advance the flow:

<body data-nav-stage="room-selection">
  <!-- Main page content -->
  <button class="btn-continue"
          data-nav-criticalpath="true"
          data-nav-interactiontype="CLICK"
          data-nav-interactionorder="1">
    Book This Room
  </button>
  <!-- Rest of the Main page content -->
</body>

Example 2: final price page with stage indicator and minimal microdata

The following snippet demonstrates the last page before payment (ready-for-payment) where data-nav-stage-final="true" is declared on the wrapper and the authoritative stay price is visibly exposed:

<body data-nav-stage="ready-for-payment" data-nav-stage-final="true">

  <!-- Root Container: Co-Typed Hotel & LodgingReservation -->
  <div itemscope itemtype="https://schema.org/Hotel https://schema.org/LodgingReservation">

    <!-- Hotel Identifier (Can be hidden meta tag) -->
    <meta itemprop="identifier" content="grand-view-001" />
    <h1 itemprop="name">Grand View Hotel</h1>

    <!-- Visible Stay Dates & Occupancy -->
    <p>
      Check-in: <span itemprop="checkinTime" content="2026-08-10">Aug 10, 2026</span> |
      Check-out: <span itemprop="checkoutTime" content="2026-08-15">Aug 15, 2026</span> |
      Guests: <span itemprop="numAdults">2</span> Adults, <span itemprop="numChildren">1</span> Child
    </p>

    <!-- Single Minimal Rate Offer -->
    <div itemprop="makesOffer" itemscope itemtype="https://schema.org/Offer">
      <div itemprop="priceSpecification" itemscope itemtype="https://schema.org/CompoundPriceSpecification">

        <p class="summary-total">
          Total Stay Price (Taxes & Fees Included):

          <!-- 3-letter ISO Currency Code (can use content="" for hiding the 3-letter currency code while displaying a symbol) -->
          <span itemprop="priceCurrency" content="EUR">€</span>

          <!-- VISIBLE TOTAL STAY PRICE (Directly wrapped, no hidden attributes) -->
          <span itemprop="price">650.00</span>
        </p>

      </div>
    </div>

  </div>

</body>

4. Verify your implementation

Before deploying your changes, test your markup and navigation tagging to ensure the crawler can successfully validate your pricing data.

4.1 Verify price and microdata structure

You can use the Schema.org Validator or Google Rich Results Test to inspect the microdata extracted from your page URL or HTML content. Verify the extracted fields against the specification in the hotel price structured data reference , and ensure that your itemprop="price" element is a visible item on the page.

4.2 Verify navigation

To verify your tags, open Browser Developer Tools in Google Chrome by right-clicking your webpage and selecting Inspect (or pressing F12) to examine the HTML DOM. Find elements with data-nav-* attributes.

Confirm that your implementation meets the following navigation requirements:

  • Top-of-page stage is declared: Every page in the booking flow includes data-nav-stage="..." on its <body> or root wrapper tag, and the final price page includes data-nav-stage-final="true".
  • Critical path is complete: Intermediate pages have their interactive elements (buttons or links) marked with data-nav-criticalpath="true", data-nav-interactiontype, and data-nav-interactionorder. If multiple actions are present, ensure order numbers are set correctly.

4.3 Verify visibility and markup quality

Confirm that your microdata satisfies all structure and visibility requirements (for details, see simplified structured data tree and critical visibility rules):

  • Strict hierarchy: Ensure your markup follows the required nesting order (either the simplified tree or the full structured data reference specification). Out-of-order or improperly nested elements might be missed by the crawler.
  • Visible price: itemprop="price" directly wraps the visible price number without hidden attributes or <meta> tags.
  • Machine-readable stay dates: checkinTime and checkoutTime (or alternatively checkinDate and checkoutDate attributes) contain standard YYYY-MM-DD date format (optionally using content="..." attributes if needed). If a full time (e.g. 2027-08-21T23:59:59 or similar) is used, the date part YYYY-MM-DD in beginning is only extracted.
  • Direct element tagging: All visible properties (hotel name, stay dates, occupancy) wrap visible page text.