Skip to main content

Editor Integration

The Typograph Editor is a full browser-based document editor, delivered as a CDN-hosted script. It builds its own chrome — menu bar, toolbar, content bar, parameter bar — and exposes a window.typograph global for wiring up loading, saving, and the image/font manifest.

There is no npm package — include the script from the CDN.

Quick Start​

Two scripts, and no markup to author.

<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8">
<title>Typograph Editor</title>
<script src="https://cdn.typograph.nl/editor/latest/scripts/jsColorEngineWeb.js"></script>
</head>
<body>
<div id="app" style="position:absolute; top:0; left:0; right:0; bottom:0;"></div>

<script type="module">
import { Editor } from 'https://cdn.typograph.nl/editor/latest/editor.js';
const editor = new Editor({ container: '#app' });
</script>
</body>
</html>

The colour engine (jsColorEngineWeb.js) is a bare-global script and stays your responsibility. Everything else — the chrome, the stylesheet, the webfont — the bundle brings itself.

Editor is also exposed as window.Typograph.Editor if you would rather not use a module script.

Upgrading from an older integration

Earlier versions of these docs required the page to reproduce ~31 specific element ids (wrapper, dialoglayer, dialoglayer_01, popupsliderlayer, htmllayer, menulayer, …). That is no longer necessary. The bundle builds its own DOM.

Existing pages keep working: the bundle detects that markup and leaves it alone, and a page that links its own stylesheets/style.css or Source Sans Pro keeps using its own copy — including a customised stylesheet. Nothing needs to change on your side, but new integrations should not author that markup.

Full-page vs. contained​

Load the two scripts and construct nothing, and the editor mounts itself on document.body, filling the viewport. Every existing host relies on this, so it is not going away.

Construct an Editor with a container and the chrome is built inside that box instead of taking over the page.

container​

A CSS selector, a bare element id, or an HTMLElement — all three work:

new Editor({ container: '#app' });                          // selector
new Editor({ container: 'app' }); // bare id
new Editor({ container: document.getElementById('app') }); // element

If nothing matches, a warning is logged and the editor mounts on document.body rather than failing silently.

The container must have a height​

This is the one requirement. The editor's layout is absolutely positioned and resolves against the container's box, so a container with no height gives you an editor with no height. A plain <div id="app"></div> collapses to zero.

#app { position: absolute; top: 44px; left: 24px; right: 24px; bottom: 24px; }
#app { height: 90vh; }
/* Inside a flex or grid parent that gives it a track. */
#app { min-height: 0; }

An unpositioned container is given position: relative, and one with visible overflow is given overflow: hidden so nothing escapes the box you allotted. Both are restored on destroy().

Everything stays inside the container​

Menus, dialogs, colour pickers and the floating tool palettes are all confined to the box you gave the editor — none of them can be drawn over the rest of your page. A dialog that would be centred on screen in the full-page editor is centred in your container instead.

The one consequence worth knowing: a container much smaller than the editor's natural layout clips its own overlays rather than letting them escape. The chrome assumes roughly the room a desktop application gets.

Construct it early​

Construct the Editor before the window load event. Later than that and the editor has already mounted itself on document.body under the full-page defaults; container is then ignored and a warning says so. A module script in the page body is early enough.

Options​

OptionTypeDefaultWhat it does
containerselector | id | HTMLElementdocument.bodyWhere the chrome is built.
header_spaceboolean | number57 (px)The empty strip above the menu bar, for a host's logo.
hostobject of providersnoneLists, manifest and save calls the editor uses — see Host Providers.
legacy_php_fallbackbooleantrue on latestfalse: no php/*.php request, ever, and UI without a provider is hidden.
locale'en' | 'nl'data-typograph-locale on <html>, else 'en'The language of the editor's own texts and dates. 'nl-NL' counts as 'nl'; another language falls back to English.

The editor reserves 57px above the menu bar for a host logo. If your application already has its own header, you will not want it:

new Editor({ container: '#app', header_space: false });   // remove it
new Editor({ container: '#app', header_space: 80 }); // or set your own height

Everything below moves with it. Omit the option and the layout is exactly what it has always been.

The language​

The editor shows its menus, toolbar, panels, dialogs and comments in English or Dutch, and writes its dates in that language, in Europe/Amsterdam time. Pass locale, or, for a page that lets the editor mount itself, set the language on <html> before editor.js loads:

new Editor({ container: '#app', locale: 'nl' });
<html data-typograph-locale="nl">

The option wins over the attribute. Without either the editor is English, as it has always been. The document's own text, font and style names, and the names of elements and layers are never translated, and menu items keep the English ids you address them by. window.gEditorUI.get_locale() returns the language in force.

Script URL​

ChannelURL
Latest stablehttps://cdn.typograph.nl/editor/latest/editor.js
Minifiedhttps://cdn.typograph.nl/editor/latest/editor.min.js
Colour enginehttps://cdn.typograph.nl/editor/latest/scripts/jsColorEngineWeb.js

Load editor.js as a module (<script type="module" src="...">).

Lifecycle​

const editor = new Editor({ container: '#app' });

editor.get_document(); // the canvas document, once booted
editor.destroy(); // remove the editor's DOM and styles

Re-mounting after destroy() is not supported — reload the page instead. Constructing a second Editor destroys the first; two editors side by side is not supported.

Lifecycle Events​

The editor emits document events you listen to with document.addEventListener(...):

EventWhen it firesHandler receives
typograph_init_readyCanvas is up and window.typograph is populated.—
typograph_editor_readyThe editor has finished setting up. Providers you add late go on get_document().host here.—
typograph_document_openedA document has been opened.—
typograph_user_fonts_loadedUser-provided fonts (from the manifest) finished loading.—
typograph_pageelement_selectedThe user selected a page element.CustomEvent with detail.uuid and detail.message

See Document Events and Element Events for the full set.

Host Providers​

The editor has no backend of its own. Everything it needs from outside the open document goes through host providers: listing documents, templates and clips, the image/font manifest, and saving. You pass them once, as host, when you construct the editor:

const editor = new Editor({
container: '#app',
host: {
// Lists — return an array (or a promise of one) of { name, url } objects.
documents: () => getJson('/api/documents'),
templates: () => getJson('/api/templates'),
clips: () => getJson('/api/clips'),

// The manifest: pass it directly if you have it at page load (push)…
manifest_data: { images, fonts },
// …or supply `manifest: () => getJson('/api/manifest')` to load it lazily (pull). Not both.

// Saving — `data` is the serialized document JSON string.
save_document: (name, data) => postForm('/api/documents', { name, data }),
save_template: (name, data) => postForm('/api/templates', { name, data }),
save_clip: (name, data, thumbnail) => postForm('/api/clips', { name, data, thumbnail }),
},
legacy_php_fallback: false,
});

async function getJson(url) {
const response = await fetch(url);
return response.json();
}

async function postForm(url, fields) {
const form = new FormData();
Object.entries(fields).forEach(([key, value]) => value !== undefined && form.append(key, value));
await fetch(url, { method: 'POST', body: form });
}

Every provider may return its value directly or as a promise, and the editor always awaits it. Providers you only know after startup can be added later with editor.get_document().host.set_providers({ … }).

Missing providers hide their UI​

With legacy_php_fallback: false, a capability you don't provide simply isn't offered. Leave out save_template and Save as template disappears; leave out clips and the Clips tab disappears. Save As (download to a local file) and Open local always stay. The full table is in EditorUI API → Capability-driven UI.

legacy_php_fallback

Older editors fell back to relative php/*.php endpoints on your host when a callback was missing. That still happens on latest unless you pass legacy_php_fallback: false, and each fallback logs a [TYP-697] deprecation warning. New integrations should always turn it off. Version 2 removes it.

Deprecated: set_*_callback

set_documents_callback, set_templates_callback, set_clips_callback, set_save_document_callback, set_save_template_callback and set_save_clip_callback still work on latest, but they are deprecated, log a warning, and are removed in version 2. The form widget has no provider path yet. Its own widget.set_save_template_callback is not deprecated; registering on the canvas document instead, as its quick start does, logs this warning. Each has a provider of the same name, but the shapes differ, so adapt the function rather than passing it unchanged:

Setter callbackProvider
list: (message) => JSON string() => array of { name, url }
save: ({ name, data }) => void(name, data) => void
clip: ({ name, data, thumbnail }) => void(name, data, thumbnail) => void

data stays the serialized document JSON string in both. Never call typograph.setup() yourself next to the editor: it replaces the editor's document with an empty one. Pass host to the constructor instead.

The per-panel resolution order and provider details are in Embedding.

Manifest — images, fonts and swatches​

The manifest lists the images, fonts and swatches the editor offers. host.manifest_data (see above) takes { images, fonts } and backs the Images tab. host.manifest, the pull variant, is a function that returns the same { images, fonts } object, or a promise of one. Both providers pass on only images and fonts: swatches in either are ignored, and are applied only through set_manifest_data. To (re)apply a manifest to the open document at runtime, call set_manifest_data on the editor's document. It is async, so await it before assuming the assets are in place.

document.addEventListener('typograph_editor_ready', async () => {
const typograph_document = editor.get_document();

await typograph_document.set_manifest_data({
images: [
{
id: '019b28fb-a11e-7641-a28f-e978f892ec06',
name: 'hero',
size: { w: 5213, h: 4160 },
original_url: 'https://cdn.example.com/hero/original.jpg',
editor_url: 'https://cdn.example.com/hero/editor.webp',
print_url: 'https://cdn.example.com/hero/print.jpg',
thumbnail_url: 'https://cdn.example.com/hero/thumb.webp',
},
],
fonts: [/* { id, name, url } — Open Sans / Source Sans Pro are built in */],
swatches: [/* named colours available in the editor */],
});
});

The document exists once the editor has booted, so the snippets below run inside that same listener.

The second argument, preserve_geometry, says what happens to a placed image whose picture the manifest replaces:

ValueImage set to Scale to fit or Scale to fillImage sized by hand
true (default)keeps its fit, recomputed for the new picturekeeps its size: a picture with other proportions is stretched
falsereset to Scale to fit, centredreset to Scale to fit, centred
'refit'keeps its fit, recomputed for the new pictureset to refit_scale (Scale to fill by default)

To replace pictures, use 'refit': the new picture keeps its own proportions and the fit the user chose. A third argument, refit_scale, gives images sized by hand Scale to fit instead. It is an argument, not a key in the manifest:

await typograph_document.set_manifest_data(data, 'refit');
await typograph_document.set_manifest_data(data, 'refit', 'Scale_to_Fit');

'refit' is available on latest. An editor pinned to an older version treats it as true, without an error.

To fetch a manifest from a URL instead, use set_manifest_url(url, replace = true, preserve_geometry = true, refit_scale), which takes the same preserve_geometry and refit_scale values:

await typograph_document.set_manifest_url('https://example.com/manifest.json');

Pass replace: false to merge into the existing manifest rather than clearing it first.

Opening a Document Programmatically​

document.addEventListener('typograph_editor_ready', () => {
editor.get_document().open_document_url(url);
});

window.typograph.get_typograph_document() returns the same document, for code that has no editor at hand.

Backend Glue​

The editor expects your backend to:

  1. List user-owned documents / templates / clips. Your documents, templates and clips providers return { name, url } objects pointing at JSON payloads the editor can fetch.
  2. Persist save operations. Your save_* providers receive name and data (the serialized document JSON string) and store them.
  3. Host images, fonts and swatches from the manifest at the *_url fields you supply.

If you want those backing URLs to be Typograph-hosted, wire your backend to the Typograph File Service and the presigned-URL upload flow. Before publishing a document, run it through POST /v1/document/validate to catch schema issues early.