f451 1.3: Import from BookStack and Export Back

Anyone who wanted to move their documentation to f451 used to have to carry it over by hand. Version 1.3.0 takes over shelves, books, and individual pages from BookStack and, if needed, writes spaces back there as well. Both work from the command line or directly in the browser.
The export is part of the concept. Pages in f451 are ordinary Markdown in Git, and with 1.3 a space can also be handed on as a BookStack book without copying pages by hand.
The Import Creates Drafts with a Review
By default, an import does not write to the published version. For every page from BookStack, f451 creates a draft and opens a review, technically one pull request per page. A page only becomes visible in the space once someone approves its review. If you trust the source, pass --release at the start and the import approves the reviews itself.
The command line needs two tokens. One is a personal access token from f451 with write access, whose owner has a connected Git account, because all changes run under that account. The other is an API token from BookStack that can read the content to be taken over.
export F451_URL=https://wiki.example.org
export F451_TOKEN=f451_pat_...
export BOOKSTACK_URL=https://bookstack.example.org
export BOOKSTACK_TOKEN_ID=...
export BOOKSTACK_TOKEN_SECRET=...
pnpm --filter @f451/import-cli start -- bookstack --book betriebshandbuch --space betrieb
Instead of --book, you can use --shelf for a whole shelf or --page for a single page. With --parent, the imported tree lands under an existing page. With --dry-run, the import writes nothing to f451 and only saves the generated Markdown for inspection.
From Shelf to Page
BookStack organises content into shelf, book, chapter, and page. f451 only knows pages with subpages, so the import turns everything above the page into a folder page.
| BookStack | f451 |
|---|---|
| Shelf | Folder page with the books below it |
| Book | Folder page |
| Chapter | Folder page |
| Page | Page |
BookStack stores pages as HTML, f451 as Markdown. The import translates whatever has an equivalent. Tags become page tags, callouts become GFM alerts (info to [!NOTE], warning to [!WARNING], and so on), and collapsible <details> blocks become an alert with a bold heading. Links between imported pages become wikilinks. Images are downloaded and stored as media of the page.
Diagrams from draw.io come over as editable .drawio.svg files. For that, the import needs Docker or Podman to render them. Without either, it takes the PNG from BookStack and notes this in the report.
The frontmatter of every imported page contains a source field.
source:
type: bookstack
id: "42"
url: https://bookstack.example.org/books/betriebshandbuch/page/einleitung
A later import uses it to recognise its own pages. With --update, it updates them through a new review instead of creating them twice. Only text and tags are replaced. Everything else f451 keeps in the frontmatter, such as ID, classification level, metadata, and version, stays as it is. A page with an open draft is skipped and reported rather than having its draft overwritten.
The import does not delete pages, not even those that have since disappeared from BookStack.
The Report Lists What Gets Lost
Not everything in BookStack has an equivalent in Markdown. Table cells with colspan or rowspan are split into empty cells, and lists and code inside table cells are squeezed onto one line. HTML without a Markdown counterpart is dropped. Media larger than 10 MiB are skipped.
So something does get lost, but the report names it. Every run writes a report, import-report.md, listing created, updated, skipped, and failed pages with the reason. It also lists skipped media, diagrams that stayed PNG, and every place where content without a Markdown equivalent was lost. A trial run with --dry-run before the actual move shows in advance where manual rework will be needed. Tables with merged cells cause the most of it.
The Way Back to BookStack
The export goes in the opposite direction and only needs a read-only token on the f451 side, because it never writes to f451. The BookStack token must be allowed to create and change content.
pnpm --filter @f451/import-cli start -- export bookstack --space betrieb
The space becomes a book, or with --page only a subtree. Because BookStack does not nest arbitrarily deep, the export flattens deeper levels.
| f451 | BookStack |
|---|---|
| Root of the export | Book |
| Page with subpages | Chapter, its own content becomes the chapter’s first page |
| Page without subpages | Page |
| Deeper levels | inside the chapter, with the path as a name prefix (B / C) |
The export renders pages with the same pipeline as the f451 reading view. GFM alerts turn back into BookStack callouts. Links to pages included in the export point to BookStack, links to other pages of the space point back to f451.
Images go into the BookStack gallery, identical images only once, recognised by a hash of their content. BookStack does not accept SVG, so the export converts every SVG to PNG. draw.io diagrams are the exception. They become BookStack drawings that carry the diagram source and can be edited further in BookStack’s draw.io editor.
Every exported book, chapter, and page gets the tags f451-id and f451-space. A second export finds them again and updates in place, renames included. Pages that no longer exist in f451 are listed as stale in the report. Nothing is deleted here either. Importing an exported book back into f451 creates no duplicates, because the import recognises the f451-id tag as the source.
Import and Export from the Browser
If you would rather not open a command line, you can start both from the space sidebar under “Import und Export”. There you pick the BookStack instance, enter token ID and secret, load the books, and choose a book, shelf, or single page as the source. The command-line options appear as switches below, from “Bereits importierte Seiten aktualisieren” (update already imported pages) to the trial run. The page shows the progress, the history per page, and at the end the report for download. The run continues in the background even if you close the page.
Permissions are the same as everywhere else in f451. Anyone who can write to the target space may import. Anyone who can read it may export. The job runs under the account of the person who starts it, with a short-lived token that f451 revokes at the end. Review, classification levels, and all other rules therefore apply exactly as when working by hand.
The BookStack token is entered anew for every run. f451 keeps it in memory only for the duration of the job and writes it neither to a database nor to a log or the report. One job runs per user, at most three per API process. A running job can be cancelled, but only ever between two pages. Cancelling cleans up drafts that were created but are still empty, and a later run with updating enabled picks up without duplicates.
Only BookStack Instances the Operator Allows
In the browser, it is not your own computer that connects to BookStack but the f451 server. If anyone could enter any address there, the server could be abused as a stepping stone to query machines on the internal network that are not reachable from outside. This attack is called server-side request forgery (SSRF).
f451 prevents this with an allowlist. Import and export in the browser stay switched off until the operator enters the permitted BookStack instances in an environment variable of the API:
F451_IMPORT_BOOKSTACK_URLS=https://bookstack.example.org,https://doku.intern.example.org
If the variable is empty or unset, the “Import und Export” entry in the sidebar stays hidden. If it is set, the form only offers these instances. The API checks the address again on every request, starting with loading the book list, and rejects any other. Only plain http or https addresses without credentials, query parameters, or fragments are allowed. An invalid entry stops the API from starting at all. The error message names the entry, but masks any password in it.
The list also guards against detours through the content. A BookStack page cannot make the import fetch images or other files from addresses outside the allowed instance, and the import does not follow redirects to another host. These gaps were found by a dedicated review before the release, and the changelog lists the fixed findings one by one.
Other bugs from this review concerned robustness. A page that fails before its content is saved leaves no empty draft behind. If BookStack throttles requests (HTTP 429), the import waits instead of giving up on the page.
Approving Many Reviews at Once
An import without --release leaves as many open reviews as pages. So that nobody has to open them one by one, 1.3 adds a review overview per space at /wiki/<space>/reviews. It lists all open reviews and approves several at once, for example all pages of an import.
Mermaid, Backlinks, and Link Preview
- Mermaid diagrams. A code block with the language
mermaidappears as a diagram in the reading view and as a live preview in the editor. In the Markdown it stays an ordinary code block. - Backlinks. The info bar lists every page that links to the current one, with the text around the link.
- Link preview. Hovering over an internal link, or reaching it with the keyboard, shows the title and first paragraph of the target.
Deleting Goes Through a Review
Deleting a page now also goes through a review and takes the whole folder with it, meaning subpages, media, and releases. Until now, subpages of a deleted page were left behind as orphans. Over the HTTP API, DELETE /api/pages/:id therefore no longer deletes immediately but opens the review. MCP tools, page format, and configuration remain unchanged.
After the update, operators must run the database migration, otherwise page requests fail. Then run POST /admin/reindex once so that backlinks get their text snippets. Anyone who wants to offer import and export in the browser also sets F451_IMPORT_BOOKSTACK_URLS.
Sources
- f451 release 1.3.0: github.com/rotecodefraktion/f451/releases/tag/v1.3.0
- Changelog: CHANGELOG.md
- Import and export documentation: docs/import-export.md
- BookStack: bookstackapp.com
- Previous release: The Emperor’s New Clothes: f451 Templates