---
title: "Derive methods and the Derivation index"
description: "Register one way to make a block from another block, find the Derived blocks of an original and handle a missing source."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/derive-methods"
---
# Derive methods and the Derivation index

> Register one way to make a block from another block, find the Derived blocks of an original and handle a missing source.

Transcription, text extraction (PDF text, OCR, image description), summary, AI fill, AI derivation into another Block type and bibliography fetch have one thing in common: they make something from a Block. Each is one **derive method**. A Feature contributes its methods as `deriveMethods`, and the preset merges all of them into one registry. The [derive-method contract](https://skaile-ai.github.io/forge-editor/reference/derive-methods) lists the fields.

A Block that was made this way is a **Derived block**. It records in `meta.derivation` what it was made from (its **Derivation**). The original keeps nothing, so the link cannot disagree with the document after a move, a delete or a merge.

## Register a derive method

A derive method declares the capabilities the original must have (`requires`), the ability (`ability`) and, where the capabilities cannot say it, `available(ctx)`: the backend flag `ctx.ctx.backend.ai`, the public AI flags, a readable source. `run` writes the result. A method with sub-choices (derive into one of several Block types) gives `menu` instead of `run`.

The example adds „Kernaussagen ableiten“. It is offered on every Container block with a title when the backend has AI, and it inserts a Typed block of the Block type `note` that records its Derivation:

```ts [docs/snippets/derive-method.ts]
import type { Derivation, EditorFeature } from '@skaile/forge-editor/core'
import { derivationState, derivedOf, newTypedBlockJSON } from '@skaile/forge-editor/core'
import { ABILITY, blockIdOf } from '@skaile/forge-editor/core/schema'
import type { Node as PMNode } from '@tiptap/pm/model'

// One registration: the block action, its gate (`requires`, `ability`, `available`) and the work.
export function keyPointsFeature(): EditorFeature {
  return {
    id: 'key-points',
    dimension: 'ai',
    requires: ['composites'],
    deriveMethods: [{
      id: 'key-points',
      label: () => 'Kernaussagen ableiten',
      icon: 'i-lucide-list-checks',
      ability: ABILITY.compositeDerive,
      requires: ['container', 'title'],
      available: ({ ctx }) => !!ctx.backend.ai,
      run: ({ editor, node, pos }) => {
        const block = blockIdOf(node)
        if (!block) return
        const derivation: Derivation = { from: { block }, method: 'key-points', createdAt: new Date().toISOString(), inputHash: '', outputHash: '' }
        const result = newTypedBlockJSON('note', [{ type: 'paragraph', content: [{ type: 'text', text: 'Kernaussagen ...' }] }])
        // The Derived block records where it came from; the original keeps nothing.
        editor.commands.insertContentAt(pos + node.nodeSize, { ...result, attrs: { ...result.attrs, meta: { derivation } } })
      },
    }],
  }
}

/** The Derived blocks made from the original at `originalPos`, computed from block IDs and asset names. */
export const derivedBlocksOf = (doc: PMNode, originalPos: number, documentId: string) =>
  derivedOf(doc, originalPos, documentId).map(entry => ({ pos: entry.pos, method: entry.derivation.method }))

/** True when the original of the Derived block at `derivedPos` was deleted. */
export const isSourceMissing = (doc: PMNode, derivedPos: number, documentId: string) =>
  derivationState(doc, derivedPos, () => '', documentId)?.source === 'missing'
```

`defineEditor` merges the methods of all Features in feature order into `preset.deriveMethods` and throws for a duplicate id, a method with neither or both of `run` and `menu`, and a `requires` entry that no Feature declares (the built-in capabilities count).

The menu of the user does not change. `blockActionsOf` turns every method into a Block action with the same id (`deriveActionsOf`), so the overlay filter applies `requires`, the ability and `available` like for every other action. `deriveMethodsAt(editor, ctx, pos)` lists the methods that can run on one Block.

The built-in `composite-ai` integration registers its six methods this way:

<table>
<thead>
  <tr>
    <th>
      Method id
    </th>
    
    <th>
      <code>
        requires
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        composite-fill
      </code>
    </td>
    
    <td>
      <code>
        ai-brief
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        composite-summarize
      </code>
      
      , <code>
        composite-derive
      </code>
    </td>
    
    <td>
      <code>
        container
      </code>
      
      , <code>
        title
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bibliography-fetch
      </code>
    </td>
    
    <td>
      <code>
        bibliography
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rendition-create
      </code>
    </td>
    
    <td>
      <code>
        source
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rendition-regenerate
      </code>
    </td>
    
    <td>
      <code>
        derived
      </code>
    </td>
  </tr>
</tbody>
</table>

## The Derivation

`Derivation` is the record in `meta.derivation`: `from` (an asset by asset name, or a Block by block ID, with `document` when the Block lives in another Document), `method` (a `DerivationMethod` such as `'transcription'`, `'ocr'`, `'description'` or any string a host uses), `model`, `createdAt`, `inputHash`, `outputHash` and `output`. The `derived` capability, which the composites feature declares, applies to every Block that has the record (`perNode`), and `derivationOf(node)` reads it.

A Block made from several originals (a mixdown, a summary of several Blocks) names them in `inputs`, a record of `{ from, hash }` by input name. Then `from` is the primary input and `inputHash` holds `derivationFingerprint({ method, inputs })`: the SHA-256 of the method and the hash of every input, by input name, so the order the inputs were written in does not change it. `activity` (`{ id, agent?, at? }`) records the run that produced the Block, for example one AI request and the model that answered; the AI rendition and „Ableiten (KI)“ write it.

## Find the Derived blocks of an original

`derivationIndexOf(doc)` computes the reverse index from block IDs and asset names. It is memoised per `doc` node, so it is recomputed for every new document version, and it cannot disagree with the document after a move or delete (Yjs merges included). The functions on top of it:

- `derivedOf(doc, originalPos, documentId?)` lists the Derived blocks of the original at `originalPos`, in document order. A Derived block that was moved out of its original still counts. Derivations from another Document are not listed.
- `derivationOriginOf(doc, derivedPos, documentId?)` finds the original and answers `found` (with node and position), `missing` or `foreign`.
- `derivationState(doc, derivedPos, bodyHash, documentId?, foreignHash?, inputHashOf?)` adds `stale` (an original changed since) and `edited` (the body changed since). For a Derivation with `inputs`, `inputHashOf(from)` returns the live hash of one input (`null` = unknown): `stale` is `true` when any input differs, `false` when all match and `null` without `inputHashOf` or while an input is unknown.
- `derivedStateOf(state)` folds a `DerivationState` into one `DerivedStateId`: `missing`, `stale`, `edited`, `foreign` or `fresh`, the first that applies.

## State badge

The composites chrome, the Document tree and the row badge use that one state. `derivedChromeOf(editor, row)` returns `{ state }`. The composites feature registers `derivedRowBadge` under `rowBadges`: a Derived block whose original changed shows „veraltet“ (English: "outdated"), one whose original is gone shows „Quelle fehlt“. A tree row of a Derived block that is stale, missing or edited shows a dot with the reason as its label (`OutlineNode.derived`, `TreeItem.derived`). The labels are the keys `composites.derivation.state.<fresh|stale|edited|missing|foreign>`.

## Source missing

When the original is deleted, the Derived block stays. `derivationState(...).source` is `'missing'`, and the composite chrome, the details card, the Block inspector and the outline of the Document tree show „Quelle fehlt“ (English: "Source missing"). A Derivation from another Document is `foreign` and never missing, because listing those needs a query on the documents port. The last two exports of the example read both states:

```ts
derivedBlocksOf(doc, originalPos, documentId)   // [{ pos, method }, ...]
isSourceMissing(doc, derivedPos, documentId)    // true after the original was deleted
```

`derivationIndexOf`, `derivedOf`, `derivationOriginOf`, `derivationState`, `derivedStateOf`, `derivationFingerprint`, `derivationOf`, `derivedChildrenOf` and `DERIVATION_KEY` are exported from `@skaile/forge-editor/core`, and only from there: `/composites` and `/composites/schema` do not re-export them.


## Sitemap

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