Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • Anchor API
    • Location API
    • Path API
    • PointEntry API
    • Point API
    • Range API
    • Selection API
    • Location Types APIs
    • Span API
    • Editor
    • Element API
    • NodeEntry API
    • Node API
    • Node Types APIs
    • Text API
    • Debug Value Scrubbing
    • Transforms API
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • FAQ
    • Resources

Persistence And Formats

PreviousNext

Persist Plite JSON values and keep external format conversion with its owning adapter.

Plite owns canonical editor JSON, typed value persistence, schema admission, ContentSlice, and browser DataTransferFormat negotiation. External syntax such as HTML, Markdown, or DOCX belongs to a framework or application adapter that knows the document schema.

Save the complete document

const document = editor.read.value();
const json = JSON.stringify(document);

RenderingNormalizing

On This Page

Save the complete documentDefine value persistenceExternal formatsBrowser transfer formats
Build your editor
Production-ready AI template and reusable components.
Get all-access
const document = editor.read.value();
const json = JSON.stringify(document);

editor.read.value() includes primary children, named roots, and persistent metadata. Saving only editor.read.children() drops roots and metadata.

Load untrusted JSON through the application's schema and migration boundary before publishing it. Persistence must reject malformed, unknown-version, or future-version data without mutating the current editor.

Define value persistence

EditorValuePersistence<TValue, TEncoded> maps one live value to its current canonical JSON representation and owns the stored envelope version.

import type { EditorValuePersistence } from "plitejs";
 
type Preferences = Readonly<{ compact: boolean }>;
type PreferencesJson = Readonly<{ compact: boolean }>;
 
const preferencesPersistence: EditorValuePersistence<
  Preferences,
  PreferencesJson
> = {
  decode(value) {
    if (
      typeof value !== "object" ||
      value === null ||
      typeof (value as PreferencesJson).compact !== "boolean"
    ) {
      throw new Error("Invalid preferences");
    }
 
    return { compact: (value as PreferencesJson).compact };
  },
  encode(value) {
    return { compact: value.compact };
  },
  legacyDecoders: {
    1: (value) => decodeLegacyPreferences(value),
  },
  version: 2,
};
import type { EditorValuePersistence } from "plitejs";
 
type Preferences = Readonly<{ compact: boolean }>;
type PreferencesJson = Readonly<{ compact: boolean }>;
 
const preferencesPersistence: EditorValuePersistence<
  Preferences,
  PreferencesJson
> = {
  decode(value) {
    if (
      typeof value !== "object" ||
      value === null ||
      typeof (value as PreferencesJson).compact !==













encode must return canonical EditorJsonValue: finite numbers, strings, booleans, null, arrays, and plain objects. Plite rejects undefined, bigint, symbols, functions, non-finite numbers, sparse arrays, accessors, cycles, and exotic prototypes at owned persistence boundaries.

Legacy entries are decode-only. After decoding one, encode the live value with the current declaration to produce canonical current JSON. Never substitute current defaults for an unknown or malformed version.

State fields and effect descriptors use persist for this contract. A state field without persist is runtime-only.

External formats

Plite does not infer HTML or Markdown from arbitrary custom nodes. A format owner must:

  1. parse its source,
  2. map syntax to the application's node schema,
  3. return a complete document or the correct slice carrier,
  4. report every expected recovery or loss, and
  5. reject invalid canonical model data.

A complete file parse produces EditorDocumentValue. An insertion parser produces ContentSlice. Direct syntax slices should be closed and rootless; only a native fragment transport can grant authority for open edges or detached roots.

const result = parseExternalSlice(source);
 
if (result.ok) editor.update.slice.replace(result.slice);
const result = parseExternalSlice(source);
 
if (result.ok) editor.update.slice.replace(result.slice);

The insertion owner fits the slice after the destination parent, root, and selection exist. A detached parser should not invent that context.

Browser transfer formats

Use DataTransferFormat only for browser MIME representations. Register one or more formats through dataTransferFormats(owner, formats) from plitejs/dom.

import { dataTransferFormats } from "plitejs/dom";
 
const ProductTransferPlugin = dataTransferFormats("product", [
  {
    key: "product-json",
    mimeType: "application/x-product+json",
    decode: ({ data }) => decodeProductSlice(data),
    encode: ({ slice }) => encodeProductSlice(slice),
  },
]);
import { dataTransferFormats } from "plitejs/dom";
 
const ProductTransferPlugin = dataTransferFormats("product", [
  {
    key: "product-json",
    mimeType: "application/x-product+json",
    decode: ({ data }) => decodeProductSlice(data),
    encode: ({ slice }) => encodeProductSlice(slice),
  },
]);

decode returns a ContentSlice and encode returns a string. accept returning false, or decode or encode returning null, lets the next format try. The DOM adapter fits a decoded slice at the actual insertion destination; a slice that does not fit also falls through to the next format.

See Clipboard And Paste for the complete negotiation and insertion pipeline.

"boolean"
) {
throw new Error("Invalid preferences");
}
return { compact: (value as PreferencesJson).compact };
},
encode(value) {
return { compact: value.compact };
},
legacyDecoders: {
1: (value) => decodeLegacyPreferences(value),
},
version: 2,
};