Moving a GitBook space to WordPress is a four-stage job: get the Markdown out, decide your structure before importing anything, import, then fix links and add redirects. The stage that surprises people is the second one, and the part that breaks is GitBook’s own syntax extensions: nothing in stock WordPress understands {% hint %} or {% tabs %}.
This guide covers what actually transfers, what silently does not, and how to avoid the two mistakes that make a migration painful to undo.
What can you get out of GitBook?
GitBook stores pages as Markdown and gives you two ways to reach them:
- Git Sync: mirrors the space into a GitHub or GitLab repository. This is the better route: you get the real directory structure, full history, and a working copy you can re-export at any point.
- Export: produces a PDF or an HTML bundle. Fine for archiving, poor for migrating, because you are converting away from Markdown and back again.
Use Git Sync if the space has it or can have it. If you are stuck with an HTML export, convert to Markdown with Pandoc first and accept that you will be cleaning up by hand.
What breaks in a GitBook migration?
Standard Markdown transfers without drama. GitBook’s extensions are the problem, because they are custom template tags that mean nothing outside GitBook:
# Rotating API keys
{% hint style="warning" %}
Rotating a key invalidates the previous one immediately.
{% endhint %}
{% tabs %}
{% tab title="cURL" %}
curl -X POST https://api.example.com/v1/keys/rotate
{% endtab %}
{% tab title="Python" %}
client.keys.rotate()
{% endtab %}
{% endtabs %}
{% content-ref url="authentication.md" %}
authentication.md
{% endcontent-ref %}Imported naively, every one of those lines lands in the page as literal text. What each construct needs on the other side:
| GitBook construct | What it should become | If unhandled |
|---|---|---|
| {% hint %} | A callout or admonition block | Literal text in the page |
| {% tabs %} | A tabbed code block | All tabs concatenated |
| {% stepper %} | A numbered procedure | Steps flattened into paragraphs |
| {% content-ref %} | An internal link or card | A stray filename |
| {% embed %} | An embed block | A bare URL |
| $$math$$ | A rendered formula | Raw LaTeX |
| {% include %} | Nothing; resolve before export | A broken reference |
Before you write a conversion script, check whether your destination already handles these. A documentation theme built with GitBook migrations in mind will map most of them for you, which turns a week of scripting into an upload.
Stage 1: Get the Markdown out
Enable Git Sync, let it push, and clone the repository. You now have a folder of .md files and, at the root, a SUMMARY.md describing GitBook’s navigation order. Keep that file: it is the only record of the intended reading order, and you will want it in stage two.
Stage 2: Decide the structure before you import
This is the stage people skip, and it is the expensive one to redo. Importers derive a page’s category from the folder it sits in, and most flatten the path rather than rebuilding a folder tree as a category tree. A file at guides/getting-started/install.md typically becomes one category named “Guides Getting Started”, not a child of “Guides”.
That is usually fine, since two navigation levels is the right depth anyway, but it has to be a decision rather than a surprise. Reorganise the folders to match the sections you actually want before importing, while it is still a matter of moving files.
Importers that match on slug will update a post in place, which is what you want. Change the folder layout after importing and you will get a second copy of everything under new categories, with the originals still published.
While you are in the files, add front matter. Title, description and tags map cleanly onto a post, its excerpt and its taxonomies, and the excerpt is what shows up in listings and search results:
---
title: Rotating API keys
description: Replace a live API key without downtime.
tags: [security, api, keys]
---
Rotate a key whenever someone with access leaves the team.Stage 3: Run the import
-
Import one file first. Pick your most complicated page (the one with tabs, a hint and a table) and check the result before doing anything at scale.
-
Import the rest as drafts. A good importer defaults to draft. Resist the urge to publish on import: you want a chance to look at three hundred pages before your readers do.
-
Spot-check across page types, not just the first ten alphabetically. Tutorials, reference tables and troubleshooting pages fail in different ways.
-
Check the editor, not only the front end. Content can render correctly and still be stored as an unrecognised block, which surfaces later as a recovery prompt the first time someone edits the page.
-
Publish in one pass, then immediately move to redirects.
Stage 4: Fix links and redirects
GitBook links are relative paths to .md files. After import they need to point at the new permalinks. Two things to do:
- Rewrite internal links. A find-and-replace across the imported content handles most of them. Search the published site for
.mdto catch the rest. - Redirect the old URLs. Your GitBook space has inbound links from support tickets, Stack Overflow answers and other people’s blog posts. Map every old path to its new one and keep the map.
Then crawl the new site with a link checker before announcing anything. Broken internal links after a migration are the fastest way to make a good move look like a bad one.
Troubleshooting
Every imported page is a draft
That is deliberate on most importers, and worth keeping. Bulk-publish from the posts list once you have checked a representative sample: filter by category and use the bulk edit action rather than opening each page.
Nested folders all collapsed into one category
Expected behaviour for most importers: the folder path is flattened into a single category name rather than rebuilt as a hierarchy. Fix it before importing by flattening the folders yourself into the sections you want, or afterwards by reassigning categories in bulk.
Code blocks lost their language
GitBook wraps some fences in its own {% code %} tag, and the language annotation sits on the inner fence. If your importer reads the outer tag it will miss the language. Check one wrapped block; if it is unlabelled, the fix is usually a find-and-replace to unwrap the tags before importing.
Images are missing
Git Sync stores images in the repository, usually under .gitbook/assets/. They are not uploaded to your media library by the Markdown import, so copy them across and rewrite the paths, or the pages will import cleanly with every image broken.
How long does it take?
For a space of a few hundred pages with Git Sync already enabled: a day for stages one and two, an hour for the import, and a day for links, redirects and checking. The two-day version is the one where the structure was decided up front. The two-week version is the one where it was not.
Doku handles the GitBook constructs in the table above directly: hints, tabs, steppers, columns, content refs, expandable sections and $$math$$ all map onto matching blocks on import. See the importer documentation for the full list of what converts.