Type-safe templates
right inside your editor
Variables, conditions, loops and filters that render to clean text, with live validation, quick-fixes and staged autocomplete.
Everything you'd expect from a typed language
…now living inside your rich-text editor.
Variables & filters
Dotted paths, chained filters (default, upper, trim, date, round, currency…), and nullable-aware fallbacks so you never render a blank.
Conditions & loops
if · elseif · else · for … else with JavaScript-style lexical block scope. A block opened in a branch must close in that same branch.
Local validation
The model-language validate() engine runs in-process on every keystroke: unknown fields, missing defaults, unbalanced blocks. Zero round-trips.
Quick-fixes
Hover a squiggle for a one-click fix: add a type-aware default, insert the missing {{/if}} in the right scope, or correct a mistyped operator.
Staged autocomplete
field, then operator, then value, step by step. Enums list options, dates offer format presets with a live preview, timezones from a dropdown.
i18n & theming
Override every string for localization, toggle diagnostic severities, and style with your own Tailwind classes. Light and dark themes included.
Three moving parts
Pass a typed schema
The host hands the extension a FieldSchema (dotted paths, types, nullability and enum options) via a prop or setModelData. It's the single source of truth for autocomplete and validation.
Author in the editor
Writers get VS Code-style feedback while typing: syntax highlighting, staged autocomplete, hover diagnostics and one-click quick-fixes. Broken tags and empty fields are caught at edit time.
Render clean text
The very same model-language engine renders the template against live data, so what you validate is exactly what ships: clean output, never leaked syntax, never a blank.
Built for teams that render dynamic text against live data: AI agent prompts, transactional email, chat and notification templates, no-code message builders. Framework-agnostic, offline-capable, fully themeable, and impossible to leak raw syntax from.
Up and running in three steps
Drop it into any React + Tiptap app. Copy, paste, done.
import { EditorContent, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { ModelSyntax } from "tiptap-model-language";
// 1. your org's field schema (types, nullability, enum values)
const schema = [
{ path: "contact.first_name", type: "string", nullable: true },
{ path: "contact.priority", type: "enum", values: ["high", "low"] },
];
// 2. namespaces power the {{ autocomplete (grouped by prefix)
const namespaces = [
{ key: "contact", label: "Contact", fields: [
{ key: "first_name", type: "string", label: "First name" },
{ key: "priority", type: "enum", label: "Priority",
values: ["high", "low"] },
]},
];
export function TemplateEditor() {
const editor = useEditor({
extensions: [
StarterKit,
// 3. pass schema + namespaces straight in (static case)
ModelSyntax.configure({
namespaces,
schema,
onResult: (r) => console.log(r.diagnostics, r.maxTokenEstimate),
}),
],
});
// Schema loads async or changes at runtime? Push it live instead:
// editor.commands.setModelData({ namespaces, schema })
return <EditorContent editor={editor} />;
}Want every prop and command? Read the docs