> ## Documentation Index
> Fetch the complete documentation index at: https://developers.notion.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Worker capability

> Declare a custom block on a worker with worker.customBlock().

<Warning>
  Custom blocks are in private alpha:

  * Do not discuss publicly or share with anyone outside your workspace.
  * Access must be enabled per workspace. Installing the SDK does not grant access.
  * These APIs may experience breaking changes without notice.
</Warning>

<h2 id="worker-custom-block">
  worker.customBlock()
</h2>

Registers a custom block: a custom interface that Notion embeds in a page and provides data. See the [custom blocks quickstart](/custom-blocks/quickstart) for a walkthrough of building, previewing, and deploying one.

Unlike other worker capabilities, a custom block has no `execute` handler — its code runs in the browser, not the Workers runtime, so there is nothing to invoke with `ntn workers exec`. During `ntn workers deploy`, the configured source directory is built and published alongside the worker.

```typescript theme={null}
worker.customBlock(
	"issueBoard", // Declaration key: identifies this block within the worker.
	{
		path: "./blocks/issueBoard",
		slashCommand: "issues",
		name: "Issue board",
		description: "Track issues by status",
		icon: { type: "emoji", emoji: "🐛" },
		command: "npx vite build",
		output: "dist",
		dataSources: {
			issues: { // Data source key: use in useDataSource("issues").
				name: "Issues",
				properties: {
					title: { name: "Title", type: "title" },
					status: { // Property key: read with item.propertiesByKey.status.
						name: "Status",
						type: "status",
					},
				},
			},
		},
	},
);
```

Data source and property keys are names you choose. The property keys match their types in the example above, but they do not need to. For example, the first property key could be `name` or `Title`. The `type` field defines the kind of data the property holds and uses Notion Public API type names.

The declaration key appears in deploy output, so choose a stable value.

| Property | Type | Description |
| - | - | - |
| `path` | `string` | Path to the block directory, relative to the worker root. |
| `name` | `string` (optional) | Display name, up to 128 characters. Defaults to the declaration key. |
| `description` | `string` (optional) | Description shown during block selection, up to 1024 characters. |
| `icon` | `{ type: "emoji", emoji: string }` (optional) | Block icon. Supports emoji only. |
| `type` | `"project"` or `"static"` (optional) | Use `"project"` for a directory that needs a build, or `"static"` for prebuilt assets. Defaults to `"project"`. |
| `slashCommand` | `string` (optional) | Slash command that inserts the block. Use 1–64 lowercase letters, numbers, hyphens, or underscores, starting with a letter or number. Omit the leading `/`. Must be unique within the worker. |
| `dataSources` | `object` (optional) | Map of declared keys to [data source schemas](#data-source-schemas). Defaults to `{}`. |
| `command` | `string` (optional) | Build command to run in `path`. Defaults to `npm run build`. Not allowed when `type` is `"static"`. |
| `output` | `string` (optional) | Directory containing the built files, relative to `path`. Defaults to `dist`. Not allowed when `type` is `"static"`. |
| `version` | `1` (optional) | Manifest version. Defaults to `1`. |

When `slashCommand` is set, members who can connect to the worker see the custom block in the slash menu after deployment. Custom blocks in different workers can share a slash command name. Within one worker, each custom block slash command must be unique. The registered slash command does not appear on mobile.

The configuration UI uses the block key when `name` is absent. Without `icon`, Notion uses the default custom-block icon. Without `description`, the configuration UI omits the description. The slash menu preserves its command label and worker details when metadata is absent.

You can search by display name, slash command, block key, or worker name. Deploy an existing worker again with an SDK version that supports these fields to populate its metadata.

### Block sources

The default `"project"` source is built during deploy, running `command` in `path` and uploading `output`, as in the example above.

A `"static"` source is instead a directory of already-built browser assets, uploaded as-is with no build step. `command` and `output` do not apply:

```typescript theme={null}
worker.customBlock("issueBoard", {
	type: "static",
	path: "./dist",
});
```

### Remove or rename a custom block

Each Worker deployment reconciles the declared custom block keys with the previous deployment. If you remove a `worker.customBlock()` declaration, Notion soft-deletes that block definition. Deleting the Worker soft-deletes all its custom block definitions.

Changing a block key removes the old definition and creates a new one. Existing block instances stay connected to the old key and stop rendering their custom interface.

To recover from an accidental removal or rename, add the `worker.customBlock()` declaration with its original key. Then deploy the Worker again. For 30 days after removal, Notion retains the definition and its deployments. Redeploying the same key on the same Worker in the same workspace restores the definition during this period. Existing block instances can then render again.

After 30 days, Notion permanently deletes the retained definition, deployment records, and bundles. After permanent deletion, redeploying the same key on the same Worker in the same workspace recreates the definition with the same ID. Existing instances can render the new deployment. You cannot recover the deleted deployment history.

Choose stable block keys. Repeated deployments without the removed key do not restart the 30-day retention period.

### Data source schemas

`dataSources` declares what Notion data the block expects to read. Each entry describes a shape, not a specific data source — the person configuring a block instance in Notion binds each declaration to a concrete Notion data source. The worker never names one.

```typescript theme={null}
dataSources: {
	issues: {
		name: "Issues",
		description: "The team's issues",
		icon: { type: "emoji", emoji: "🐛" },
		properties: {
			title: { name: "Title", type: "title" },
			status: { name: "Status", description: "Workflow state", type: "status" },
		},
	},
}
```

| Property | Type | Description |
| - | - | - |
| `name` | `string` | Display name shown when someone binds this data source. |
| `description` | `string` (optional) | Description of what the block expects the data source to contain. |
| `icon` | `object` (optional) | An emoji icon, `{ type: "emoji", emoji: string }`, or an external image, `{ type: "external", url: string }`. |
| `properties` | `object` (optional) | Map of property keys to schemas. Each schema requires `name` and `type`, with an optional `description`. |

Property `type` accepts the Notion property types: `title`, `rich_text`, `number`, `select`, `multi_select`, `status`, `date`, `people`, `files`, `checkbox`, `url`, `email`, `phone_number`, `formula`, `relation`, `rollup`, `created_time`, `created_by`, `last_edited_time`, `last_edited_by`, `last_visited_time`, `button`, `unique_id`, `location`, `verification`, and `place`.

Supported property types vary by operation. See [supported filters, sorts, and updates](/custom-blocks/sdk/data-sources).

See the [data sources reference](/custom-blocks/sdk/data-sources) for querying and updating rows.
