---
title: "Templates: start a Document from a Dokumentvorlage"
description: "The Dokumentvorlagen folder, the placeholders expandTemplate fills in ({{title}}, {{date}}, {{time}}, {{cursor}}), and the templateId of the Document create route."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/templates"
---
# Templates: start a Document from a Dokumentvorlage

> The Dokumentvorlagen folder, the placeholders expandTemplate fills in ({{title}}, {{date}}, {{time}}, {{cursor}}), and the templateId of the Document create route.

A **Dokumentvorlage** is an ordinary Document that sits in a folder named `Dokumentvorlagen` (`TEMPLATES_FOLDER`, `@skaile/forge-editor/core`). A new Document can start as a copy of it: the editor server copies the Dokumentvorlage's content and fills in the placeholders in every text node. The Dokumentvorlage itself is never changed. There is no separate template store, so a Dokumentvorlage has the same access rules, history and editor as any other Document, and a user who may read it may create from it.

## Placeholders

`expandTemplate(text, { title, now, locale })` is pure and framework-free. It returns `{ text, cursor }`.

<table>
<thead>
  <tr>
    <th>
      Placeholder
    </th>
    
    <th>
      Becomes
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        {{title}}
      </code>
    </td>
    
    <td>
      the title of the new Document
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        {{date}}
      </code>
    </td>
    
    <td>
      the date, <code>
        medium
      </code>
      
       style in <code>
        locale
      </code>
      
       (<code>
        25.10.2026
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        {{date:short}}
      </code>
      
      , <code>
        {{date:medium}}
      </code>
      
      , <code>
        {{date:long}}
      </code>
    </td>
    
    <td>
      the date in that <code>
        Intl.DateTimeFormat
      </code>
      
       style
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        {{date:iso}}
      </code>
    </td>
    
    <td>
      the date as <code>
        2026-10-25
      </code>
      
       (the local day of <code>
        now
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        {{time}}
      </code>
    </td>
    
    <td>
      the time, <code>
        short
      </code>
      
       style in <code>
        locale
      </code>
      
       (<code>
        14:05
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        {{cursor}}
      </code>
    </td>
    
    <td>
      nothing; <code>
        cursor
      </code>
      
       holds its offset in the expanded text. Every further <code>
        {{cursor}}
      </code>
      
       is removed too
    </td>
  </tr>
</tbody>
</table>

Names are not case-sensitive and may have spaces inside the braces (`{{ Title }}`). Anything else in double braces stays exactly as written, an unknown name (`{{author}}`) and an unknown format (`{{date:weird}}`) included, so a Dokumentvorlage can carry text that only looks like a placeholder. Block templates for the AI (`BlockTypeAi.contentTemplate`) keep their own `{key}` syntax; the two are not mixed, and Templater scripts are not translated.

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

const env = { title: 'Sitzung', now: new Date(2026, 9, 25, 14, 5), locale: 'de-DE' }
expandTemplate('# {{title}} ({{date:iso}})\n\n{{cursor}}', env)
// { text: '# Sitzung (2026-10-25)\n\n', cursor: 24 }
```

`expandTemplateContent(content, env)` applies the same expansion to every text node of TipTap JSON. It keeps marks, attributes and block IDs, returns a new tree and drops the cursor, because a new Document has no caret yet.

## Creating from a Dokumentvorlage

`POST /api/editor/documents` takes an optional `templateId` next to `title` and `folder`. In the client it is `backend.documents.create({ title?, folder?, templateId? })`.

```ts
const sitzung = await backend.documents.create({ title: 'Sitzung', folder: 'Projekte', templateId: protokoll.id })
```

The server checks the Dokumentvorlage before it creates anything: the Document must be listed in a folder named `Dokumentvorlagen` (`templatesOf(tree)`, any depth) and the user must be allowed to read it. It then creates the Document through `DocumentAdmin.createDocument` and stores the expanded content with `storeContent`, so the Block index follows as for any write. `{{title}}` is the title the Document got, which is the default title when the request names none. The date and time use the first language tag of the `Accept-Language` header, `de` when there is none.

<table>
<thead>
  <tr>
    <th>
      Answer
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        201
      </code>
      
       with the <code>
        DocumentSummary
      </code>
    </td>
    
    <td>
      created
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      <code>
        templateId
      </code>
      
       is an empty string, or the body is otherwise invalid
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      the user may not read the Dokumentvorlage
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      <code>
        templateId
      </code>
      
       names no Document inside a <code>
        Dokumentvorlagen
      </code>
      
       folder, or the folder is unknown
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        501
      </code>
    </td>
    
    <td>
      the documents port has no <code>
        createDocument
      </code>
      
       or no <code>
        listTree
      </code>
    </td>
  </tr>
</tbody>
</table>

The content of the Dokumentvorlage is the live one when the collaboration server has it open, so an unsaved edit is part of the copy. Block IDs are copied unchanged: they are unique per Document only, so the copy and the Dokumentvorlage do not clash.

## In the host

The host lists the Dokumentvorlagen from the tree it already has: `templatesOf(tree)` returns the Documents of the first folder named `Dokumentvorlagen`, newest first, as `DocumentSummary`s. The reference app shows them in the menu „Aus Dokumentvorlage“ next to „Neues Dokument“ on `app/pages/documents/index.vue` and passes the choice as `templateId`:

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

const templates = templatesOf(tree) // [{ id, title, … }]
await backend.documents.create({ templateId: templates[0]!.id })
```


## Sitemap

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