Reference

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.

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.

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

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

CapabilityContext

FieldMeaning
blockTypeIdThe persisted attrs.blockType of the node; null for a node without one.
blockTypeOf(node)The Block type of node among the editor's Block types; null for a node without one, an unknown id, or an editor without Block types.
configOf(node, capability)The config the Block type of node gives capability; undefined when the type does not list it or lists it without config.

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

Registry and functions

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

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

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

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.

IdConfig (configSchema)Applies toread returns
container{ accepts?: string[] | 'all' }: the Block types accepted as nested Typed blocksnodes that hold Blocks of their own (doc, a Typed block)none
titlenonenodes whose content starts with chrome (the title row of a Typed block)none
properties{ definitions: PropertyDefinition[] }a node with the meta attribute, if its Block type lists itthe meta values of the keys the definitions name
source{ media: ('image' | 'audio' | 'video' | 'file')[] } (at least one)a container node, if its Block type lists itthe Source media block, or null while the body holds none
bibliographynonea node with meta, if its Block type lists itthe CSL-JSON item (meta.bib), or null
basketnonea node with meta, if its Block type lists itthe Basket id (meta.basket), or null
titleHiddennonea node with meta, if its Block type lists itwhether the title is hidden (meta.titleHidden)
ai-brief{ purpose, instructions?, instructionsField?, contentTemplate? }any node whose Block type lists itnone
referencesnonea node with meta, if its Block type lists it or one of its Property definitions has the type referencenone
import{ media?: ('image' | 'audio' | 'video' | 'file')[] }a container node, if its Block type lists itnone
exportnonea container node, if its Block type lists itnone
derivednonea node with meta whose meta.derivation is a Derivation (perNode; any Typed block can be derived, whatever its Block type lists)the Derivation

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

Copyright © 2026