Embed

Drop the editor into your own site or app with one script tag. A Plus license key removes the watermark.

Install

Paste the snippet below and you are done. It runs from a single 82KB file with no dependencies.

<div id="typdit-editor"></div>
<script src="https://typdit.com/embed/v1.js"></script>
<script>
  const editor = Typdit.create(document.getElementById('typdit-editor'), {
    placeholder: 'Write here',
    licenseKey: 'YOUR_KEY',
    onChange: (snapshot) => console.log(snapshot),
  });
</script>

Take it straight from the editor page

You do not have to write the install code by hand. Set the mode and theme the way you want on the editor page, scroll down, and the install code with exactly those settings is waiting there. Options that match the defaults are left out, so what is special about your setup stands out.

Copy with document also carries what is on screen as the snapshot option. Use it when you opened a template, liked the skeleton, and want to take the skeleton with you.

All options

Everything goes in the second argument of Typdit.create(element, options). All fields are optional; defaults are noted below.

OptionWhat it does
snapshotInitial document (JSON snapshot, see the format section). Omit to start empty
theme: 'light' | 'dark'Editor theme (default light)
mode: 'notion'Notion-style preset: hides the toolbar and turns on block handles (see below). Default classic
handles: trueToggle block handles on their own (overrides the mode preset)
writingMode: 'paper'Writing mode. One of plain, focus, book, paper, essay, script, techdoc, or a preset object. It switches setting, counts, outline, footnotes and screenplay flow together
writingMode: { id, extends }Override a few fields on a builtin to make your own mode. Leave out extends and you start from a bare base
writingLabelsReplace the mode UI strings (metric names, outline, footnotes, goal). Partial is fine; the rest fall back to English
writingSlashLabelsStrings for the mode specific slash entries (footnote, screenplay blocks)
handleLabelsReplace block handle labels (partial objects allowed)
toolbar: falseHide the top toolbar (shown by default)
menus: falseDisable the slash command and selection bubble menus (on by default)
markdown: falseDisable markdown input shortcuts like # headings and - lists (on by default)
typography: falseDisable smart quotes and ellipsis substitution (on by default)
placeholderHint text shown in an empty document
autofocus: trueFocus the editor right after creation (off by default)
toolbarLabelsReplace toolbar labels (partial objects allowed, see below)
licenseKeyPlus license key. Removes the watermark after domain verification
onChange(snapshot)Called with the latest snapshot on every content change
onRequestImage()Image uploader hook. Resolve {src, alt?} to insert, null to cancel. Without it the toolbar falls back to URL input
onRequestFile()File attachment hook returning {src, name?, size?}. The file button renders only when this hook is provided
onRequestVideo()Video upload hook returning {src}. Adds an upload button next to the video link input (link input always works)

Notion-style mode

mode: 'notion' hides the toolbar and turns on block handles. Hover a block and an add (+) and a menu (⋮⋮) button appear on its left; the menu moves the block up or down, duplicates, deletes, or turns it into another block type. Formatting stays available through slash commands and the selection bubble menu.

Without a toolbar, the image, video and file items also drop out of the slash menu (their input UI belongs to the toolbar). If you need both handles and attachments, pass toolbar: true alongside; handles and the toolbar coexist.

Typdit.create(el, {
  mode: 'notion',
  handleLabels: { moveUp: '위로 이동', moveDown: '아래로 이동', delete: '삭제' },
});

Instance methods

The handle returned by Typdit.create() reads and writes the document. Storage is entirely up to the host site.

MethodWhat it does
getSnapshot()Returns the current document as a JSON snapshot
setSnapshot(json)Replaces the whole document with a snapshot (load)
getText()Returns plain text without formatting (for search indexing or counting)
focus()Moves focus into the editor
destroy()Removes the editor and detaches listeners. Call on unmount in an SPA
versionSDK version string (currently 1.0.0)

The snapshot format

A snapshot is flat JSON you can store and hand back as is. Each line of text maps 1:1 to an entry in blocks, and marks are offset ranges into text. Unknown block or mark types are silently skipped on load, so a snapshot written by a newer version still opens in an older one.

{
  "version": 1,
  "text": "제목\n첫 문단입니다.",
  "blocks": [
    { "type": "heading", "attrs": { "level": 1 } },
    { "type": "paragraph" }
  ],
  "marks": [
    { "type": "bold", "from": 3, "to": 7 }
  ]
}

Wiring up autosave

onChange fires on every keystroke. The usual pattern is to debounce and send to your server once typing pauses.

let timer;
const editor = Typdit.create(el, {
  onChange: (snapshot) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fetch('/api/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(snapshot),
      });
    }, 800);
  },
});

Hooking up image and file uploads

The editor never uploads files itself. Your hook handles picking and uploading, returns a URL, and the editor does the inserting and rendering. Without hooks, images still work via URL input, and the file button appears only when onRequestFile is provided.

Typdit.create(el, {
  onRequestImage: async () => {
    const file = await pickFile('image/*');   // 파일 선택 UI는 호스트 구현
    if (!file) return null;                   // null = 취소
    const src = await uploadToMyServer(file); // 업로드도 호스트 구현
    return { src, alt: file.name };
  },
  onRequestFile: async () => {
    const file = await pickFile('*/*');
    if (!file) return null;
    const src = await uploadToMyServer(file);
    return { src, name: file.name, size: file.size };
  },
  onRequestVideo: async () => {
    const file = await pickFile('video/*');
    return file ? { src: await uploadToMyServer(file) } : null;
  },
});

Changing toolbar labels

Default labels are English. Pass only the keys you want to change in toolbarLabels; the rest keep their defaults. Full key list: text, quote, code, divider, table, image, video, file, upload, font, fontDefault, color, highlight, none, alignLeft, alignCenter, alignRight, imageUrl, videoUrl, link, linkApply, linkRemove, undo, redo.

Typdit.create(el, {
  toolbarLabels: {
    text: '본문', quote: '인용', code: '코드', divider: '구분선',
    image: '이미지', video: '영상', link: '링크',
    undo: '되돌리기', redo: '다시 실행',
  },
});

Using it with React

Grab the container with a ref, create on mount, and call destroy() on unmount.

import { useEffect, useRef } from 'react';

function TypditEditor({ onChange }) {
  const ref = useRef(null);
  useEffect(() => {
    const editor = window.Typdit.create(ref.current, { onChange });
    return () => editor.destroy();
  }, []);
  return <div ref={ref} />;
}

Using it with Vue

Create in onMounted and call destroy() in onBeforeUnmount.

<template><div ref="host"></div></template>

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
const host = ref(null);
let editor;
onMounted(() => { editor = window.Typdit.create(host.value, {}); });
onBeforeUnmount(() => editor?.destroy());
</script>

Using it with SSR frameworks (Next.js etc.)

The script runs only in the browser. There is no window during server rendering, so load the script and create the editor inside a client-only component.

'use client';
import { useEffect, useRef } from 'react';

export default function Editor() {
  const ref = useRef(null);
  useEffect(() => {
    let editor;
    const s = document.createElement('script');
    s.src = 'https://typdit.com/embed/v1.js';
    s.onload = () => { editor = window.Typdit.create(ref.current, {}); };
    document.head.appendChild(s);
    return () => editor?.destroy();
  }, []);
  return <div ref={ref} />;
}

Multiple editors on one page

Every Typdit.create() call makes an independent instance, so a page can hold as many as you need. Styles are injected once as a single style tag on first creation, and each instance is removed individually via its own destroy().

License and watermark

  • Without a key the editor is fully functional, with a small watermark at the bottom.
  • Plus subscribers issue per-domain keys from the Embed card on the editor page. A key is valid on its registered domains and their subdomains (writing *.example.com means the same thing).
  • A key expires with the subscription term it was issued under. After extending your plan, issue a new key.
  • A failed check or blocked network never disables the editor. The watermark just stays; no error is thrown.

Network and CSP

  • Document content is never transmitted anywhere. The editor makes no network requests for content; storage is entirely the host site's.
  • The only request is a single license check when licenseKey is set (read-only, to firestore.googleapis.com).
  • Sites with a CSP allow typdit.com in script-src, and firestore.googleapis.com in connect-src if a key is used. Images and videos placed in the document are separate: their origins must be open in img-src and frame-src (YouTube embeds use youtube-nocookie.com).
  • Styles arrive as one style tag scoped by td- prefixed classes, so they do not mix with host CSS.

Versioning

/embed/v1.js is a major-version-pinned URL. Backward compatibility holds within the v1 URL; breaking changes ship as a new URL (v2). Check the loaded version at runtime with Typdit.version.

Next: AI tools →