---
title: "Block type contract"
description: "BlockType, the config types of the built-in capabilities, PropertyDefinition and the functions that read and combine Block types."
canonical_url: "https://skaile-ai.github.io/forge-editor/reference/block-types"
---
# Block type contract

> BlockType, the config types of the built-in capabilities, PropertyDefinition and the functions that read and combine Block types.

A Block type is plain data. The types come from `packages/editor/src/features/composites/block-types.ts`, `block-types-json.ts` and `core/properties.ts`; the Block type types are exported from `@skaile/forge-editor/composites` and `@skaile/forge-editor/composites/schema`; `PropertyDefinition` and `PROPERTY_TYPES` only from `@skaile/forge-editor/core`. For how to write and load them, see [Block types in code and JSON](https://skaile-ai.github.io/forge-editor/guides/block-types).

## `BlockType`

`BlockType` extends `BlockTypeData` (`{ id, capabilities }`). A Block type JSON file is one array of `BlockType`.

<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>
      Stored in the <code>
        blockType
      </code>
      
       attribute of a Typed block, in Markdown fences and in AI requests. 1 to 50 letters, digits, <code>
        _
      </code>
      
       or <code>
        -
      </code>
      
       (checked by <code>
        parseBlockTypes
      </code>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Name shown to the user.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Iconify name, e.g. <code>
        i-lucide-gavel
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        capabilities
      </code>
    </td>
    
    <td>
      <code>
        { id: string, config?: unknown }[]
      </code>
    </td>
    
    <td>
      The <a href="https://skaile-ai.github.io/forge-editor/reference/block-capabilities">
        capabilities
      </a>
      
       the Typed blocks of this type have, with their config. A capability that is not listed is absent. No other field switches a behaviour.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        titlePlaceholder
      </code>
    </td>
    
    <td>
      <code>
        string?
      </code>
    </td>
    
    <td>
      Placeholder of the empty title row.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        descriptionPlaceholder
      </code>
    </td>
    
    <td>
      <code>
        string?
      </code>
    </td>
    
    <td>
      Placeholder of the empty description row.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        chrome
      </code>
    </td>
    
    <td>
      <code>
        { tags?, tone?, controls? }?
      </code>
    </td>
    
    <td>
      How much chrome the type shows (below). Absent: all of it.
    </td>
  </tr>
</tbody>
</table>

`chrome`:

- `tags: false` hides the tag row of the type.
- `tone` is a CSS colour or `var(...)`. A Typed block of the type gets `data-block-type-tone` and `--sk-block-type-tone` on its wrapper (tinted surface and a left border), and its tree rows tint the type icon.
- `controls: false` makes the chrome of the Typed block read-only although the editor is editable: no basket menu, status menu, title-visibility toggle, tag editing or drag handle on the type icon, and a read-only details popover. An existing basket, status and tags still show as chips. Default `true`.

## Config of the built-in capabilities

<table>
<thead>
  <tr>
    <th>
      Interface
    </th>
    
    <th>
      Capability
    </th>
    
    <th>
      Members
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ContainerConfig
      </code>
    </td>
    
    <td>
      <code>
        container
      </code>
    </td>
    
    <td>
      <code>
        accepts?: readonly string[] | 'all'
      </code>
      
      : the Block types accepted as nested Typed blocks; absent: none. Content that is no Typed block is always allowed. Only new moves are checked.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PropertiesConfig
      </code>
    </td>
    
    <td>
      <code>
        properties
      </code>
    </td>
    
    <td>
      <code>
        definitions: readonly PropertyDefinition[]
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SourceConfig
      </code>
    </td>
    
    <td>
      <code>
        source
      </code>
    </td>
    
    <td>
      <code>
        media: readonly SourceMedia[]
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ImportConfig
      </code>
    </td>
    
    <td>
      <code>
        import
      </code>
    </td>
    
    <td>
      <code>
        media?: readonly SourceMedia[]
      </code>
      
      : the media the type takes. Absent or empty: the type takes every imported file that no other type takes, text and Markdown included.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        BlockTypeAi
      </code>
    </td>
    
    <td>
      <code>
        ai-brief
      </code>
    </td>
    
    <td>
      see <a href="#blocktypeai">
        <code>
          BlockTypeAi
        </code>
      </a>
    </td>
  </tr>
</tbody>
</table>

`SourceMedia` is `'image' | 'audio' | 'video' | 'file'` (`SOURCE_MEDIA_TYPES`).

### `BlockTypeAi`

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

<tbody>
  <tr>
    <td>
      <code>
        purpose
      </code>
    </td>
    
    <td>
      One or two sentences: what a Typed block of this type is for and what its body holds. Goes into every AI request's block model (chat, generate, tasks).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        instructions?
      </code>
    </td>
    
    <td>
      Default instruction for filling the body of a Typed block of this type (what to write, structure, tone).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        instructionsField?
      </code>
    </td>
    
    <td>
      Key of a <code>
        textarea
      </code>
      
       property. A Typed block whose value is non-empty uses it instead of <code>
        instructions
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        contentTemplate?
      </code>
    </td>
    
    <td>
      AI content template: target text as Markdown. <code>
        {key}
      </code>
      
       placeholders are replaced by the Typed block's <code>
        meta[key]
      </code>
      
       before it goes to the model; unknown ones stay.
    </td>
  </tr>
</tbody>
</table>

`instructions`, `instructionsField` and `contentTemplate` are read only by the AI fill (`fillComposite`).

## `PropertyDefinition`

One Block property of a Block type, stored under `key` in the `meta` attribute of the Typed block. Defined in `core/properties.ts`.

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

<tbody>
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Key under which the value is stored in <code>
        meta
      </code>
      
      . Non-empty; must not start with <code>
        $
      </code>
      
       (reserved for the pseudo-fields of a Block query).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Label in the inspector, the details popover and the table.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        type
      </code>
    </td>
    
    <td>
      <code>
        'text' | 'textarea' | 'date' | 'number' | 'select' | 'multiselect' | 'reference' | 'bibliography' | 'derivation' | 'checkbox' | 'url' | 'status' | 'reference-list' | 'source-anchor'
      </code>
    </td>
    
    <td>
      The entries of <code>
        PROPERTY_TYPES
      </code>
      
      . <code>
        bibliography
      </code>
      
      : CSL-JSON item under <code>
        meta.bib
      </code>
      
      . <code>
        derivation
      </code>
      
      : read-only Derivation of a Derived block. <code>
        reference
      </code>
      
      : block ID of another Typed block. <code>
        multiselect
      </code>
      
      : several of <code>
        options
      </code>
      
      , stored as <code>
        string[]
      </code>
      
      ; the key is removed when none is chosen. <code>
        checkbox
      </code>
      
      : <code>
        true
      </code>
      
      ; unchecked removes the key. <code>
        url
      </code>
      
      : a link as text. <code>
        status
      </code>
      
      : one of <code>
        options
      </code>
      
      , whose options carry a <code>
        group
      </code>
      
      . <code>
        reference-list
      </code>
      
      : <code>
        BlockAddress[]
      </code>
      
       (<code>
        documentId
      </code>
      
       <code>
        ''
      </code>
      
       = this Document). <code>
        source-anchor
      </code>
      
      : <code>
        SourceAnchor
      </code>
      
      , a passage of a PDF (<code>
        page
      </code>
      
      , <code>
        rects
      </code>
      
      , <code>
        quote
      </code>
      
      ); read-only in the form.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options
      </code>
    </td>
    
    <td>
      <code>
        PropertyOption[]?
      </code>
      
       (<code>
        { value: string, label: string, group?: 'todo' | 'doing' | 'done' }
      </code>
      
      )
    </td>
    
    <td>
      Choices of a <code>
        select
      </code>
      
      , <code>
        multiselect
      </code>
      
       or <code>
        status
      </code>
      
       property; required for <code>
        multiselect
      </code>
      
      . <code>
        group
      </code>
      
       belongs to <code>
        status
      </code>
      
       options.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        targets
      </code>
    </td>
    
    <td>
      <code>
        readonly string[]?
      </code>
    </td>
    
    <td>
      <code>
        reference
      </code>
      
       only: the Block type ids the reference may point to. Absent: any Typed block.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hidden
      </code>
    </td>
    
    <td>
      <code>
        boolean?
      </code>
    </td>
    
    <td>
      Stored and queried like any property, but not shown in the Block inspector, the details popover or the Block table.
    </td>
  </tr>
</tbody>
</table>

For the behaviour of each type, see [Block properties](https://skaile-ai.github.io/forge-editor/guides/block-properties). `PROPERTY_KINDS` (`@skaile/forge-editor/core`) maps every type to a `PropertyKind` (`ops`, `sortKey`, `groupKey`, `aggregates`, `text`, `longText`, `align`); the filter, aggregate and environment types it uses (`FilterOp`, `AggregateFn`, `QueryEnv`) live in `core/query-types.ts`.

## Functions

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

<tbody>
  <tr>
    <td>
      <code>
        defaultBlockTypes(t)
      </code>
    </td>
    
    <td>
      the built-in Block types in the language of translator <code>
        t
      </code>
      
       (<code>
        createTranslator('de')
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blockTypeOf(blockTypes, id)
      </code>
    </td>
    
    <td>
      the Block type with that id; an unknown id gets the presentation of the <code>
        section
      </code>
      
       type (else the first type) under its own id and label
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blockTypeConfigOf(type, id)
      </code>
      
      , <code>
        listsCapability(type, id)
      </code>
    </td>
    
    <td>
      the config the type gives capability <code>
        id
      </code>
      
       (<code>
        undefined
      </code>
      
       when it gives none) and whether it lists it. Both are in <code>
        @skaile/forge-editor/core
      </code>
      
       (<a href="https://skaile-ai.github.io/forge-editor/reference/block-capabilities">
        Block capabilities
      </a>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isRootBlockType(type)
      </code>
    </td>
    
    <td>
      whether the <code>
        container
      </code>
      
       config of the type says <code>
        root: true
      </code>
      
      : the type stands for the file of a document tree (the built-in Document type)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        propertiesOf(type)
      </code>
    </td>
    
    <td>
      the Property definitions of the type (none without the <code>
        properties
      </code>
      
       capability)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sourceMediaOf(type)
      </code>
    </td>
    
    <td>
      the media of its <code>
        source
      </code>
      
       capability, or <code>
        undefined
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aiBriefOf(type)
      </code>
    </td>
    
    <td>
      its <code>
        ai-brief
      </code>
      
       config, or <code>
        undefined
      </code>
      
       (the AI fill is not offered then)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        acceptsChild(blockTypes, parentType, childType)
      </code>
    </td>
    
    <td>
      whether <code>
        parentType
      </code>
      
       accepts <code>
        childType
      </code>
      
       as nested Typed block; a parent type the list does not know accepts nothing
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        acceptsImport(blockTypes, parentType)
      </code>
    </td>
    
    <td>
      whether an imported file can go into a Typed block of <code>
        parentType
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        importTypeOf(blockTypes, media)
      </code>
    </td>
    
    <td>
      the id of the Block type that takes an imported file of <code>
        media
      </code>
      
       (<code>
        null
      </code>
      
      : text, Markdown or another file the media pipeline does not know): the first type whose <code>
        import
      </code>
      
       names <code>
        media
      </code>
      
      , else the first that lists <code>
        import
      </code>
      
       without media; <code>
        undefined
      </code>
      
       when none takes it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aiBlockTypesOf(blockTypes)
      </code>
    </td>
    
    <td>
      the Block types as the AI chat model learns them: <code>
        AiContextBlockType[]
      </code>
      
      , <code>
        { id, label, fields, purpose? }
      </code>
      
       (<code>
        fields
      </code>
      
      : the Property definitions)
    </td>
  </tr>
</tbody>
</table>

## JSON files

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

<tbody>
  <tr>
    <td>
      <code>
        parseBlockTypes(input, { registry? })
      </code>
    </td>
    
    <td>
      reads a JSON array of <code>
        BlockType
      </code>
      
       (text or parsed value) and checks each entry: the fields (strict: unknown members are refused), a unique <code>
        id
      </code>
      
      , every listed capability exists and is listed once, and its <code>
        config
      </code>
      
       passes the <code>
        configSchema
      </code>
      
      . A capability without a <code>
        configSchema
      </code>
      
       takes no config. <code>
        registry
      </code>
      
       defaults to the built-in capabilities and those of the composites feature; pass <code>
        preset.capabilities
      </code>
      
       when a Feature adds more. Throws <code>
        BlockTypeJsonError
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mergeBlockTypes(base, extra)
      </code>
    </td>
    
    <td>
      layers <code>
        extra
      </code>
      
       over <code>
        base
      </code>
      
      : a type with an id that <code>
        base
      </code>
      
       has replaces it in place, the others follow
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        BlockTypeJsonError
      </code>
    </td>
    
    <td>
      <code>
        Error
      </code>
      
       with <code>
        issues: BlockTypeJsonIssue[]
      </code>
      
      ; <code>
        message
      </code>
      
       has one line per issue
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        BlockTypeJsonIssue
      </code>
    </td>
    
    <td>
      <code>
        { index, typeId, capability, path, message }
      </code>
      
      : the entry's position (<code>
        -1
      </code>
      
       for the file), its <code>
        id
      </code>
      
       (<code>
        null
      </code>
      
       when it has none), the capability whose config is wrong (<code>
        null
      </code>
      
       for the entry itself) and the path inside the entry or config, e.g. <code>
        definitions[0].type
      </code>
    </td>
  </tr>
</tbody>
</table>

## Where Block types go in

<table>
<thead>
  <tr>
    <th>
      Place
    </th>
    
    <th>
      Option
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        compositeFeature({ blockTypes })
      </code>
      
       (Vue and React), <code>
        compositeFeatureCore(ui, { blockTypes })
      </code>
    </td>
    
    <td>
      the Block types of the editor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        compositeSchemaExtensions({ blockTypes })
      </code>
    </td>
    
    <td>
      schema only; Block types are storage, not schema
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        composeEditor({ blockTypes })
      </code>
      
      , <code>
        AdvancedEditor
      </code>
      
       and <code>
        ComposedSurface
      </code>
      
       prop <code>
        blockTypes
      </code>
    </td>
    
    <td>
      the same, for the surfaces
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        installEditorServer(nitroApp, { ports, blockTypes })
      </code>
      
      , <code>
        provideEditorServer({ ports, blockTypes })
      </code>
      
      , <code>
        AiHandlerContext.blockTypes
      </code>
    </td>
    
    <td>
      the server side: the AI chat describes these types to the model
    </td>
  </tr>
</tbody>
</table>

Without `blockTypes` the built-in types apply: the editor falls back to `defaultBlockTypes` in the language of its translator.


## Sitemap

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