Substack → Ghost
Move a Substack publication to Ghost while reconciling content, free and paid members, Stripe continuity, custom-domain URLs, newsletter delivery, and a reversible first send.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Substack
- Publishing, email, recommendations, network discovery, and paid subscriptions work out of the box
- Audience, campaigns, automations, and reporting live in one publishing system
- Brand control, automation, data depth, and platform independence are limited
- Automation logic, analytics history, and deliverability settings are not very portable
Ghost
- A focused publishing and membership platform is fast and pleasant for writers
- Editors get a purpose-built publishing workflow instead of raw files
- Its narrower ecosystem is less suitable for complex sites and custom application behavior
- Themes, extensions, and content models add maintenance and portability constraints
Ghost: A focused publishing and membership platform is fast and pleasant for writers. This removes a major source-side concern: Brand control, automation, data depth, and platform independence are limited.
What you lose: Publishing, email, recommendations, network discovery, and paid subscriptions work out of the box. What you inherit: Its narrower ecosystem is less suitable for complex sites and custom application behavior.
Know the shape of the move.
This timeline assumes
- Profile: one publication with at most 2,000 posts, 25,000 total subscribers, one primary newsletter, one custom domain, and optional paid subscriptions in Stripe.
- You control the Substack owner account, Ghost owner account, DNS, Stripe account, sending-domain records, and the publication's support email.
- If paid members exist, Ghost is connected to the same Stripe account used by Substack before member import.
- No full-audience Ghost email is sent until content, member, payment, domain, redirect, and seed-delivery checks pass.
- The built-in migrator covers the documented content and member path; comments, Notes, recommendation relationships, referral data, and historical analytics are treated as non-portable unless a separate export proves otherwise.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Published posts | partial | high | Ghost's built-in Substack migrator imports content from the Substack export, but theme presentation, complex embeds, buttons, captions, paywall placement, and email-only markup require visual verification. | Compare representative public, paid, image-heavy, embedded, and email-originated posts before changing the domain. |
| Post images | partial | high | The migrator handles content and images, but every source and rendered destination image still needs availability and ownership checks. | Create an image URL inventory, request every destination asset, and keep the export ZIP unchanged. |
| Free subscribers | clean | critical | Ghost's Substack migrator accepts the free-subscriber CSV and imports members, subject to the data present in the export. | Record source counts immediately before export and reconcile Ghost free and email-subscribed segments after import. |
| Paid subscribers and access | partial | critical | Paid migration requires Ghost to use the same Stripe account connected to Substack. Member access depends on matching Stripe customers and imported member records. | Connect the same live Stripe account first, import paid members separately, and test access with current monthly, annual, canceled-at-period-end, and complimentary cases. |
| Existing paid subscription fees | partial | high | Ghost documents that Substack's 10% fee continues on existing paid subscriptions after migration unless it is removed through Ghost's migration support process. | Model the continuing fee, contact Ghost before cutover if removal matters, and do not promise immediate fee savings for existing subscriptions. |
| Member names, labels, notes, and creation dates | partial | medium | Ghost member CSV supports email, name, note, subscribed_to_emails, Stripe customer ID, complimentary plan, labels, and created_at, but the Substack export may not contain every destination field. | Use only documented columns, preserve the source CSV, and list every field intentionally left blank. |
| Email subscription preferences | partial | critical | Ghost maps imported members through subscribed_to_emails behavior. Multiple newsletters and prior unsubscribe state require explicit review. | Import free and paid files deliberately, reconcile subscribed and unsubscribed totals, and never globally opt in an uncertain record. |
| Custom-domain post URLs | manual | critical | Substack commonly uses /p/ in post URLs while Ghost reserves /p/ for previews. Ghost documents a specific negative-lookahead redirect pattern. | Upload the documented redirects.yaml rule, test real historic URLs and Ghost preview URLs, and verify single-hop 301 results. |
| Publication design and navigation | manual | medium | Substack theme, layout, navigation, signup surfaces, and email design do not become a Ghost theme automatically. | Configure the Ghost theme, Portal, navigation, logo, colors, publication metadata, and email settings before import acceptance. |
| Comments and discussion history | lost | medium | The documented built-in migration covers content and members but does not state that Substack comments and discussion history are imported. | Treat comments as unavailable, preserve public or exported records where permitted, and tell readers before the source is retired. |
| Substack Notes, recommendations, and referral network | lost | medium | These Substack network features have no documented mapping in the Ghost migrator. | Export available records for reference, recreate only essential outbound recommendations, and do not claim network continuity. |
| Historical email analytics and delivery events | lost | medium | Campaign history may remain visible in Substack, but opens, clicks, deliverability events, and historical dashboard analytics are not imported into Ghost. | Export required reports and record baseline engagement before the first Ghost send. |
| Sending-domain reputation | partial | critical | Ghost custom sending domains require DNS and DMARC. Ghost documents automatic warm-up over approximately six weeks for a new custom sending domain. | Configure early, allow 15 minutes to 24 hours for DNS, use Ghost's warm-up process, and monitor the first several sends. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Substack post title | Ghost post title | automatic | Compare encoded punctuation and the rendered email subject where the post is resent. |
| Substack post body | Ghost post content | automatic | Inspect embeds, captions, buttons, footnotes, dividers, and paywall boundaries. |
| Substack post slug | Ghost post slug plus redirect | transform | Keep the slug and redirect /p/:slug to /:slug without breaking Ghost UUID preview routes. |
| Substack publication export ZIP | Ghost Substack migrator content upload | automatic | Upload the unchanged ZIP from Settings → Advanced → Import/Export. |
| Free-subscriber CSV email | Ghost member email | automatic | Email is required and acts as the reconciliation key. |
| Free-subscriber name | Ghost member name | automatic | Name is optional; do not synthesize it from email. |
| Subscriber email status | subscribed_to_emails | transform | Preserve opt-out state and review Ghost behavior when multiple newsletters subscribe new members. |
| Paid-subscriber email and Stripe customer | Ghost member plus stripe_customer_id | transform | Connect the same Stripe account first and verify the customer/subscription association after import. |
| Complimentary access | complimentary_plan | manual | Ghost supports complimentary plans; map duration and access intentionally. |
| Substack sections or audience groupings | Ghost labels and newsletters | manual | There is no safe generic mapping; define audience and sending semantics before import. |
| Custom publication domain | Ghost custom domain | manual | Activate in Ghost and apply the exact DNS records Ghost provides for the chosen root or subdomain setup. |
| Custom sending domain | Ghost custom sending domain | manual | Requires an eligible Ghost(Pro) plan, a custom publication domain, provided DNS records, and DMARC. |
Make the move recoverable.
Create the source-of-truth backup
Preserve an immutable publication, audience, billing, URL, DNS, and analytics snapshot before Ghost imports or sends anything.
- Record publication settings, theme and navigation screenshots, custom domain, sender name and address, email schedule, sections, recommendations, paid plans, prices, benefits, and active automations.
- Create and download a new Substack publication export ZIP; record its timestamp, size, and post count.
- Download free- and paid-subscriber CSV files separately and record total, email-subscribed, unsubscribed, paid, canceled, complimentary, monthly, and annual counts.
- Export Stripe customer and active-subscription reports; record the account ID, currencies, products, prices, coupon behavior, tax settings, statement descriptor, and current Substack fee treatment.
- Crawl public post and page URLs, including /p/ paths, archives, about, subscribe, and high-traffic landing pages.
- Export available email and web analytics reports, DNS records, sending-domain records, and screenshots of scheduled drafts.
- Store untouched files with checksums and a manifest; perform all cleaning or mapping on copies.
Proof to capture: One dated manifest with file checksums, post and member segment counts, Stripe account ID and subscription totals, public URL count, DNS records, scheduled-send inventory, and named restore owner.
Run the built-in Substack content migration
Import supported content through Ghost's documented migration path.
- Enter the public Substack URL and upload the unchanged publication export ZIP.
- Upload free and paid subscriber CSVs in the requested stages.
- Before confirming import, compare Ghost's previewed post and member counts with the backup manifest.
- Save the migration confirmation and any warnings; do not repeat the import blindly after a partial result.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Reconcile and enrich members
Preserve consent, paid access, and useful audience metadata without creating duplicate or over-subscribed members.
- Normalize email for comparison without altering the untouched source file.
- Map only documented Ghost member columns and leave uncertain consent values unsubscribed.
- Join paid members to the same live Stripe account using the documented customer association.
- Compare source and Ghost segments by email and explain missing, duplicate, invalid, suppressed, and merged records.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Preserve custom-domain URLs
Keep Substack post links working while preserving Ghost preview routes.
- Start with Ghost's documented negative-lookahead redirect for /p/ post paths.
- Add explicit mappings for any changed static pages, sections, or nonstandard slugs.
- Test the complete high-value source URL set plus a newly generated Ghost preview URL.
- Reject loops, chains, temporary status codes, and any rule that catches Ghost's UUID preview paths.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Configure sending safely
Start Ghost newsletters without duplicate sends or avoidable reputation damage.
- Configure publication identity, newsletter, reply-to, unsubscribe, Portal, membership access, and custom domain before the first send.
- Add the exact custom sending-domain DNS records Ghost provides and wait until Ghost shows the domain as active.
- Create a seed segment across major mailbox providers and send a private test before scheduling the full audience.
- Keep every Substack campaign paused once Ghost owns sending; verify no duplicate automation or scheduled draft remains.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Connecting the wrong Stripe account
Ghost instructs paid Substack migrations to use the same Stripe account connected to Substack.
- Consequence
- Existing paid members may import without recognized subscriptions or correct paid access.
- Mitigation
- Record and compare the Stripe account ID before Ghost connection and before paid-member import.
Stop if: The connected live Stripe account ID differs from the account containing current Substack subscriptions.
Assuming existing Substack fees disappear
Ghost states that Substack's 10% fee continues on existing paid subscriptions unless separately removed.
- Consequence
- Revenue projections and member communications can be wrong.
- Mitigation
- Model continuing fees and resolve removal with Ghost before promising savings.
Stop if: The business case requires immediate fee removal but the removal path is not confirmed.
Overwriting consent during member import
Ghost's subscribed_to_emails field controls newsletter subscription behavior and interacts with multiple-newsletter defaults.
- Consequence
- Unsubscribed people can receive email or intended recipients can be suppressed.
- Mitigation
- Reconcile source consent states, use conservative mappings, and audit every destination newsletter segment.
Stop if: Opted-in and opted-out totals cannot be independently reconciled.
Breaking /p/ links or Ghost preview links
A simple /p/* redirect can capture Ghost's own post-preview URLs.
- Consequence
- Historic links can break or editors can lose preview access.
- Mitigation
- Use Ghost's documented negative-lookahead pattern and test both historic posts and UUID previews.
Stop if: Any high-value Substack URL or Ghost preview URL does not reach the intended page.
Sending before domain and seed checks
Custom sending-domain DNS can take 15 minutes to 24 hours, and Ghost warms a new domain over approximately six weeks.
- Consequence
- Authentication, delivery, reputation, replies, or unsubscribe behavior can fail at full-audience scale.
- Mitigation
- Activate DNS early, allow Ghost's warm-up, send to a representative seed list, and monitor mailbox placement.
Stop if: Ghost does not show the sending domain active, DMARC is absent, or any seed authentication, link, reply, or unsubscribe test fails.
Duplicate full-audience sends
Scheduled or automated Substack and Ghost campaigns can overlap during the move.
- Consequence
- Subscribers can receive duplicate messages and generate complaints or unsubscribes.
- Mitigation
- Inventory scheduled mail, assign one sending system, pause the other, and require a named send owner.
Stop if: Any unowned campaign, automation, or scheduled draft remains active in either platform.
Do the work in this order.
- Day 1 · inventory-backup
Inventory publication, audience, billing, and URLs
2–3 hours active1 day elapsedAllow Substack export generation to finish waiting- Capture settings, plans, scheduled mail, domains, URLs, analytics, content export, member CSVs, Stripe reports, and counts.
- Store untouched files with checksums and create the reconciliation workbook.
- Choose the exact first Ghost send date and working-backwards cutover window.
Stop / go checkpointIs the source state complete and frozen for planning?
Go when: Post, audience, paid-subscription, URL, DNS, and scheduled-send inventories reconcile.
Stop when: A paid segment, consent state, scheduled campaign, or required owner is unknown.
- Day 2 · ghost-setup
Configure Ghost, Stripe, domain, and sending
2–4 hours active1–2 days elapsed15 minutes to 24 hours for custom sending-domain DNS; approximately six weeks of automatic warm-up after activation waiting- Configure Ghost publication, theme, navigation, Portal, access, newsletters, tiers, staff, and email identity.
- Connect the same live Stripe account and verify its account ID.
- Activate the publication and sending domains using Ghost-provided DNS records and DMARC.
Depends on: inventory-backup
Stop / go checkpointIs Ghost safe to receive members?
Go when: The correct Stripe account is connected, domains are active, membership settings are explicit, and no production campaign is enabled.
Stop when: Stripe, DNS, DMARC, plan access, or publication ownership is uncertain.
- Day 3 · import
Import content and members
2–3 hours activeHalf to one day elapsedAllow the Ghost migrator and member-import email confirmation to complete waiting- Upload the unchanged Substack export ZIP and separate free and paid member files through the built-in migrator.
- Review Ghost's proposed counts before import and save every result or warning.
- Reconcile posts, free members, paid members, subscribed members, and Stripe status.
Depends on: ghost-setup
Stop / go checkpointDid the import preserve the recorded source state?
Go when: All counts match or each variance has an evidence-backed explanation and owner.
Stop when: Paid access, consent, duplicates, content totals, or import errors cannot be reconciled.
- Day 4 · preview-verify
Verify content, access, redirects, and seed delivery
3–4 hours active1 day elapsedAt least one hour between DNS activation and final seed test; longer if Ghost still reports verification pending waiting- Check representative public, member-only, paid, image-heavy, and embedded posts.
- Upload and test redirects using historic /p/ URLs plus Ghost preview URLs.
- Test free, paid, canceled, complimentary, unsubscribed, and new signup member journeys.
- Send only to the seed list and verify authentication, links, reply, unsubscribe, and mailbox delivery.
Depends on: import
Stop / go checkpointCan Ghost own the public domain and next send?
Go when: Every blocking content, URL, member, payment, DNS, and seed-delivery check passes.
Stop when: Any test recipient receives incorrect access, duplicate mail, broken links, or failed authentication.
- Day 5 · cutover
Capture the final delta and switch the publication
1–2 hours active2–4 hours elapsedObserve DNS and certificate activation before announcing waiting- Pause Substack publishing and scheduled sends; record the freeze time.
- Import the final post and member delta and rerun count reconciliation.
- Change the publication domain, run production checks, and keep the full Ghost audience unscheduled.
Depends on: preview-verify
Stop / go checkpointKeep Ghost public or return the domain to Substack?
Go when: Public URLs, SSL, member login, paid access, signup, redirects, analytics, and seed delivery pass on the real domain.
Stop when: A blocking production check fails or a source delta is unexplained.
- Days 6–8 · first-send-monitor
Send deliberately and monitor
1 hour plus 20 minutes daily active2–3 days before cleanup; six weeks for full sending-domain warm-up elapsedWait 24 hours after the first full send before retiring any source send configuration waiting- Review the final audience segment and ensure Substack has no active campaign.
- Send the first Ghost newsletter at the planned time with a named operator and observer.
- Monitor accepted, delivered, bounced, complained, unsubscribed, clicked, replied, and paid-access signals.
Depends on: cutover
Stop / go checkpointCan source sending be retired?
Go when: The first full send completes without a rollback trigger and 24-hour metrics are within the agreed baseline.
Stop when: Duplicate sends, authentication failures, abnormal bounces or complaints, broken links, or incorrect paid access appear.
Cut over with a way back.
Cutover
Make Ghost the public and sending system without losing the last Substack changes or sending the audience twice.
- Begin the publication and campaign freeze; record UTC time and confirm all Substack scheduled sends and automations are paused.
- Export and import the final content and member delta; reconcile segment and Stripe counts again.
- Confirm Ghost custom domain, SSL, sending domain, DMARC, redirects, Portal, newsletter, tiers, and analytics configuration.
- Change the publication-domain DNS exactly as Ghost specifies and preserve the previous records.
- Run blocking production checks for content, redirects, member access, signup, paid access, email preferences, analytics, and seed delivery.
- Keep the full Ghost audience unscheduled until the public-domain report passes; announce the move only after approval.
Proof to capture: Freeze timestamp, final post and member reconciliation, Stripe account ID and paid totals, before/after DNS records, redirect results, member-journey captures, seed message headers, and named approval.
Rollback
Return the public domain and next send to Substack without creating duplicate campaigns or corrupting paid access.
- Declare rollback, cancel any Ghost campaign, disable new Ghost campaign scheduling, and preserve import and delivery logs.
- Restore the recorded publication-domain DNS values for Substack and confirm SSL and public URLs.
- Keep Ghost member and Stripe data unchanged for investigation; do not disconnect the shared Stripe account during an active incident.
- Reconcile any post, signup, unsubscribe, or payment changes made after freeze and assign a system of record for each.
- Confirm exactly one platform owns the next send and communicate the revised schedule.
Proof to capture: The custom domain serves Substack, critical posts and signup paths work, no duplicate campaign is scheduled, paid subscriptions remain active in the same Stripe account, and all cutover deltas are accounted for.
- Paid-member access or Stripe association fails for any representative active plan.
- Email opt-in and opt-out totals cannot be reconciled.
- Historic high-value URLs or Ghost preview URLs fail after one redirect correction.
- Sending-domain authentication, seed delivery, reply, unsubscribe, or link checks fail.
- A production-blocking problem cannot be corrected and fully reverified within two hours.
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
CNT-01Blocking | Post reconciliation | Compare source export posts with Ghost posts by slug, date, title, visibility, and access level. | Every expected post exists once or has an approved exception. | Attach the post reconciliation CSV and exception list. | |
VIS-01Blocking | Representative render parity | Compare public, paid, image-heavy, embedded, and email-originated samples on desktop and mobile. | Headings, body, images, captions, embeds, buttons, paywall boundary, metadata, and links are usable and deliberate. | Attach paired source/destination captures. | |
MEM-01Blocking | Audience segment reconciliation | Compare total, free, paid, subscribed, unsubscribed, complimentary, monthly, and annual counts. | Counts match or every variance is an explained invalid, duplicate, suppressed, or intentionally omitted record. | Attach the segment table and variance notes. | |
PAY-01Blocking | Stripe account and paid access | Confirm the live Stripe account ID and test active monthly, annual, canceled-at-period-end, and complimentary members. | The account ID matches Substack's account and each test member receives the correct Ghost access without a new charge. | Attach redacted Stripe and Ghost member captures. | |
CON-01Blocking | Email preference preservation | Sample subscribed and unsubscribed records from each imported file and compare their Ghost newsletter state. | No opted-out member is subscribed and no unexplained global opt-in occurred. | Attach the sampled member IDs and aggregate counts. | |
URL-01Blocking | Historic /p/ URLs and Ghost previews | Request all high-value source URLs, a random historic sample, and a newly generated Ghost UUID preview URL. | Historic posts use one 301 to the correct post; Ghost preview URLs still render and are not caught by the redirect. | Attach status, redirect chain, and final URL output. | |
DNS-01Blocking | Publication domain and SSL | Resolve the custom domain from multiple public resolvers and request root, www or chosen subdomain, and critical paths over HTTPS. | The canonical hostname serves Ghost with a valid certificate and the alternate hostname redirects deliberately. | Attach DNS answers, certificate details, and HTTP results. | |
EML-01Blocking | Seed delivery and authentication | Send to controlled addresses at major mailbox providers and inspect authentication, from, reply-to, links, images, unsubscribe, and placement. | Ghost shows the domain active; SPF, DKIM, and DMARC align as configured; every seed message works and unsubscribes once. | Attach redacted message headers, inbox captures, and unsubscribe result. | |
SCH-01Blocking | Single sending system | Review scheduled, draft, automated, and recurring sends in both platforms immediately before the first full send. | Only one named Ghost campaign is eligible to send and Substack has no active campaign. | Attach schedule captures and the named send owner. | |
ANA-01Blocking | Web and newsletter measurement | Generate one labeled pageview, signup, and test-email click on the production domain. | The intended properties record the correct domain, path, campaign, and conversion once. | Attach realtime or debug captures. |
Remove the scaffolding safely.
Safe after: One successful full-audience Ghost send, 24 hours of acceptable delivery metrics, seven stable public-domain days, and a signed verification report.
- Archive the final Substack export ZIP, free and paid member CSVs, Stripe reports, URL inventory, DNS records, analytics, and verification evidence with checksums.
- Cancel or remove every remaining Substack scheduled send and automation; confirm Ghost owns the next send.
- Update Stripe public details and statement descriptor where appropriate, while preserving existing subscription products and prices.
- Document any continuing Substack fee on existing subscriptions and the owner of its removal process.
- Revoke migration-only users and tokens; remove temporary seed labels and test members without deleting required evidence.
- Keep Substack readable for the agreed retention period before canceling; preserve content and billing records required for tax, support, and disputes.
- Update support replies, privacy policy, terms, unsubscribe documentation, publishing runbook, sender ownership, and next review date.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Rey Katz explains a completed move from Substack to Ghost that was more difficult and more expensive than expected. Ghost restored a stronger sense of owning the site and mailing list, but it introduced hosting cost and lacked Substack’s built-in social and recommendation network. The migration included decisions around Ghost Pro versus self-hosting, DNS, design, and email deliverability rather than being only a content import.
- Compare managed Ghost Pro with self-hosting using total operational and email-delivery cost, not hosting price alone.
- Plan DNS, branding, sender authentication, and deliverability as first-class workstreams alongside posts and subscribers.
- Greater ownership came with direct platform cost and the loss of Substack’s discovery and social features.
- The technical move reached beyond content and subscribers into identity, design, DNS, and mail operations.
Built to be reviewed.
Tested 2026-07-18. Next scheduled review: 2026-10-18.
- Ghost: Migrating from SubstackAccessed 2026-07-18
- Ghost: Import membersAccessed 2026-07-18
- Ghost: Connect StripeAccessed 2026-07-18
- Ghost: Custom sending domainsAccessed 2026-07-18
- Ghost: Custom publication domainsAccessed 2026-07-18