Geodocs.dev

AEO Comparison Table Schema Specification: Markup Standard for Side-by-Side Answers AI Engines Can Extract

ShareLinkedIn

Open this article in your favorite AI assistant for deeper analysis, summaries, or follow-up questions.

This spec defines a JSON-LD pattern that pairs a semantic HTML

with ItemList, Product (or Thing), and PropertyValue so AI answer engines can extract each row of a comparison as a structured fact set instead of free-form prose.

TL;DR

AI answer engines (Google AI Overviews, Perplexity, ChatGPT Search) cite comparison content far more reliably when each compared entity is exposed as a structured object with row-aligned property values. Because Schema.org has no native Table type, this spec layers ItemList + PropertyValue over a clean HTML

and adds an outer Article or WebPage wrapper so a single block of markup is fully extractable.

Why this spec exists

Comparison queries — "X vs Y", "best X for Y", "difference between X and Y" — are one of the highest-volume query classes routed to AI answer engines. ESEOSpace flagged comparison tables as a top-performing format in Generative Engine Optimization research because the row-and-column structure already maps onto how transformer-based extractors build feature vectors for entities (ESEOSpace, 2025).

But Schema.org intentionally leaves out a generic Table type. Without a defined pattern, content engineers either fall back to plain HTML (which AI engines parse but cannot trust as a fact set) or invent ad hoc JSON-LD that validators accept but answer engines ignore. This specification fixes both problems by:

  1. Defining the canonical Schema.org type stack (ItemList → Product/Thing → PropertyValue).
  2. Aligning the JSON-LD with the rendered HTML
so retrieval and rendering reference the same data.
  • Constraining property names to a controlled vocabulary that maps to recognizable answer-engine slots.
  • How AI engines parse comparison content

    AI answer engines do not "render" your page the way a browser does. They extract semantic units. For comparisons, three signals matter:

    • Entity boundaries. The engine must know that "Stripe" and "Adyen" are two distinct compared things, not subsections of one product page. ItemList with one ListItem per entity establishes the boundary.
    • Aligned attributes. For each pair of (entity, attribute) the engine wants a single value with a clear name. PropertyValue does this: { "name": "Transaction fee", "value": "2.9% + 30¢" } — far more extractable than a
    blob.
  • Provenance. A wrapping Article or WebPage with author, datePublished, and mainEntity ties the comparison to a citable source. AirOps and Search Engine Land both observe that AI citations track schema provenance signals more closely than they track ranking signals.
  • Schema markup does not by itself force a citation, but it removes ambiguity, and ambiguity is what costs comparisons their citation slot (Search Engine Land, 2025).

    The canonical type stack

    WebPage / Article
    └── mainEntity → ItemList
        ├── itemListElement[0] → ListItem (position: 1)
        │   └── item → Product / SoftwareApplication / Thing
        │       ├── name
        │       ├── description
        │       └── additionalProperty[] → PropertyValue (one per compared attribute)
        ├── itemListElement[1] → ListItem (position: 2)
        │   └── item → ...
        └── itemListElement[N] → ...

    Three Schema.org types do the heavy lifting:

    • ItemList — declares a list of items with an explicit order.
    • ListItem — wraps each entity with a position so the engine preserves ordering.
    • PropertyValue — exposes attribute-value pairs. Schema.org explicitly recommends PropertyValue via additionalProperty whenever no specific property exists for a feature (Product type).

    For pure pros/cons rows, swap additionalProperty for positiveNotes and negativeNotes, both of which accept ItemList values (Recommendation type).

    Required fields

    Every conformant AEO comparison block MUST emit:

    • @context: "https://schema.org"
    • @type: "ItemList" (or wrapped under an Article/WebPage mainEntity)
    • itemListOrder — one of "https://schema.org/ItemListOrderAscending", "https://schema.org/ItemListOrderDescending", or "https://schema.org/ItemListUnordered". Comparison tables are typically ItemListUnordered.
    • numberOfItems — integer count.
    • For each ListItem: position, plus item with at least @type, name, and one of description/url.
    • Per row: additionalProperty array of PropertyValue, each with name and value.

    Optional but strongly recommended:

    • image on each item (helps Google Lens and visual answer surfaces).
    • aggregateRating, offers, or award when comparing products.
    • sameAs for entity disambiguation against Wikidata or official sites.

    Reference implementation

    HTML

    <table aria-label="Stripe vs Adyen vs Braintree" data-comparison-id="payments-2026">
      <thead>
        <tr>
          <th scope="col">Provider</th>
          <th scope="col">Transaction fee (US card-present)</th>
          <th scope="col">Settlement time</th>
          <th scope="col">Best for</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">Stripe</th>
          <td>2.9% + 30¢</td>
          <td>2 business days</td>
          <td>SaaS startups</td>
        </tr>
        <tr>
          <th scope="row">Adyen</th>
          <td>Interchange + 0.6%</td>
          <td>1-3 business days</td>
          <td>Global enterprise</td>
        </tr>
        <tr>
          <th scope="row">Braintree</th>
          <td>2.59% + 49¢</td>
          <td>1-2 business days</td>
          <td>PayPal-aligned merchants</td>
        </tr>
      </tbody>
    </table>

    JSON-LD

    {
      "@context": "https://schema.org",
      "@type": "ItemList",
      "name": "Stripe vs Adyen vs Braintree (US, 2026)",
      "itemListOrder": "https://schema.org/ItemListUnordered",
      "numberOfItems": 3,
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "item": {
            "@type": "SoftwareApplication",
            "name": "Stripe",
            "url": "https://stripe.com",
            "applicationCategory": "PaymentApplication",
            "additionalProperty": [
              { "@type": "PropertyValue", "name": "Transaction fee (US card-present)", "value": "2.9% + 30¢" },
              { "@type": "PropertyValue", "name": "Settlement time", "value": "2 business days" },
              { "@type": "PropertyValue", "name": "Best for", "value": "SaaS startups" }
            ]
          }
        },
        {
          "@type": "ListItem",
          "position": 2,
          "item": {
            "@type": "SoftwareApplication",
            "name": "Adyen",
            "url": "https://adyen.com",
            "applicationCategory": "PaymentApplication",
            "additionalProperty": [
              { "@type": "PropertyValue", "name": "Transaction fee (US card-present)", "value": "Interchange + 0.6%" },
              { "@type": "PropertyValue", "name": "Settlement time", "value": "1-3 business days" },
              { "@type": "PropertyValue", "name": "Best for", "value": "Global enterprise" }
            ]
          }
        },
        {
          "@type": "ListItem",
          "position": 3,
          "item": {
            "@type": "SoftwareApplication",
            "name": "Braintree",
            "url": "https://braintreepayments.com",
            "applicationCategory": "PaymentApplication",
            "additionalProperty": [
              { "@type": "PropertyValue", "name": "Transaction fee (US card-present)", "value": "2.59% + 49¢" },
              { "@type": "PropertyValue", "name": "Settlement time", "value": "1-2 business days" },
              { "@type": "PropertyValue", "name": "Best for", "value": "PayPal-aligned merchants" }
            ]
          }
        }
      ]
    }

    Wrapping for citation

    Wrap the ItemList inside the page's primary Article so the AI engine can cite a single source URL:

    {
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": "Stripe vs Adyen vs Braintree: 2026 Pricing Comparison",
      "datePublished": "2026-04-29",
      "author": { "@type": "Organization", "name": "Geodocs" },
      "mainEntity": { "@type": "ItemList", "...": "..." }
    }

    Property naming rules

    Column headers are the single biggest factor in extraction quality. Apply these constraints:

    1. Use noun phrases, not sentences. "Transaction fee", not "What is the transaction fee?".
    2. Include the unit and scope when ambiguous. "Transaction fee (US card-present)", not "Fee".
    3. Match the HTML
    text exactly. Mismatch between JSON-LD name and the rendered cell weakens trust signals.
  • Avoid HTML inside values. Use plain text in value. If you need rich content, add a sibling description field on the item.
  • Pick a consistent scale. All currencies in the same column should use the same unit; all times the same granularity.
  • Pros/cons variant

    For pro/con comparisons (e.g., Solution A vs Solution B), use Recommendation-style markup. Schema.org allows positiveNotes and negativeNotes on Product and Review, with ItemList as an accepted value type (Recommendation).

    {
      "@type": "Product",
      "name": "PostgreSQL",
      "positiveNotes": {
        "@type": "ItemList",
        "itemListElement": [
          { "@type": "ListItem", "position": 1, "name": "Mature ecosystem and JSONB support" },
          { "@type": "ListItem", "position": 2, "name": "Strict ACID semantics" }
        ]
      },
      "negativeNotes": {
        "@type": "ItemList",
        "itemListElement": [
          { "@type": "ListItem", "position": 1, "name": "Vertical scaling ceiling without sharding extensions" }
        ]
      }
    }

    Validation checklist

    Before publishing:

    • [ ] JSON-LD parses in the Schema.org validator.
    • [ ] numberOfItems matches the itemListElement array length.
    • [ ] Every ListItem has a unique position starting at 1.
    • [ ] Every PropertyValue has both name and value.
    • [ ] Property names are identical across all rows of the same column.
    • [ ] HTML
    text matches JSON-LD property name 1:1.
  • [ ] The wrapping Article or WebPage includes author and datePublished.
  • [ ] No additionalProperty value is an HTML fragment.
  • [ ] Optional: sameAs points to Wikidata for each compared entity.
  • Common mistakes

    • Using Table as a type. It does not exist in Schema.org.
    • One Product for the whole page. Each compared entity needs its own ListItem → item.
    • Encoding the table inside description. This collapses the comparison back into prose and removes the structural signal.
    • Mixing ItemList ordering modes. Pick Unordered for "X vs Y" pages; pick Descending only when ranking matters and you state the ranking criterion.
    • Skipping the HTML table. Schema-only comparisons lose semantic signal and accessibility. The HTML and JSON-LD reinforce each other.
    • Using FAQ schema as a substitute. FAQ markup is for Q&A, not row-aligned attributes; HubSpot and BizIQ both flag FAQ misuse explicitly.

    How to apply

    1. Audit the comparison page. Confirm the has a row and scope attributes on header cells.
    2. Generate the ItemList JSON-LD with one ListItem per row.
    3. Map each
    4. column to a PropertyValue.name and each to its value.
    5. Wrap the ItemList under the page's primary Article via mainEntity.
    6. Validate with the Schema.org validator and Google's Rich Results Test.
    7. Re-test in AI engines: query the comparison verbatim in Perplexity and ChatGPT Search to confirm the table is cited.
    8. FAQ

      Q: Why doesn't Schema.org have a native Table type?

      The Schema.org vocabulary models entities and their relationships, not visual layouts. A table is a presentation; the underlying data is a list of items with shared attributes — which is exactly what ItemList + PropertyValue represent (PropertyValue, Schema.org).

      Q: Do AI answer engines actually read this markup?

      Schema markup does not guarantee citation, but it materially improves an engine's ability to recognize entities, attributes, and provenance. AirOps reports that pages combining clean structure with schema earn more AI citations than prose-only pages. Search Engine Land cautions against treating schema as a magic boost: it works as a comprehension layer, not a ranking lever.

      Q: Should I use Product, SoftwareApplication, or Thing for items?

      Use the most specific type that fits. SoftwareApplication for SaaS; Product for physical goods or hybrid offerings; Service for service businesses; fall back to Thing only when nothing else applies. Specific types unlock specific properties (for example, applicationCategory on SoftwareApplication).

      Q: What if my comparison has 20+ columns?

      Limit a single ItemList to the 5-8 attributes most likely to be queried. Move long-tail attributes to a sibling Dataset resource and link it via subjectOf. AI engines truncate verbose additionalProperty arrays.

      Q: Can I reuse this spec for pro/con lists instead of feature tables?

      Yes. Use positiveNotes and negativeNotes (each typed as ItemList) on a Product or Review. Schema.org explicitly supports this pattern for symmetric pro/con presentation (Recommendation).

      Stay Updated

      GEO & AI Search Insights

      New articles, framework updates, and industry analysis. No spam, unsubscribe anytime.