Skip to content

Schema Builder API for SEO Fields

Until now, schema rendering in SEO Fields was all-or-nothing: the plugin auto-generates a basic JSON-LD schema based on your section-level settings, or you disable it entirely and roll your own. There was no middle ground — no way to set a main schema type in a template and then progressively add to it from includes.

That changes with the new Schema Builder API released in version 5.4.0 .

How it works

SEO Fields now holds a shared Graph instance per request. You can access it from any template using seoFields.graph, add schema types, set properties — and at render time, the full graph gets output as a single <script type="application/ld+json"> tag. No manual output needed.

The default schema (organization + section-level type) is still added automatically. Anything you add from your templates merges into the same graph.

Basic usage

Add a schema type to the graph using `seoFields.graph`

The graph reuses the same node per type — calling `.event()` twice returns the same node, so you can set properties across multiple templates without conflicts.

{% do seoFields.graph.event()
    .name(entry.title)
    .description(entry.intro|striptags)
    .url(entry.url)
%}

Building across includes

This is where it gets powerful. Your entry template and its includes all contribute to the same graph:

{# _events/_entry.twig #}
{% do seoFields.graph.event()
    .name(entry.title)
    .description(entry.intro|striptags)
    .url(entry.url)
%}

{% include '_snippets/_faq' with { faqs: entry.faqBlocks } %}

{# _snippets/_faq.twig #}
{% if faqs|length %}
    {% set questions = [] %}
    {% for faq in faqs.all() %}
        {% set questions = questions|merge([
            seoFields.schema.question()
                .name(faq.title)
                .acceptedAnswer(
                    seoFields.schema.answer().text(faq.answer|striptags)
                )
        ]) %}
    {% endfor %}
    {% do seoFields.graph.fAQPage().mainEntity(questions) %}
{% endif %}

The result is a single JSON-LD script tag containing both the Event and the FAQPage, alongside the default organization node:

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "#organization",
      "name": "My Site",
      "url": "https://example.com/"
    },
    {
      "@type": "Event",
      "@id": "#page",
      "name": "My Event",
      "description": "Event description here",
      "url": "https://example.com/events/my-event"
    },
    {
      "@type": "FAQPage",
      "mainEntity": [
        {
          "@type": "Question",
          "name": "How does it work?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Like this!"
          }
        }
      ]
    }
  ]
}

seoFields.graph vs seoFields.schema

There are two entry points, and they serve different purposes:

  • seoFields.graph — the shared Graph instance for the current request. Use this when adding top-level schema types to the page (Event, FAQPage, Article, etc.). Types added here end up in the @graph array.
  • seoFields.schema — a factory for creating standalone schema types. Use this for nested objects like Question and Answer that get passed as properties to graph nodes.

Setting properties

Every schema type supports the fluent API from spatie/schema-org. You can set any schema.org property as a method call:
 

{% do seoFields.graph.article()
    .name(entry.title)
    .author(entry.author.fullName)
    .datePublished(entry.postDate|date('c'))
    .image(entry.featuredImage.one().url)
%}

## For properties that aren't available as named methods, use `setProperty`:


{% do seoFields.graph.event()
    .setProperty('@id', '#my-event')
    .setProperty('eventAttendanceMode', 'https://schema.org/OfflineEventAttendanceMode')
%}

What happens without any template code

Nothing changes for existing sites. If you don't use seoFields.graph in your templates, the plugin still outputs the same default schema it always has — the organization node plus the section-level type (WebPage, Article, etc.) configured in your SEO Fields settings.

Organisation settings

In order to for your sites to have a base level of organizational- or project-level that, the plugin also comes with fields where you can add a name, type, logo and aliases (for socials and other places on the that reflect the organization, company or project)

Screenshot of the settings screen where fields for organizational details have been added in this update

Debugging & development

Latest, the plugin now adds a panel to Craft's debug bar where you can see the structured data for the page and wether or not the data is valid.

And lastly, for those building with AI, the documentation for the plugin and these new features are now also available on Context7, which makes it easy to instruct Claude Code or Codex to use this feature.

SEO Fields

Full featured SEO solution with meta data, sitemaps, redirects & structured data

$59 Plugin Store Github

Studio Espresso

Studio Espresso Monthly

Once a month, I send out a personal newsletter where I talk about my work, life and interesting things I’ve seen and done. Broader than web development and a bit more personal. If you’re interested you can subscribe below.
© 2026 Studio Espresso