Skip to content

MDX content

Posts, pages, your own content types and sections can all be written in MDX: Markdown plus HTML and the components your site allows.

  • Posts and pages in the starter are MDX collections (format: "mdx" in wuppy.config.mjs). New entries are saved as .mdx. Existing .md files keep working and stay .md until a component is inserted, when Wuppy promotes that entry to .mdx; the same collection can hold both.
  • Content types you create in the admin are MDX by default. You can still pick Markdown.
  • Sections store their body as MDX.
  • Widgets: a custom widget’s body is MDX; an HTML widget gets the raw-HTML checks below before it is saved (and is still sanitised when shown). Text widgets are shown as plain text.
  • Markdown, HTML elements and the components listed in components in wuppy.config.mjs:

    export default { components: ["Callout", "YouTube"], collections: [/* … */] };
  • Component props are plain values only: text, numbers, true/false, lists and objects written as JSON. <Callout tone="info" items={["a", "b"]}> is fine.

Saving is refused with the reason (HTTP 422, DISALLOWED_CONTENT) when a body contains:

  • import or export lines.
  • JavaScript expressions such as {process.env.SECRET} or title={user.name}. A comment, {/* … */}, is allowed.
  • Components that are not in the allowed list, and member names such as <a.b>.
  • Spread props ({...props}).
  • Event handlers (onclick, onError…), Astro directives (set:html, is:raw, client:*), dangerouslySetInnerHTML and srcdoc.
  • script, style, iframe, object, embed, form, link, meta, base and similar tags.
  • javascript:, vbscript: and data: links. data: images (PNG, JPEG, GIF, WebP, AVIF) are allowed.
  • MDX that does not parse. The error shows the line, so the site build never breaks on it.

Markdown (.md) bodies are checked for the same dangerous tags, attributes and links.

The body is checked with the same parser Astro uses. Code inside code blocks is text and is never checked.

  • The starter has @astrojs/mdx in astro.config.mjs and loads **/*.{md,mdx}. For an existing Astro project wuppycms init does the same: it adds mdx() and @astrojs/mdx, and widens pattern: "**/*.md" globs to **/*.{md,mdx}.

  • Theme components are mapped automatically: Wuppy adds a components map to <Content /> in the theme’s pages, so a theme’s src/components/*.astro files can be inserted from Add → Components without an import line.

  • For a site’s own components, add their names to components in wuppy.config.mjs and pass them when you render an entry: <Content components={{ Callout }} />.

  • An MDX entry edited in the admin shows at once, without a rebuild. It is compiled with the site’s Markdown settings: headings get the same ids and headings list as in the build, code blocks the same highlighting.

  • Render a section (or custom widget) body with mdxContent from wuppycms/mdx. It runs the same checks again before rendering and returns what render() does, { Content, headings }:

    ---
    import { getSection } from "wuppycms";
    import { mdxContent } from "wuppycms/mdx";
    import Callout from "../components/Callout.astro";
    const section = await getSection("cta");
    const { Content: Body } = section ? await mdxContent(section.body) : {};
    ---
    {Body && <Body components={{ Callout }} />}