> ## 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.

# Binding a block to Notion data

> Understand how a block's declared data sources get bound to real Notion data.

<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>

A custom block does not name a data source. It declares the *shape* of the data it needs, and whoever inserts the block binds each declaration to a real Notion data source. This keeps a block reusable across workspaces and keeps the block author out of the business of guessing data source IDs.

That indirection is the single most important thing to understand about custom block data, and it is the source of most confusion when a block does not render.

## The three stages

**You declare.** In `dataSources` on `worker.customBlock()`, you describe what your block needs: one or more named data sources, each with the properties it expects and their types.

```typescript src/index.ts theme={null}
worker.customBlock("issueBoard", {
	path: "./blocks/issueBoard",
	dataSources: {
		issues: {
			name: "Issues",
			description: "The team's issues",
			properties: {
				title: { name: "Title", type: "title" },
				status: { name: "Status", type: "status" },
			},
		},
	},
})
```

The `name` and `description` are what a person sees when they configure the block, so write them for that reader. See the [worker capability reference](/custom-blocks/workers/capability) for every field.

**Someone adds and binds.** A user with access to the Worker can add an instance of the custom block on a page. Anyone with edit access to the page containing that instance can bind it to a Notion data source where they have at least *viewer access*. They map its properties to the ones you declared. For example, `Ticket status` can satisfy `status`.

**Your frontend queries by key.** At runtime the block asks for `issues`, not for a data source ID, and reads `propertiesByKey.status`, not a property ID. The keys you chose in the worker are the keys the frontend uses.

```tsx theme={null}
const { items } = useDataSource("issues")
items[0].propertiesByKey.status
```

Keeping those keys in sync between the worker declaration and the frontend is the main discipline this design asks of you.

## Access and permissions

The binding does not grant access to the data source. Every viewer must have access to each bound data source and its database. Access to the block's page does not grant access to a separately bound database.

Notion checks access, bindings, and schema before initialization. If a viewer lacks access to a required data source, the block does not load or render partial data. Give the viewer access or bind the block to a data source they can access. If a data source is unbound or its schema no longer matches the declaration, initialization fails with a binding error.

If your component renders, its declared data sources are bound and match their declarations. Your code does not need to handle a half-configured state or check whether a declared data source exists.

Viewer access to a bound data source does not grant edit access. A block can load successfully but still receive an access error when it writes. Handle these errors in your block code. See [error handling](/custom-blocks/sdk/errors).

## Prefer keys over IDs

Every row exposes its values twice: `propertiesByKey`, keyed by the keys you declared, and `propertiesById`, keyed by the bound data source's real property IDs.

Prefer `propertiesByKey`. It is stable across every workspace your block is installed in, whereas property IDs belong to whichever data source someone happened to bind. Reach for `propertiesById` and `propertyIdsByKey` only when you need to work with properties beyond the ones you declared.

## Declaring less is usually better

Every property you declare is a property someone must map before your block will run. A block that declares four properties is meaningfully easier to install than one that declares twelve.

Declare what your block genuinely reads or writes. If a property is only used in one optional view, consider whether the block can degrade without it rather than making it a hard requirement of installation.

## Next

* [Data sources reference](/custom-blocks/sdk/data-sources) — querying, updating rows, and the full result shape.
* [Worker capability](/custom-blocks/workers/capability) — every field of the declaration.
* [Pages](/custom-blocks/sdk/pages) — creating and archiving rows, and working outside a bound data source.
