Hosted MDX pages can publish successfully and then fail to render because the publish runner skips their source files. Restore source downloads and compilation for MDX Content Blocks, including dynamic article queries and template dependencies. Reuse the existing publication planner and local content compiler. Markdown-only pages keep their bodies on demand; hosted MDX pages download article sources and pay the necessary compilation cost in the runner, not the publish API. Reject missing content databases and MDX materialization errors instead of deploying an incomplete page. Ref #6450 Ref #6454 ## Checklist - [x] Reproduce missing MDX content using the affected deployed build. - [x] Restore hosted MDX compilation without duplicating the static-export compiler. - [x] Add a sync → compile → generated query → React server-render regression with a metadata-only API bundle and a frontmatter-bound Content Block. - [x] Verify new regression checks fail without the required behavior and pass with it. - [x] Reject missing MDX sources and content databases; preserve remote article bodies when no MDX Content Blocks need compilation. - [x] Review documentation impact and update the Content Engine publishing explanation. - [x] Run CLI tests, typecheck, lint, formatting, and package-boundary checks. - [x] Run the full fixture link/sync/build pipeline; no generated changes. - [x] Rebuild the affected project locally and exercise its generated production request handler: HTTP 200 with the article title and body. Asset requests were served from synced local files. The live project was not modified or republished. Its existing title/description bindings still use an array access for a single-result query; that separate project-data issue is not changed here. Agent evaluations were not run. Affected sites need republishing after the updated CLI artifact is deployed.
31 KiB
description
| description |
|---|
| Create a file-based blog with Markdown or MDX, Assets queries, and dynamic pages. |
📚 Content Engine
Webstudio's Content Engine turns Markdown, MDX, and JSON files in Assets into content you can query and bind in the visual editor. The files remain the source of truth, including their filenames, folders, metadata, and relative links. You can move the same files between projects or use them outside Webstudio without exporting them from a database first.
MDX is a widely used file format that combines Markdown with JSX. In Webstudio, it lets designers pre-style every article element while editors write directly on the canvas with a Notion-style interface. Editors can also work in the MDX source, and AI agents can work through Webstudio MCP.
It brings Content mode to file-based sites without requiring an external CMS or a runtime content database on the published site. Use it to:
- Query file fields and render hundreds of pages from one visual design.
- Edit complete MDX articles on the canvas with changes saved back to the file.
- Give editors pre-styled Markdown elements and custom interactive components.
- Create and edit structured entries through forms generated from a collection schema.
- Build or change the same setup with Webstudio MCP while keeping every setting available in the visual interface.
This guide creates a blog overview at /blog and one dynamic article page at
/blog/:slug.
To see the finished setup first, start with the Markdown Blog marketplace template.
Watch the complete workflow, from the first MDX file to visual editing and structured collection entries:
{% embed url="https://youtu.be/Sn-fhVajpgU" %}

Markdown articles stored alongside their assets
Choose Markdown or MDX
Choose the file format based on how editors will work with the article body:
| Format | Publishing | Editing in Content mode |
|---|---|---|
.mdx with a Content Block |
Publishing downloads the article files and compiles the MDX for every article the page can render. More articles can make publishing slower. | Editors can change the article visually on the canvas with a Notion-style experience, or edit the MDX source file. |
.md with Markdown Embed |
No per-article MDX compilation is needed. A hosted page reads the selected Markdown body when it is requested. | Editors can edit the .md file in the Markdown source editor in Assets, but cannot edit its body visually on the canvas. |
Hosted builds receive a content database for their Assets queries. Markdown body-reference queries use stored frontmatter for that database and fetch the selected body when a visitor opens the page. Static exports download article sources and build the database in the publish runner; this transfer and any MDX compilation still take time. Hosted pages using MDX Content Blocks also download article sources and compile the required MDX in the publish runner. If required MDX content is unavailable or compilation reports an error, the build fails instead of publishing an incomplete page. Use Content Block when visual article editing matters, or Markdown Embed when source editing is enough.
Decide if the Content Engine fits
Use the Content Engine for bounded, file-based content that should live with the site and remain portable as Markdown, MDX, or JSON.
Good fits include:
- Blogs with hundreds of text-and-image articles, within the limits below.
- Portfolios, team directories, resource libraries, and case studies.
- Small product catalogues with infrequently changed display data. Keep orders, inventory, and payments in an ecommerce system.
Use an external CMS, commerce backend, or media service when you need:
- Several thousand entries or complex search and filtering.
- Large image or video galleries, media processing, or streaming.
- Live inventory, customer-specific prices, editorial workflows, scheduled publishing, or content shared across many applications.
Limits that affect this choice
Queries can consider and return at most 1,000 documents. All reachable Assets resources share a 500 KiB published content database. Markdown body reference keeps article bodies out of the database.
Images and videos remain separate Assets, but the Content Engine does not provide media processing or streaming. Review the complete query and content limits before using it for a content-heavy project.
Build it with MCP
An AI agent can complete this entire workflow through Webstudio MCP. It can create the folders and MDX files, upload images, build both pages, configure the Assets resources, connect a Content Block, add the content collection, set the page metadata, and check the rendered result with vision. Everything remains editable in the visual editor.
Give the agent an editable project share link when it asks for one, then make a request such as:
Install Webstudio CLI and use MCP to build a blog with the Content Engine. Store complete articles in MDX files, create
/blogand/blog/:slug, make every article field editable in Content mode, let editors create entries with a collection form, exclude drafts, and check both pages on desktop and mobile.
The remaining steps explain the same workflow when you want to build or inspect it manually.
1. Organize the files
The Content Engine does not require a particular folder structure. This guide uses the following organization in the Assets panel:
blog/
posts/
assets/
Keeping all articles in one folder makes them easy to query. Put images and other supporting files in a nested folder.
Open the settings for the posts folder and copy its ID. Both Assets resources
in this guide use that ID to query only files in this folder.
Make the folder a content collection
This step is optional. The Assets queries and dynamic page work without a collection. Add one when editors should create and update entries through forms instead of editing MDX frontmatter by hand.
Turn on Use as content collection when you create the posts folder if
editors should be able to add articles without writing MDX frontmatter. For an
existing folder, choose Use as content collection from its context menu,
actions menu, or Folder settings, then confirm setup. This creates two files
in the same folder without changing its ID or existing content:
collection.jsondefines the entry fields and their rules with JSON Schema.template.mdxsupplies the starting frontmatter and body for each entry.
Existing selected entries must be MDX files. Setup leaves their content unchanged, even when fields are missing or not yet configured. The collection shows field errors for repair after setup. Other files stay visible and are ignored by collection validation. Setup stops if a selected file has an incompatible type or conflicts with the generated configuration or template. Choose Configure collection after setup to define the fields used by your existing entries.
Select collection entries
The generated collection.json includes filename patterns in x-webstudio.entries:
{
"x-webstudio": {
"template": "template.mdx",
"entries": ["*.mdx"]
}
}
These use URLPattern syntax,
not filesystem globs. Patterns match direct filenames in the current folder,
case-insensitively; subfolders are independent. For example, use
["post-*.mdx", "!post-private-*.mdx"] to select public post files. A leading
! excludes a pattern; exclusions take priority over inclusions. At least one
inclusion is required. Slashes are not allowed. The configuration and configured
template are always excluded from entries, regardless of patterns.
Omitting entries preserves the default ["*.mdx"]. The setting accepts up to
64 patterns of 256 characters each. New entry filenames must match the patterns.
Only MDX entries are currently supported; a pattern selecting another format
reports an error rather than treating that file as editable MDX. Files not
selected remain visible and can still appear in ordinary Assets query results.
Configure fields and the template
The folder remains a normal Assets folder. The direct collection.json file is
what makes Webstudio treat it as a collection and show the collection badge.
The configuration and referenced template files also show this badge. You can
open them or download them when permitted, but cannot cut, copy, duplicate,
move, or delete them individually. Manage them through Collection settings.
Their asset Settings remain available to collection designers: Name is
read-only and Folder is disabled, while Description remains editable.
Open Collection settings → Fields and select a field from the list. Edit Label and Field key together, add a Description that tells editors what to enter, then choose the field's Type. The description appears as help in entry forms without repeating the field label. Set required fields and length or number limits under Validation. Choose Add field to add another entry field. The configurator writes the schema; designers do not need to edit JSON.
Use the Entry template tab to rename the entry template and edit its default frontmatter and starter content with the built-in Markdown editor. Creating an entry keeps you on the current page.
Use the Entry page tab to choose the dynamic page that displays collection
entries. The page must have one URL parameter, such as /blog/:slug.
Webstudio fills the page parameter with the entry filename when you choose
Open on canvas from an entry's menu or Entry settings. The action stays
visible but disabled until the collection has a compatible entry page; its help
points back to this setting. If the page does not exist yet, finish step 5 and
then return to this setting. Creating an entry does not navigate automatically.
The collection format uses a supported subset of JSON Schema draft 2020-12. The configurator exposes string, number, integer, boolean, and slug fields, along with required fields and length or value limits. Set starting values in the entry template frontmatter. Collection fields are flat; arrays and nested objects are not supported. Slug fields reference Webstudio's bundled schema:
{
"$ref": "https://webstudio.is/schemas/slug",
"title": "URL slug",
"description": "The URL-safe name used in this entry's web address.",
"maxLength": 120,
"x-webstudio": { "control": "slug" }
}
Webstudio resolves this reference from its bundled rules, without a network request. Length limits alongside the reference still apply. The configurator writes the reference automatically; designers do not need to write a regex. Other references, composition keywords, enums, formats, and custom regular-expression patterns are not supported. Webstudio reports unsupported rules with their location instead of silently ignoring them.
Editors choose New entry, complete the generated form, and select Create
entry. Webstudio creates a lowercase, dash-separated slug from the configured
source field, which is Title by default.
Slugs preserve letters and numbers from any language, including accents:
Привет мир becomes привет-мир, and 你好世界 stays 你好世界.
Spaces and punctuation become separators. If the title contains only emoji or
symbols, enter a slug manually. Existing filenames are never renamed.
Webstudio recognizes its previously generated slug patterns and uses the bundled Unicode rule for those collections too. Saving collection settings replaces the old pattern with the reference. Existing entry content and filenames remain unchanged. Future rule updates do not require editing each collection's regex.
Editors can change the slug before creating the entry. New entries must satisfy the schema. Existing entries report field errors without blocking incremental edits or publication. The slug becomes the MDX filename and cannot be changed after creation.
Slug fields are optional. Change the Slug field to another type if the collection does not need one. New entries then receive unique generated MDX filenames, without adding a filename field to their frontmatter. Existing filenames stay unchanged. For a manual slug, set Generate from to None (manual entry).
For optional fields, click the field label and choose Reset value, or Alt-click the label, to omit the value, including a value supplied by the template. An empty text value is different from an omitted value. Optional boolean fields offer Yes, No, and Not set. Validation errors appear beside their fields.
Collection settings save automatically when valid. Webstudio checks the entry template against the new rules. Existing entries remain editable if a rule changes. The open collection folder checks entry frontmatter in the background and shows a warning when an entry needs attention. Choose Review first entry, select an entry's error indicator, or choose Entry settings from its menu to edit its fields using the same controls as New entry. Edits save automatically, including partial repairs. Unknown frontmatter properties and the MDX body are preserved. Slugs remain fixed to the filename; an existing mismatch has a repair action.
Use Edit file to edit the body or inspect unconfigured fields. Use Open on canvas to edit the configured entry page visually. Invalid field values are underlined in the source editor; hover them for details. Content-mode text and number controls bound to these fields also show their collection errors. Fixing the values clears the errors. A field error does not hide the entry, stop the collection rendering, or block publication of unrelated changes.
A failed read is reported separately from field errors and can be retried with Check again.
The configurator rejects Integer limits that leave no possible whole number.
Slug limits must allow at least one character, even when minimum length is unset.
Without a Slug field, use a single *.mdx or entry-*.mdx entry pattern when
saving collection settings. These match automatically generated filenames.
Custom filename patterns require a Slug field. Existing configurations remain
readable so their content can still be repaired and published.
A collection folder accepts entries, supporting assets, and subfolders. Use
New entry for filenames selected by the entry patterns. Files excluded by
the patterns behave like ordinary assets: upload, create, paste, move, rename,
and duplicate them normally. The collection configuration and template remain
protected and do not appear in Content
Engine query results. Keep collection.json valid and its referenced template
available. Broken collection configuration can still block collection queries
and publishing when Webstudio cannot safely identify the reserved files.
An invalid template still blocks entry creation and publication. Malformed YAML
and unreadable files are not ordinary field-validation errors; those existing
parsing and access checks still apply.
Delete unused assets does not list the files directly inside a collection folder. Collection entries are loaded dynamically and may not have a direct page or component reference.
Choose Convert to regular folder in Collection settings, the folder's
context or actions menu, or the empty-space context menu inside the folder.
Confirm the conversion. This deletes only
collection.json. The folder becomes a normal folder again, while its entries
and template remain. The former template is then a regular MDX file and can
appear in Assets query results.
2. Create an article
If posts is a content collection, choose New entry and fill in its form.
Otherwise, create the MDX file and frontmatter manually:
- Open
blog/postsin the Assets panel. - Open the add menu and choose Create text file.
- Name the file
hello-world.mdx. - Add the article metadata between the two
---lines, followed by the article body:
---
title: Hello world
slug: hello-world
publishedAt: 2026-08-10
excerpt: A short introduction to the article.
draft: false
featureImage:
$ref: ./assets/hello-world.png
author: Ada Lovelace
---
# Hello world
Write the article here.

An article's metadata and body in the Markdown editor
Frontmatter
The opening YAML block is called frontmatter. It must be the first block in
the Markdown file and must start and end with --- on separate lines.
Frontmatter accepts strings, numbers, booleans, null, arrays, and nested
objects. The Content Engine exposes these fields under properties, such as
properties.title and properties.slug.
You define the fields yourself. Keep their names and value types consistent between articles so one query and one page design work for every article.
{% hint style="warning" %}
draft is a field you defined, not the visual editor's automatic
page draft setting. Add a query filter that
excludes draft: true anywhere unpublished articles must not appear.
{% endhint %}
The Markdown below the closing --- is the body. It is available separately
at content.text when the Assets resource uses Markdown body reference.
Relative asset paths
Upload hello-world.png to blog/posts/assets. The article's $ref resolves
relative to the Markdown file. It declares that featureImage is structured
asset data without deciding which metadata a query must return.
Relative paths also work in nested objects, arrays, and JSON files. Query
strings and fragments are preserved, so a value such as
./assets/hello-world.png?width=1200#cover remains usable.
Select only the referenced fields the page uses, such as
properties.featureImage.src and properties.featureImage.description. The
value becomes structured data:
post.properties.featureImage.src;
post.properties.featureImage.description;
Bind the Image source to post.properties.featureImage.src. Bind its
alternative text to
post.properties.featureImage.description ?? post.properties.title. This
uses the Asset Manager description when present and falls back to the article
title. Existing content that stores the asset path as a plain string continues
returning a URL string.
Imported content can also use published asset paths such as
./assets/hero.png or /assets/hero.png. Webstudio matches the final filename
across project Asset folders, so the content file and referenced asset do not
need to share the same folder hierarchy. The filename must identify exactly
one project asset.
Webstudio resolves a path only when it uniquely matches a project asset. External URLs, other root-relative URLs, missing paths, and ambiguous paths remain unchanged. Keep the clean path in the source instead of hardcoding an asset ID or generated filename. This is what makes the content portable.
JSON files
The Content Engine can query JSON files as well as Markdown and MDX. JSON files can contain objects, arrays, or scalar values. A root object exposes its fields for structured queries:
{
"name": "Ada Lovelace",
"role": "Author",
"avatar": "./assets/ada.png"
}
The object's fields are exposed under properties, such as properties.name
and properties.avatar. Root arrays and scalars remain valid JSON content but
do not expose top-level properties fields. Use JSON when the file contains
structured data without a Markdown body. Name the file with a .json extension
in the Create text file dialog. A new file starts with an empty object, which
you can replace with any JSON value. The editor accepts JSON-compatible syntax
and formats it as strict JSON when saving. Unsupported or incomplete syntax is
reported without saving the file. You can also change an existing text file's
extension to .json by editing its complete filename in Asset settings;
Webstudio validates and formats the current content before converting it.
3. Query the articles for the overview
Create a static page with the path /blog, then add an Assets resource to its
page-level Dynamic data:
- Create a System Resource and choose Assets.
- Name it
posts. - Under Output, choose Selected fields, turn off File metadata, and
include only the fields the overview renders, such as
properties.title,properties.slug,properties.publishedAt,properties.excerpt, andproperties.featureImage.srcandproperties.featureImage.description. - Under Result, choose Many.
- Under Content, choose Metadata only. The overview does not render complete article bodies.
- Add these filters:
extensionequals"mdx"folder idequals the quotedpostsfolder IDproperties.draftdoes not equaltrue
- Sort
properties.publishedAtin descending order. Addidin ascending order as a second sort so articles with the same publication date keep a stable order.

An overview query for the Webstudio Updates project
Choosing only the fields the page renders keeps the published content data small. Leaving article bodies out of the overview also avoids loading every article just to display a list.
4. Build the overview
- Add a Collection to the page.
- Bind the Collection data to
posts.data. - Rename the Collection Item to
Post. - Design one article card inside the Collection.
- Bind the card's text and image to fields on
Post.value, such asPost.value.properties.titleandPost.value.properties.featureImage.src. - Bind the image alternative text to
Post.value.properties.featureImage.description ?? Post.value.properties.title. - Bind the card link to
"/blog/" + Post.value.properties.slug.
The Assets resource returns many results as an object keyed by asset ID. The
current article inside the Collection is available through the Collection
Item's value.
5. Create the dynamic article page
Create one page with the path /blog/:slug. This page is the template for all
articles. In the visual editor's address bar, enter hello-world as the
preview value for :slug.
Add another Assets resource to the page-level Dynamic data:
- Create a System Resource and choose Assets.
- Name it
post. - Under Output, choose Selected fields, turn off File metadata, and
include the fields this page renders, such as
properties.title,properties.publishedAt,properties.excerpt,properties.featureImage.src,properties.featureImage.description, andproperties.author. - Under Result, choose Exactly one.
- Under Content, choose Metadata only. The Content Block loads the matching MDX file directly.
- Add these filters:
extensionequals"mdx"folder idequals the quotedpostsfolder IDproperties.slugequalssystem.params.slugproperties.draftdoes not equaltrue

The article Resource uses the dynamic slug from the page URL
Exactly one returns the matching article directly at post.data. It also
reports an error if two articles use the same slug. You do not need a
Collection on the article page.
Metadata only keeps article bodies out of the published content database. The resource finds the matching article and returns its Asset ID. The Content Block then loads that MDX file directly.
If posts is a content collection, return to Collection settings → Entry
page and select this dynamic page. This enables Open on canvas for its
entries.
6. Bind the article
Add a Content Block
and bind its Source to post.data.id. Every Assets query result includes its
Asset ID, even when file metadata is disabled. The Content Block loads the
matching article and adds an MDX content region for its body.
Build the complete article inside the Content Block:
- Bind the heading directly to
document.frontmatter.title. - Bind the author directly to
document.frontmatter.author. - Bind the Image source directly to
document.frontmatter.featureImage.srcand make the binding writable. - Bind the Image alternative text to
document.frontmatter.featureImage.description. - Place the MDX content region where the article body should render.
- Style the standard Markdown elements in Templates, then add any custom interactive elements editors should be able to insert.
Keep each editable value directly bound to one frontmatter field. Computed expressions remain read-only. Put fixed wording in separate text elements and use a Date Time component for dates so Content mode shows the appropriate controls.
Editors can now change the title, author, images, dates, body, links, and custom interactive elements directly on the canvas. These changes save back to the MDX file. Verify every article-owned value in Content mode before handing the site to an editor.
In Page Settings, bind the fields needed for search and sharing:
- Title:
post.data.properties.title - Description:
post.data.properties.excerpt - Social image:
post.data.properties.featureImage.src - Status code:
post.data ? 200 : 404
The status expression returns a real 404 when no article matches the URL.
Render a Markdown body without visual editing
Use a .md file and Markdown Embed when
editors can work in the Markdown source editor instead of editing the body on
the canvas. Content mode editors can still open and edit the .md file in
Assets. Set the article resource's
extension filter to md and Content to Markdown body reference, then
bind the Markdown Embed code to post.data.content.text. Style its nested
document elements once for every article. See Choose Markdown or MDX
for the publishing tradeoff.
7. Publish the article
Start new articles with draft: true. The overview and article queries above
will exclude them. Change draft to false when the article is ready:
draft: false
The overview query will now include it. If you build a
custom sitemap for the dynamic article URLs,
give its Assets resource the same properties.draft does not equal true
filter. The metadata field does not automatically remove an article from a
custom sitemap.
To publish another collection article, choose New entry. For the manual workflow, duplicate the MDX file and change its title, slug, publication date, image, and body. The existing overview and dynamic page will render it without another page design.
Connect content with document references
The Content Engine calls links between Markdown, MDX, and JSON files document
references. A document reference replaces an exact $ref object with data
from another content file. This is Webstudio's syntax. It uses URI references
for file paths and JSON Pointer
for values inside JSON files, but it does not implement JSON Schema resolution.
All supported source formats can reference the other content formats:
| Source | Where references can appear | JSON target | Markdown or MDX target |
|---|---|---|---|
| Markdown or MDX | YAML frontmatter | Yes | Yes |
| JSON | Anywhere in the document | Yes | Yes |
References do not work inside a Markdown or MDX body. A $ref object can be
nested in an object or array, including inside a file reached through another
reference.
Reference syntax
A reference is an object with one field:
{ "$ref": "<relative-path>[#<fragment>]" }
The object must contain only $ref, and its value must be a string. An object
that has $ref plus another field remains ordinary content. Resolve the path
relative to the file containing the reference, not the project root.
The optional fragment selects which value to insert:
| Reference | Value inserted at $ref |
|---|---|
../authors/ada.json |
The complete JSON value |
../authors/ada.json#/profile/name |
The value at JSON Pointer /profile/name |
../authors/ada.md |
The complete Markdown source, including frontmatter |
../authors/ada.md#frontmatter |
The Markdown frontmatter as an object |
../authors/ada.md#body |
The Markdown body without frontmatter |
JSON Pointer fragments apply only to JSON files. Use ~1 for / and ~0 for
~ inside a property name. For example, #/social~1links/0 selects the first
item in a property named social/links. URI-encode characters that belong to a
filename but have a special meaning in a URL. A file named draft#1.json, for
example, becomes draft%231.json in a reference.
Reference Markdown from Markdown
For example, keep author details in blog/authors/ada.md and insert its
frontmatter into an article in blog/posts:
author:
$ref: ../authors/ada.md#frontmatter
The queried article exposes the result under properties.author. Bind the
author's name with post.data.properties.author.name.
Reference JSON from JSON
JSON files use the same syntax. This post references one value from an author file:
{
"title": "Hello world",
"author": { "$ref": "../authors/ada.json#/profile" }
}
Given this ada.json file:
{
"profile": {
"name": "Ada Lovelace",
"role": "Author"
}
}
the resolved properties.author value is:
{
"name": "Ada Lovelace",
"role": "Author"
}
The same rules cover JSON to Markdown or MDX and Markdown or MDX frontmatter to JSON. A referenced file can contain its own references, so shared records can be composed across several files.
The Content Engine loads referenced data when a query filters, sorts, or returns a field that depends on it. The target must be another Markdown, MDX, or JSON file in the project's compiled Assets. Missing files, invalid fragments, and reference cycles fail instead of returning partial data.
Related
- Content Engine reference – Check query fields, modes, diagnostics, references, and limits
- Assets – Create, edit, organize, and reference project files
- Content Block – Design and visually edit MDX articles
- Data variables – Define resources and understand their scope
- Collection – Render article lists
- Markdown Embed – Render and style an article body