---
title: "Block capability contract"
description: "BlockCapability, the capability registry, the built-in capabilities with their config and appliesTo, and the BlockAction.requires gate."
canonical_url: "https://skaile-ai.github.io/forge-editor/reference/block-capabilities"
---
# Block capability contract

> BlockCapability, the capability registry, the built-in capabilities with their config and appliesTo, and the BlockAction.requires gate.

The types below come from `packages/editor/src/core/capabilities.ts` and are exported from `@skaile/forge-editor/core`. For how to declare a capability and use it, see [Block capabilities, abilities and actions](https://skaile-ai.github.io/forge-editor/guides/block-capabilities).

## `BlockCapability`

A Feature declares capabilities in `EditorFeature.blockCapabilities`. `defineEditor` merges them with the built-in ones into `preset.capabilities` and throws for a duplicate id or an id of a built-in capability.

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Unique across the preset. A Block type's <code>
        capabilities
      </code>
      
       entry and a <code>
        BlockAction.requires
      </code>
      
       or <code>
        DeriveMethod.requires
      </code>
      
       entry name it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        appliesTo
      </code>
    </td>
    
    <td>
      <code>
        (node, ctx: CapabilityContext) => boolean
      </code>
    </td>
    
    <td>
      Whether <code>
        node
      </code>
      
       has the capability. Memoised per node type and Block type id, so it may read only the node type (spec and flags), <code>
        ctx.blockTypeId
      </code>
      
       and the Block type's config (<code>
        ctx.configOf
      </code>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        perNode
      </code>
    </td>
    
    <td>
      <code>
        boolean?
      </code>
    </td>
    
    <td>
      <code>
        appliesTo
      </code>
      
       reads attributes of the node itself. It then runs for every node, so keep it a constant-time attribute check.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        configSchema
      </code>
    </td>
    
    <td>
      <code>
        ZodType?
      </code>
    </td>
    
    <td>
      Validates the config a Block type gives. Absent: the capability takes no config, and a Block type that gives one is refused.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        read
      </code>
    </td>
    
    <td>
      <code>
        (node, ctx) => unknown?
      </code>
    </td>
    
    <td>
      What the Block holds for the capability (Source child, Basket id, Bibliographic record), or <code>
        null
      </code>
      
      . Runs only for Blocks that have the capability; not memoised.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        storage
      </code>
    </td>
    
    <td>
      <code>
        { metaKeys?, childTypes? }?
      </code>
    </td>
    
    <td>
      Where the data lives: keys of the <code>
        meta
      </code>
      
       attribute, node type names of the children. Descriptive only.
    </td>
  </tr>
</tbody>
</table>

A capability never installs schema, extensions, node views or settings.

### `CapabilityContext`

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        blockTypeId
      </code>
    </td>
    
    <td>
      The persisted <code>
        attrs.blockType
      </code>
      
       of the node; <code>
        null
      </code>
      
       for a node without one.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blockTypeOf(node)
      </code>
    </td>
    
    <td>
      The Block type of <code>
        node
      </code>
      
       among the editor's Block types; <code>
        null
      </code>
      
       for a node without one, an unknown id, or an editor without Block types.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        configOf(node, capability)
      </code>
    </td>
    
    <td>
      The config the Block type of <code>
        node
      </code>
      
       gives <code>
        capability
      </code>
      
      ; <code>
        undefined
      </code>
      
       when the type does not list it or lists it without config.
    </td>
  </tr>
</tbody>
</table>

`listsCapability(ctx.blockTypeOf(node), id)` is the usual `appliesTo` of a capability a Block type opts into.

## Registry and functions

<table>
<thead>
  <tr>
    <th>
      Export
    </th>
    
    <th>
      Does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        BUILT_IN_BLOCK_CAPABILITIES
      </code>
    </td>
    
    <td>
      the structural capabilities every editor has (<code>
        container
      </code>
      
      , <code>
        title
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        createCapabilityRegistry(capabilities)
      </code>
    </td>
    
    <td>
      a <code>
        CapabilityRegistry
      </code>
      
      : <code>
        capabilities
      </code>
      
       (built-in first, then the Features' in feature order), <code>
        has(id)
      </code>
      
      , <code>
        get(id)
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        createCapabilityIndex(registry, blockTypes)
      </code>
    </td>
    
    <td>
      the per-editor <code>
        CapabilityIndex
      </code>
      
      : <code>
        of(node)
      </code>
      
      , <code>
        read(node, id)
      </code>
      
      , <code>
        configOf(node, id)
      </code>
      
      . It depends on the editor's Block types, so editors do not share it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        capabilitiesOf(editor, node)
      </code>
    </td>
    
    <td>
      ids of the capabilities <code>
        node
      </code>
      
       has
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        capabilitiesAt(editor, pos)
      </code>
    </td>
    
    <td>
      the same for the Block at <code>
        pos
      </code>
      
      ; empty where no node starts
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        capabilityConfigOf(editor, node, id)
      </code>
    </td>
    
    <td>
      the config the Block type gives the capability, or <code>
        undefined
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        capabilityDataOf(editor, node, id)
      </code>
    </td>
    
    <td>
      what <code>
        read
      </code>
      
       returns, or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        validateBlockTypes(registry, blockTypes)
      </code>
    </td>
    
    <td>
      throws when a Block type id is defined twice, a listed capability is unknown or listed twice, or a config fails its <code>
        configSchema
      </code>
      
      . <code>
        capabilityIssues(registry, type)
      </code>
      
       is the same check for one Block type, and returns the problems instead of throwing. <code>
        defineEditor
      </code>
      
       runs <code>
        validateBlockTypes
      </code>
      
       for the Block types of the Features when an editor is created.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blockTypeConfigOf(type, id)
      </code>
    </td>
    
    <td>
      the config <code>
        type
      </code>
      
       gives the capability, or <code>
        undefined
      </code>
      
       (also without a <code>
        type
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        listsCapability(type, id)
      </code>
    </td>
    
    <td>
      whether <code>
        type
      </code>
      
       lists the capability; <code>
        false
      </code>
      
       without a <code>
        type
      </code>
    </td>
  </tr>
</tbody>
</table>

`typeIdOf(node)` (the Typed block module of `@skaile/forge-editor/core`) gives the `attrs.blockType` of a node (`''` for a node without one); `findBlockType(blockTypes, node)` is the Block type lookup. `BlockTypeCapability` (`{ id, config? }`) and `BlockTypeData` (`{ id, capabilities }`) are what the registry knows of a Block type; the public `BlockType` extends `BlockTypeData` ([Block type contract](https://skaile-ai.github.io/forge-editor/reference/block-types)).

## `BlockAction.requires`

`BlockAction.requires?: readonly string[]` lists capability ids. The action is hidden on a Block that lacks one of them. `blockActionsOf(preset, ctx)` throws when an id is declared by no Feature; `defineEditor` throws the same for a derive method. It replaces node-name checks in `isVisible`. `blockTypes?: readonly string[]` limits an action to named Block types and is for actions that are truly specific to a type. `ability` is a separate gate: `requires` is what the Block is, `ability` what its place in the document allows (`abilitiesAt(editor, pos, node)`); it hides the action but refuses no edit ([Abilities](https://skaile-ai.github.io/forge-editor/guides/block-capabilities#abilities)).

## Built-in capabilities

`container` and `title` are built in. The composites feature declares the others (`compositeBlockCapabilities()`), so a Block type file that is parsed with the default registry may name all of them. A Block type that lists a capability its node cannot hold (for example `source` on a type without a container body) has no effect.

<table>
<thead>
  <tr>
    <th>
      Id
    </th>
    
    <th>
      Config (<code>
        configSchema
      </code>
      
      )
    </th>
    
    <th>
      Applies to
    </th>
    
    <th>
      <code>
        read
      </code>
      
       returns
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        container
      </code>
    </td>
    
    <td>
      <code>
        { accepts?: string[] | 'all' }
      </code>
      
      : the Block types accepted as nested Typed blocks
    </td>
    
    <td>
      nodes that hold Blocks of their own (<code>
        doc
      </code>
      
      , a Typed block)
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      nodes whose content starts with chrome (the title row of a Typed block)
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        properties
      </code>
    </td>
    
    <td>
      <code>
        { definitions: PropertyDefinition[] }
      </code>
    </td>
    
    <td>
      a node with the <code>
        meta
      </code>
      
       attribute, if its Block type lists it
    </td>
    
    <td>
      the <code>
        meta
      </code>
      
       values of the keys the definitions name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        source
      </code>
    </td>
    
    <td>
      <code>
        { media: ('image' | 'audio' | 'video' | 'file')[] }
      </code>
      
       (at least one)
    </td>
    
    <td>
      a container node, if its Block type lists it
    </td>
    
    <td>
      the Source media block, or <code>
        null
      </code>
      
       while the body holds none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bibliography
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a node with <code>
        meta
      </code>
      
      , if its Block type lists it
    </td>
    
    <td>
      the CSL-JSON item (<code>
        meta.bib
      </code>
      
      ), or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        basket
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a node with <code>
        meta
      </code>
      
      , if its Block type lists it
    </td>
    
    <td>
      the Basket id (<code>
        meta.basket
      </code>
      
      ), or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        titleHidden
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a node with <code>
        meta
      </code>
      
      , if its Block type lists it
    </td>
    
    <td>
      whether the title is hidden (<code>
        meta.titleHidden
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai-brief
      </code>
    </td>
    
    <td>
      <code>
        { purpose, instructions?, instructionsField?, contentTemplate? }
      </code>
    </td>
    
    <td>
      any node whose Block type lists it
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        references
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a node with <code>
        meta
      </code>
      
      , if its Block type lists it <strong>
        or
      </strong>
      
       one of its Property definitions has the type <code>
        reference
      </code>
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        import
      </code>
    </td>
    
    <td>
      <code>
        { media?: ('image' | 'audio' | 'video' | 'file')[] }
      </code>
    </td>
    
    <td>
      a container node, if its Block type lists it
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        export
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a container node, if its Block type lists it
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        derived
      </code>
    </td>
    
    <td>
      none
    </td>
    
    <td>
      a node with <code>
        meta
      </code>
      
       whose <code>
        meta.derivation
      </code>
      
       is a Derivation (<code>
        perNode
      </code>
      
      ; any Typed block can be derived, whatever its Block type lists)
    </td>
    
    <td>
      the <code>
        Derivation
      </code>
    </td>
  </tr>
</tbody>
</table>

Storage of the built-ins that keep data: `properties` and `derived` use `meta` (`derived`: the key `derivation`), `bibliography` uses `meta.bib`, `basket` uses `meta.basket`, `titleHidden` uses `meta.titleHidden`, `source` keeps its media block in the body (`childTypes` `image`, `audio`, `video`, `file`), `references` uses `reference` nodes.

`titleHidden` gates the eye button of the chrome („Titel ausblenden“ / „Titel einblenden“, Vue and React `CompositeChrome`): a Typed block of a Block type that does not list it shows no button, so a host that shows or edits the title elsewhere can leave it out. The flag itself (`meta.titleHidden`) is a reserved key that every Block type keeps, and `setCompositeTitleHidden` works for every type; every built-in Block type lists the capability.

`ai-brief` is what the Block type tells the model: `purpose` goes into every AI request's block model; `instructions`, `instructionsField` and `contentTemplate` are read only by the AI fill ([Block types](https://skaile-ai.github.io/forge-editor/reference/block-types#blocktypeai)).


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
