---
title: "Blok Title API — page title and icon"
description: "Read and set the page title and its icon, move them into your own element, and write them without an undo step."
source: https://blokeditor.com/docs/title-api/
lastmod: 2026-10-08
---

Framework JavaScript

Interface Title

On this page title.get()

# Title API: the page title and icon

Read and change the page title and its icon. They sit above the first block, or in an element of your own. The title is one line of plain text. Both are stored in the document and saved as `title` and `icon` in OutputData.

Turn it on with the `pageTitle` option. `editor.title` exists right after `new Blok()`, and calls made before `isReady` are replayed once the editor is ready. `title.focus()` is the exception: before `isReady` it does nothing.

The opt-in `title: true` option carries the title through conversions. Without it, every conversion works as it did before.

- `blocksToHtml` writes it first as an `<h1>`, led by the icon.
- `blocksToMarkdown`, `blocksToMarkdownWithReport` and `blocks.exportMarkdown` write it as a `#` heading. An image icon is left out.
- `blocksToPlainText` writes it as the first line.
- `markdownToBlocksWithReport` and `htmlToBlocksWithReport` lift a leading `#` heading or `<h1>` out of the blocks. They return it as `title` and `icon`. A heading that starts with an emoji and a space comes back as the icon plus the title.
  - The title is plain text, so formatting in that heading is dropped. Both reports add a `heading` warning for it.
  - `markdownToBlocks` and `htmlToBlocks` with the option only drop the heading.
- `blocks.importMarkdown` sets the editor's title and icon from that heading. A heading with no emoji clears the icon, and no heading clears both.
- `extractTexts` puts the title first in the list, and `injectTexts` writes it back from there. Every other index moves by one. A blank translation removes the title.

These limits are known.

- Restoring a version saved before the server kept page titles clears the current title.
- A collaborative editor that opens offline shows its last-known blocks, but no title.
- A host cannot set the title through the server's `/edit` endpoint.
- When two people type in the title at once, the last write wins.

Last updated Oct 8, 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

### title.get()

string

The current title. It is `''` when there is none.

TypeScript

```
console.log(editor.title.get()); // 'Roadmap'
```

### title.set(text, options?)

void

Replaces the title. It makes one undo step and calls `onChange` with source `api`. Setting the text the title already has does nothing.

Pass `{ record: false }` for a write that should add no undo step, such as a rename that came from your own backend. Peers still get the write, and `onChange` gets `{ source: 'api', record: false }`.

- Undo does not go past this value until the next recorded title change. After that, undo can bring back a title from before it.
- To make the value a floor, call `history.clear()` right after the write.
- A typing run that goes on after it stays one undo step. Undo goes back to the value you wrote, not to the value before the run.

Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `text` | `string` | Required | — | The new title. `''` clears it. |
| `options.record` | `boolean` | — | `true` | `false`: no undo step. Peers still get the write. |

TypeScript

```
editor.title.set('Roadmap 2027');

// A rename from your backend: no undo step
editor.title.set(serverTitle, { record: false });
```

### title.focus(position?)

void

Puts the caret in the title, at the end by default. Pass `'start'` for the start. It does nothing when the title is off, and before `isReady`.

TypeScript

```
editor.title.focus('start');
```

### title.mount(holder)

void

Moves the title and icon into `holder`, an element or a selector. Focus and the caret are kept. `null` puts the title back above the first block.

After `isReady`, a selector that matches nothing throws. Before `isReady` the call waits for the editor. A bad selector is then logged, and the title stays where it is.

To line up a section of your own between the title and the editor, copy the title's layout. Its `--blok-content-column` variable is set on the title's own element, so a section beside it cannot read it.

- Pad the section's start by the editor gutter, `56px` by default.
  - It is `0` with `hideToolbar: true` or `readOnly: { hideControls: true }`.
  - `toolbarPosition: 'right'` moves it to the end.
- Inside it, give the content `box-sizing: border-box`, `max-width: var(--blok-content-max-width, 720px)` and `padding-inline: var(--blok-block-padding-inline, 2px)`.
- In `full` width, use `none` instead of `720px`.
- Align it as `style.contentAlign` says.
  - The default `'left'` needs no margin.
  - `'center'` needs `margin-inline: auto`.
  - `'right'` needs `margin-inline-start: auto`.

The section reads these variables only from its own ancestors. Blok applies `style.tokens` to its own elements only, so a section beside the title does not see them.

- If you set `--blok-content-max-width`, declare it on an element that holds both the section and the editor.
- Otherwise the section falls back to `720px` and drifts away from the title.

The gutter works differently. Blok declares `--blok-editor-gutter-start` on the editor's own element, so a value on an ancestor does not reach the editor. `style.tokens` skips gutter variables with a warning.

- Change the gutter with a CSS rule on the editor, such as `[data-blok-interface] { --blok-editor-gutter-start: 20px; }`.
- The section cannot read that rule. Give it the same value yourself, written out or as the same variable on one of its own ancestors.

TypeScript

```
editor.title.mount('#page-title');

// A section of your own between the title and the editor
// .page-meta { box-sizing: border-box; padding-inline-start: 56px; }
// .page-meta > div {
//   box-sizing: border-box;
//   max-width: var(--blok-content-max-width, 720px);
//   padding-inline: var(--blok-block-padding-inline, 2px);
// }

// Back above the first block
editor.title.mount(null);
```

### title.icon.get()

PageIcon | null

The current icon: `{ type: 'emoji', value }` or `{ type: 'image', url }`. It is `null` when there is none.

TypeScript

```
const icon = editor.title.icon.get(); // { type: 'emoji', value: '🗺️' }
```

### title.icon.set(icon, options?)

void

Sets the icon, or removes it with `null`. It makes one undo step and calls `onIconChange` with source `api`. `{ record: false }` works as it does for `title.set`.

TypeScript

```
editor.title.icon.set({ type: 'emoji', value: '🚀' });
editor.title.icon.set(null);
```

TypeScript

```
import { Blok } from '@bloklabs/core';

const editor = new Blok({
  holder: 'editor',
  pageTitle: {
    placeholder: 'Untitled',
    onChange: (title, change) => {
      if (change.source !== 'remote') {
        saveTitle(title);
      }
    },
  },
  data: { title: 'Roadmap', icon: { type: 'emoji', value: '🗺️' }, blocks: [] },
});

editor.title.get(); // 'Roadmap'

const saved = await editor.save();
// saved.title === 'Roadmap'
```

## Title API

| Property | Description |
| --- | --- |
| `holder` | `HTMLElement | string` | Element or selector to draw the title in. Without it, the title sits above the first block. |
| `placeholder` | `string` | Shown while the title is empty. The default is the localized `title.placeholder` message. |
| `icon` | `boolean` | `false` hides the icon and the Add icon button. |
| `onChange` | `(title: string, change: TitleChange) => void` | Called on every title change, from any source. `change.source` is `user`, `undo`, `redo`, `remote` or `api`. Save on `user`, `undo`, `redo` and `api`. `change.record` is `false` only for a write made with `{ record: false }`. Otherwise the key is absent. |
| `onIconChange` | `(icon: PageIcon | null, change: TitleChange) => void` | Called on every icon change, from any source. `null` means the icon was removed. `change` is the same as for `onChange`. |
