-
Notifications
You must be signed in to change notification settings - Fork 3
Add Substack migration support #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
joeboydston
wants to merge
1
commit into
Automattic:main
Choose a base branch
from
joeboydston:improvement/substack-migration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ This file is the entry point for any AI agent using this repository. Read it bef | |
| 3. **User prompts** — non-technical users paste these into their AI to drive the whole migration | ||
| 4. **A living playbook** — this repo improves itself through AI-contributed discoveries | ||
|
|
||
| Currently supports: **Wix**. Squarespace, Webflow, and Shopify are planned. | ||
| Currently supports: **Wix** and **Substack**. Squarespace, Webflow, and Shopify are planned. | ||
|
|
||
| ## If you're helping a user migrate from Wix | ||
|
|
||
|
|
@@ -96,6 +96,57 @@ This approach works for any JavaScript-heavy platform, not just Wix. | |
|
|
||
| --- | ||
|
|
||
| ## If you're helping a user migrate from Substack | ||
|
|
||
| ### Step 1 — Understand the publication | ||
|
|
||
| ```bash | ||
| node scripts/substack/discover.js <substack-url> | ||
| ``` | ||
|
|
||
| This queries Substack's public API to list all posts, categorize them (free/paid/podcast), and writes `output/inventory.json`. No browser needed. | ||
|
|
||
| ### Step 2 — Extract all content | ||
|
|
||
| ```bash | ||
| node scripts/substack/extract.js <substack-url> | ||
| ``` | ||
|
|
||
| This fetches each post via the API. For **paid posts**, the API only returns the free preview. To get full paid content: | ||
|
|
||
| 1. Ask the user to export from Substack: Settings > Exports > Create new export | ||
| 2. They'll get a ZIP with a CSV file | ||
| 3. Unzip it and pass the CSV: | ||
|
|
||
| ```bash | ||
| node scripts/substack/extract.js <substack-url> --csv-export posts.csv | ||
| ``` | ||
|
|
||
| The extractor merges API metadata (rich) with CSV content (complete), giving you the best of both. | ||
|
|
||
| **If scripts aren't available**, you can extract directly: | ||
| 1. Fetch the archive: `<substack-url>/api/v1/archive?sort=new&limit=50&offset=0` | ||
| 2. For each post: `<substack-url>/api/v1/posts/<slug>` — returns full JSON with `body_html` | ||
| 3. Download images by unwrapping CDN URLs: `substackcdn.com/image/fetch/.../https://substack-post-media.s3.amazonaws.com/...` — the real URL is embedded after the transform params | ||
|
|
||
| ### Step 3 — Import to WordPress.com | ||
|
|
||
| Same as Wix — the import script is platform-agnostic: | ||
|
|
||
| ```bash | ||
| node scripts/import.js --site <wordpress-site> --token <app-password> | ||
| ``` | ||
|
|
||
| ### Step 4 — Handle Substack-specific concerns | ||
|
|
||
| After import, help the user with: | ||
| - **Subscribers**: Export from Substack (Settings > Exports), import into their email service or WordPress.com newsletter | ||
| - **Paid subscriptions**: Complex — involves Stripe migration. Discuss options before acting. | ||
| - **Redirects**: If they have a custom domain, set up redirects from `/p/<slug>` to new WordPress paths. If on `*.substack.com`, they can't redirect — suggest a pinned farewell post. | ||
| - **Podcast hosting**: Audio files need to be re-hosted if they have podcast episodes. | ||
|
|
||
| --- | ||
|
|
||
| ## Platform-specific barriers | ||
|
|
||
| ### Wix | ||
|
|
@@ -109,6 +160,20 @@ This approach works for any JavaScript-heavy platform, not just Wix. | |
| | Anti-bot blocking | Use real browser via Playwright (not fetch/curl); add delays | | ||
| | Dynamic pages (CMS collections) | Query Wix's `/_api/wix-data-server/` endpoints directly | | ||
|
|
||
| ### Substack | ||
|
|
||
| | Problem | Solution | | ||
| |---|---| | ||
| | Paid content truncated in API | Use Substack's CSV export (`--csv-export`) for full body HTML | | ||
| | No official API | Undocumented `/api/v1/posts` and `/api/v1/archive` endpoints are public | | ||
| | Images behind CDN wrapper | Unwrap `substackcdn.com/image/fetch/.../` URLs to get original S3 paths | | ||
| | URL structure (`/p/slug`) | Generate redirect map; set WordPress permalinks to match if possible | | ||
| | Subtitles not in WordPress | Add as styled first paragraph or use a subtitle plugin | | ||
| | Subscriber migration | Separate CSV export; requires email service setup | | ||
| | Paid subscriber migration | Stripe account transfer needed; no automated path | | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have APIs for doing both paid and free subscriber imports to WP.com and Jetpack sites, including the Stripe account switch, so those should first be exposed via MCP and then connected here. Cc @Automattic/loop team who's looking after subscriber importer. |
||
| | Comments not exportable | Not included in any export; consider a scraping approach if critical | | ||
| | Notes/threads | Newer feature; may not export cleanly via API | | ||
|
|
||
| --- | ||
|
|
||
| ## How to contribute improvements back | ||
|
|
@@ -149,11 +214,15 @@ data-liberation-agent/ | |
| ├── DISCOVERIES.md ← log of community-contributed findings | ||
| ├── package.json | ||
| ├── prompts/ | ||
| │ └── wix.md ← what users paste into their AI for a Wix migration | ||
| │ ├── wix.md ← what users paste into their AI for a Wix migration | ||
| │ └── substack.md ← what users paste into their AI for a Substack migration | ||
| ├── scripts/ | ||
| │ ├── wix/ | ||
| │ │ ├── discover.js ← inventory the Wix site (sitemap + categorization) | ||
| │ │ └── extract.js ← extract all content via network interception | ||
| │ ├── substack/ | ||
| │ │ ├── discover.js ← inventory the Substack publication via API | ||
| │ │ └── extract.js ← extract content via API + CSV export | ||
| │ └── import.js ← publish to WordPress.com via REST API (platform-agnostic) | ||
| ├── examples/ | ||
| │ ├── wix-api-blog-post.json ← example of Wix internal API response | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # Substack to WordPress.com Migration Prompt | ||
|
|
||
| Copy everything below this line and paste it into your AI assistant (Claude, ChatGPT, Gemini, etc.). | ||
|
|
||
| --- | ||
|
|
||
| I want to migrate my publication from Substack to WordPress.com. My Substack URL is: **[PASTE YOUR SUBSTACK URL HERE]** | ||
|
|
||
| I have (or will create) a WordPress.com account. Please help me migrate using the playbook at https://github.com/Automattic/data-liberation-agent — read AGENTS.md first for full instructions. | ||
|
|
||
| Here's what I need you to do: | ||
|
|
||
| ## Step 1: Inventory my publication | ||
|
|
||
| - Use the Substack API to list all my posts: fetch `[MY SUBSTACK URL]/api/v1/archive?sort=new&limit=50&offset=0` (paginate through all results) | ||
| - Categorize each post: free, paid, podcast, thread, page | ||
| - Note my publication name, description, and about page | ||
| - Count total posts, paid vs. free breakdown, and any podcast episodes | ||
| - Show me the inventory and wait for my approval before proceeding | ||
|
|
||
| ## Step 2: Export and extract content | ||
|
|
||
| **For free content:** | ||
| - Fetch each post's full content via the API: `[MY SUBSTACK URL]/api/v1/posts/[SLUG]` | ||
| - This gives you the full HTML body, metadata, cover images, and dates | ||
|
|
||
| **For paid content (if I have any):** | ||
| - Ask me to go to Substack Settings > Exports > Create new export | ||
| - I'll download the ZIP and give you the CSV file | ||
| - Use the CSV's `body_html` column for paid post content — the API only gives the free preview | ||
|
|
||
| **For all content:** | ||
| - Download every image — Substack wraps images through `substackcdn.com/image/fetch/...`. Extract the original URL from inside the CDN wrapper to get full-resolution images | ||
| - Preserve for each post: title, subtitle, URL slug, publish date, categories/sections, cover image, audience (free/paid), word count | ||
|
|
||
| ## Step 3: Set up WordPress.com | ||
|
|
||
| I need to create/have a WordPress.com site. Help me: | ||
| - Recommend a theme that works well for newsletters/blogs | ||
| - Create categories based on any Substack sections I have | ||
| - Configure basic settings: site title, tagline, permalink structure matching `/p/[slug]` if possible (for easier redirects) | ||
|
|
||
| For connecting to WordPress.com, I can either: | ||
| - Enable MCP at wordpress.com/me/mcp and connect you directly | ||
| - Generate an Application Password at wordpress.com/me/security/application-passwords | ||
|
|
||
| Tell me which you need. | ||
|
|
||
| ## Step 4: Publish everything | ||
|
|
||
| In this order: | ||
| 1. Upload all images to the WordPress media library (needed first to get new URLs) | ||
| 2. Create all posts with correct dates, featured images, and content | ||
| 3. Rewrite all internal links and image `src` attributes from Substack URLs to new WordPress URLs | ||
| 4. Set up navigation and homepage | ||
|
|
||
| **Special handling needed:** | ||
| - **Subtitles**: Substack has a subtitle field — add it as the first line of the post in `<em>` tags, or use a subtitle plugin if available | ||
| - **Paid content**: If I want to gate content on WordPress, ask me which membership/paywall plugin to use (WooCommerce Memberships, Restrict Content Pro, etc.) and which posts should stay gated | ||
| - **Podcast episodes**: If I have podcast content, check if the audio files are downloadable from Substack and re-host them. Set up a podcasting plugin if needed. | ||
|
|
||
| ## Step 5: Handle subscribers | ||
|
|
||
| - Ask me to export my subscriber list from Substack (Settings > Exports) | ||
| - Help me choose an email service (Mailchimp, ConvertKit, ActiveCampaign, etc.) or the built-in WordPress.com newsletter feature | ||
| - Import the subscriber list | ||
|
|
||
| **Note about paid subscribers**: If I have paid subscribers through Substack, this is complex — I may need to: | ||
| - Set up a new payment system (Stripe on WordPress) | ||
| - Communicate with subscribers about the move | ||
| - Plan a transition period | ||
|
|
||
| Tell me what my options are before doing anything. | ||
|
|
||
| ## Step 6: Verify and redirect | ||
|
|
||
| When done: | ||
| - Give me a URL mapping table: old Substack URL → new WordPress URL (for setting up redirects) | ||
| - Check that no images still point to `substackcdn.com` | ||
| - If I use a custom domain on Substack, help me set up 301 redirects from `/p/[slug]` to the new WordPress paths | ||
| - If I'm on `[name].substack.com`, note that I can't set up redirects — but I can add a pinned post on Substack linking to my new site | ||
| - List anything that needs manual attention | ||
|
|
||
| Work methodically — do one step at a time, show me progress, and wait for my go-ahead before moving to the next step. If you hit something unexpected, tell me what you found rather than guessing. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some potential problems to solve, not necessarily in this PR: