Dollie Editor SDK guide
Catalog Templates
Create, save, package, load, and safely apply portable starter Pages.
A Page Template is a complete PageConfig plus metadata and a computed list of required Sections and Elements. It contains data, not component code.
Templates can come from two places:
- a Catalog package can ship curated starters with its components;
- the host application can let an Editor user save a Page as a private or shared template.
Both use the same PageTemplate contract. The host decides where user-created templates are stored, who can see them, and whether they may be edited or deleted.
Create a template from a Page
Use exportTemplate with a trusted Page definition. The function deep-clones the definition, records its wire-format version, and computes requires; do not maintain requirements by hand.
import {
exportTemplate,
serializeEditorState,
} from "@dollie_ai/editor";
const template = exportTemplate(
serializeEditorState(editor.state),
{
id: "product-launch",
name: "Product launch",
description: "A focused launch page.",
},
{ sections: sectionDefinitions },
);
Pass the active Section definitions when Element slots may contain illustrations or widgets. This lets exportTemplate identify bare-string illustration references as well as object-form Element references.
The return value is plain JSON:
interface PageTemplate {
id: string;
name: string;
description?: string;
vertical?: string;
_version?: number;
definition: PageConfig;
requires: {
sections: string[];
elements?: Array<{
kind: "illustration" | "widget";
id: string;
}>;
};
}
vertical is descriptive metadata. Scope templates by the library or surface that loaded them, not by treating vertical as an authorization boundary.
Save host-owned templates
exportTemplate creates the document; it does not choose a database or write a file. Persist the returned JSON through your own backend, object store, repository, or development tooling.
async function saveAsTemplate(name: string) {
const template = exportTemplate(
serializeEditorState(editor.state),
{ id: slugify(name), name },
{ sections: runtime.sections },
);
await fetch("/api/page-templates", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(template),
});
}
The API route is host-owned. Validate access and tenant scope on the server. Treat the submitted definition as untrusted input even when it came from the SDK.
The website prototype uses the same pattern with a development-only Vite endpoint: Save as template calls exportTemplate, writes JSON under templates/<surface>/, and reloads the static import.meta.glob library. Production applications normally replace that file-writing endpoint with their own template store.
Bundle templates in a Catalog
When a template depends on one Catalog, ship it with that CatalogPackage:
import {
createCatalog,
defineCatalog,
exportTemplate,
} from "@dollie_ai/editor";
export const productCatalog = defineCatalog({
id: "product-marketing",
name: "Product marketing",
sources: [productSource],
definitions: productSectionDefinitions,
templates: [
exportTemplate(
launchPage,
{
id: "product-launch",
name: "Product launch",
description: "A focused launch page.",
},
{ sections: productSectionDefinitions },
),
],
});
const catalog = createCatalog([productCatalog]);
defineCatalog rejects malformed bundled templates. createCatalog aggregates package templates in registration order as catalog.templates. Hosts can place their own templates after the catalog defaults:
const templates = [...catalog.templates, ...accountTemplates];
Catalog templates are portable content. Keeping them inside the package means the Sections, field definitions, AI guidance, and starters can be installed and discovered together.
Offer templates in the Editor
Pass the templates for the current surface through the runtime:
const editorRef = useRef<PageBuilderEditorHandle>(null);
<PageBuilderEditorProvider
runtime={{
catalog,
sections,
templates,
}}
>
<PageBuilderEditor
ref={editorRef}
editor={editor}
onSaveBeforeOverride={saveDraft}
topBarRight={
<button onClick={() => editorRef.current?.openTemplatePicker()}>
Import template
</button>
}
{...shellProps}
/>
</PageBuilderEditorProvider>;
The empty canvas offers Open template when templates are available and the document has the insert capability. A host action can open the same picker through PageBuilderEditorHandle.openTemplatePicker().
Applying a template to a non-empty Page asks for confirmation. Supply onSaveBeforeOverride to offer Save and override. The Editor awaits the callback and does not replace the Page when it throws. Applying the template is one undoable replaceAll action.
Pass only the templates valid for this editor surface. runtime.templates is deliberately separate from catalog.templates so a host that composes several Catalogs can keep website, landing-page, and slide-deck starters in the correct UI.
Load and import safely
Template JSON from a registry, API, upload, or database is untrusted. The safe import sequence is:
- migrate its page wire format when necessary;
- confirm the required catalog entries exist;
- validate the embedded definition;
- return a page definition for review.
Do not trust remote template JSON merely because it uses the Dollie format.
const { template, result } = parsePageTemplate(json);
if (!template) {
return { errors: result.errors };
}
const compatibility = checkTemplateCompatibility(template, catalog);
if (!compatibility.compatible) {
return { compatibility };
}
const { definition } = importTemplate(template, catalog, {
sections,
});
importTemplate repeats the important checks. It does not trust the declared requires: it migrates the definition, recomputes requirements, checks compatibility, and calls validatePageConfig.
By default, an incompatible import throws TemplateIncompatibleError. onIncompatible: "partial" drops unavailable Sections and object-form Element references; use that only when the UI makes the data loss explicit. Malformed or invalid templates throw InvalidTemplateError.
Use templateCatalogIdsFromManifest(manifest) when the importing process has a generated manifest rather than a live PageBuilderCatalog.
Load templates through a transport
A TransportClient can implement:
listTemplates?(): Promise<TemplateSummary[]>;
getTemplate?(templateId: string): Promise<PageTemplate>;
The list call returns lightweight cards. Fetch the complete template only when it is selected, then pass it through importTemplate. The transport intentionally has no saveTemplate method: template creation, tenancy, sharing, and lifecycle remain host policy.
Use the CLI for JSON files
Export a PageConfig or BuilderDocument as a template:
npx dollie-editor export \
--page page.json \
--id product-launch \
--name "Product launch" \
--out templates/product-launch.json
Check it against a generated manifest:
npx dollie-editor import \
--template templates/product-launch.json \
--manifest public/editor-manifest.json \
--check
Write the validated Page definition with --out page.json. Add --partial only when dropping incompatible content is an explicit decision.
Template API reference
| API | Purpose |
|---|---|
computeTemplateRequirements |
Derive stable Section and Element requirements from a definition |
exportTemplate |
Create a portable, deep-cloned PageTemplate |
parsePageTemplate |
Narrow unknown JSON with structural errors |
checkTemplateCompatibility |
Compare declared requirements with a Catalog or manifest id set |
templateCatalogIdsFromManifest |
Build the lightweight compatibility surface from a manifest |
importTemplate |
Migrate, recompute, check, validate, and return a Page definition |
buildTemplateRemixPrompt |
Ask a Copilot to adapt copy and data while preserving template structure |