---
title: "Edges: backlinks and authored connections"
description: "Which Blocks point at a Block: the Edges the Block index projects from references, Derivations, embeds and tags, the ones a person adds in the inspector, the edges route and the drizzle table."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/edges"
---
# Edges: backlinks and authored connections

> Which Blocks point at a Block: the Edges the Block index projects from references, Derivations, embeds and tags, the ones a person adds in the inspector, the edges route and the drizzle table.

An **Edge** is a directed, typed connection from one Block to another Block or to a tag name. The [Block index](https://skaile-ai.github.io/forge-editor/guides/workspace) holds the Edges of every Block next to its row, so the question „which Blocks point at this one?“ needs no scan of the Documents.

## Where Edges come from

`projectDocument` (`@skaile/forge-editor/core`) fills `BlockRow.edges` while it projects a Document. One Edge exists per kind and target; a Block never lists the same Edge twice.

<table>
<thead>
  <tr>
    <th>
      <code>
        kind
      </code>
    </th>
    
    <th>
      <code>
        origin
      </code>
    </th>
    
    <th>
      Written by
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        reference
      </code>
    </td>
    
    <td>
      <code>
        content
      </code>
    </td>
    
    <td>
      a reference chip (<code>
        [@…]
      </code>
      
      ) anywhere in the Block, nested Blocks included
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        derivation
      </code>
    </td>
    
    <td>
      <code>
        content
      </code>
    </td>
    
    <td>
      the Block the Block's Derivation was made from (not an uploaded asset)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        embed
      </code>
    </td>
    
    <td>
      <code>
        content
      </code>
    </td>
    
    <td>
      a <code>
        blockEmbed
      </code>
      
       (<code>
        ![[…]]
      </code>
      
      ) in the Block
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tag
      </code>
    </td>
    
    <td>
      <code>
        content
      </code>
    </td>
    
    <td>
      a tag chip or a tag of a Typed block; the target is <code>
        { tag }
      </code>
      
      , not a Block
    </td>
  </tr>
  
  <tr>
    <td>
      any other string
    </td>
    
    <td>
      <code>
        meta
      </code>
    </td>
    
    <td>
      an Edge a person added by hand: <code>
        meta.edges
      </code>
      
       of the Typed block
    </td>
  </tr>
</tbody>
</table>

A target without a Document resolves to the Document the Block is in. The index never checks that the target exists: an Edge to a deleted Block stays until its source changes.

```ts
import { projectDocument } from '@skaile/forge-editor/core'

const [row] = projectDocument(content, 'doc-1')
row.edges
// [{ to: { documentId: 'doc-2', blockId: 'b' }, kind: 'reference', origin: 'content' },
//  { to: { tag: 'klima' }, kind: 'tag', origin: 'content' },
//  { to: { documentId: 'doc-1', blockId: 'c' }, kind: 'supports', origin: 'meta' }]
```

## Ask for the Edges of a Block

`BlockIndex.edges(address, direction, scope)` answers `BlockEdge[]`: the row's Edge with `from`, the Block it is written in.

- `direction: 'out'` are the Edges written in the Block itself, `tag` Edges included.
- `direction: 'in'` are the Edges of other Blocks that point at it, in Document order of `scope`. A `tag` Edge never points at a Block, so it is never an incoming one.

The **backlinks** of a Block are its incoming Edges. A Block that does not exist has no Edges (`[]`), not an error.

### Route and client

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Route
    </td>
    
    <td>
      <code>
        GET /api/editor/blocks/:documentId/:blockId/edges?direction=in|out
      </code>
      
       (<code>
        API.blockEdges
      </code>
      
      , port <code>
        index
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Default
    </td>
    
    <td>
      <code>
        in
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Client
    </td>
    
    <td>
      <code>
        EditorBackend.index.edges(address, direction?, signal?)
      </code>
      
      , optional: a backend without Edges leaves it out
    </td>
  </tr>
</tbody>
</table>

The route answers only Edges written in Documents the user may read (the same scope as the [Workspace search](https://skaile-ai.github.io/forge-editor/guides/workspace)). It answers `403` when the user may not read the Document of the Block, `400` for a `direction` other than `in` or `out`, `401` without a user and `501` without the `index` port.

## Add Edges by hand

The inspector and the details card of a Typed block show the section **Kanten** (the `edges` slot of `compositeFeature`, `EdgesSection` in both bindings). It lists the Edges of `meta.edges` with the kind and the title of the target, and it has a form to add one: the kind as free text, then the target, searched with the same list as the `[@` popup (the blocks of this Document, and with the `index` capability those of other Documents). A click on an entry adds the Edge; the remove button next to an Edge takes it out again. A read-only editor shows the list without the form.

`meta.edges` is a list of `{ to: { documentId?, blockId }, kind }` (`AuthoredEdge`, `META_EDGES_KEY`). The key is written through `setBlockProperty`, so it follows the same rules as every other Block property: in a collaboration session the server writes it into the open Y.Doc. Entries without a block ID or without a kind are ignored by the projection.

The framework-free model is `features/composites/edges-model.ts` (`edgesSectionOf`, `addAuthoredEdge`, `removeAuthoredEdge`, `edgeTargetChoicesOf`); the Vue and React components only render it.

## The drizzle table

The drizzle adapter writes the Edges to `editor_block_edges` in the same transaction as the rows of `replace`:

<table>
<thead>
  <tr>
    <th>
      Column
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        from_document_id
      </code>
      
      , <code>
        from_block_id
      </code>
    </td>
    
    <td>
      the Block the Edge is written in
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        seq
      </code>
    </td>
    
    <td>
      position in the row's list; with <code>
        from_document_id
      </code>
      
       the primary key
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        to_document_id
      </code>
      
      , <code>
        to_block_id
      </code>
    </td>
    
    <td>
      the target Block; <code>
        NULL
      </code>
      
       for a <code>
        tag
      </code>
      
       Edge
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tag
      </code>
    </td>
    
    <td>
      the tag name of a <code>
        tag
      </code>
      
       Edge, else <code>
        NULL
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        kind
      </code>
      
      , <code>
        origin
      </code>
    </td>
    
    <td>
      as in <code>
        BlockEdgeRow
      </code>
    </td>
  </tr>
</tbody>
</table>

An index on `(to_document_id, to_block_id)` answers the incoming Edges without a scan. `blockIndexTables()` returns `{ blocks, edges }`, so a host that migrates the tables with `drizzle-kit` adds both to its schema; `bootstrapBlockIndexTables(db)` creates both with `IF NOT EXISTS`. The memory adapter answers from its rows; both pass the same contract suite (`test/support/block-index-contract.ts`).


## Sitemap

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