---
title: "Typst: render a Document as a PDF with citations"
description: "The add-on @skaile/forge-editor-typst: the render target typst-pdf that turns a Document or a paper into Typst markup, writes the bibliography as BibTeX, and compiles a PDF in one of four citation styles."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/typst"
---
# Typst: render a Document as a PDF with citations

> The add-on @skaile/forge-editor-typst: the render target typst-pdf that turns a Document or a paper into Typst markup, writes the bibliography as BibTeX, and compiles a PDF in one of four citation styles.

The add-on `@skaile/forge-editor-typst` adds one [render target](https://skaile-ai.github.io/forge-editor/guides/render), `typst-pdf`. It turns a whole Document, or one Typed block of the Block type `paper`, into a PDF made by [Typst](https://typst.app). The [Cite](https://skaile-ai.github.io/forge-editor/guides/citations) nodes of the content become citations, and the Bibliographic records inside the content become the bibliography.

The add-on is server-side only. It needs no editor feature: the block action **„Erzeugen als …“** and the command **„Dokument erzeugen als …“** list every target of the `render` capability, this one among them.

## Install and wire

```ts
import { createRenderer, markdownTarget } from '@skaile/forge-editor/adapters/render'
import { typstTarget } from '@skaile/forge-editor-typst'

// installEditorServer({ ports: { render } })
export const render = createRenderer([markdownTarget(), typstTarget({ style: 'apa', locale: 'de' })])
```

<table>
<thead>
  <tr>
    <th>
      Option of <code>
        typstTarget
      </code>
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        style
      </code>
    </td>
    
    <td>
      citation style when the render names none: one of <code>
        CSL_STYLES
      </code>
      
      , default <code>
        apa
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locale
      </code>
    </td>
    
    <td>
      language of the PDF (hyphenation, the bibliography title) and of the error messages: <code>
        de
      </code>
      
       (default) or <code>
        en
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        label
      </code>
    </td>
    
    <td>
      the name in the export menu (default <code>
        PDF (Typst)
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        accepts
      </code>
    </td>
    
    <td>
      Block types accepted besides <code>
        'document'
      </code>
      
       (default <code>
        ['paper']
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        compile
      </code>
    </td>
    
    <td>
      <code>
        { engine?: 'auto' | 'node' | 'binary', binary?: string }
      </code>
      
      , see below
    </td>
  </tr>
</tbody>
</table>

A render input picks the style per request: `options: { style: 'ieee' }`.

## The compiler

`compileTypst` tries two engines in this order:

1. The native compiler `@myriaddreamin/typst-ts-node-compiler`, an optional dependency of the add-on. It runs inside the server process; no `typst` program is needed. The compile is synchronous and blocks the event loop while it runs.
2. The `typst` program on the `PATH` (or `compile.binary`). The add-on writes `main.typ` and `refs.bib` to a temporary directory, runs `typst compile`, reads the PDF and removes the directory. A client that disconnects aborts the process.

When neither exists the render fails with the error code `typst-missing`. Markup that Typst rejects fails with `typst-failed`; the message holds Typst's own diagnostics. A style outside `CSL_STYLES` fails with `bad-style` before any compile starts. All three are `TypstError` with a stable `code`; the message is in the language of the render and reaches the client as the `error` event.

## What the markup holds

`typstOf(content, bib, style)` is pure. It maps the content like the editor's Markdown export does:

<table>
<thead>
  <tr>
    <th>
      Content
    </th>
    
    <th>
      Typst
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      heading (level <em>
        n
      </em>
      
      )
    </td>
    
    <td>
      <code>
        =
      </code>
      
       repeated <em>
        n
      </em>
      
       times
    </td>
  </tr>
  
  <tr>
    <td>
      Typed block (depth <em>
        d
      </em>
      
      )
    </td>
    
    <td>
      heading of level <em>
        d
      </em>
      
       with its title (not when the title is hidden), the description in <code>
        #emph
      </code>
      
      , the tags in small grey text, then the body
    </td>
  </tr>
  
  <tr>
    <td>
      paragraph, hard break
    </td>
    
    <td>
      text; a break is <code>
        \
      </code>
      
       and a newline
    </td>
  </tr>
  
  <tr>
    <td>
      bold, italic, strike, underline, highlight, sub, super, code, link
    </td>
    
    <td>
      <code>
        #strong
      </code>
      
      , <code>
        #emph
      </code>
      
      , <code>
        #strike
      </code>
      
      , <code>
        #underline
      </code>
      
      , <code>
        #highlight
      </code>
      
      , <code>
        #sub
      </code>
      
      , <code>
        #super
      </code>
      
      , <code>
        #raw
      </code>
      
      , <code>
        #link
      </code>
      
       (the function form works inside a word, which <code>
        *…*
      </code>
      
       does not)
    </td>
  </tr>
  
  <tr>
    <td>
      bullet, ordered and task lists
    </td>
    
    <td>
      <code>
        -
      </code>
      
       and <code>
        1.
      </code>
      
       items, nested by indentation; a task item starts with ☐ or ☑
    </td>
  </tr>
  
  <tr>
    <td>
      table
    </td>
    
    <td>
      <code>
        #table
      </code>
      
       with <code>
        table.header
      </code>
      
       for a header row, <code>
        colspan
      </code>
      
       and <code>
        rowspan
      </code>
      
       kept
    </td>
  </tr>
  
  <tr>
    <td>
      code block, quote, rule
    </td>
    
    <td>
      <code>
        #raw(block: true)
      </code>
      
      , <code>
        #quote(block: true)
      </code>
      
      , <code>
        #line
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      mention, tag, reference chip
    </td>
    
    <td>
      their text
    </td>
  </tr>
  
  <tr>
    <td>
      image, audio, video, file, block embed
    </td>
    
    <td>
      the kind and name in italics: the editor's asset URLs are not files Typst can read
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cite
      </code>
      
       node
    </td>
    
    <td>
      <code>
        @key
      </code>
      
      , or <code>
        @key[locator]
      </code>
    </td>
  </tr>
</tbody>
</table>

A `cite` whose key names an entry of the bibliography becomes a Typst citation. A `cite` whose key names none reads as the text `(key)`, like the editor's gone chip. When at least one citation resolved, the markup ends with `#bibliography("refs.bib", style: …, title: …)`; Typst prints only the works that are cited. A bare `@key` followed by unmarked text that continues the word gets an empty content block (`@key#[]`), so Typst does not read the text as part of the key.

## The bibliography

`bibEntriesOf(content)` collects the CSL-JSON record of every Typed block with `meta.bib`, in Document order, with the block id and the title. `bibFileOf(entries)` writes one BibTeX entry for each through the library's `bibtexOf`; the citation key is the one the `cite` node holds (`bibtexKey`). Two records that derive one key keep the first. The file is `refs.bib`, next to the main file.

<table>
<thead>
  <tr>
    <th>
      Style
    </th>
    
    <th>
      Typst style
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        apa
      </code>
    </td>
    
    <td>
      APA
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ieee
      </code>
    </td>
    
    <td>
      IEEE
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        chicago-author-date
      </code>
    </td>
    
    <td>
      Chicago author-date
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        vancouver
      </code>
    </td>
    
    <td>
      Vancouver
    </td>
  </tr>
</tbody>
</table>

The styles are Typst's built-in CSL styles, so the add-on has no citation processor of its own.

## Pieces for your own pipeline

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

<tbody>
  <tr>
    <td>
      <code>
        typstTarget(options?)
      </code>
    </td>
    
    <td>
      the <code>
        RenderTarget
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        typstOf
      </code>
      
      , <code>
        typstSourceOf
      </code>
    </td>
    
    <td>
      markup, or markup with its <code>
        .bib
      </code>
      
       text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bibEntriesOf
      </code>
      
      , <code>
        bibFileOf
      </code>
      
      , <code>
        BIB_FILE
      </code>
    </td>
    
    <td>
      the bibliography of a content tree
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        compileTypst
      </code>
      
      , <code>
        typstEngineOf
      </code>
    </td>
    
    <td>
      the PDF; which engine would run, or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CSL_STYLES
      </code>
      
      , <code>
        isCslStyle
      </code>
    </td>
    
    <td>
      the citation styles
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        TypstError
      </code>
    </td>
    
    <td>
      the error with its <code>
        code
      </code>
    </td>
  </tr>
</tbody>
</table>


## Sitemap

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