---
title: "Blok Class API — new Blok(), isReady, destroy"
description: "Create, await, and tear down an editor instance, and what is safe to call before isReady resolves."
source: https://blokeditor.com/docs/core/
lastmod: 2026-09-07
---

Framework JavaScript

Core Blok Class

On this page save()

# The Blok class: create and destroy an editor

The main editor class that initializes and manages the Blok editor instance. Every namespace a tool reaches through `api.*` is also reachable on the instance as `editor.*` — the properties below are that same surface, plus the `width`, `placeholder`, `tokens` and `i18n` namespaces the class declares itself.

Last updated Jun 30, 2026 [Edit this page on GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/api-data.ts)

### Reaching the editor instance

The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.

TypeScript

```
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');
```

## Methods

### save()

Promise<OutputData>

Extracts the current editor content as structured JSON data. This is the primary method for persisting editor content.

When to use

Call after `await editor.isReady`. The returned JSON is your source of truth — store it and feed it back to `render()`.

Errors

- The editor is in read-only mode when save() is called. Blok's content can not be saved in read-only mode Call `readOnly.set(false)` before saving, or persist from the last `onSave` payload / your own mirrored state.

TypeScript

```
// Save editor content
const data = await editor.save();
console.log(data.blocks); // Array of block data
```

### render(data)

Promise<void>

Renders editor content from previously saved JSON data. Accepts the loose wire shape (`LooseOutputData`) — `null` values for block `data`, `id`, or `time` from backend DTOs are normalized at the boundary.

When to use

Loads saved content and replaces the current document. To append instead of replace, use `blocks.insertMany()`.

TypeScript

```
// Load saved content
const savedData = {
  blocks: [
    { id: '1', type: 'paragraph', data: { text: 'Hello' } }
  ]
};
await editor.render(savedData);
```

### focus(atEnd?)

boolean

Sets focus to the editor. Optionally positions cursor at the end of content.

When to use

Pass `true` to place the caret at the very end. For a specific block or offset, use the `caret` API instead.

TypeScript

```
// Focus at start
editor.focus();

// Focus at end
editor.focus(true);
```

### clear()

Promise<void>

Removes all content from the editor. One empty block of the default tool is left behind, so the editor is never block-less — a subsequent save() still returns `blocks: []`, because the blank default block does not validate and is dropped from the output.

When to use

Wipes all blocks and leaves one empty paragraph. It's undoable — unlike `destroy()`, which tears the instance down.

TypeScript

```
// Clear all content
await editor.clear();
```

### destroy()

void

Destroys the editor instance and removes all DOM elements and event listeners.

When to use

Call from your framework's unmount hook to avoid leaked listeners. The instance is unusable afterwards — create a new `Blok` to start again.

TypeScript

```
// Clean up on component unmount
editor.destroy();
```

### handlers.set(handlers)

void

Installs, replaces or removes the live editor callbacks — `onChange`, `onSave`, `onEnter`, `onSubmit`, `onBeforeRender`, `onAfterRender` — in place, so caret, selection, scroll and undo history all survive. Only the keys you pass are touched; a key whose value is `undefined` UNSETS that handler. That matters because callback presence is itself the semantics: an `onSubmit` makes Enter serialize-and-submit instead of splitting the block, and an `onSave` arms the change-observation pipeline. Use it to make a callback reactive without recreating the editor — the React, Vue and Angular adapters drive this setter for you when a prop, listener or `[config]` callback appears or disappears.

When to use

Only the keys you pass are touched — a key set to `undefined` unsets that handler, which is how "Enter sends" is turned back off.

Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `handlers` | `LiveHandlers` | Required | — | Partial map of live callbacks. Omitted keys are left as they are; a key set to `undefined` unsets that handler. |

TypeScript

```
// "Enter sends" while composing, default Enter while editing a draft
editor.handlers.set({
  onSubmit: sendsOnEnter ? (data) => send(data) : undefined,
});

// Start mirroring content into your store, later stop again
editor.handlers.set({ onSave: (data) => store.set(data) });
editor.handlers.set({ onSave: undefined });
```

### whenAllReady(options?)

Promise<void>

Static method — resolves once every Blok instance in scope has finished booting (each instance's `isReady` has settled; rejections count as settled). A collective-readiness signal for pages hosting several instances, replacing hand-aggregated per-instance `onReady` callbacks. Pass `within` (an Element) to count only instances mounted inside a subtree you own, so an unrelated editor elsewhere on the page cannot hold your gate closed. Pass `settleOn: 'rendered'` to extend readiness from construction to content-in-the-DOM, which also covers post-boot re-renders from `render(data)`. An empty scope resolves immediately. Instances that appear while the promise is pending extend the wait; instances constructed after it resolves are not covered — call again, or use `subscribeReady()` for a live signal.

When to use

Call it as `Blok.whenAllReady()` on the class, not on an instance. An instance that is still booting and whose wrapper is not attached to the document yet counts in every scope — over-waiting is safe, under-waiting is a bug.

TypeScript

```
// A comments list: N read-only bodies + a composer.
// Wait only for the editors inside this list.
await Blok.whenAllReady({
  within: listElement,
  settleOn: 'rendered',
});
composer.focus();
```

### readyState(options?)

{ total: number; pending: number; ready: boolean }

Static method — synchronous readiness snapshot for a scope: how many instances match `within`, how many are still pending at the requested `settleOn` depth, and whether the scope is settled. An empty scope reports `ready: true`, so no "nothing to wait for" special case is needed.

When to use

Cheap enough to call on every notification from `subscribeReady()`; it walks the registered instances and tests DOM containment.

TypeScript

```
const { pending, ready } = Blok.readyState({ within: listElement });

if (!ready) {
  showSkeleton(pending);
}
```

### subscribeReady(listener)

() => void

Static method — subscribes to readiness changes across all instances (construction, boot, render-state flip, destroy) and returns an unsubscribe function. The listener takes no arguments: re-read `Blok.readyState(scope)` when it fires. Pairs with `useSyncExternalStore` and other store adapters, giving a live signal instead of a one-shot latch.

When to use

Framework users should prefer the adapter wrappers — `useBlokReady()` in @bloklabs/react and @bloklabs/vue, `injectBlokReady()` in @bloklabs/angular — which wrap this subscription and the scope lookup.

TypeScript

```
const unsubscribe = Blok.subscribeReady(() => {
  setReady(Blok.readyState({ within: listElement }).ready);
});

// later
unsubscribe();
```

### createSelector(attr, value?)

string

Named export of the package root (not a member of the Blok class) — builds a CSS selector from a `DATA_ATTR` value. With no `value` it produces a presence selector; with one it produces an equality selector. Use it together with `Blok.DATA_ATTR` instead of matching Blok's internal class names, which are not part of the public surface.

TypeScript

```
import { DATA_ATTR, createSelector } from '@bloklabs/core';

createSelector(DATA_ATTR.element); // '[data-blok-element]'
document.querySelectorAll(createSelector(DATA_ATTR.selected, true));
```

### icons

string

Named exports of the `@bloklabs/core/icons` subpath (not members of the Blok class) — Blok's own glyphs as SVG strings, one `Icon*` constant per glyph (`IconBold`, `IconPlus`, `IconTrash`, `IconWarning`, …). Because they are plain strings they drop straight into the places a tool must supply markup: a tool's `static get toolbox()` icon and the entries returned by `renderSettings()`. The subpath ships a generated, self-contained declaration file (`types/icons.d.ts`) listing every constant, so editor autocomplete is the reference for the full set.

TypeScript

```
import { IconBold, IconPlus } from '@bloklabs/core/icons';

class Callout {
  static get toolbox() {
    return { title: 'Callout', icon: IconPlus };
  }

  renderSettings() {
    return [{ icon: IconBold, title: 'Bold text', onActivate: () => this.toggleBold() }];
  }
}
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `DATA_ATTR` | `Record<DataAttrKey, DataAttrValue>` | Named export of the package root (`import { DATA_ATTR } from '@bloklabs/core'`), not a property of the editor instance — the stable `data-blok-*` attribute names Blok writes on its DOM. This is the supported way to query editor DOM and write host tests, instead of matching internal class names. The `DataAttrKey` / `DataAttrValue` types and the `createSelector()` helper are exported alongside it. |
| `BLOK_FONT_SIZE_TOKENS` | `BlokFontSizeTokens` | Named export of the package root (`import { BLOK_FONT_SIZE_TOKENS } from '@bloklabs/core'`), not a property of the editor instance — the CSS custom property each `style.fontSize` scenario writes, in a map shaped exactly like the config itself (`BLOK_FONT_SIZE_TOKENS.paragraph`, `.heading[1]`, `.list.checklist`, `.bookmark.link`, …). Use it wherever typography is driven by a channel other than the constructor config — a per-region CSS rule, or `editor.tokens.set({ [BLOK_FONT_SIZE_TOKENS.paragraph]: '18px' })` at runtime — so the custom property names never have to be hand-copied and a rename is a compile error instead of a silent no-op. |
| `version` | `string` | Named export of the package root (`import { version } from '@bloklabs/core'`), not a property of the editor instance — the running editor version, the same value stamped into `OutputData.version`. |
| `PendingBlok` | `{ isReady; isRendered; destroy(); theme; width; placeholder; tokens; i18n }` | A type exported from the package root (`import type { PendingBlok } from '@bloklabs/core'`), not a property of the editor instance — the surface guaranteed to exist synchronously between `new Blok(config)` and `isReady` resolving. Blok builds its module APIs (`blocks`, `caret`, `history`, `readOnly`, …) asynchronously, so reading them earlier returns `undefined`. `PendingBlok` declares only the eight members listed here, which turns that window into a compile error instead of an `undefined` at runtime: `const pending: PendingBlok = new Blok(config); const editor = await pending.isReady;`. |
| `isReady` | `Promise<Blok>` | Promise that resolves with the ready editor instance. The API namespaces below (`blocks`, `caret`, `history`, `readOnly`, …) are built asynchronously and are `undefined` until it resolves — type the reference you hold during that window as `PendingBlok`. |
| `isRendered` | `boolean` | Synchronous render-readiness flag — true once the current render batch has landed in the DOM (mirrors the `data-blok-rendered` wrapper attribute); false before the first render and while a re-render is in flight. Complements the async `isReady`/`onReady`: no await or callback needed, so mount state can be polled synchronously. |
| `blocks` | `Blocks` | Blocks API module |
| `caret` | `Caret` | Caret API module |
| `history` | `History` | History API module |
| `saver` | `Saver` | Saver API module |
| `toolbar` | `Toolbar` | Toolbar API module |
| `inlineToolbar` | `InlineToolbar` | Inline toolbar API module |
| `tools` | `Tools` | Tools API module |
| `uploader` | `Uploader` | Uploader API module — asset uploads routed by asset kind |
| `events` | `Events` | Events API module |
| `listeners` | `Listeners` | Listeners API module |
| `notifier` | `Notifier` | Notifier API module |
| `sanitizer` | `Sanitizer` | Sanitizer API module |
| `selection` | `Selection` | Selection API module |
| `marks` | `Marks` | Marks API module — range-aware inline-mark operations |
| `styles` | `Styles` | Styles API module |
| `tooltip` | `Tooltip` | Tooltip API module |
| `readOnly` | `ReadOnly` | ReadOnly API module |
| `ui` | `Ui` | UI API module |
| `theme` | `Theme` | Theme API module |
| `width` | `Width` | Width API module |
| `placeholder` | `Placeholder` | Placeholder API module |
| `tokens` | `Tokens` | Runtime theme-tokens API module |
| `i18n` | `EditorI18n` | I18n API module — everything a tool gets through `api.i18n`, widened with `update()` |
| `config` | `Readonly<Pick<BlokConfig, 'linkPaste' | 'link'>>` | Read-only view of selected editor configuration: the `link` and `linkPaste` options this instance was constructed with. A custom inline or link tool reads the host's link policy from here (as `api.config`) instead of re-deriving it. |
| `rectangleSelection` | `{ cancelActiveSelection(): void; isRectActivated(): boolean; clearSelection(): void; startSelection(pageX: number, pageY: number, shiftKey?: boolean): void; endSelection(): void }` | Drag-select (rubber-band) control, also reachable inside a tool as `api.rectangleSelection`. `startSelection(pageX, pageY, shiftKey?)` begins a rubber-band from page coordinates; `endSelection()` resets the drag state and hides the overlay; `isRectActivated()` reports whether a rubber-band is currently active; `clearSelection()` drops the active flag; `cancelActiveSelection()` aborts a selection in progress (clear + end) — what another selection system, e.g. table cell selection, calls when it takes priority. |
