# Migration
Laurel's supported Ghost migration path is one-way:
1. Export content from Ghost Admin.
2. Run `laurel import-ghost`.
3. Review the generated Markdown and `laurel.toml`.
4. Build and deploy the static site.
Laurel does not write back to Ghost and does not replace Ghost Admin. Treat the
import as a content conversion step from a Ghost export into files that live in
Git.
For the full walkthrough, use [`docs/migration/ghost.md`](./migration/ghost.md).
## Ghost Admin export to Markdown
From Ghost Admin, open **Settings -> Labs -> Export your content** and download
the JSON export. That JSON contains posts, pages, tags, authors, and many
Ghost-side records. It does not include uploaded media files.
Export or copy the Ghost `content/` media directories separately. The importer
looks for these subdirectories:
- `images/`
- `files/`
- `media/`
Run the importer from the root of a Laurel project:
```bash
laurel import-ghost ./your-site.ghost.2026-05-20.json --assets ./ghost-content
```
You can also pass an unzipped Ghost export directory or the export ZIP itself:
```bash
laurel import-ghost ./ghost-export/
laurel import-ghost ./ghost-export.zip
```
Useful review-first options:
```bash
laurel import-ghost ./ghost-export.zip --dry-run
laurel import-ghost ./ghost-export.zip --output ./review-import
laurel import-ghost ./ghost-export.zip --on-conflict rename
```
See [`docs/cli.md`](./cli.md#laurel-import-ghost) for every flag.
## Hugo / Jekyll Markdown posts
`laurel import-hugo
` and `laurel import-jekyll ` provide a
conservative first-pass import for Markdown posts. They are intended for review
imports into a Laurel project, not a full static-site migration.
```bash
laurel import-hugo ../old-hugo-site --dry-run
laurel import-hugo ../old-hugo-site --on-conflict rename
laurel import-jekyll ../old-jekyll-site --dry-run
laurel import-jekyll ../old-jekyll-site
```
The Hugo importer scans `content/posts/`, `content/post/`, `content/blog/`, then
`content/`. The Jekyll importer scans `_posts/`. Both import Markdown files into
`content/posts/.md`, preserve the body, and remap common frontmatter:
| Source frontmatter | Laurel output |
| --- | --- |
| `categories` | Merged into `tags` as slug-normalized values |
| `aliases` | Appended to root `redirects.yaml` as 301 redirects to `//` |
| `draft: true` | `status: draft` |
| Jekyll `YYYY-MM-DD-slug.md` filename | `slug` plus `date` when frontmatter omits them |
This first slice supports YAML frontmatter and Hugo TOML `+++` frontmatter. It
does not convert layouts, shortcodes, theme templates, site config, data files,
asset pipelines, or custom collections. Review the generated Markdown and
`redirects.yaml` before publishing.
## Imported automatically
`src/ghost/import.ts` currently imports these Ghost export records and assets:
| Ghost export data | Laurel output |
| --- | --- |
| Posts | `content/posts/.md` with Markdown body and frontmatter |
| Pages | `content/pages/.md` with Markdown body and frontmatter |
| Tags with metadata | `content/tags/.md` |
| Authors | `content/authors/.md` |
| Post-tag and post-author joins | `tags: [...]` and `authors: [...]` frontmatter |
| Tiers attached to posts | `tiers: [...]` frontmatter for posts |
| Feature, Open Graph, Twitter, profile, and cover image fields | Frontmatter image fields, sanitized to http(s) or relative paths |
| `images/`, `files/`, `media/` from `--assets` | Copied under the target content directory |
| Ghost content images/media with `--download-images` | Downloaded into `content/images/` and references rewritten; third-party service URLs remain external |
| Ghost `content/data/redirects.json` | Reviewable redirect snippets under `migration/redirects/` |
Posts with `status: published` and `status: draft` are imported. Other statuses
such as `scheduled` are filtered out. Drafts remain drafts in frontmatter, so a
normal `laurel build` still excludes them unless you opt into draft builds.
## Manual after import
Ghost settings are not converted into `laurel.toml`. After import, copy the
settings that matter to your static site into the Laurel config:
- `[site]` title, description, URL, logo, icon, cover image, locale, timezone,
and accent color.
- `[[navigation]]` and `[[secondary_navigation]]`.
- Theme custom settings under `[theme.custom]`.
- Optional components such as RSS, sitemap, search, comments, analytics,
Portal/newsletter wiring, and deployment settings.
The Ghost theme itself is also a separate step. Put the theme under `themes/`
and point `[theme].path` at it, or start from the bundled Source example.
## Not imported
These Ghost features either require a server runtime or do not have a stable
Markdown/frontmatter representation in Laurel:
- Members, subscribers, member labels, customer records, sessions, comped
subscriptions, and Stripe billing state.
- Custom integrations from Ghost Admin, including Zapier, Slack, webhooks, and
Admin API credentials.
- Snippets and editor-only reusable content records.
- Custom fields or plugin-owned tables that are not represented in Laurel
frontmatter.
- Ghost Admin users' permissions, roles, staff invites, and authentication
settings.
- Ghost settings as an automatic config migration. Move the values you need to
`laurel.toml` manually.
- Newsletter sending state, email-only delivery settings, and per-recipient
email analytics.
- Server-side paywall decisions. `visibility` and `tiers` are preserved as
frontmatter, but a static build cannot decide who is signed in or paid.
- Site-wide code injection. Post-level `codeinjection_head` and
`codeinjection_foot` are also skipped by default unless you pass
`--keep-code-injection` and trust the source export.
For member and Portal migration choices, read [`docs/MEMBERS.md`](./MEMBERS.md).