Posted by

JSON-LD Structured Data in HubSpot CMS: Build Schema Markup with HubL and tojson

Structured data tells search engines what a page is about in a machine-readable way. The format Google recommends is JSON-LD: a small <script type="application/ld+json"> block that describes the page using the schema.org vocabulary. In HubSpot CMS you can generate it directly from the page's own data with HubL, so it never drifts out of sync with the content editors publish.

This guide shows how to build a BlogPosting object in a blog post template, output it safely with the tojson filter, skip fields that are empty, and validate the result.

Why generate JSON-LD in the template

  • One source of truth. The title, description, image and dates come from the same variables that render the page.
  • No hand-written JSON. Building a HubL dictionary and passing it to tojson means quotes and special characters in titles are escaped for you, instead of breaking the JSON with a stray ".
  • Applies to every post. Add it once to the blog post template and every post gets it.

Build the object in HubL

Start by collecting the required values in a dictionary. Inside a blog post template, the content variable holds the current post.

{% set schema = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": content.name,
  "mainEntityOfPage": content.absolute_url,
  "datePublished": content.publish_date|format_datetime("yyyy-MM-dd", "UTC"),
  "author": {
    "@type": "Person",
    "name": content.blog_post_author.display_name
  }
} %}

Keep headline reasonably short. Google recommends a concise headline for article markup, so use the post title rather than a long marketing sentence.

Add optional fields only when they exist

Not every post has a featured image or a meta description. Emitting an empty value produces markup that validators flag, so add optional keys only when there is something to put in them. HubL dictionaries support the update method for this:

{% if content.meta_description %}
  {% do schema.update({"description": content.meta_description}) %}
{% endif %}

{% if content.featured_image %}
  {% do schema.update({"image": [content.featured_image]}) %}
{% endif %}

Google's guidance for article markup recommends providing images that are crawlable and indexable, so use the real featured image URL rather than a placeholder.

Output it with tojson

Once the dictionary is ready, print it inside a script tag:

<script type="application/ld+json">
  {{ schema|tojson }}
</script>

The tojson filter converts the dictionary into valid JSON text, quoting strings and escaping characters inside them. This is the same filter covered in our article on escaping output safely in HubSpot CMS: it is the right tool when the destination is JavaScript or JSON, rather than escape, which is meant for HTML. Always check the rendered page source once to confirm the output is valid JSON for your own content.

Where to place the script

JSON-LD works in either the <head> or the <body>. In a template, a simple option is to put it in the <head>, next to {{ standard_header_includes }}, which every HubSpot template needs. If you would rather ship it as a reusable piece, you can place the same code in a custom module and move it into the head with the {% require_head %} tag. Check the current HubL documentation for the exact behavior before relying on it in production.

Common mistakes

  • Marking up content that is not on the page. Structured data should describe what visitors can actually see. Do not add ratings, authors or dates in JSON-LD that the page does not show.
  • Hand-building JSON with string concatenation. A quote in a post title will break the whole block. Use a dictionary and tojson.
  • Duplicate markup. Some themes already output schema. If your theme does, adding a second BlogPosting can create conflicting data. View the page source and search for ld+json first.
  • Wrong date format. Use an ISO 8601 date such as 2026-11-07. A formatted display date like "Nov 7, 2026" is not valid for datePublished.
  • Forgetting dateModified. If your posts are updated often, consider adding dateModified using the post's last-updated date variable (look up its exact name in the HubSpot blog post variables reference), formatted the same way as datePublished.

Validate before you publish

  1. Open the published or preview page, view the source, and find the ld+json block. Confirm it is valid JSON and that the values match the page.
  2. Run the URL through Google's Rich Results Test, and the Schema Markup Validator at validator.schema.org for general schema.org syntax.
  3. After release, watch the enhancement reports in Google Search Console for errors or warnings.

Note that valid structured data does not guarantee a rich result in search. It helps search engines understand the page, and eligibility for special display is decided by the search engine.

Quick checklist

  • Build the data as a HubL dictionary, not as a string.
  • Output it with tojson inside application/ld+json.
  • Add optional fields only when they have a value.
  • Use ISO 8601 dates.
  • Check for duplicate theme markup.
  • Validate with the Rich Results Test and Schema Markup Validator.

Sources

  • HubSpot Developers: HubL variables, filters and functions reference (tojson, format_datetime, dictionary methods)
  • HubSpot Developers: Blog post template variables (content)
  • Google Search Central: Intro to structured data and Article structured data
  • schema.org: BlogPosting