Custom Schemas
The collaboration server checks every edit against a schema and stores documents compacted against it, so the server and every editor must build the same schema, or one the server only extends. This page covers building that schema once and giving it to both sides.
One source of props
Keep the stesuraSchema props in one module of your own and import it on both sides:
// schema-props.ts
import type { StesuraSchemaProps } from "@stesura/core/schema";
export const schemaProps = {
nodes: { legal_notice: legalNoticeSpec },
// No pictures at all; see "Excluding a node".
excludeNodes: ["image", "image_anchored"],
} satisfies StesuraSchemaProps;// The editor
const schema = stesuraSchema(schemaProps);
const { editorState, dispatch, pluginFactory } = useCollabEditor({ schema });Removing core nodes works by whole families, and every feature that would create them steps aside (no toolbar button, refused paste and drop, skipped DOCX pictures). See Excluding a node.
Hosting the backend in your app
When the backend runs in your own Next or Node process, pass it the same schema:
const schema = stesuraSchema(schemaProps);
export const backend = createCollabBackend({ schema, store: postgresStore({ url, schema }), broadcast });The collab server image
A prebuilt image cannot import your code, so it takes the props from a module at
COLLAB_SCHEMA_MODULE, exporting schema. The image builds the schema from its own copy of the
editor core, so the module holds plain specs: bundle schema-props.ts (and any Stesura extension
/schema subpath you use) into one .mjs and mount it at /app/schema.mjs. Without the module
the image serves core plus the Mermaid, automation and e-sign nodes.
A module that is wrong (an unknown key, specs that do not build) stops the server at startup with
the key named. The served schema’s fingerprint is logged. The on-prem bundle ships
examples/schema-module.mjs.
Several schemas on one server
Export schemas and defaultSchema instead of schema from the module (or pass them to
createCollabBackend when hosting in your app). Each document keeps the schema it was created
under: create it with a name (addDoc({ ..., schemaName: "forms" }) on the room provider), or it
gets the default. The room manifest names each document’s schema, so the editor can build the
matching one:
// schema.mjs
export const schemas = {
docs: { schema: docsProps },
forms: { schema: formsProps },
};
export const defaultSchema = "docs";Changing the schema
Editors send their schema’s fingerprint with every document request. When it differs, the
server accepts the editor only if it has merely added nodes or marks; any other difference stops
the editor with docStatus: "schema-mismatch". Deploy the server before the editors. The full
rules are in schema compatibility.