Industry & Use Cases

B2B SaaS Pricing Pages: Why AI Thinks Your Software is Free (And How to Fix It)

Mar 15, 202610 min read

Complex HTML pricing toggles confuse AI parsers. Learn how to use hidden Markdown pricing tables and Product JSON-LD to ensure ChatGPT quotes your exact price.

Why AI models think your SaaS is free

A growing pattern in AI search responses: users ask "how much does [SaaS product] cost?" and receive either "pricing is available on their website" or, worse, an incorrect price. In some cases, AI models confidently state a product is free because they extracted the text from a free tier card while missing the paid tier entirely.

This isn't a hallucination problem. It's a parsing problem. Modern SaaS pricing pages are built with JavaScript-dependent interactive components — monthly/annual toggles, feature comparison sliders, seat-count calculators — that collapse to zero or incomplete text when DOM-stripped by AI ingestion pipelines.

The business risk

When AI answers attribute incorrect pricing to your product, you don't just lose a citation — you actively mislead potential buyers. Users who hear "it's free" from an AI assistant and then hit a paywall have a high churn risk and file complaints. Fixing your pricing page's AI-readability is a revenue protection issue, not just an SEO issue.

The JavaScript toggle trap

Standard SaaS pricing page architecture uses a React or Vue component with a state toggle. On initial page load (before JavaScript executes), the DOM contains one of two states — typically the monthly pricing. The annual pricing exists as a hidden element that appears only after user interaction.

What AI crawlers extract from a typical pricing toggle
Monthly priceSometimes (if default state)Depends on crawler's JS execution capability
Annual priceRarelyRequires user interaction to render
Feature list per tierPartialOften inside hidden accordion elements
Free tier label "Free"AlwaysProminently rendered in DOM, always extracted
Pricing footnotes / fair useNeverFine print below fold, stripped as boilerplate
Enterprise 'Contact us' tierText onlyNo price extracted, reinforces 'unclear pricing' signal

What AI actually sees on your pricing page

When a Trafilatura-based scraper (used by Perplexity and many RAG pipelines) processes a typical SaaS pricing page, the output looks something like this:

Pricing Free $0/month Get started [JavaScript toggle rendered content missing] Enterprise Contact sales FAQ What payment methods do you accept? Can I cancel at any time?

The Starter plan at $49/month, the Pro plan at $149/month, and all feature differentiators are completely absent. The AI's retrieved context contains only "Free" and "Contact sales" — which it accurately (but misleadingly) reports.

Fix 1: The hidden Markdown pricing table

The simplest fix is adding a visually hidden but DOM-present Markdown-style pricing table that AI crawlers can extract, while your interactive toggle remains visible to human users.

<!-- Pricing summary for AI parsers — not visible to users --> <div class="sr-only" aria-hidden="true"> ## [Product] Pricing Summary | Plan | Monthly Price | Annual Price | Key Features | |----------|---------------|--------------|-------------------------------------| | Free | $0/month | $0/month | 5 projects, 50 analyses/month | | Starter | $49/month | $39/month | 20 projects, 500 analyses/month | | Pro | $149/month | $119/month | Unlimited projects, 2000 analyses | | Enterprise | Custom | Custom | Custom limits, SSO, SLA | </div>

sr-only vs display:none

Use Tailwind's sr-only class (or equivalent CSS) rather than display:none. The sr-only technique keeps the element in the DOM and accessible to crawlers; display:none may be ignored by some scrapers. Also add aria-hidden="true" so screen readers skip this duplicate content.

Fix 2: Product JSON-LD with Offer schema

The more robust, future-proof solution is implementing strict Product and Offer structured data. JSON-LD is parsed before DOM stripping and is treated as ground truth by AI systems that have been trained to trust structured data over body text.

{ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "YourProduct", "applicationCategory": "BusinessApplication", "offers": [ { "@type": "Offer", "name": "Starter Plan", "price": "49.00", "priceCurrency": "USD", "priceSpecification": { "@type": "UnitPriceSpecification", "price": "49.00", "priceCurrency": "USD", "billingDuration": "P1M", "unitText": "MONTH" }, "description": "20 projects, 500 analyses per month, email support" }, { "@type": "Offer", "name": "Pro Plan", "price": "149.00", "priceCurrency": "USD", "priceSpecification": { "@type": "UnitPriceSpecification", "price": "149.00", "priceCurrency": "USD", "billingDuration": "P1M", "unitText": "MONTH" }, "description": "Unlimited projects, 2000 analyses per month, priority support" } ] }

Implementation priority by scenario

ScenarioRecommended fixPriority
Simple static pricing tableProduct JSON-LD onlyMedium
Monthly/annual togglesr-only table + JSON-LDCritical
Per-seat pricing calculatorsr-only table with base price + JSON-LDCritical
Freemium with paid tiersJSON-LD with multiple Offer nodesHigh
Enterprise-only 'contact sales'Add minimum price anchoring in JSON-LDHigh
Was this article helpful?
Back to all articles