Periscopy
Articles
Methodology9 min read

Schema.org for B2B SaaS: the minimum viable in JSON-LD

The five schema types every B2B SaaS company needs, with JSON-LD snippets ready to paste: Organization, Service, FAQPage, BreadcrumbList and Article.


For a B2B SaaS company, there are five schema types worth more than all the others combined: Organization, Service, FAQPage, BreadcrumbList and Article. Implemented in JSON-LD, with stable @ids and links between them, they are the minimum viable to start being citable by AI. This piece has the snippet for each one, ready to adapt.

Key takeaways
  • JSON-LD is the right serialisation in 2026
  • five types solve 80% of the upside: Organization, Service, FAQPage, BreadcrumbList, Article
  • stable @ids turn loose entities into a graph
  • validate at validator.schema.org and in the Rich Results Test
  • irrelevant schema is worse than no schema

Why schema matters for GEO

Schema.org is the standard vocabulary for structured data. In HTML, describing something as an organisation is implicit (from tags like header, from CSS classes, from context). In schema, it is explicit: @type: Organization.

For traditional engines, schema unlocks rich results (stars, expanded FAQ, visible breadcrumbs). For LLMs, schema is the only reliable way to know, without ambiguity, what each thing is. When ChatGPT cites "Acme is a cybersecurity consultancy in Lisbon", it is inferring three things, what it is, what it does, where, and schema narrows the room for error.

1. Organization

The foundation. One Organization entity per company, with a stable @id, referenced by all the other entities as provider, author, publisher.

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://acme.com/#organization",
  "name": "Acme SaaS",
  "url": "https://acme.com",
  "logo": "https://acme.com/logo.png",
  "description": "Inventory management platform for EU retailers.",
  "foundingDate": "2021",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Rua X, 42",
    "postalCode": "1234-567",
    "addressLocality": "Lisboa",
    "addressCountry": "PT"
  },
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "sales",
    "email": "contacto@acme.com",
    "availableLanguage": ["pt-PT", "en", "es"]
  },
  "sameAs": [
    "https://www.linkedin.com/company/acme-saas",
    "https://github.com/acme-saas"
  ],
  "knowsAbout": ["Inventory Management", "Retail Tech", "ERP"]
}

Three critical fields: sameAs (links to consistent external profiles, LinkedIn, GitHub, Wikipedia if it exists), knowsAbout (3 to 10 topics that assert the company's expertise), and contactPoint with availableLanguage.

2. Service

One Service per service offered. It links to the Organization through provider:

{
  "@context": "https://schema.org",
  "@type": "Service",
  "@id": "https://acme.com/#service-inventory",
  "serviceType": "Inventory Management Platform",
  "provider": { "@id": "https://acme.com/#organization" },
  "areaServed": [
    { "@type": "Country", "name": "Portugal" },
    { "@type": "Country", "name": "Spain" }
  ],
  "audience": {
    "@type": "BusinessAudience",
    "audienceType": "Retail SMB"
  },
  "description": "Real-time inventory management SaaS platform.",
  "offers": {
    "@type": "AggregateOffer",
    "priceCurrency": "EUR",
    "lowPrice": "99",
    "offerCount": 3
  }
}

For B2B SaaS with variable pricing, AggregateOffer with lowPrice signals the entry point without committing to an exposed price table. It works well for "from EUR X" without revealing the ceiling.

3. FAQPage

A block of questions and answers in structured format. AI models prefer to cite this format because it is trivial to extract:

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "@id": "https://acme.com/#faq",
  "isPartOf": { "@id": "https://acme.com/#website" },
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Does Acme integrate with ERP?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. Native integration with SAP B1, Primavera and PHC."
      }
    }
  ]
}

Rule of thumb: 5 to 10 questions per FAQPage. More than that is noise. The questions should match real queries customers make, not queries invented by the marketing team.

4. BreadcrumbList

Small but important. It helps crawlers understand the hierarchy of your pages. It produces a visible result in Google SERPs and gives structural clues to LLMs:

{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "@id": "https://acme.com/product/inventory#breadcrumb",
  "itemListElement": [
    { "@type": "ListItem", "position": 1, "name": "Home",      "item": "https://acme.com" },
    { "@type": "ListItem", "position": 2, "name": "Product",   "item": "https://acme.com/product" },
    { "@type": "ListItem", "position": 3, "name": "Inventory", "item": "https://acme.com/product/inventory" }
  ]
}

5. Article (or BlogPosting)

Every blog article should have BlogPosting (more specific than Article) with the critical fields:

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "@id": "https://acme.com/blog/integrate-sap#article",
  "headline": "How to integrate Acme with SAP B1 in 30 minutes",
  "description": "Technical integration guide via REST API.",
  "image": "https://acme.com/og-image.png",
  "datePublished": "2026-05-01",
  "dateModified": "2026-05-01",
  "author":    { "@id": "https://acme.com/#organization" },
  "publisher": { "@id": "https://acme.com/#organization" },
  "mainEntityOfPage": {
    "@id": "https://acme.com/blog/integrate-sap#webpage"
  },
  "articleSection": "Integrations",
  "inLanguage": "en"
}

Three details many sites get wrong: mainEntityOfPage should point at a WebPage that actually gets emitted (not an @id that never exists). articleSection helps LLMs categorise. author and publisher reference the Organization through @id instead of duplicating data.

The @graph idea (and why it matters)

If you have several entities on the same page, you can wrap them in a @graph instead of scattering multiple application/ld+json script blocks:

{
  "@context": "https://schema.org",
  "@graph": [
    { "@type": "Organization", "@id": "https://acme.com/#organization", ... },
    { "@type": "WebSite",      "@id": "https://acme.com/#website",     ... },
    { "@type": "WebPage",      "@id": "https://acme.com/#webpage",     ... },
    { "@type": "Service",      "@id": "https://acme.com/#service-inv", ... },
    { "@type": "FAQPage",      "@id": "https://acme.com/#faq",         ... }
  ]
}

The advantage: less parsing, shared context, explicit links through @id. For LLMs, a coherent graph is significantly more legible than five loose scripts.

Validation is not optional

Schema without validation is a trap. Before deploying, three tools:

  • validator.schema.org, checks conformance with the specification.
  • Rich Results Test, tells you which of your schema is eligible for rich results in Google.
  • Manual inspection in DevTools: copy the JSON, parse it, confirm the @ids are linked.

Typical mistakes we see

  • Duplicated entities. Organization redeclared on every page instead of referenced by @id.
  • Missing @id. The schema works, but LLMs treat each block as an independent entity.
  • FAQPage with fabricated questions. The team invents SEO-friendly questions instead of copying real ones. Result: it discredits everything.
  • Article with no dateModified. Models prefer to cite recent content; with no dateModified, they have no update signal.

Frequently asked questions

Microdata, RDFa or JSON-LD?

JSON-LD. It is the only one Google explicitly recommends, it is the cleanest to maintain (it separates semantic markup from presentation HTML), and it is what LLMs extract with the highest fidelity. Microdata and RDFa are still supported for compatibility, but they are not worth starting with in 2026.

Where should the JSON-LD go: head or body?

Either works, the specification does not require one. In the Next.js App Router, the typical pattern is to inject it with a JsonLd component inside the page's return (in the body, before the content). In static sites, the head is cleaner. In both cases, one script tag per semantic block avoids duplication.

Do I have to have @id on every entity?

It is not mandatory, but it is the difference between having a set of loose entities and having a coherent graph. With stable @ids (like https://example.com/#organization) you can reference the same entity from several pages without duplicating it. Engines and LLMs strongly prefer linked graphs.

How much schema is too much?

Irrelevant or invented schema is worse than no schema. Each type should correspond to something real on the page. Marking a pricing page as Recipe is plainly wrong; marking it as AggregateOffer makes sense. Rule: if you cannot defend why you put it there, take it out.

Sources