Structured Content
This guide helps you configure an existing Edge Delivery Services project with Document Authoring to use the Structured Content feature, enabling schema-based content editing and JSON data delivery.
Structured Content brings form-based editing to Document Authoring: you define a schema, authors fill in a generated form, and the result is delivered as JSON that your Edge Delivery Services blocks can consume. If you're familiar with JSON Schema or form builders the concepts will feel familiar—but even if you're new to them, the guided editor makes it approachable.
Key Concepts
Before you begin, here are a few important concepts:
- Schemas define structure. Every structured content document is based on a schema that defines what fields are available and how they're organized.
- The editor has two panels. The Navigation panel shows your content hierarchy as a tree, while the Editor area provides full-width forms for each section.
- Changes auto-save. Your content is saved automatically as you type—no save button needed.
- Data is delivered as JSON. Published content is automatically available via a JSON endpoint for your blocks to consume.
Overview
Structured Content allows you to:
- Define content schemas using a documented subset of JSON Schema
- Edit structured data through a form-based editor
- Deliver content as JSON for consumption by blocks on your Edge Delivery Services site
- Preview and publish structured content
Prerequisites
Required Access and Permissions
To set up and use Structured Content, you need:
- An existing Edge Delivery Services project with Document Authoring enabled, or the ability to create a new one
- Organization Administrator role to configure the
editor.pathsetting - Content Author permissions for the site to create folders, documents, and schemas
If you don't have the required permissions, contact your Document Authoring organization administrator to request access.
Setup Steps
1. Create or Use an Existing Edge Delivery Services Project
If you don't have an Edge Delivery Services project yet, follow the official tutorial at https://www.aem.live/developer/tutorial
2. Configure Structured Content Storage
Structured content documents are stored in your project's content tree in Document Authoring.
Create a dedicated folder:
- Navigate to your project in Document Authoring at
https://da.live/#/<ORG>/<SITE> - Create a new folder (e.g.,
/forms,/products, or any custom name) - This folder will store all your structured content documents
Enable the Structured Content Editor:
You must configure Document Authoring to use the Structured Content editor for documents in your chosen folder. This is done by adding a configuration entry that maps a folder path to the editor URL.
- Open the Document Authoring config page:
https://da.live/config#/<ORG>/(requires Organization Administrator role) - Add a new config entry with the following:
| key | value |
|
|
Example:
| key | value |
|
|
This configuration tells Document Authoring: "For any document created in the /aemsites/myproject/products folder, open it with the Structured Content editor instead of the default Document Authoring editor."
Multiple Folder Configuration:
To use the Structured Content editor for multiple folders, add multiple editor.path entries:
| key | value |
|
|
|
|
3. Create a Schema
Schemas define the structure of your content and are created using the Schema Editor app.
- Open the Schema Editor app: https://da.live/apps/schema
- Use the dropdown menu to select "New Schema"
- Define your schema following the documented subset (see Working with Schemas below, and the canonical schema-spec.md)
- Save the schema
Note: Schemas are stored as documents under /<ORG>/<SITE>/.da/forms/schemas/, but they are only visible and manageable through the Schema Editor app.
4. Create Your First Structured Content Document
- Navigate to your structured content folder:
https://da.live/#/<ORG>/<SITE>/<FOLDER_NAME> - Click "New" to create a new document
- Give your document a name (e.g.,
morning-muse-light-roast)
5. Select a Schema
When the Structured Content editor loads:
- You'll be prompted to select one of your schemas
- Choose the appropriate schema for your content type (e.g., "Coffee Product")
- The form-based editor loads with fields corresponding to your schema
6. Editing Your Content
The editor makes managing complex data structures straightforward. It has two main areas:
Navigation Panel:
- Reflects the structure of your content as a tree, with nested objects and array items
Editor Area:
- Provides a flat view of the current section using the full width of the editor
- Shows all fields for the selected node with appropriate input controls (text inputs, number steppers, checkboxes, dropdowns for
enum, and add/remove controls for arrays)
Changes are saved automatically as you type.
7. Preview and Publish
When you preview or publish your document:
- Use the standard Preview or Publish buttons in the editor
- You'll be redirected to the delivery URL
- The page displays an overview of your structured data
- The same URL can be used by blocks to fetch JSON data
8. Consume Data in Blocks
Blocks on your Edge Delivery Services site can fetch structured content as JSON using the delivery endpoint (see Delivery Endpoint).
Walkthrough: a Frescopa coffee catalog
This end-to-end example ties the pieces together using Frescopa, the Edge Delivery Services coffee demo. We'll model a Coffee Product, author one product (Frescopa's "Morning Muse Light Roast"), and fetch it as JSON. The schema exercises most of the supported features—strings with length and pattern constraints, numbers and integers with bounds, enum dropdowns, a boolean, an array of primitives, a nested object, and a reusable $def referenced via $ref.
The schema (paste this into the Schema Editor as a new schema named coffee):
- schema (json)
{
"$defs": {
"Faq": {
"type": "object",
"title": "FAQ",
"required": [
"question"
],
"properties": {
"question": {
"type": "string",
"title": "Question"
},
"answer": {
"type": "string",
"title": "Answer"
}
}
}
},
"type": "object",
"title": "Coffee Product",
"required": [
"name",
"slug",
"price",
"status"
],
"properties": {
"name": {
"type": "string",
"title": "Product Name",
"minLength": 2,
"maxLength": 80
},
"slug": {
"type": "string",
"title": "Slug",
"minLength": 3,
"maxLength": 60,
"pattern": "^[a-z0-9-]+$"
},
"description": {
"type": "string",
"title": "Description",
"maxLength": 400
},
"category": {
"type": "string",
"title": "Category",
"enum": [
"Bagged Coffee",
"Coffee Pods",
"Coffee Machines",
"Bundles",
"Accessories"
],
"default": "Bagged Coffee"
},
"roastLevel": {
"type": "string",
"title": "Roast Level",
"enum": [
"Light",
"Medium",
"Medium-Dark",
"Dark"
],
"default": "Medium"
},
"origin": {
"type": "string",
"title": "Origin"
},
"price": {
"type": "number",
"title": "Price",
"minimum": 0
},
"currency": {
"type": "string",
"title": "Currency",
"enum": [
"USD",
"EUR",
"GBP"
],
"default": "USD"
},
"weightGrams": {
"type": "integer",
"title": "Bag Weight (g)",
"minimum": 0
},
"status": {
"type": "string",
"title": "Status",
"enum": [
"Draft",
"Active",
"Discontinued"
],
"default": "Draft"
},
"inStock": {
"type": "boolean",
"title": "In Stock",
"default": true
},
"rating": {
"type": "number",
"title": "Average Rating",
"minimum": 0,
"maximum": 5
},
"tastingNotes": {
"type": "array",
"title": "Tasting Notes",
"minItems": 1,
"maxItems": 10,
"items": {
"type": "string",
"title": "Note"
}
},
"brewing": {
"type": "object",
"title": "Brewing Guide",
"properties": {
"method": {
"type": "string",
"title": "Recommended Method"
},
"ratio": {
"type": "string",
"title": "Coffee-to-Water Ratio"
},
"temperature": {
"type": "integer",
"title": "Water Temperature (°C)",
"minimum": 0,
"maximum": 100
}
}
},
"faqs": {
"type": "array",
"title": "FAQs",
"items": {
"$ref": "#/$defs/Faq"
}
}
}
}
The data. After selecting the coffee schema for a new document and filling in the form, the saved document delivers the following JSON. (You can also paste this JSON to seed a local instance for testing.)
- data (json)
{
"name": "Morning Muse Light Roast",
"slug": "morning-muse-light-roast",
"description": "A bright, citrusy light roast with a smooth finish—a perfect start to any day.",
"category": "Bagged Coffee",
"roastLevel": "Light",
"origin": "Yirgacheffe, Ethiopia",
"price": 16.5,
"currency": "USD",
"weightGrams": 340,
"status": "Active",
"inStock": true,
"rating": 4.7,
"tastingNotes": ["citrus", "floral", "smooth finish"],
"brewing": { "method": "Pour-over (V60)", "ratio": "1:16", "temperature": 94 },
"faqs": [
{ "question": "Is this coffee whole bean or ground?", "answer": "It ships as whole bean by default; choose your grind at checkout." },
{ "question": "How fresh is it?", "answer": "Every bag is roasted to order and shipped within 48 hours." }
]
}
Fetch it from a block once the document is published:
- sample (javascript)
const response = await fetch(
'https://da-sc.adobeaem.workers.dev/live/aemsites/da-frescopa/products/morning-muse-light-roast'
);
const product = await response.json();
Working with Schemas
Schemas are the foundation of structured content—they define what fields exist, what types of data they accept, and how they're organized. Structured Content consumes a documented subset of JSON Schema 2020-12. Anything outside the subset is ignored.
Read the full specification before authoring a schema. The complete, authoritative, and always-current reference lives in the da-sc-sdk repository: schema-spec.md. It defines the exact authoring rules, the supported types, the presentation annotations and validation constraints, how $defs / $ref reuse works, and the empty-value semantics that decide what ends up in the delivered JSON. This guide intentionally does not duplicate it—treat the spec as the single source of truth.
A few orientation points to know before you open the spec:
- Every node needs an explicit
typeand atitle. - Use
enumfor closed value sets—it renders as a dropdown. - Factor repeated shapes into
$defsand reference them with same-document$ref(#/$defs/Name). - The property keys
metadataandsection-metadataare reserved (see Page metadata & indexing). - Empty values are treated as absent and stripped from the saved document.
The walkthrough above shows a schema that exercises these in practice.
Delivery Endpoint
Once your content is published, it becomes available as JSON through a delivery endpoint. This is how your Edge Delivery Services blocks fetch the structured data.
Endpoint Format
https://da-sc.adobeaem.workers.dev/<ENVIRONMENT>/<ORG>/<SITE>/<PATH_TO_DOCUMENT>
Parameters:
<ENVIRONMENT>:live(published) orpreview(preview content)<ORG>: your organization name<SITE>: your site name<PATH_TO_DOCUMENT>: path to your document (without file extension)
Example
curl 'https://da-sc.adobeaem.workers.dev/live/aemsites/da-frescopa/forms/offer'
Note: The delivery endpoint URL may change in the future. We will communicate any changes through official channels.
Accessing protected content
The delivery API supports authenticated access, so it can serve content from protected sites and paths. If your content is protected, the client must first complete the site authentication setup to obtain a token—follow Authentication setup for a site.
Once you have a token, pass it in the authorization header (replace hlx__TOKEN with your token):
GET https://da-sc.adobeaem.workers.dev/<ENVIRONMENT>/<ORG>/<SITE>/<PATH_TO_DOCUMENT>
authorization: token hlx__TOKEN
Listing and querying structured content
The delivery endpoint returns one structured-content document at a time. When a block needs to render a list across many documents (or to filter, sort, or project specific fields without downloading each document one by one), configure a query-index.
The index runs as part of the Edge Delivery Services pipeline and produces a single JSON file at a path you choose, listing one row per included document with whichever properties you extract. The resulting JSON is publicly cacheable, so the same index can be consumed by EDS blocks and by external clients.
Define indices in helix-query.yaml at the root of your project's GitHub repository (see the Indexing reference for the full schema).
Recommended selector pattern (key-anchored): the form editor renders each property key as an element with an id matching that key. Select properties by key name with :has():
- schema (json)
version: 1
indices:
products:
target: /products-index.json
include:
- /products/**
exclude:
- "**/fragments/**"
- "**/drafts/**"
- "**/*.json"
properties:
name:
select: div > div > div:has(#name) > div:last-child
value: textContent(el)
price:
select: div > div > div:has(#price) > div:last-child
value: textContent(el)
status:
select: div > div > div:has(#status) > div:last-child
value: textContent(el)
inStock:
select: div > div > div:has(#inStock) > div:last-child
value: textContent(el)
This produces an index at https://main--<REPO>--<OWNER>.aem.live/products-index.json with name, price, status, and inStock extracted for every product document under /products/.
Page metadata & indexing (robots, sitemap)
When a structured content page is published it behaves like any other Edge Delivery Services page: it can be crawled, it appears in query-index.json, and it can be listed in sitemap.xml. Today the form editor view does not expose a way to set page-level metadata (for example robots: noindex, nofollow, a canonical URL, or a custom title/description). If you need to keep certain structured pages out of search results or out of the sitemap, use one of the workarounds below.
Why there's no field for it: page metadata is a property of the page, not of your schema's data. In fact the keys metadata and section-metadata are reserved in schemas (see the schema specification) precisely so your data never collides with the page's metadata block.
Workaround 1 — Metadata block (per page, via Edit mode)
Open the document in the standard Document Authoring editor (Edit mode) rather than the form editor, and add a Metadata block—a two-column table whose first row is the word metadata. Each subsequent row is a key/value pair:
| metadata | |
| robots | noindex, nofollow |
| title | Morning Muse Light Roast |
| description | A bright, citrusy light roast with a smooth finish. |
On publish this renders as <meta name="robots" content="noindex, nofollow"> (and the corresponding title/description tags) in the page <head>.
Workaround 2 — Bulk Metadata sheet (many pages by path)
To apply metadata to many structured pages at once, create or edit a Bulk Metadata spreadsheet (commonly /metadata) with a URL column that matches a path pattern. For example, to keep an entire folder out of search engines:
| URL | robots |
| /products/drafts/** | noindex, nofollow |
See the Bulk Metadata reference for the sheet format and matching rules.
Keeping pages out of the sitemap and query-index
robots: noindextells search engines not to index the page, but does not by itself remove it from a generatedsitemap.xml.- To exclude pages from
query-index.json(and therefore from a sitemap derived from it), add anexcludeglob inhelix-query.yaml(see the example in the previous section). - Configure the sitemap itself via
helix-sitemap.yaml(see the Sitemap reference).
Limitations
Maximum Depth Level
Using $ref / $defs can produce recursive structures. To prevent infinite loops the current implementation limits expansion to 10 levels. This value is experimental and may change. Prefer flattening deeply nested structures.
For the full set of supported and unsupported constructs, see schema-spec.md. If you need something it doesn't cover, please reach out to discuss your use case.
Under the hood: da-sc-sdk
The schema validation, the JSON↔HTML conversion, and the editing engine are provided by da-sc-sdk—a headless, pure-ESM SDK with no DOM or I/O dependencies. The form editor and the delivery API build on it.
Useful exports for developers building their own tooling:
validateSchema— lint a schema against the supported subsetvalidateData— validate a document's data against a schema (errors keyed by JSON Pointer)convertJsonToHtml— serialize a{ metadata, data }document to the DA EDS HTML wire formatconvertHtmlToJson— parse the wire format back into JSONcreateEngine— a stateful editing engine (setField,addItem,removeItem,moveItem, …)
Reminder: da-sc-sdk is an early version. Treat schema-spec.md as the source of truth for what a schema may contain.
Troubleshooting
Structured Content editor not loading
- Verify the
editor.pathconfig in your Document Authoring config is correct - Ensure the folder path matches exactly (case-sensitive)
- Confirm you have Organization Administrator permissions to modify the configuration
- Clear browser cache and try again
Schema not appearing in Schema Editor app
- Check that the schema was saved successfully
- Verify you have Content Author permissions for the site
- Refresh the schema list in the Schema Editor app
Data not fetching from delivery endpoint
- Verify the document has been published (not just saved)
- Check the delivery endpoint URL format
- Ensure
<ENVIRONMENT>islivefor published content orpreviewfor preview content
A field's value is missing from the delivered JSON
- Empty strings, empty arrays, and empty objects are treated as absent and stripped on save—fill the field with a real value
- Confirm the property key matches your query-index selector exactly (selectors are key-anchored)