← All playbooks
Newsletter migration

Ghost → Substack

Move Ghost posts, members, and Stripe-paid subscriptions onto Substack without sending the list twice or leaving images and redirects on the old host.

Typical timeline5–10 calendar days before first full send10–18 hours active work
Statusneeds review
Source profileGhost documentation reviewed 2026-10-28
Destination profileSubstack documentation reviewed 2026-10-28
Documentation date
Review window
Evidence4 sources
This route needs review.Use it as a planning baseline, then verify the dated sources and test with representative data before production.
Before you migrate

Should you make this move?

Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.

Current platform

Ghost

Reasons to stay
  • A focused publishing and membership platform is fast and pleasant for writers
  • Editors get a purpose-built publishing workflow instead of raw files
Reasons to leave
  • Its narrower ecosystem is less suitable for complex sites and custom application behavior
  • Themes, extensions, and content models add maintenance and portability constraints
New platform

Substack

What gets better
  • Publishing, email, recommendations, network discovery, and paid subscriptions work out of the box
  • Audience, campaigns, automations, and reporting live in one publishing system
What gets worse
  • Brand control, automation, data depth, and platform independence are limited
  • Automation logic, analytics history, and deliverability settings are not very portable
Best of the move

Substack: Publishing, email, recommendations, network discovery, and paid subscriptions work out of the box. This removes a major source-side concern: Its narrower ecosystem is less suitable for complex sites and custom application behavior.

Worst of the move

What you lose: A focused publishing and membership platform is fast and pleasant for writers. What you inherit: Brand control, automation, data depth, and platform independence are limited.

Jump to a section
01At a glance

Know the shape of the move.

Transfer outcome11 features audited
Transfer outcome distributionClean transfer: 0, Partial transfer: 6, Manual rebuild: 3, Not transferred: 2.
Clean0
Partial6
Manual3
Lost2
Mapping route8 of 9 fields have a destination path

This timeline assumes

  • One publication with at most 2,000 posts, 25,000 members, one custom domain, and optional paid subscriptions in Stripe
  • You control the Ghost owner account, Substack owner account, DNS, Stripe account, and the publication support email.
  • Paid members use the same Stripe account on Ghost and Substack.
  • No full-audience Substack email goes out until content, member, payment, domain, redirect, and seed-delivery checks pass.
  • Comments, Portal design, themes, recommendations, and historical Ghost analytics stay out of scope unless a separate export proves otherwise.
02What transfers

What survives the move.

“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.

ItemOutcomeImpactWhat happensMitigation
Published postspartialcriticalSubstack can import from your Ghost URL after you export the JSON archive. Imported posts often keep Ghost subscribe blocks at the bottom. URL import by itself can stop after a short first page of posts.Use the Ghost JSON plus the Substack importer. Clean leftover subscribe widgets. Compare public, paid, and image-heavy posts.
Post images and mediapartialcriticalGhost(Pro) can include media in the export zip. The Substack importer can still leave image src values on your Ghost CDN. If you retire Ghost too soon, those images die.Open imported posts and check the image hosts. Re-upload anything that still points at Ghost before you cancel hosting.
Free memberspartialcriticalGhost can export all members as CSV from Members or from Settings → Advanced → Import/Export. Substack imports that file as free subscribers.Record counts before export. Import members last, after sending is paused on Ghost.
Paid members and StripepartialcriticalSubstack connects to the same Ghost Stripe account, scans subscriptions for up to 30 minutes, then imports paid members. You must revoke Ghost's Stripe access first so people are not charged twice.Connect the same live Stripe account. Wait for the scan email. Revoke Ghost in Stripe Installed apps, then import paid subscriptions.
Email consent and newsletterspartialcriticalGhost member CSV includes labels and subscription status. Substack's email import does not recreate Ghost newsletter objects. Everyone lands as free until paid import runs.Reconcile subscribed and unsubscribed totals. Never import while Ghost is still sending the same list.
Custom-domain post URLsmanualcriticalGhost posts often live at /slug. Substack posts live at /p/slug. Substack has no in-app redirect table. You create redirects at the DNS or domain provider.Map every public URL. Add provider redirects before DNS cutover. Test long slugs the provider may reject.
Publication domain and email From addressmanualcriticalSubstack charges a one-time $50 domain fee, wants a subdomain such as www, and can take up to 36 hours to verify. Newsletters still send from [email protected].Add the domain in Substack before you change DNS. Turn Cloudflare proxy off. Keep the *.substack.com settings URL bookmarked.
Theme, Portal, and navigationlostmediumA Ghost Handlebars theme, Portal settings, and routes.yaml do not become a Substack website.Rebuild About, navigation, and signup surfaces in Substack before the domain move.
Comments, recommendations, and Ghost analyticslostmediumThe documented move covers members and posts. It does not import Ghost comments, recommendation network state, or post-analytics CSVs.Export post analytics from Ghost if you need them. Tell readers comments will not follow.
Pages, tags, and tierspartialhighGhost's content export includes posts, pages, tags, tiers, and settings. Substack imports posts. Pages and tags need a manual home.Recreate About and landing pages. Map paid tiers to Substack plans before paid import.
Sending overlapmanualcriticalIf you import members while Ghost still sends, people get two newsletters. That is the usual way a Ghost-to-Substack move burns the list.Pause Ghost campaigns first. Import members last. Keep one named send owner.
03Field and feature mapping

Where each thing goes.

SourceDestinationMethodNotes
Ghost post titleSubstack post titleautomaticCompare punctuation and the email subject if you resend.
Ghost post bodySubstack post bodytransformRemove leftover Ghost subscribe blocks and check embeds.
Ghost post slugSubstack /p/slug plus a provider redirecttransformSubstack cannot store the redirect itself.
Ghost content JSONSubstack Import postsautomaticPaste the public Ghost URL. Do not rely on a 15-post URL scrape.
Member emailSubstack subscriber emailautomaticEmail is the reconciliation key. Status starts as free.
Member name, labels, and subscribed_to_emailsSubstack profile and segmentsmanualThere is no safe automatic newsletter map.
Stripe customer and Ghost paid memberSubstack paid subscribertransformRevoke Ghost's Stripe access before the paid import.
Ghost custom publication domainSubstack custom domainmanualPay the $50 fee, add CNAME to target.substack-custom-domains.com, then check status.
Ghost sending domainSubstack senderunsupportedMail still comes from [email protected].
04Before you begin

Make the move recoverable.

Backup procedure

Create the source-of-truth backup

Preserve an immutable publication, audience, billing, URL, DNS, and analytics snapshot before Substack imports or sends anything.

  1. Export Ghost content, members, post analytics, redirects, routes, themes, and media from Settings → Advanced → Import/Export.
  2. Export the current member list from the Members dashboard as a second CSV and record free, paid, subscribed, and unsubscribed counts.
  3. Export Stripe customer and active-subscription reports. Record the account ID and Ghost's installed Stripe app.
  4. Crawl public post and page URLs, including high-traffic landing pages.
  5. Store untouched files with checksums. Do mapping on copies.

Proof to capture: One dated manifest with file checksums, post and member segment counts, Stripe account ID, public URL count, and a named restore owner.

Transformation · Ghost JSON export plus Substack Import posts

Import posts from Ghost

Bring the archive into Substack without a short URL-only scrape.

  1. Keep the Ghost site online so the importer can fetch the archive.
  2. Enter the public Ghost URL and import. Mark paid posts if payments are already on.
  3. Delete leftover Ghost subscribe options from imported posts.
  4. Do not send the imported archive to the full list.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

Transformation · Ghost member CSV, Substack Import emails, and Stripe Installed apps

Import members and paid Stripe state

Move the list once, with paid access, and without a second charge.

  1. Pause Ghost newsletters and automations first.
  2. Import the member CSV as free subscribers.
  3. Connect the same Stripe account, wait for the scan email, revoke Ghost access, then import paid subscriptions.
  4. Reconcile free, paid, and unsubscribed counts before any Substack send.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

Transformation · Domain-provider redirects plus Substack Domain settings

Preserve URLs and the custom domain

Keep Ghost links working after posts move to /p/slug.

  1. Map every public Ghost URL to the Substack /p/ path.
  2. Add the custom domain in Substack and pay the $50 fee before you change DNS.
  3. Create the CNAME to target.substack-custom-domains.com. Add the root A record only if you enable root redirects.
  4. Turn Cloudflare proxy off and wait up to 36 hours.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

05Handle with care

The things most likely to hurt.

These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.

Members

Double-sending the list

criticalpossible likelihood

Ghost and Substack both have the same emails while Ghost still sends.

Consequence
Complaints and unsubscribes spike.
Mitigation
Pause Ghost first. Import members last. Assign one send owner.

Stop if: Any unowned campaign remains active in either platform.

Payments

Double-charging paid members

criticalpossible likelihood

Substack tells you to revoke Ghost's Stripe access before paid import.

Consequence
Subscribers are billed twice for the same period.
Mitigation
Revoke Ghost in Stripe Installed apps, then tick the Substack confirmation box.

Stop if: Ghost still has Stripe access when paid import starts.

Content

Images keep living on Ghost

criticalpossible likelihood

Imported posts can still request images from your Ghost CDN.

Consequence
The archive breaks the day you cancel Ghost.
Mitigation
Inspect image hosts on a sample of posts and re-upload stragglers.

Stop if: Any high-traffic post still loads media from Ghost.

Routing

Historic URLs 404 after DNS

criticallikely likelihood

Substack has no redirect UI, and some providers reject long paths.

Consequence
Search and shared links miss the new posts.
Mitigation
Build the URL map first and test every high-value path.

Stop if: Any high-value Ghost URL fails after one redirect correction.

Domain

Custom domain leaves you locked out

highpossible likelihood

A bad DNS change can make the custom host fail while verification takes up to 36 hours.

Consequence
Editors cannot find the dashboard.
Mitigation
Bookmark yourpublication.substack.com/publish/settings before you touch DNS.

Stop if: You cannot reach settings on the substack.com host.

06Precise timeline

Do the work in this order.

Estimate forUp to 2,000 posts, 25,000 members, one custom domain, optional Stripe paid members
Total elapsed5–10 calendar days before first full send
Active work10–18 hours
BufferReserve 36 hours for domain verification and extra time if images must be re-uploaded by hand.
01
Day 1Inventory publication, audience, billing, and URLs1 day
02
Day 2Configure Substack, Stripe, and domain1–2 days
03
Day 3Import posts, then members1 day
04
Day 4Verify content, access, redirects, and seed delivery1 day
05
Day 5Switch the publication domain2–36 hours
06
Days 6–10Send deliberately and monitor2–5 days
  1. Day 1 · inventory-backup

    Inventory publication, audience, billing, and URLs

    2–3 hours active1 day elapsedGhost export generation, up to 7 days for a Ghost(Pro) download link waiting
    • Export content, members, analytics, redirects, and media.
    • Record Stripe account ID, scheduled sends, and public URLs.
    • Choose the first Substack send date.
    Stop / go checkpoint

    Is the source state complete?

    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.

  2. Day 2 · substack-setup

    Configure Substack, Stripe, and domain

    2–4 hours active1–2 days elapsedUp to 36 hours for custom-domain verification waiting
    • Create the publication, About page, navigation, and plans.
    • Connect the same live Stripe account and start the paid-subscription scan.
    • Add the custom domain in Substack before you change public DNS.

    Depends on: inventory-backup

    Stop / go checkpoint

    Is Substack safe to receive members?

    Go when: The correct Stripe account is connected, domain setup is started, and no production campaign is enabled.

    Stop when: Stripe, DNS, plan access, or publication ownership is uncertain.

  3. Day 3 · import

    Import posts, then members

    2–4 hours active1 day elapsedPost import and the Stripe scan email waiting
    • Import posts from the Ghost URL and clean subscribe leftovers.
    • Pause Ghost sending, then import the member CSV.
    • Revoke Ghost Stripe access and import paid subscriptions.

    Depends on: substack-setup

    Stop / go checkpoint

    Did the import preserve the recorded source state?

    Go when: All counts match or each variance has an evidence-backed explanation.

    Stop when: Paid access, consent, duplicates, content totals, or image hosts cannot be reconciled.

  4. Day 4 · preview-verify

    Verify content, access, redirects, and seed delivery

    3–4 hours active1 day elapsedAt least one hour after DNS changes waiting
    • Check representative public, paid, and image-heavy posts.
    • Test provider redirects from Ghost slugs to /p/ slugs.
    • Send only to a seed list and confirm unsubscribe and links.

    Depends on: import

    Stop / go checkpoint

    Can Substack 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, or broken images.

  5. Day 5 · cutover

    Switch the publication domain

    1–2 hours active2–36 hours elapsedSubstack domain verification waiting
    • Confirm Ghost sending is still paused.
    • Point the custom domain at Substack and keep the previous records.
    • Run production checks and keep the full audience unscheduled.

    Depends on: preview-verify

    Stop / go checkpoint

    Keep Substack public or return DNS to Ghost?

    Go when: Public URLs, SSL, member access, paid access, redirects, and seed delivery pass.

    Stop when: A blocking production check fails or a source delta is unexplained.

  6. Days 6–10 · first-send-monitor

    Send deliberately and monitor

    1 hour plus 20 minutes daily active2–5 days elapsed24 hours after the first full send waiting
    • Review the final audience and confirm Ghost has no active campaign.
    • Send the first Substack newsletter with a named operator.
    • Monitor bounces, complaints, unsubscribes, and paid access.

    Depends on: cutover

    Stop / go checkpoint

    Can source sending be retired?

    Go when: The first full send completes without a rollback trigger.

    Stop when: Duplicate sends, broken images, or incorrect paid access appear.

07The point of change

Cut over with a way back.

Go live

Cutover

Make Substack the public and sending system without losing the last Ghost changes or sending the audience twice.

Recommended window: Cut over 48–72 hours before the next planned newsletter, during support hours, with the Substack owner, DNS owner, and Stripe owner available.

  1. Pause Ghost publishing, newsletters, and automations. Record the freeze time.
  2. Import the final post and member delta and reconcile counts again.
  3. Revoke Ghost Stripe access if paid import has not already done so.
  4. Change publication-domain DNS to the records Substack shows and keep the previous values.
  5. Run blocking production checks for content, images, redirects, member access, paid access, and seed delivery.
  6. Keep the full Substack audience unscheduled until the public-domain report passes.

Proof to capture: Freeze timestamp, final post and member reconciliation, Stripe account ID, before/after DNS records, redirect results, and named approval.

Return to safety

Rollback

Return the public domain and next send to Ghost without creating duplicate campaigns or corrupting paid access.

Deadline: The cleanest rollback point is before the first full-audience Substack send. Keep Ghost available for at least seven days after that send.

  1. Declare rollback, cancel any Substack campaign, and preserve import logs.
  2. Restore the recorded publication-domain DNS values for Ghost and confirm SSL.
  3. Keep Substack member and Stripe data unchanged for investigation. Do not reconnect Ghost to Stripe during an active incident unless you also pause Substack billing.
  4. Reconcile any post, signup, unsubscribe, or payment changes made after freeze.
  5. Confirm exactly one platform owns the next send.

Proof to capture: The custom domain serves Ghost, critical posts and signup paths work, no duplicate campaign is scheduled, and paid subscriptions remain active in the same Stripe account.

Rollback immediately when
  • 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 fail after one redirect correction.
  • Imported images still depend on Ghost for high-traffic posts.
  • A production-blocking problem cannot be corrected and fully reverified within two hours.
08Verification report

Prove the migration worked.

Every blocking check must pass. Capture the evidence before cleanup begins.

0%
Interactive report preview0 / 8 checks passed
PassIDCheckMethodExpected resultEvidence
CNT-01BlockingPost reconciliationCompare Ghost export posts with Substack posts by slug, date, title, and visibility.Every expected post exists once or has an approved exception.Attach the post reconciliation CSV and exception list.
VIS-01BlockingImages and leftover Ghost chromeInspect image hosts and subscribe leftovers on public, paid, and image-heavy samples.Images resolve from a host you control after Ghost is gone. Subscribe leftovers are removed.Attach paired source/destination captures and image URLs.
MEM-01BlockingAudience segment reconciliationCompare total, free, paid, subscribed, and unsubscribed counts.Counts match or every variance is an explained invalid, duplicate, or omitted record.Attach the segment table and variance notes.
PAY-01BlockingStripe account and paid accessConfirm the live Stripe account ID and test active monthly, annual, and complimentary members.The account ID matches Ghost's account. Ghost access is revoked. Each test member has the correct Substack access without a new charge.Attach redacted Stripe and Substack captures.
CON-01BlockingEmail preference preservationSample subscribed and unsubscribed Ghost records and compare their Substack state.No opted-out member is subscribed and no unexplained global opt-in occurred.Attach the sampled member IDs and aggregate counts.
URL-01BlockingHistoric Ghost URLsRequest all high-value source URLs and a random historic sample.Historic posts use one 301 to the matching /p/ path.Attach status, redirect chain, and final URL output.
DNS-01BlockingPublication domain and SSLResolve the custom domain from multiple public resolvers and request critical paths over HTTPS.The canonical hostname serves Substack with a valid certificate. Settings still open on *.substack.com.Attach DNS answers, certificate details, and HTTP results.
EML-01BlockingSeed delivery and single senderSend to controlled addresses and inspect from, links, unsubscribe, and both platforms' schedules.Every seed message works. Only one named Substack campaign is eligible to send. Ghost has no active campaign.Attach redacted headers, inbox captures, and schedule screenshots.
09Post-migration cleanup

Remove the scaffolding safely.

Safe after: One successful full-audience Substack send, 24 hours of acceptable delivery metrics, seven stable public-domain days, and a signed verification report.

  1. Archive the final Ghost export zip, member CSVs, Stripe reports, URL inventory, DNS records, and verification evidence.
  2. Cancel remaining Ghost scheduled sends and automations.
  3. Re-upload any leftover Ghost-hosted images, then cancel Ghost hosting only after those URLs are gone.
  4. Revoke migration-only users and tokens.
  5. Keep Ghost readable for the agreed retention period before canceling.
  6. Update support replies, privacy policy, unsubscribe documentation, and the next review date.
10Experiences from the field

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.

First-person accountSara Taher · The SEO Riddler

My Latest CMS Migration Examined!2026

Sara Taher moved The SEO Riddler from Ghost to Substack to stop paying $55 a month. Subscriber CSV import was easy and too early. Ghost and Substack both sent mail, which produced complaints and unsubscribes. Substack's import-from-URL path only pulled the first 15 of about 100 posts, so she used Ghost's export and then pasted images by hand when they did not come across. Substack has no redirect UI. Ghost slugs became /p/slug. Some long paths could not be entered at the domain provider. DNS cutover left the site unreachable for a few hours. Search Console kept data after a DNS verification.

What they recommend
  • Import members only after Ghost sending is paused, or the same people get two newsletters.
  • Do not trust Substack's URL importer for a large archive, and check image hosts before you cancel Ghost.
Worth noticing
  • Redirects have to live at the DNS or domain provider, and long Ghost slugs can be rejected there.
  • The expensive mistake was overlapping sends, not the content copy.
Sources and evidence

Verify against the primary material.

Platform behavior changes. Check these sources and the review dates above before executing a production migration.

  1. Substack: move from Ghost to SubstackAccessed 2026-10-28
  2. Ghost: export content and dataAccessed 2026-10-28
  3. Substack: set up a custom domainAccessed 2026-10-28
  4. Ghost: member management and exportAccessed 2026-10-28