## Summary - Document how people create, connect, edit, validate, and publish MDX-backed Content Blocks. - Document the complete MCP workflow, including scoped expression sources, frontmatter updates, diagnostics, conflicts, and disconnect behavior. - Add two focused screenshots for the human workflow. - Fix issues found by a low-reasoning MCP agent while executing every documented workflow. Ref #6182 ## Implementation notes - Connection confirmation uses the public `confirmReplacement` field because `confirm` is reserved as a hidden transport field. - MCP edits frontmatter with `update-content-block-frontmatter`; materialized MDX elements are not persistent targets for generic instance mutation tools. - Reload now intentionally discards a conflicted local source and accepts the Asset's latest storage revision before an edit is reapplied. - Generated MCP documentation and runtime contracts are updated from their source files. ## Checklist - [x] Document the human MDX Content Block workflow. - [x] Document MCP discovery, connection, editing, diagnostics, frontmatter, dynamic sources, conflicts, and disconnection. - [x] Add only screenshots that clarify otherwise hard-to-describe UI steps. - [x] Test every documented MDX capability through the local MCP CLI with a low-reasoning agent. - [x] Reproduce and fix the replacement-confirmation schema mismatch. - [x] Reproduce and fix persistent-session conflict reload and reapply. - [x] Add regression tests that failed before each product fix and pass afterward. - [x] Regenerate runtime contracts and generated MCP documentation. - [x] Review authoritative `docs/` content and update affected pages. - [x] Run affected tests, typechecks, formatting, and diff checks. ## Verification - Project Build: 208 affected tests passed. - Content Engine Asset session: 8 tests passed. - CLI documentation tests: 14 tests passed. - MCP docs generator: 3 tests passed. - Content Engine, Project Build, CLI, and MCP docs typechecks passed. - `pnpm oxfmt --check` passed. - `git diff --check` passed. - Low-reasoning MCP evaluation passed all documented workflows, including a real two-client revision conflict, reload, reapply, and final saved inspection.
18 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.
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.

Markdown articles stored alongside their assets
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:
- Small blogs with text and images.
- 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 Markdown files, upload images, build both pages, configure the Assets resources, add the Collection, bind the article directly, 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:
Use Webstudio CLI to build a blog with the Content Engine. Store the articles as Markdown files, create
/blogand/blog/:slug, 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. Images can live next to the articles or 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.
2. Create an article
- Open
blog/postsin the Assets panel. - Open the add menu and choose Create text file.
- Name the file
hello-world.md. - 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"md"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 Markdown body reference.
- Add these filters:
extensionequals"md"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.
Markdown body reference keeps article bodies out of the published content database. Webstudio first finds the matching article, then loads only that Markdown body from Assets.
6. Bind the article
Bind the article components directly to post.data:
- Heading:
post.data.properties.title - Author:
post.data.properties.author - Image source:
post.data.properties.featureImage.src - Image alternative text:
post.data.properties.featureImage.description ?? post.data.properties.title - Markdown Embed code:
post.data.content.text
Add a Markdown Embed for the body. Style its nested headings, paragraphs, links, lists, and images once; the styles apply to every article.
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.
Edit the complete article in Content mode
The setup above uses a .md file and Markdown Embed when the article body is
edited in the file editor. Use an .mdx file connected to a
Content Block
when an editor should change the article visually in Content mode.
- Name the article files with the
.mdxextension and change the resource'sextensionfilter frommdtomdx. - Change the article resource's Content setting to Metadata only. The Content Block loads the selected file directly, so the resource does not need to return its body.
- Replace the Markdown Embed with a Content Block and place its Body outlet where the article body should render.
- Bind the Content Block's Source to
post.data.id. Every Assets query result includes its Asset ID, even when file metadata is disabled. - Move the article title, image, excerpt, and other designed fields inside the
Content Block shell. Bind them directly to values such as
document.frontmatter.titleanddocument.frontmatter.featureImage.src.
Editors can now change the MDX body and directly bound frontmatter values on
the canvas. Computed frontmatter expressions and values reached through a
document $ref remain read-only; edit their source file instead.
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 article, duplicate the Markdown 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
- Data variables – Define resources and understand their scope
- Collection – Render article lists
- Markdown Embed – Render and style an article body