Skip to content

How to Migrate Blogger to WordPress (Tested in 2026)

Updated November 2026 by Marius: rewritten after moving a Blogger test blog to WordPress 7.1 with three importers. The 2014 method no longer works. Originally published by Hardeep Asrani in 2014.

When I went to update this guide, I first tried the method it described: back up the blog in Blogger, then go to Tools → Import → Blogger in WordPress. It failed twice. Blogger’s “Back up content” button doesn’t give you a file anymore, it sends you to Google Takeout. And when I uploaded the Takeout file to the official Blogger Importer plugin, it said “All done. Have fun!” and imported nothing. Not one post.

WordPress Blogger Importer showing All done, Have fun after importing nothing
The official Blogger Importer on WordPress 7.1: “All done”, zero posts imported.

So I built a small test blog on Blogger (posts with labels, comments with a reply, uploaded images, a page, a draft, a post with a long title and one with a Romanian title) and moved it to a fresh WordPress 7.1 site with three importers. In this guide, I will show you step-by-step what worked, what each importer gets wrong, and how to redirect your old Blogger links so you keep your visitors and your Google rankings.

The short version

  • Export your blog with Google Takeout (Blogger sends you there now).
  • Up to 20 posts: use Blogger Importer Extended (free). It keeps your comments and your post URLs.
  • More than 20 posts: BtW Importer is free and has no limit, but it doesn’t import comments.
  • Don’t use the official Blogger Importer. It can’t read Takeout files.
  • Neither importer redirects label pages, so add the small snippet from step 7.
  • If your blog is on a blogspot.com address, install the redirect template from step 8 and don’t delete the Blogger blog. The redirects only work while it’s online.

Before you start

Here’s what we need to get started:

  • A WordPress site on your hosting, ideally with the domain you want to keep. Install WordPress first, and import into an empty site if you can (you’ll see why in step 3).
  • Admin access to the Blogger blog.
  • Your Blogger blog set to Public (Settings → Permissions → Reader access). Blogger Importer Extended reads the public blog, and the redirects in step 8 need it online.
  • A backup of the WordPress site if it already has content.

1. Export your blog with Google Takeout

In Blogger, go to Settings → Manage Blog → Back up content and click Download. Instead of a file, you’ll land on Google Takeout with Blogger already selected:

Google Takeout with Blogger selected for export
Blogger’s “Back up content” now opens Google Takeout.
  1. Click Next step.
  2. Keep “Send download link via email”, “Export once” and the .zip file type, then click Create export.
  3. Wait for the email “Your Google data is ready to download”. For my small test blog, it arrived in under a minute. Big blogs can take longer.
  4. Click the download link. Google asked me for the account password again before the download started.
  5. Unzip it. Your blog is the file Takeout/Blogger/Blogs/<your blog name>/feed.atom.

Two things to know. Takeout exports every blog on your Google account, there’s no option to pick one. And the zip also has an Albums folder with your uploaded images at their original size, which is a nice extra backup.

Keep the zip somewhere safe even if you use Blogger Importer Extended (which doesn’t need the file). It’s your full backup, drafts included.

2. Choose an importer

I tested three importers with the same blog. This is what ended up in WordPress:

Blogger Importer (official)Blogger Importer Extended (free)BtW Importer
Works with the Takeout fileNo (imports nothing)Doesn’t need it, reads the public blogYes
Limit–20 posts and pagesNone mentioned
Comments–Yes, replies stay threadedNo
Labels become–TagsCategories
Drafts–SkippedKept as drafts
Posts in Blogger’s trash–SkippedImported as published
Post URLs–Same as on BloggerNew, made from the full title
Images–Downloaded at the size shown in the post (320 px wide in my test)Downloaded at original size
301s from old post and page URLs–Yes, after you turn them onYes
Label and search URLs–Not redirectedNot redirected
Feed URL–RedirectedNot redirected

For a blog with up to 20 posts, I would use Blogger Importer Extended. It’s the only one that brought over my comments, and it kept the exact post URLs from Blogger, so the old links map one-to-one. The image size is its weak spot, but step 5 fixes that.

For a bigger blog, the free version of Blogger Importer Extended stops at 20 posts. You can buy its unlimited license (I didn’t test that one), or use BtW Importer, which imported everything with full-size images. The price is your comments: BtW skips them on purpose. If your old comments don’t matter much, BtW is the easier choice.

3. Import with Blogger Importer Extended (up to 20 posts)

  1. Install and activate Blogger Importer Extended.
  2. Go to Tools → Blogger Importer and choose 20 posts for free.
  3. Enter your Blog ID. It’s the number in the address bar when you’re in the Blogger dashboard (blogger.com/blog/posts/1234567890).
  4. Click Start import! and confirm.

It should look like this when it’s done:

Blogger Importer Extended success screen: imported 8 posts, 1 pages, 2 comments and 2 images
My test blog: 8 posts, 1 page, 2 comments and the images.

Next, go to Settings → Blogger Importer and turn on Redirect old Blogger links. With this on, my old URLs like /2026/11/how-we-moved-bakerys-weekly-bread.html (also with Blogger’s mobile ?m=1), /p/contact.html and /feeds/posts/default all 301-redirected to the new WordPress addresses.

I left Redirect 404s to homepage off. Sending every missing page to the homepage looks helpful, but Google treats those redirects as soft 404s, and visitors who land on your homepage instead of the post they wanted just leave.

A few things I noticed after the import:

  • Labels became tags, and all posts are in “Uncategorized”. Create your categories afterwards if you want them.
  • The author was created as a new WordPress user with a long number as the username. You can keep it (change the display name) or delete that user and give the posts to your own account.
  • Careful with the “reset” link on the settings page (“Need to reset this site’s content…”). It doesn’t only remove the imported posts: it empties the posts and comments tables of the whole site. On my test site, it also deleted a post I had written in WordPress, the sample page, the privacy policy page and the navigation menu. That’s why I import into an empty site.

4. Or import with BtW Importer (more than 20 posts)

  1. Install and activate BtW Importer and open it from the admin menu.
  2. Tick the box that you’ve read the notes, upload your feed.atom file and click Upload File.
  3. Click Extract Data. It shows how many posts and pages it found.
  4. Keep the recommended batch size and click Start. Don’t leave the page until it’s done.
BtW Importer import completed screen
BtW Importer: my test blog was imported in 4 seconds.

Then three checks, because of what I found in my test:

  • Look for posts you had deleted on Blogger. BtW imported a post that was in my Blogger trash as a published post. Go through your posts and trash any that shouldn’t be there.
  • Delete the uploaded export file. BtW leaves it in wp-content/uploads/btw-importer-temp/, and on my test site anyone could download it from there, even after I clicked “Done”. It contains your drafts. Delete that folder with your host’s file manager or FTP.
  • Labels became categories, and every post was assigned to my admin account. That’s fine for a one-person blog.

BtW also gives your posts new URLs from the full title, so /2026/10/post-with-jump-break.html became /post-with-a-jump-break/. Its own 301 redirects handled that correctly for the posts and the page I checked.

5. Get the full-size images back (Blogger Importer Extended only)

Blogger shows a smaller copy of each image in the post (320 px wide by default) and links it to a bigger one. Blogger Importer Extended downloads the small copy. My 800×500 photo arrived in WordPress as 320×200, and clicking it still opened the big version on Google’s servers. It also downloaded the same photo twice because I had used it in two posts.

The original is still on Google: Blogger serves it if you replace the size part of the image address with /s0/. So I wrote a WP-CLI script that downloads each original once, points the image and its link to the local copy and swaps the featured image too. It keeps the display size from Blogger, so your posts look the same.

Back up the database first (wp db export), save this as blogger-full-size-images.php and copy & paste the following code snippet:

<?php
/**
 * After a Blogger import: replace resized copies of Blogger images with the originals.
 *
 * Blogger posts show resized images (/s320/, /s640/ ...) and link them to a bigger copy on
 * blogger.googleusercontent.com. Importers download the resized one. This script downloads
 * each image's original (size segment /s0/) into the Media Library once, points the <img> and
 * its link at the local original, and swaps the featured image if it was the resized copy.
 *
 *   wp eval-file blogger-full-size-images.php            # dry run: lists what would change
 *   wp eval-file blogger-full-size-images.php apply      # does it
 *
 * Back up the database first (wp db export).
 */
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';

$apply   = isset( $args[0] ) && 'apply' === $args[0];
$google  = '#https?://(?:blogger\.googleusercontent\.com/img/|\d\.bp\.blogspot\.com/)[^"\'\s>]+#';
$size    = '#/(?:s\d+|w\d+-h\d+)(?:-[a-z0-9-]+)?/(?=[^/]+$)#';
$done    = array(); // original URL => attachment ID, so an image used in several posts is downloaded once.

$original = function ( $url ) use ( $size ) {
	return preg_replace( $size, '/s0/', html_entity_decode( $url ) );
};

$fetch = function ( $url, $post_id ) use ( &$done, $apply ) {
	if ( isset( $done[ $url ] ) ) {
		return $done[ $url ];
	}
	if ( ! $apply ) {
		return $done[ $url ] = 0;
	}
	$id = media_sideload_image( $url, $post_id, null, 'id' );
	if ( is_wp_error( $id ) ) {
		WP_CLI::warning( "$url: " . $id->get_error_message() );
		return $done[ $url ] = false;
	}
	return $done[ $url ] = $id;
};

$posts = get_posts( array( 'post_type' => array( 'post', 'page' ), 'post_status' => 'any', 'numberposts' => -1 ) );

foreach ( $posts as $post ) {
	$html    = $post->post_content;
	$changed = 0;

	// <a href="Blogger original"> ... <img src="anything"> ... </a>, then bare <img src="Blogger ..."> left over.
	$html = preg_replace_callback(
		'#<a\s[^>]*href="([^"]+)"[^>]*>\s*(<img\s[^>]*>)\s*</a>|(<img\s[^>]*src="([^"]+)"[^>]*>)#i',
		function ( $m ) use ( $google, $original, $fetch, $post, &$changed ) {
			$link = ! empty( $m[1] );
			$src  = $link ? $m[1] : $m[4];
			if ( ! preg_match( $google, $src ) ) {
				return $m[0];
			}
			$url = $original( $src );
			$id  = $fetch( $url, $post->ID );
			$changed++;
			WP_CLI::log( sprintf( '  post %d: %s', $post->ID, $url ) );
			if ( ! $id ) {
				return $m[0]; // dry run or download failed: leave as is
			}
			$local = wp_get_attachment_url( $id );
			if ( ! $link ) {
				return str_replace( $m[4], $local, $m[0] );
			}
			$img = preg_replace( '#\ssrc="[^"]+"#', ' src="' . esc_url( $local ) . '"', $m[2] );
			return str_replace( array( $m[1], $m[2] ), array( esc_url( $local ), $img ), $m[0] );
		},
		$html
	);

	if ( ! $changed ) {
		continue;
	}
	WP_CLI::log( sprintf( 'post %d "%s": %d image(s)', $post->ID, $post->post_title, $changed ) );
	if ( $apply && $html !== $post->post_content ) {
		wp_update_post( array( 'ID' => $post->ID, 'post_content' => $html ) );
		// Featured image: if it is a resized copy whose original we now have, use the original.
		$thumb = (int) get_post_thumbnail_id( $post->ID );
		if ( $thumb && preg_match( '#<img\s[^>]*src="([^"]+)"#', $html, $first ) ) {
			$new = attachment_url_to_postid( $first[1] );
			if ( $new && $new !== $thumb ) {
				set_post_thumbnail( $post->ID, $new );
			}
		}
	}
}
WP_CLI::success( ( $apply ? 'Downloaded ' : 'Would download ' ) . count( $done ) . ' original image(s).' );Code language: PHP (php)

Run it without arguments first. It only lists what it would change:

wp eval-file blogger-full-size-images.php
wp eval-file blogger-full-size-images.php applyCode language: Bash (bash)

On my test site, the second command downloaded two originals (800×500 and 480×320) and updated both posts and their featured images. The small copies stay in the Media Library, and you can delete them once you’ve checked your posts. This needs WP-CLI, so ask your host if you’re not sure you have it. BtW users can skip this step: it downloads the originals already.

Links between your own posts need a cleanup too. This step is optional, because the redirects send visitors to the right post anyway, but direct links save a redirect.

Blogger Importer Extended changes them to your new domain but keeps the Blogger format (https://www.example.com/2026/10/post-name.html). Since it also keeps the Blogger slug, this WP-CLI command turns them into normal WordPress links. Replace the domain with yours, and remove --dry-run when the count looks right:

wp search-replace '(href="https://www\.example\.com)/\d{4}/\d{2}/([^"/]+)\.html' '\1/\2/' wp_posts --include-columns=post_content --regex --regex-delimiter='#' --dry-runCode language: Bash (bash)

BtW Importer leaves them pointing at blogspot.com. Replace the blog address with your domain and its redirects do the rest:

wp search-replace 'https://yourblog.blogspot.com/' 'https://www.example.com/' wp_posts --include-columns=post_content --dry-runCode language: Bash (bash)

7. Redirect labels, search and the feed

Neither importer redirects Blogger’s label pages (/search/label/Recipes). With Blogger Importer Extended, WordPress shows an empty search results page for “label/Recipes” instead. The plugin tries, but its code looks for the labels in a taxonomy that doesn’t exist (tag instead of post_tag). Blogger’s search (/search?q=bread) ended in a 404. BtW doesn’t redirect the feed (/feeds/posts/default) either, which is the address your feed subscribers use.

This snippet redirects all three: labels go to the matching tag or category, searches go to the WordPress search and the feed goes to the WordPress feed. It also has the part that step 8 needs. Copy & paste the following code snippet:

<?php
/**
 * Plugin Name: Blogger redirects
 * Description: Finishes the redirects after a Blogger import. 301s old label URLs (/search/label/Name)
 *              to the matching tag or category, Blogger search (/search?q=word) to WordPress search,
 *              Blogger post feeds (/feeds/posts/default) to the WordPress feed,
 *              and requests sent by the Blogger redirect template (?from_blogger=<old URL>) to the
 *              same path on this site, where the importer's own redirects take over.
 */
add_action( 'template_redirect', function () {
	// 1. Sent here by the Blogger template: keep the old path and query, drop the blogspot host.
	if ( isset( $_GET['from_blogger'] ) ) {
		$old   = wp_parse_url( wp_unslash( $_GET['from_blogger'] ) );
		$path  = isset( $old['path'] ) ? $old['path'] : '/';
		$query = isset( $old['query'] ) ? '?' . $old['query'] : '';
		wp_safe_redirect( home_url( $path . $query ), 301 );
		exit;
	}

	$path = wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );

	// 2. Blogger search: /search?q=word
	if ( '/search' === untrailingslashit( $path ) && isset( $_GET['q'] ) ) {
		wp_safe_redirect( add_query_arg( 's', rawurlencode( wp_unslash( $_GET['q'] ) ), home_url( '/' ) ), 301 );
		exit;
	}

	// 3. Blogger post feeds: /feeds/posts/default, /feeds/posts/summary
	if ( 0 === strpos( $path, '/feeds/posts/' ) ) {
		wp_safe_redirect( get_feed_link(), 301 );
		exit;
	}

	// 4. Blogger labels: /search/label/Name
	if ( preg_match( '#^/search/label/([^/]+)/?$#', $path, $m ) ) {
		$name = rawurldecode( $m[1] );
		foreach ( array( 'post_tag', 'category' ) as $taxonomy ) {
			$term = get_term_by( 'name', $name, $taxonomy );
			if ( $term ) {
				wp_safe_redirect( get_term_link( $term ), 301 );
				exit;
			}
		}
	}
}, 1 );Code language: PHP (php)

I ran it as a must-use plugin: save it as wp-content/mu-plugins/blogger-redirects.php (create the mu-plugins folder if it’s not there). It should also work from a code snippets plugin, but the must-use plugin is what I tested. On my test site, /search/label/Bakery%20News went to /tag/bakery-news/, the Romanian label Rețete went to /tag/retete/, /search?q=bread went to the WordPress search for “bread” and /feeds/posts/default went to /feed/. Labels work the same when they’re categories (BtW).

8. Send visitors from blogspot.com to WordPress

This depends on your Blogger address.

If your blog had a custom domain (like www.example.com), point that domain to your new host. The old URLs then reach WordPress directly, and the redirects from steps 3, 4 and 7 take care of them. That’s what my test site did: everything I checked above was requested on the WordPress domain.

If your blog is on yourblog.blogspot.com, Blogger doesn’t let you set up real server redirects. The only way is to replace your Blogger theme with one that redirects every page. Blogger Importer Extended has a “Download Template” button for this, but I don’t recommend it. On my test blog, its JavaScript redirect worked in the browser, but the canonical tag and the meta refresh pointed to broken addresses like https://www.example.com/https://yourblog.blogspot.com/2026/11/post.html. Google prefers those over a JavaScript redirect. Label pages also went to the homepage.

So I made a fixed template. The meta refresh sends the full old address to WordPress, where the snippet from step 7 strips the blogspot part and redirects to the same path on your site. The JavaScript line takes browsers straight to the new address. According to Google’s redirect documentation, an instant meta refresh counts as a permanent redirect, and JavaScript is the last resort, so the meta refresh is the important part here.

First, back up your current theme: Theme → the arrow next to Customize → Backup. Switching templates also drops your gadget settings, so you can’t get them back without the backup. Then go to Theme → Edit HTML, replace everything with this, change https://www.example.com (in two places) to your domain and save:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
<head>
<meta http-equiv='refresh' expr:content='"0;url=https://www.example.com/?from_blogger=" + data:view.url.canonical'/>
<script>
//<![CDATA[
window.location.replace("https://www.example.com" + window.location.pathname + window.location.search);
//]]>
</script>
<b:skin><![CDATA[]]></b:skin>
</head>
<body>
<b:section id='header' showaddelement='no'>
<b:widget id='Header1' locked='true' title='Header' type='Header'/>
</b:section>
</body>
</html>Code language: HTML, XML (xml)

This is what happened on my test blog, with the snippet from step 7 on the WordPress side:

  • A post: /2026/11/how-we-moved-bakerys-weekly-bread.html (with or without ?m=1) → the same post on WordPress, in the browser and with JavaScript off (two redirects).
  • The page: /p/contact.html → /contact/.
  • A label: /search/label/Bakery%20News → the “Bakery News” tag (or category with BtW).
  • Search: /search?q=bread → the WordPress search for “bread”.
  • The homepage → the new homepage.

The redirects only exist as long as the Blogger blog is online and public. Don’t delete it and don’t make it private (the 2014 version of this guide suggested making it private, which would break the redirects).

9. Test the old URLs

I wouldn’t call a migration done without opening a few old links myself. Take some old URLs from your Blogger blog (or from Google Search Console if the blog is verified there) and check each type:

  • A post, and the same post with ?m=1 at the end
  • A post with a long title (Blogger shortens those URLs)
  • A page (/p/…html)
  • A label page, especially one with a space or special characters
  • The homepage and /feeds/posts/default

Every one should end on the matching WordPress page. If a post lands on a 404, check that it was imported at all: Blogger Importer Extended skips drafts and stops at 20 posts on the free version.

That’s it! You have now moved your Blogger blog to WordPress with your posts, images and links, and the old addresses send people to the right place.

Closing remarks

  • I only tested the free version of Blogger Importer Extended. The paid version promises unlimited posts and extra options, but I can’t tell you how it handles images or labels.
  • If you only want to keep the Blogger URL format, you can set WordPress permalinks to /%year%/%monthnum%/%postname%.html. With Blogger Importer Extended’s slugs, the old post URLs then work without redirects. You still need the redirects for pages and labels, though, and ?m=1 gave a 404 without them (WordPress reads m as its own date parameter).
  • Everything was tested on WordPress 7.1.4 with PHP 8.3, Blogger Importer Extended 3.5.1, BtW Importer 4.2.2 and Blogger Importer 0.9.3, in November 2026.

If something on your blog didn’t move the way it did on mine, let me know in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *