Add real Offer/AggregateOffer pricing schema to /pricing/ - #20552
Add real Offer/AggregateOffer pricing schema to /pricing/#20552workprentice[bot] wants to merge 1 commit into
Conversation
The pricing page's SoftwareApplication JSON-LD named the product 'Pricing' (leaked from the page title) and emitted a single Offer with a currency but no price, so AI engines and Google's Software App rich result had no answer for 'how much does Pulumi cost'. - Add a schema_name frontmatter override (Pulumi) so the entity name no longer defaults to the page title; additive, no effect on other product pages. - When a page defines tiers.trialed.items (currently only /pricing/), build a real per-tier Offer for each plan (Individual $0, Team $40, Enterprise $400, Business Critical custom - left priceless, never fabricated) plus an AggregateOffer with lowPrice/highPrice/offerCount, mirroring the Offer-array pattern already used on the homepage. - Non-tiered product pages keep the existing generic Offer fallback unchanged.
Pre-merge Review — Last updated 2026-07-29T12:21:54ZTip Summary: This PR adds real schema.org Review confidence:
Investigation log
🔍 Verification trail6 claims extracted · 3 verified · 0 unverifiable · 0 contradicted
🚨 Outstanding in this PRNo outstanding findings in this PR.
|
|
Your site preview for commit 891c67b is ready! 🎉 http://www-testing-pulumi-docs-origin-pr-20552-891c67bb.s3-website.us-west-2.amazonaws.com Changed pages: |
Lighthouse Performance ReportCommit: 891c67b | Metric definitions
|
CamSoper
left a comment
There was a problem hiding this comment.
Requesting changes on the schema semantics, not the template logic — the tier mapping, the "Custom" exclusion, and the fallback are all handled well.
The prices publish without a billing period
For Team, this emits:
{ "@type": "Offer", "name": "Team", "priceCurrency": "USD", "price": 40 }The page says $40 /month base, "Includes 40 Credits", "additional usage billed on demand". A bare price with no priceSpecification denotes an unqualified flat price, so the machine-readable claim becomes "Pulumi Team costs $40" against an annual floor of $480 before usage billing. Enterprise reads as $400 flat against $4,800/yr.
The HTML readers see /month base right next to the number. Google's rich results and LLM crawlers don't — they only get the JSON-LD, which is the entire point of the PR.
Please carry the billing period in the structured data: a priceSpecification with a UnitPriceSpecification expressing the monthly period, rather than (or in addition to) the bare price. Confirm the exact property set against Google's current subscription / merchant structured-data guidance before implementing — I don't want a guessed shape here.
Worth deciding at the same time whether the consumption component can be represented at all, or whether these Offers should be scoped to the base price with that stated explicitly. Publishing a base price as if it were the total is the failure mode to avoid.
Two smaller items
-
lowPricedisagrees with the Neo branch. The/neo/branch directly above floors at"40"withofferCount: 3, deliberately excluding the free tier. This branch will emitlowPrice: 0,offerCount: 4. Both are defensible, but two AggregateOffers for the same product family shouldn't disagree on the floor. Pick one and make them match. -
Type drift. The Neo branch emits prices as strings (
"40"); this branch emits numbers viafloat. Both are valid schema.org, but align them within the file.
The schema_name: Pulumi key and the blast radius are fine — content/pricing/_index.md is the only page in the repo with either schema_type: product or tiers.trialed.items, so nothing else changes behavior.
Problem
The
/pricing/page'sSoftwareApplicationJSON-LD (the schema block Google's Software App rich result and AI engines read to answer "how much does X cost") had two commercial-intent defects, both confirmed live:name: "Pricing"— the page title leaked into the product-entity name. Crawlers and LLMs parsing this node are told the product is literally called "Pricing."offershad apriceCurrencybut noprice. A currency with no amount is meaningless to a rich result and gives an LLM nothing to cite when asked what Pulumi costs — despite the same page's own frontmatter already containing the full, authoritative per-tier price list (Individual $0, Team $40/mo, Enterprise $400/mo, Business Critical custom).The homepage already proves the correct shape is achievable: its
graph-builder.htmlemits a real array of per-tierOfferobjects with names and prices./pricing/, the one page whose entire purpose is communicating price, was the one page not doing this.Fix
layouts/partials/schema/collectors/product-entity.html:$productNamenow prefers a newschema_namefrontmatter field over.Title, falling back to the old behavior for every other product page. Setschema_name: Pulumioncontent/pricing/_index.mdonly — no behavior change anywhere else.tiers.trialed.items— currently just/pricing/. It ranges over the page's own tier list and:Offerper tier (name,description,priceCurrency,availability,url) using the tier'scta.hrefwhen absolute, else the pricing URL.$/,and parses the price; when it's numeric, adds apricefield and folds it into anAggregateOffer(lowPrice/highPrice/offerCount) wrapping all tier offers — same idiom the file already uses for Neo'sAggregateOffer.Offeris emitted with nopricefield — never fabricated.Offerrather than emitting a brokenAggregateOffer.tiersdata) is unaffected — same output as before.No prices were invented; every number comes from the page's own already-published frontmatter, matching the pattern already live on the homepage.
Verification
hugobuild andmake lintare blocked in this environment (no Node/Yarn toolchain for asset fingerprinting / markdown-lint scripts) — a known, pre-existing gap, not related to this change.{{/}}delimiter count balanced (91/91), and every newif/rangehas a matchingend(12/12), matching the file's existing style throughout.merge $schema (dict ...), theAggregateOffershape) mirrors an idiom already present and working in this same file (the NeoAggregateOfferbranch).https://www.pulumi.com/pricing/after merge to confirm the rendered JSON-LD carries real per-tier prices.🧠 This PR was created by workprentice on behalf of the Pulumi SEO/AEO agent's operator.