Khalid / Migrate / Medium

Move from Medium to Khalid: your posts on your own domain, with a map of every old address.

Download your Medium archive, try the import with a dry run, then bring the posts in. Khalid keeps each readable slug and writes down where every old Medium address now leads.

Free and open source. Every command here was run with Khalid 1.3.2.

The whole move
khalid init blog --address https://www.example.com --mount /blog/ -y
khalid --site blog import medium medium-export --dry-run
khalid --site blog import medium medium-export
khalid --site blog lint
Four commands, run from the folder that holds your export. The dry run writes nothing.
01Medium

Export. Take your blog out of Medium.

Medium gives you an archive of your account. It holds one HTML file per post in its posts folder. Khalid reads that folder.

  1. Ask Medium for your archive

    In Medium, open Settings, then Security and apps, and choose Download your information. Medium sends a zip file.

  2. Unpack the zip

    You get a folder with a posts folder inside. Name the folder medium-export.

  3. Put it beside your new blog

    Keep it in the folder where you run the khalid commands. Khalid reads the posts folder inside it.

The menu names are the ones Khalid's importer describes. Medium's help pages did not load for us on 4 Nov 2026, so check them in your own account.

02Terminal

Dry run. See what would come in. Nothing is written.

First make the new blog with khalid init. Use your own address, and the folder the blog will live in, such as /blog/. Put your export file beside the new blog folder.

The dry run lists what would come in and what would stay behind. It also counts the old addresses on medium.com, which only Medium can answer.

Make the blog
khalid init blog --address https://www.example.com --mount /blog/ -y

Made a Khalid site in blog for https://www.example.com/. It holds site.toml, routes.toml, theme/tokens.toml, theme/templates/, content/en/posts/ and drafts/en/.

Dry run
khalid --site blog import medium medium-export --dry-run
What it printed
This is a dry run: nothing was written.
Coming in: 1 post, 0 drafts and 1 author.
Slugs kept: 1. Slugs made for drafts that had none: 0.
Old addresses mapped: 2, of which 2 move and 0 stay the same.
Old addresses on medium.com: 2. Medium cannot redirect them, so link each post there to its new home, or unpublish it.
Content with no place in Khalid, left out: 0.
Posts that could not come in: 0.
Files that are not Medium posts: 0.
What Khalid 1.3.2 printed for the sample export in its source code, run on 4 Nov 2026.
03Terminal

Import. Run it again without --dry-run.

Published posts keep their dates and go live at the next build once they pass the linter. Drafts go to the drafts folder. A post's subtitle becomes its description.

Each post's slug is the readable part of its Medium address, without the id at the end.

Import
khalid --site blog import medium medium-export
What it printed
Wrote 3 files and stored 0 redirects.
1 imported post is blocked by the linter, so readers do not see it yet: content/en/posts/2024-03-12-how-to-bake-rye-bread.md.
Coming in: 1 post, 0 drafts and 1 author.
Slugs kept: 1. Slugs made for drafts that had none: 0.
Old addresses mapped: 2, of which 2 move and 0 stay the same.
Old addresses on medium.com: 2. Medium cannot redirect them, so link each post there to its new home, or unpublish it.
Content with no place in Khalid, left out: 0.
Posts that could not come in: 0.
Files that are not Medium posts: 0.
Run `khalid lint` to see how each post scores.
What Khalid 1.3.2 printed for the sample export in its source code, run on 4 Nov 2026.
04Redirects

Addresses. Every old address gets an entry in the map.

Medium does not send readers on to a new home. So Khalid cannot store redirects for addresses on medium.com, and the sample stored none.

It still writes every old address and its new home to blog/import/medium-redirects.csv, including Medium's short /p/ address for each post. Use the list to add a link to the new post at the top of each Medium post, or unpublish them.

If your Medium publication used your own domain and Khalid now runs on that domain, those old addresses are yours. Khalid answers the ones inside the blog's folder, and your main site's server can redirect the rest from the same file.

The redirect map
from,to,status,match
https://medium.com/@mara/how-to-bake-rye-bread-3f1a8c4e2b9d,https://www.example.com/blog/how-to-bake-rye-bread/,301,exact
https://medium.com/p/3f1a8c4e2b9d,https://www.example.com/blog/how-to-bake-rye-bread/,301,exact
The file Khalid 1.3.2 wrote for the sample export, 4 Nov 2026.

Images.

The importer does not download images. Each image keeps the address it had on Medium's own image servers, so it loads from Medium for as long as Medium keeps it there.

To host an image yourself, save it into the blog and change its address in the post.

The honest limits.

Old medium.com addresses cannot redirect. Search results that point there stay on Medium until you link or unpublish each post.

The import brings posts, not followers. Your Medium followers and claps stay on Medium.

05After the move

Check. Lint, build, audit, then Search Console.

The linter scores each post out of 100 and names anything that blocks it. A blocked post stays out of the blog until you fix it, and its old addresses wait with it. So no old link lands on a page readers cannot see.

The build lists every post it left out and why. The audit crawls the built blog the way SEO crawlers do and scores its health. --offline skips the links to other sites.

Search Console. Once the blog is live, add its sitemap in Google Search Console. It is the blog's address plus sitemap.xml, such as https://www.example.com/blog/sitemap.xml. Khalid can also read your Search Console numbers with your own Google account. Then search-console index asks Google whether each post is in its index. The deploy guide has the steps.

Checks after the move
khalid --site blog lint
khalid --site blog build
khalid --site blog audit --offline
khalid --site blog search-console check
What it printed
1 block and 8 warnings.

Left out 1 post, because it is not ready to publish. The blocks are listed above.
Fix each block and build again, or move a post to drafts/<language>/ to keep it a draft.

Audited 8 pages: the ones readers and search engines reach through links and the sitemap.
Health score: 100 out of 100, the share of pages with no error. Warnings and notices do not lower it.

This site is not connected to Google Search Console, Google's report of how a site does in search.
Khalid asks Google nothing until the owner opts in under [search_console] in site.toml.
Key lines from each answer, for the WordPress sample, run on 4 Nov 2026. Lint blocked the one post because it links to no other post.

Questions about moving from Medium.

Can't find the answer? Email us.

Can I redirect my old Medium posts?

Not from medium.com, because Medium does not send readers on to a new home. Khalid lists every old Medium address with its new one, so you can link each Medium post to its new home or unpublish it.

What if my Medium publication used my own domain?

Then those addresses are yours. Once Khalid runs on that domain, it answers the old addresses inside the blog's folder, and your main site's server can redirect the rest from the file the import writes.

What happens to my Medium images?

The importer does not download them. Each image keeps its address on Medium's image servers. To host one yourself, save it into the blog and change its address in the post.

Do my Medium drafts come over?

Yes. Drafts go to the blog's drafts folder. Published posts keep their dates and go live once they pass the linter.

Bring the posts. Keep the addresses.
Move your blog to Khalid.

Free and open source. Start with the dry run: it writes nothing.