Registering blocks
Writing the component is half the job. A block becomes usable when the manifest declares it and the theme can resolve its id to a component.
blokkli.blocks[]
A block appears in the editor's block list once it's in the theme manifest, keyed by the same id as the component's bundle:
{
"blokkli": {
"blocks": [
{ "id": "badge", "title": "Badge", "description": "Small inline label with an optional icon." },
{ "id": "product_grid", "title": "Product Grid", "description": "Renders products in a responsive grid.", "requiresCapability": "products.list" }
]
}
}
| Field | What it does |
|---|---|
id | The block's identifier. Must equal the component's bundle. |
title | What an editor sees in the block picker. Write it for the editor, not the developer. |
description | The one-line explanation shown alongside the title. Say what the block produces, not how it's implemented. |
requiresCapability | Gates the block on a platform capability — see below. |
title and description are the block's user interface as far as an editor is concerned. "Product Grid — renders products in a responsive grid" tells them something; "ProductGridBlock (v2)" does not.
The block-id-to-component map
The manifest tells the platform the block exists. The theme also needs to tell its renderer which component to instantiate:
import type { Component } from 'vue'
import BlokkliBadge from '../components/blokkli/badge/index.vue'
import BlokkliProductGrid from '../components/blokkli/product_grid/index.vue'
import BlokkliText from '../components/blokkli/text/index.vue'
export const BLOCK_COMPONENTS: Record<string, Component> = {
badge: BlokkliBadge,
product_grid: BlokkliProductGrid,
text: BlokkliText,
}
So three strings must agree for every block:
defineBlokkli({ bundle: 'product_grid' })in the component{ "id": "product_grid" }intheme.jsonproduct_grid:in the block map
Drift between them is the single most common reason a block renders as "unknown." When that happens, check all three before debugging anything else.
requiresCapability
A block that reads platform data can't function on a tenant that doesn't have the providing app installed. requiresCapability makes that explicit:
{ "id": "product_grid", "title": "Product Grid", "requiresCapability": "products.list" }
The effect is per-block rather than per-theme: on a tenant without products.list, the theme still installs and every other block still works — the product grid simply isn't offered in the editor. An editor never places a block that would render an error.
That's different from the manifest's top-level requires[], which is a hard install dependency: a theme that requires a capability cannot be installed at all without it. Use requires[] when the theme is pointless without the capability, and requiresCapability when one block is.
{
"requires": [
{
"capability": "products.list",
"compatible": "^1.0",
"reason": "renders the product grid on the home and category pages"
}
]
}
More on both in Theme anatomy.
blokkli.presets[]
A preset is a starting arrangement an editor drops in as a unit, rather than assembling a page block by block:
{
"blokkli": {
"presets": [
{ "id": "home", "title": "Home page", "blocks": ["marketing_banner", "content_section", "product_swiper", "text", "product_grid"] },
{ "id": "landing", "title": "Landing page", "blocks": ["hero", "text", "image", "cta"] },
{ "id": "faq", "title": "FAQ page", "blocks": ["hero", "accordion"] }
]
}
}
blocks is an ordered list of bundle ids — the arrangement the preset stamps out. Presets are cheap to add and they do a lot of work on a real project: a customer's content team creating their fifth landing page should not have to remember which five blocks the last four used.
Keep them few and genuinely different. Three presets that mean something beat nine that overlap.
When registration takes effect
The block map is code, so it ships with the deployment. The manifest is read by the platform when the theme is registered, which happens on publish — so a new block in blokkli.blocks[] reaches the editor when you publish a new theme version, not merely when you deploy.
And because a published version is immutable, that means bumping theme.json#version. See Publishing a theme.
Next steps
- Theme anatomy — the rest of the manifest.
- How a block works — the component side of the contract.
- Block catalog — what's already registered in the reference theme.
- Publishing a theme — getting a manifest change live.
- Customize blocks — the tutorial that walks a block from component to registered.