From Substack to Squarespace: A Developer's Guide to a Seamless Migration

How I Actually Pulled This Off

There is no button that says “Move my Substack to Squarespace.”

If you’ve ever tried, you already know that. You get a CSV. You get a pile of HTML files. And Squarespace politely tells you it only wants WordPress XML. That mismatch is where most migrations die.

I just finished moving 734 posts from Substack into Squarespace. This is how I did it, working alongside Manus AI, without losing dates, titles, or content, and without re-publishing everything by hand.

The real problem

Substack gives you two things when you export:

  • A posts.csv file with metadata like post IDs, titles, subtitles, dates, and publish status

  • A folder full of HTML files, one per post

Squarespace, meanwhile, only really understands WordPress. Its importer expects a WordPress eXtended RSS file (WXR), which is a very specific XML format that WordPress uses to move sites around.

So you have data in two formats that do not speak to each other at all.

CSV and HTML in one corner
WXR XML in the other

Nothing connects them.

How Manus AI and I built the bridge

Instead of trying to hack Squarespace or manually rebuild posts, I sat down with Manus AI and treated this like an integration problem.

Substack already gave me everything I needed. Squarespace already knew how to import WordPress. The only missing piece was a translator.

So Manus AI and I created a small Python script that takes a Substack export and turns it into something Squarespace thinks is a WordPress site.

All it does is:

  1. Read posts.csv into a lookup table keyed by post_id

  2. Walk through the HTML files and grab the post ID from each filename

  3. Match each HTML file to its metadata

  4. Wrap that data in the XML structure WordPress expects

  5. Output one giant WXR file that Squarespace can import

No APIs. No plugins. No scraping. Just file transforms.

That is the entire trick.

What the WXR file looks like

WordPress export files are just XML with a specific schema. You need a header that defines the namespaces, a channel block for the site, and then one <item> per post.

At the top it looks something like this:

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
    xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:wp="http://wordpress.org/export/1.2/"
>
<channel>
    <title>Your Blog</title>
    <link>https://yourdomain.com</link>
    <description>Your site description</description>
    <wp:wxr_version>1.2</wp:wxr_version>
</channel>
</rss>

Squarespace does not care what platform actually generated this. It just checks that the XML matches what WordPress would have exported.

So we made it look like WordPress did.

How each Substack post becomes a WordPress post

Every Substack post becomes one <item> block in the XML. We map:

  • Substack title to <title>

  • Substack date to <wp:post_date>

  • HTML file contents to <content:encoded>

  • Substack publish status to <wp:status>

The content goes inside a CDATA block so the HTML does not get parsed as XML:

item_xml = f"""
<item>
    <title>{escape_xml(title)}</title>
    <link>{post_link}</link>
    <pubDate>{pub_date_rfc}</pubDate>
    <dc:creator><![CDATA[Author Name]]></dc:creator>
    <content:encoded><![CDATA[{html_content}]]></content:encoded>
    <wp:post_id>{post_id}</wp:post_id>
    <wp:status><![CDATA[{status}]]></wp:status>
    <wp:post_type><![CDATA[post]]></wp:post_type>
</item>
"""

Manus AI also helped clean the Substack HTML, stripping out scripts and styles that do not belong in a Squarespace post.

Nothing fancy. Just clean inputs and correct structure.

The moment of truth

When the script finished running, I had one file:

squarespace_import_wxr.xml

It contained all 734 posts with their original titles, dates, and content intact.

In Squarespace:

Settings → Advanced → Import/Export
Choose WordPress
Upload the file

That was it.

Squarespace ingested it like it came from a WordPress blog. Every post landed where it should, backdated correctly, and ready to be styled.

No copy-paste hell. No SEO nuking. No losing a decade of writing.

Why this matters

Platforms are sticky by design. They make it easy to publish and hard to leave. Substack is great at what it does, but if you want to own your site, your SEO, and your long-term archive, you need a way out.

This was not a Squarespace problem or a Substack problem. It was a format problem.

Once you understand that WordPress XML is the lingua franca of content migration, everything else becomes an integration exercise. Manus AI just made that exercise dramatically faster.

And that is really the story here. Not the script. Not the XML. Just the fact that with the right AI at your side, you can move your work wherever you want it to live.

Kenny Kane

Kenny Kane is an entrepreneur, writer, and nonprofit innovator with 15+ years of experience leading organizations at the intersection of business, technology, and social impact. He is the CEO of Firmspace, CEO of the Testicular Cancer Foundation, and CTO/co-founder of Gryt Health.

A co-founder of Stupid Cancer, Kenny has built national awareness campaigns and scaled teams across nonprofits, health tech, and real estate. As an author, he writes about leadership, resilience, and building mission-driven organizations.

Next
Next

Fixing the Missing Substack Logo in Squarespace