Implementing schema markup in Adobe Experience Manager requires a different approach than adding JSON-LD to a static site or a simple CMS. AEM's component-based architecture means structured data lives inside HTL (formerly Sightly) templates, gets its values from content repository nodes, and must scale cleanly across multi-site and multi-language configurations. Done correctly, schema markup in AEM becomes part of the component lifecycle – authored once, rendered consistently, and maintainable without developer intervention for every update.

This guide covers the complete AEM implementation path: from injecting JSON-LD through HTL templates to mapping Sling model properties to schema.org vocabulary, handling multi-site setups, and validating output before deployment.

Step 1: Choose Your Schema Injection Method

AEM supports two primary approaches for injecting JSON-LD structured data. The right choice depends on whether your team prioritizes authoring flexibility or developer control.

How to Implement Schema Markup in Adobe Experience Manager (AEM)
How to Implement Schema Markup in Adobe Experience Manager (AEM)

HTL template injection places the JSON-LD <script> block directly inside a component's HTL file. The template renders structured data server-side as part of the page's HTML output, which means it is immediately available to crawlers and AI systems on first load without any JavaScript execution required.

This approach is most appropriate when schema values map cleanly to content properties that are already stored in the JCR (Java Content Repository). It also integrates naturally with AEM's dispatcher cache since the output is static HTML.

Option B: Editable Template Policy Injection

For global schema types like Organization or WebSite that apply to every page, placing the JSON-LD block inside an editable template's page policy component centralizes the markup without duplicating it across individual components. Authors can update values through the policy dialog rather than touching code.

Note that JSON-LD is Google's recommended format for structured data delivery precisely because it separates markup from HTML structure – making it the natural fit for AEM's component model regardless of which injection method you choose.

Step 2: Set up the Sling Model for Schema Data

Before writing any HTL, create a Sling model that exposes the content properties your schema needs. Sling models act as the data layer between JCR content nodes and your templates.

Create the Sling Model Class

In your AEM project's core bundle, create a Java class annotated with @Model. Adapt from the relevant resource type and inject the properties your schema requires.

@Model(
 adaptables = Resource.class,
 defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ArticleSchemaModel {

 @ValueMapValue
 private String jcr:title;

 @ValueMapValue
 private String articleBody;

 @ValueMapValue
 private String publishedDate;

 @ValueMapValue
 private String authorName;

 // Getters
 public String getTitle() { return jcr:title; }
 public String getArticleBody() { return articleBody; }
 public String getPublishedDate() { return publishedDate; }
 public String getAuthorName() { return authorName; }
}

In practice, property names in AEM use namespaced keys like jcr:title. Adjust your @ValueMapValue annotations to match your content model's actual property names. Using DefaultInjectionStrategy.OPTIONAL prevents failures when optional fields are absent from a node, which is common across multi-site setups where content completeness varies by market.

Register the Model With the Correct Resource Type

The adaptables and resource type binding ensure the model only activates on the correct component nodes. Mis-scoped models are one of the most common causes of missing schema output in AEM.

Step 3: Write the HTL Template to Output JSON-LD

With the Sling model in place, the HTL template can reference its properties and render a properly formed JSON-LD block.

Basic Article Schema Example

<sly data-sly-use.schemaModel="com.yourproject.core.models.ArticleSchemaModel">
<script type="application/ld+json">{
 "@context": "https://schema.org",
 "@type": "Article",
 "headline": "${schemaModel.title @ context='scriptString'}",
 "articleBody": "${schemaModel.articleBody @ context='scriptString'}",
 "datePublished": "${schemaModel.publishedDate @ context='scriptString'}",
 "author": {
 "@type": "Person",
 "name": "${schemaModel.authorName @ context='scriptString'}"
 }
}</script>
</sly>

Two rules govern safe HTL output inside a JSON-LD block. First, always use the @ context='scriptString' attribute on every dynamic value. Without it, AEM's XSS protection applies HTML encoding, which corrupts JSON syntax – a common source of structured data errors that is easy to miss in testing because the page renders normally while the schema breaks silently. Second, wrap the entire block in <sly> tags so no additional HTML element is introduced around the script tag.

Conditional Rendering

Avoid outputting empty schema properties. Use data-sly-test to suppress the script block entirely when required fields are missing:

<sly data-sly-use.schemaModel="com.yourproject.core.models.ArticleSchemaModel"
 data-sly-test="${schemaModel.title}">
<script type="application/ld+json">...</script>
</sly>

This ensures Google's Rich Results Test and schema validators never encounter partially formed markup, which can trigger warnings even when the incomplete fields are not required properties.

Step 4: Map AEM Content Properties to Schema.org Vocabulary

Mapping is where most AEM schema implementations go wrong. AEM content models are designed around authoring workflows, not schema.org's vocabulary and the two rarely align out of the box.

Identify the Schema Type and its Required Properties

Start by confirming which schema type applies to the page. Schema.org types and their properties vary significantly by content category. An Article page, a Product page, and a LocalBusiness page each require different fields, and the required versus recommended distinction affects which gaps matter. Understanding required versus recommended schema.org properties before mapping prevents over-engineering and helps prioritize what authors must fill in.

Common AEM-to-Schema Property Mappings

AEM Property Schema.org Property Schema Type
jcr:title headline Article, NewsArticle
jcr:description description Article, Product
jcr:created datePublished Article
cq:lastModified dateModified Article
fileReference (DAM asset) imageurl All types
price offers.price Product
authorName author.name Article

For properties that do not have a direct JCR equivalent – such as sameAs for Organization schema or breadcrumb for navigational markup – extend the Sling model to compute derived values from page hierarchy or configuration OSGi services rather than expecting authors to enter them manually.

Handling DAM Asset URLs

Image properties in AEM store a DAM path (e.g., /content/dam/site/image.jpg) rather than an absolute URL. Your Sling model must resolve this to a fully qualified URL using ExternalizerService before passing it to the HTL template:

@OSGiService
private Externalizer externalizer;

public String getImageUrl() {
 return externalizer.publishLink(resourceResolver, imageReference);
}

Omitting this step causes image URLs in schema to be relative paths, which validators accept but crawlers may fail to resolve correctly across CDN configurations.

Step 5: Handle Multi-Site Manager (MSM) and Multi-Language Setups

AEM's Multi-Site Manager (MSM) creates live copies of content trees for different markets, regions, and languages. Schema markup must reflect the correct locale, currency, and regional entity data for each site – not the blueprint values.

Use Site-Specific OSGi Configurations

Store schema values that vary by site – organization name, logo URL, regional telephone numbers, and local business addresses – in OSGi configuration files scoped to each run mode. This prevents blueprint values from bleeding into live copies.

Create a configuration factory service interface:

@ObjectClassDefinition(name = "Schema Markup Configuration")
public @interface SchemaConfig {
 String organizationName() default "";
 String organizationUrl() default "";
 String logoUrl() default "";
 String telephone() default "";
}

Bind different .cfg.json files to different run modes (e.g., config.publish.en-us, config.publish.de-de) so each environment pulls the correct values at runtime without any code changes.

Locale-Aware inLanguage and datePublished Formatting

Schema.org's inLanguage property expects a BCP 47 language tag (e.g., en-US, de-DE). AEM stores locale information in the page's jcr:language property within the jcr:content node. Inject this value into your Sling model via the Page API rather than hardcoding it:

@ScriptVariable
private Page currentPage;

public String getLanguage() {
 return currentPage.getLanguage(false).toLanguageTag();
}

Date formatting similarly requires locale awareness. Schema.org expects ISO 8601 format (YYYY-MM-DD). AEM's Calendar objects stored in JCR must be formatted before output – passing a raw Calendar object through to HTL will produce a non-compliant date string that validators flag as an error.

Blueprint Vs. Live Copy Schema Differentiation

For content that is identical across live copies except for locale-specific fields, use MSM's rollout configuration to exclude schema-related properties from inheritance. Mark properties like telephone and streetAddress as cq:PropertyName exclusions in the live copy sync configuration so regional editors can override them without triggering a rollout conflict.

Step 6: Add Schema to the Page Head via Client Libraries or Page Component

JSON-LD blocks placed inside individual components render in the page body, which is valid. However, for global schema types like WebSite, Organization, or BreadcrumbList, placing markup in the <head> is cleaner and reduces the risk of duplication when multiple components render on the same page.

Inject via the Page Component HTL

AEM's base page component (/libs/wcm/foundation/components/page) exposes a customizable head.html section. Override this in your project's page component to include global schema:

<!-- In customheaderlibs.html or head.html override -->
<sly data-sly-use.pageSchemaModel="com.yourproject.core.models.PageSchemaModel">
<script type="application/ld+json">{
 "@context": "https://schema.org",
 "@type": "WebSite",
 "name": "${pageSchemaModel.siteName @ context='scriptString'}",
 "url": "${pageSchemaModel.siteUrl @ context='scriptString'}",
 "potentialAction": {
 "@type": "SearchAction",
 "target": "${pageSchemaModel.searchTarget @ context='scriptString'}",
 "query-input": "required name=search_term_string"
 }
}</script>
</sly>

This approach ensures the markup appears once per page regardless of how many content components render in the body, which matters for schema implementations that affect AI citation eligibility – duplicate Organization or WebSite blocks can confuse both validators and AI retrieval systems.

Avoid Duplicate Schema Output

When component-level and page-level schema coexist, audit the full rendered HTML to confirm no type is output twice. Duplicate @type blocks – particularly Organization – cause validation warnings and reduce the reliability of entity recognition by search engines and AI systems.

Step 7: Generate and Validate Schema Output

Testing schema markup in AEM requires validating both the raw JSON structure and its semantic correctness against schema.org expectations.

Validation Tools to Use

Use these tools in sequence before deploying schema changes to production:

  1. Google Rich Results Test (search.google.com/test/rich-results): Confirms whether the markup qualifies for rich result features. Paste the page URL or the raw HTML output.
  2. Schema.org Validator (validator.schema.org): Checks structural validity against the full schema.org specification, including property types and nesting depth.
  3. Google Search Console – Enhancements Report: After deployment, monitors for warnings and errors across all indexed pages at scale.

For teams managing schema across dozens of AEM page templates, AuthorityStack.ai's AI-powered schema markup generator can scan any URL and generate accurate JSON-LD by reading and understanding full page content – useful for auditing existing AEM pages before retrofitting structured data templates.

Test Rendered Output, Not Template Source

AEM templates render differently depending on run mode, dispatcher cache state, and personalization context. Always test against the fully rendered HTML from the publish environment, not the HTL source. Fetch the page with curl and inspect the <script type="application/ld+json"> blocks directly to confirm values are populated correctly and no encoding artifacts have corrupted the JSON.

A common issue to check: AEM's dispatcher sometimes strips script tags from cached responses depending on filter configuration. Verify that application/ld+json script blocks are explicitly allowed in your dispatcher filter rules.

Validate Multi-Site Output Separately

Run validation for at least one page from each site configuration in your MSM setup. OSGi configuration scoping errors – where a blueprint value appears in a live copy's output – only surface when you test the live copy URL, not the blueprint.

Step 8: Scale Schema Across Templates and Content Types

A single working schema implementation is a proof of concept. Scaling it across AEM requires a systematic approach to template coverage, authoring governance, and ongoing maintenance.

Map Schema Types to Page Templates

Create a schema coverage matrix that lists every page template in your AEM project against the schema type it should output. Common mappings for enterprise AEM implementations include:

Page Template Primary Schema Type Secondary Type
Article / Blog Post Article or BlogPosting BreadcrumbList
Product Page Product BreadcrumbList
Location / Store Page LocalBusiness BreadcrumbList
FAQ Page FAQPage
Author Profile Person
Homepage WebSite + Organization
Event Page Event

For SaaS product pages and e-commerce implementations, the template-to-schema mapping becomes more granular – product variants, pricing tiers, and review aggregates each require their own property handling within the same template. Plan this mapping before writing templates, not after.

Establish Authoring Guidelines

Structured data quality depends on content quality. Authors filling in the fields that feed schema properties must understand which fields are mandatory for schema output, not just for page completeness. Add field-level descriptions in the AEM component dialog that explain the schema consequence of each property:

Article Headline – Required. Maps to schema.org/headline. Leaving this blank suppresses the Article schema block for this page.

This framing shifts authors from thinking about UI fields to understanding downstream effects on search and AI visibility.

Use Workflow Models for Schema Auditing

AEM's workflow engine can trigger post-activation checks that verify schema output before a page goes live. A custom workflow step that calls the Schema.org Validator API and blocks activation on structural errors catches issues at publish time rather than after indexing.

FAQ

What Is the Correct Way to Output JSON-LD in AEM Without XSS Errors?

Use @ context='scriptString' on every dynamic value inside a <script type="application/ld+json"> block in HTL. Without this context flag, AEM's default XSS protection applies HTML encoding to output, which replaces characters like " with &quot; and breaks JSON syntax. The scriptString context escapes values safely for use inside JavaScript and JSON contexts without corrupting the structure.

Does Schema Markup in AEM Affect AI Citation Eligibility?

Yes. Structured data in JSON-LD format provides AI systems with machine-readable signals about content type, authorship, publication date, and entity relationships. Pages with correctly implemented schema are more consistently cited by systems like ChatGPT, Perplexity, and Google AI Overviews because the markup removes ambiguity about what the page contains and who it is from. The relationship between structured data and AI citation rates is well documented, and AEM implementations are no exception.

Should Schema JSON-LD Go in the <head> or the Page Body in AEM?

Both locations are valid according to Google's specification. In practice, global schema types like Organization, WebSite, and BreadcrumbList belong in the <head> via the page component to ensure they render once per page. Content-specific types like Article, Product, or FAQPage can render in the body inside the relevant component's HTL output. The key constraint is avoiding duplicate output of the same @type on a single page.

How Do You Prevent MSM Live Copies From Inheriting Wrong Schema Values?

Configure the MSM live copy sync to exclude schema-relevant JCR properties from inheritance using cq:PropertyName exclusions in the rollout configuration. Store regionally variable values – organization name, telephone, address, currency – in run-mode-scoped OSGi configuration files rather than content nodes, so each market environment loads its own values independently of the blueprint.

Can AEM Dispatcher Caching Break Schema Markup?

Yes. AEM Dispatcher filter rules sometimes strip or block <script> tags from cached responses, including application/ld+json blocks. Verify that your Dispatcher configuration explicitly allows script tags in cached HTML files. Test by fetching the cached page URL with curl -H "Accept-Encoding: identity" and searching the raw HTML for the JSON-LD blocks to confirm they are present in the cached output.

What Is the Best Way to Handle Image URLs in Schema When Using the AEM DAM?

Use AEM's ExternalizerService in your Sling model to convert DAM asset paths to fully qualified absolute URLs before passing them to the HTL template. Schema.org's image property requires an absolute URL; relative paths like /content/dam/site/image.jpg are technically invalid and may fail to resolve correctly across CDN configurations. The externalizer.publishLink() method applies the correct domain and protocol based on the publish environment's Externalizer OSGi configuration.

How Do You Validate Schema Output Across All Pages in a Large AEM Implementation?

For site-wide auditing, use Google Search Console's Enhancements Report after deployment to monitor errors and warnings at scale. For pre-deployment validation, integrate a schema validation step into the AEM workflow model that fires on page activation and calls the Schema.org Validator API. For ongoing monitoring of AI citation eligibility – not just structural validity – tools that track how structured data affects AI visibility provide a layer of insight that standard validators do not offer.

Which Schema Types Should an AEM Multi-Site Implementation Prioritize First?

Prioritize Organization and WebSite at the global level first, since these establish entity identity for the entire domain and affect how AI systems recognize and describe your brand. Then implement BreadcrumbList across all templates to support navigational clarity. After that, prioritize content-specific types by traffic volume: Article for content hubs, Product for commerce pages, LocalBusiness for location pages. Managing schema across multiple client sites follows the same priority order – global entity schema first, then content-type coverage by impact.

What to Do Now

  1. Audit your existing AEM page templates to identify which ones currently output no schema markup – this is your implementation backlog.
  2. Build and test the Sling model and HTL template for your highest-traffic template type first, validate the output with Google's Rich Results Test, and confirm dispatcher caching does not strip the script block.
  3. Extend the implementation to remaining templates in priority order, following the template-to-schema type matrix outlined in Step 8.
  4. Set up Google Search Console's Enhancements Report to monitor for errors after each deployment wave.
  5. For multi-site setups, validate at least one page per live copy configuration separately before marking the rollout complete.

Generate your JSON-LD schema markup instantly with AuthorityStack.ai's AI-powered schema generator – enter any URL and it reads your full page content to output accurate, ready-to-deploy structured data.