The complete public surface of @maildeno/editor v0.4.9. For a guided introduction see @maildeno/editor; this page is the lookup table.
Exports
// @maildeno/editor
import {
EmailEditor, // Vue component
createLocalStorageAdapter, // the default adapter
markHandled, isHandled, // adapter error helpers
registerBlock, getBlock, getAllBlocks, // block registry
registerESPSyntax, getRegisteredCustomESPs, // ESP registry
registerMergeTags, getRegisteredMergeTagIds, // merge-tag registry
setEditorTheme, palette, // theming
} from "@maildeno/editor"
// @maildeno/editor/init
import { init } from "@maildeno/editor/init"
// @maildeno/editor/element
import {
registerMaildenoEditorElement,
MaildenoEditorElement,
} from "@maildeno/editor/element"
Exported types
import type {
// Adapter
EditorStorageAdapter, PartialStorageAdapter,
TemplateSnapshot, TemplateSummary, TemplateVersionSummary, SavedRow,
// Blocks
BlockDefinition, BlockRenderContext,
// ESP
ESPWrapperOverrides,
// Merge tags
MergeTagRegistration,
// Theming
ThemeOptions, ThemeTokens,
// Init
InitOptions, EditorHandle,
// Assistant
EditorWriteApi, AssistantMount,
// Document model
Canvas, NestedRow, RowSpacer, Column,
VisibilityConfig, BorderConfig, GradientConfig,
// State
AutoSaveStatus, ColorMode,
} from "@maildeno/editor"
Canvas here is the email body’s own settings — width, padding, background, preheader — not an HTML <canvas>. Alias it on import if that reads badly in your codebase.
|
EmailEditor props
For the Vue-component path. init() accepts the same options under slightly different names where noted.
| Prop | Type | Default | Description |
|---|---|---|---|
|
|
|
Suppresses the loading overlay. Set |
|
|
localStorage |
Merged per-method with the defaults. |
|
|
— |
If given, loaded through the adapter on mount. Omit to start blank. |
|
|
package defaults |
Reactive — assigning a new object re-themes live, no remount. |
|
|
|
|
|
|
— |
Presence reveals the Save button. |
|
same as |
— |
Custom-element path only — see the note below. |
|
|
— |
Presence reveals the Send-test button. |
|
|
— |
Restricts only, never grants. |
|
|
package default |
Empty string drops the attribution line entirely. |
|
|
|
Swaps the saved-templates panel for version history. |
|
|
— |
Non-Vue hosts. Vue hosts use the |
|
Why |
capabilities
interface Capabilities {
export?: Array<"html" | "mjml" | "react" | "json">
savedRows?: { create?: boolean; rename?: boolean; delete?: boolean }
}
Omitting a key leaves that area fully enabled. This object can only take things away.
The Shared tab of the saved-rows panel is read-only for everyone regardless, because no adapter method backs writing to it.
Events
| Event | Payload | Fires |
|---|---|---|
|
|
After a successful save. Only ever fires when |
|
— |
After the canvas has actually painted, not merely mounted. Once per mount. |
save is dispatched as a DOM CustomEvent with bubbles: true and composed: true, so it escapes the shadow root and reaches the host element — which is where handle.on("save", …) attaches.
|
|
Slots
| Slot | Props | Purpose |
|---|---|---|
default |
|
Read editor state from the host. |
|
forwarded from the header |
Add your own buttons to the editor header. |
|
|
Vue-native assistant panel. |
Exposed methods
Available on a Vue template ref, on the custom element instance, and through EditorHandle.
getHtml(mode?: "prune" | "wrap"): string | null
getMjml(mode?: "prune" | "wrap"): string | null
getReactEmail(mode?: "prune" | "wrap"): string | null
getJson(): Record<string, unknown> | null
setJson(data: unknown, opts?: { history?: "undoable" | "reset" }): void
getSelection(): { id: string; type: string | null } | null
setSelection(id: string | null): boolean
onChange(cb: () => void): () => void
| Method | Notes |
|---|---|
|
|
|
The portable document. This is what you store. |
|
Accepts what |
|
|
|
Returns |
|
Keyed to history commits, not raw mutations. Typing in a text block is one call, not one per keystroke. Returns an unsubscribe function. |
setJson’s `history option defaults to "undoable" — one Ctrl+Z-able step, right for anything the user triggered. Use "reset" when swapping which document is open, where undoing back into the previous document would be wrong.
init(options)
function init(options: InitOptions): Promise<EditorHandle>
InitOptions
interface InitOptions {
container: HTMLElement | string
templateId?: string
storageAdapter?: PartialStorageAdapter
theme?: ThemeOptions
capabilities?: { export?: Array<"html" | "mjml" | "react" | "json"> }
onSendTestEmail?: (p: { to: string; subject: string; html: string })
=> Promise<void> | void
onSave?: (p: { templateId: string | null }) => void
brandName?: string
versions?: boolean
assistant?: AssistantMount
shadowDom?: boolean // default true
}
EditorHandle
interface EditorHandle {
readonly element: HTMLElement
on(event: "save", handler: (p: { templateId: string | null }) => void): void
off(event: "save", handler: (p: { templateId: string | null }) => void): void
setTheme(theme: ThemeOptions): void
getHtml(mode?: "prune" | "wrap"): string | null
getMjml(mode?: "prune" | "wrap"): string | null
getReactEmail(mode?: "prune" | "wrap"): string | null
getJson(): Record<string, unknown> | null
setJson(data: unknown, opts?: { history?: "undoable" | "reset" }): void
getSelection(): { id: string; type: string | null } | null
setSelection(id: string | null): boolean
onChange(cb: () => void): () => void
destroy(): void
}
element is the underlying <maildeno-editor> — a real DOM element, exposed for anything the handle does not wrap directly.
handle.on("save", …) only ever fires when onSave was passed to init(). Without it the editor has no Save button and never saves, so there is no event to hear. To observe saves without doing work in onSave, pass a no-op handler to enable saving and do the work in the listener.
|
AssistantMount
interface AssistantMount {
mount(el: HTMLElement, editor: EditorWriteApi): void
unmount?(el: HTMLElement): void
}
interface EditorWriteApi {
getJson(): Record<string, unknown> | null
setJson(data: unknown, opts?: { history?: "undoable" | "reset" }): void
getSelection(): { id: string; type: string | null } | null
setSelection(id: string | null): boolean
onChange(cb: () => void): () => void
}
mount is called on first open rather than at startup, and unmount on close, so a drawer the user never opens costs nothing.
Storage adapter
interface EditorStorageAdapter {
// Templates
loadTemplate(templateId?: string): Promise<TemplateSnapshot | null>
saveTemplate(snapshot: TemplateSnapshot, templateId?: string):
Promise<{ templateId: string }>
listTemplates(): Promise<TemplateSummary[]>
deleteTemplate(templateId: string): Promise<void>
// Version history
listTemplateVersions(templateId: string): Promise<TemplateVersionSummary[]>
getTemplateVersion(templateId: string, versionId: string):
Promise<TemplateSnapshot | null>
deleteTemplateVersion(templateId: string, versionId: string): Promise<void>
deleteAllTemplateVersions(templateId: string): Promise<void>
setTemplateVersionKept(templateId: string, versionId: string, kept: boolean):
Promise<void>
// Saved rows
listSavedRows(): Promise<SavedRow[]>
listSystemSavedRows(): Promise<SavedRow[]> // read-only shared library
saveSavedRow(row: Record<string, any>, name: string): Promise<SavedRow | null>
deleteSavedRow(id: string): Promise<void>
renameSavedRow(id: string, name: string): Promise<void>
cloneSavedRowForCanvas(id: string): Record<string, any> | null // sync
// Images
uploadImage(file: File): Promise<string>
}
type PartialStorageAdapter = Partial<EditorStorageAdapter>
Every method is optional in PartialStorageAdapter. See Storage adapters for the design notes and a worked backend.
Supporting types
interface TemplateSnapshot {
rows: Record<string, any>[]
canvasStyles: Record<string, any>
name?: string
tags?: string[]
updatedAt?: string
}
interface TemplateSummary {
templateId: string
name?: string
updatedAt?: string
}
interface TemplateVersionSummary {
versionId: string
createdAt: string
label?: string // human label; falls back to a relative timestamp
kept?: boolean // pinned — survives deleteAllTemplateVersions
author?: string // free-form attribution, rendered verbatim
}
interface SavedRow {
id: string
name: string
createdAt: string
row: Record<string, any>
}
TemplateSummary and TemplateVersionSummary are summaries rather than full snapshots on purpose: rendering a list of fifty versions should not mean downloading fifty documents.
Adapter error helpers
import { markHandled, isHandled } from "@maildeno/editor"
async saveTemplate(snapshot, templateId) {
const res = await fetch(...)
if (res.status === 403) {
showMyOwnToast("You don't have permission to edit this template.")
throw markHandled(new Error("forbidden")) // editor stays quiet
}
return res.json()
}
Mark an error your adapter already reported and the editor skips its own generic toast rather than stacking a vaguer message on top of yours.
Registries
// Blocks
function registerBlock(definition: BlockDefinition): void
function registerBlock(id: string, definition: BlockDefinition): void
function getBlock(id: string): BlockDefinition | undefined
function getAllBlocks(): ReadonlyMap<string, BlockDefinition>
// ESP syntaxes
function registerESPSyntax(
id: string,
meta: ESPSyntaxMeta,
overrides?: ESPWrapperOverrides,
): void
function getRegisteredCustomESPs(): Array<{ id: string; meta: ESPSyntaxMeta }>
// Merge tags
function registerMergeTags(tags: MergeTagRegistration[]): void
function getRegisteredMergeTagIds(): string[]
| All registries are module-level. Call them at import time, not inside a component — a block registered after mount will not appear in the sidebar. |
Theming
function setEditorTheme(theme: ThemeOptions): void
const palette: /* the default token maps */
ThemeOptions carries roughly 70 colour tokens plus a dark variant map. See Theming for the full list.
Where to go next
-
@maildeno/editor— the guided introduction