Skip to content

Custom formats

A format teaches Jx a new file type. .json is the single native built-in — Jx is JSON — so every other extension a project opens, saves, builds, or loads content from ( .md , .csv , your .toml ) is dispatched through a format class. A class participates in format dispatch iff its .class.json descriptor carries a top-level format object.

The format block

The parser's Markdown.class.json declares, verbatim:

"format": {
  "extensions": [".md"],
  "mediaType": "text/markdown",
  "documentKinds": ["page", "component", "content"],
  "exportTarget": true,
  "remote": false
}
Key Type Default Meaning
extensions string[] (required) File extensions claimed, with leading dot.
mediaType string MIME type; used for icons, labels, HTTP responses.
documentKinds ("page"|"component"|"content")[] [] page / component admit the extension into pages/components discovery globs; content admits it as a content source.
exportTarget boolean false When true, site builds emit a serialized sidecar per page in this format (requires a serialize capability).
remote boolean false When true, the load capability accepts http(s) URLs as sources. Remote content sources must name a remote-capable format explicitly.

Two classes may claim the same extension only with disjoint capabilities — the registry build fails on an ambiguous (extension, capability) pair. A registry never claims .json .

Format capabilities

The block declares what the class handles; the class's capability methods declare how . Four roles belong to the format block:

Role Signature Consumers
parse (source, options?) → JxDocument compiler, server, Studio (open file)
serialize (doc, options?) → string Studio (save), site build (export sidecars)
discover (source, { baseDir }) → string[] content loading (list entry files)
load (path, { schema, directiveOptions }) → ContentLoaderEntry[] content loading (parse one source)

A format implements the subset it needs: a read-only format can ship parse without serialize (Studio then opens files in this format read-only in structural modes); a data-only format like Csv needs discover / load but has no reason to be a page format.

The Markdown class implements all four, plus the standard instance resolve() — so { "$prototype": "Markdown", "src": "./about.md" } works as runtime state, satisfying the same external class contract as every other class.

How the pipeline dispatches

Hosts never hard-code file types. Each one builds a format registry from the enabled extensions' manifests and routes by extension:

  • Pages and components discovery — the site build and dev server glob for .json plus every extension whose format declares the matching documentKind , then call parse on non-JSON matches. This is why adding a Markdown page is just dropping pages/about.md in a parser-enabled project.

  • Content loading — a content section entry names a format (or derives it from the source's file extension); the loader calls discover to list entry files, then load per file, validating each entry against the content type's schema. See Content collections .

  • Studio editing — opening a claimed file calls parse to get the Jx tree the canvas edits; saving calls serialize . When a capability's timing excludes the browser, Studio round-trips through the dev server's POST /__studio/format endpoint instead of importing the implementation ( Studio routes ).

  • Export sidecars — with exportTarget: true , the build serializes each page into the format next to its HTML output.

Studio hints

Format classes describe their Studio control surface declaratively in a top-level $studio block — Studio interprets this data generically and never hard-codes per-format element sets. From Markdown.class.json , abbreviated:

"$studio": {
  "icon": "markdown",
  "modes": ["edit", "design", "preview", "source"],
  "documentMode": {
    "default": "content",
    "componentWhen": { "frontmatterKey": "tagName", "matches": ".+-.+" }
  },
  "newFileTemplate": "---\ntitle: Untitled\n---\n\n",
  "elements": {
    "block": ["h1", "h2", "h3", "p", "blockquote", "ul", "ol", "li", "pre", "…"],
    "inline": ["em", "strong", "del", "code", "a", "img", "br"],
    "void": ["hr", "br", "img"],
    "textOnly": ["code"],
    "nesting": {
      "h1": { "block": false, "inline": true, "directive": false },
      "ul": { "only": ["li"] },
      "…": {}
    }
  }
}
  • icon — file icon in the Files panel; modes — which canvas modes the format supports.

  • documentMode — whether files open as prose content or as components, with an optional frontmatter-based override (here: a hyphenated tagName means "this .md file defines a custom element").

  • newFileTemplate — the seed content for New File in this format.

  • elements — the allowlist and nesting constraints gating structural editing: which tags the element picker offers, what may nest where, which are void or text-only.

One more generic hint applies to any class, not just formats: $studio.stateDefaults — an object merged into state entries Studio creates for the prototype. The connector's TableQuery sets { "timing": "client" } so Studio-created queries default to browser resolution.