Written by
Founder & CEO, Gspikes
I own and run Gspikes, the agency I founded in 2018. Before going all-in, I led SEO at Chain Reaction — one of the region's largest digital agencies — and shipped production code at Orange and Forbes. Everything published here comes from projects I've personally built, migrated, or ranked.
Last updated · October 2026
A Drupal 7 migration moves your content, but it doesn’t move your addresses. Every old URL that a visitor has bookmarked, another site links to, or Google still has in its index needs an answer on the new site. Usually that answer is a 301 redirect to the page’s new address. Getting those Drupal redirects right is the part of a migration that quietly decides whether search traffic survives the launch.
We built Migrate Redirect Map to do that job, and version 1.0.0, its first stable release, came out on 30 October 2026. This post explains what it does, how it decides what to redirect, and where it belongs in a Drupal 7 to Drupal 10 or 11 migration.
Migrate Redirect Map in one minute
- What it does: builds the 301 redirects from legacy Drupal 7 URLs to the migrated content, and lists the URLs that would return 404 after launch.
- What it reads: the migrate map tables of your node, taxonomy term, user and media migrations, plus the Drupal 7
url_aliasandredirecttables. - What you get: every legacy URL sorted into one of seven statuses, a CSV of the whole map, and redirects created in batches from the admin UI or Drush.
- Requirements: Drupal 10.3+ or 11 and the Redirect module (1.9 or later). Install with
composer require drupal/migrate_redirect_map. - Security: 1.0.0 is covered by Drupal’s security advisory policy.
Why old URLs break in a Drupal 7 migration
There are still a lot of these migrations to come. In our State of Drupal 7 census, W3Techs data put Drupal 7 at 29.7% of all Drupal websites in August 2026, which we estimate at around 200,000 live sites. Each of them will need its old URLs handled when it moves.
Core’s migrations get you part of the way. The d7_url_alias migration copies the old aliases across, which keeps old URLs working as long as nothing else has changed. The Redirect module’s own Drupal 7 migration copies the redirects the old site already had. Neither covers the cases that cause most of the 404s after launch:
- The aliases change. New Pathauto patterns on the new site produce different URLs for the same content, so the old alias no longer exists.
- The IDs change. If entities don’t keep their Drupal 7 IDs, a system path such as
node/12now means a different page, or nothing. - Translations fold together.
d7_node_completemerges a translated node into its source node, so the translation’s own node ID never appears on the new site. - Old redirects point at old places. A Drupal 7 redirect to
node/45is only useful if it is re-pointed at wherever node 45 ended up.
None of these show up as errors during the migration. They show up weeks later as lost rankings and broken backlinks, which is why our website migration SEO guide treats the redirect map as a launch blocker rather than a clean-up task.
What Migrate Redirect Map does
After your content migrations have run, the module works through the old site’s URLs in order:
- It reads the migrate maps of the node, taxonomy term, user and media migrations. Both naming styles work: core IDs such as
d7_node_complete:articleand theupgrade_d7_*IDs written by the Migrate Drupal UI. - It reads the legacy
url_aliastable, so every alias a visitor, a backlink or a search engine might still use is accounted for. - If the Drupal 7 site used the Redirect module, it reads the legacy
redirecttable too, including query strings, fragments and status codes, and re-points those redirects at the migrated content. Disabled legacy redirects are skipped. - It looks up each migrated entity’s current alias on the new site, in the right language.
- It checks every candidate against the new site and gives it a status.
- It gives you the whole map as a CSV, and when you are ready, creates the Redirect entities in batches. Each redirect is validated before it is saved, so a collision is logged and skipped instead of stopping the run.
The seven answers for every legacy URL
The statuses are the useful part, because they turn a migration’s URL problem into a list you can work through. Two of them create redirects, two need nothing, and three need a person to look at them.
- redirect: the URL changed, so a 301 to the migrated entity’s new alias will be created.
- external: a legacy redirect to an absolute http or https URL on another site. These are listed but only recreated when you ask, through a checkbox in the form or
--include-externalin Drush. Targets usingjavascript:,data:,mailto:or a protocol-relative//hostare never created. - identical: the URL is the same on the new site. Nothing to do.
- exists: a redirect for that URL already exists, enabled or disabled, so it is left alone. That also makes the module safe to run again.
- conflict: the old URL is live on the new site. It may be an alias that now belongs to other content, an entity that reuses the legacy ID, or a real route such as
user/login. The module skips these, because Redirect answers a request before Drupal’s routing does, and a redirect would hide that page. - duplicate: another legacy entry already claims the same URL and language with a different target. This usually happens when two Drupal 7 languages both become language-neutral on the new site. The first one wins and the second is reported.
- unresolved: no migrated entity matches, so this URL would return 404. Views paths, custom menu callbacks and file paths land here. They are listed, never guessed.
That last rule is deliberate. A tool that guesses a target for every unmatched URL produces redirects nobody checked, and a wrong redirect is harder to spot than a 404. The unresolved list is a to-do list for a person who knows the site.
When to run it
Run it after the content migrations and after the new aliases exist, either because Pathauto has generated them or because you imported them with drush migrate:import d7_url_alias. If you run it earlier, the redirects point at system paths such as node/123. Those still work, but they aren’t what you want in the map.
In practice it is the last step before launch. Do a dry run, work through the unresolved, conflict and duplicate rows, run the dry run again, and create the redirects only when the list looks right. After launch, check a few old URLs with curl -I and expect a 301 to the new alias. Our Drupal 7 to Drupal 11 migration checklist shows where this sits among the other launch tasks.
Running it from the admin UI
The report page is at Configuration » Search and metadata » Migration redirect map (/admin/config/search/redirect/migrate-map). There is nothing to configure in advance and no settings are stored. Each run takes four options:
- Legacy database key: the key of the Drupal 7 database in
settings.php. It defaults tomigrate, the key Migrate Drupal already uses. - Migrations: the migration IDs whose maps are read. Leave it empty to use every
d7_node*,d7_taxonomy_term*,d7_userandd7_media*migration, including theupgrade_variants. - Also recreate legacy redirects to external URLs: off by default.
- Dry run: ticked by default. Nothing is written until you clear it and submit again.
The map is built in a batch, 200 paths per request, and redirects are created 100 per request, so the page doesn’t time out on large sites. The report shows the counts and puts the rows that need attention first. Download the full report (CSV) returns every row. The last report is kept for your account only until the next run.
The page needs its own permission, Build migration redirect maps. It is marked as restricted, because it reads a database chosen in settings.php and creates redirects in bulk, so give it to trusted administrators only.
Running it with Drush
For very large maps, Drush is faster. The command needs Drush 12.5 or 13:
drush migrate-redirect-map:build --dry-run --csv=/tmp/redirect-map.csv
drush migrate-redirect-map:build
Without --dry-run, the command shows the counts and asks before it creates anything. Add -y to skip the question in scripts. The other options are --source-key, --migrations, --include-external and --csv, and mrm:build works as a short alias.
Reading the CSV
The report has one row per legacy URL, with these columns: legacy_path, legacy_query, legacy_system_path, legacy_langcode, langcode, target, status, status_code and reason. It is meant to be opened in a spreadsheet and signed off before launch. Legacy aliases were typed by editors, so any cell that starts with =, +, -, @, a tab or a carriage return is prefixed with an apostrophe, and the spreadsheet won’t run it as a formula.
Status codes come from the legacy Redirect table where there is one, falling back to the Drupal 7 default, which is 301 unless it was changed. Codes that Redirect doesn’t support are replaced by 301, and the reason column says so.
What it doesn’t do
- It maps nodes, taxonomy terms, users and media. Files, views and custom paths are reported, not mapped.
- Drupal 7 aliases never carry query strings. Query strings on legacy redirects are kept on both the source and the target, and a query-specific redirect is created even when the bare path is live, because Redirect only matches that exact query.
- If the old site had several aliases for one node, each becomes its own redirect to the new canonical alias.
- It doesn’t touch the content or the old database. Until you create the redirects, everything it does is read-only.
Cleaning up the old database before you migrate makes the list shorter and easier to check. Our guide to cleaning a decade of Drupal database cruft covers that step.
Security coverage and the review
Before 1.0.0, the module went through drupal.org’s security advisory coverage application, where volunteer reviewers read the code by hand. Their review shaped the release. The conflict check, validation before saving, the opt-in for external targets, the spreadsheet-safe CSV, the restricted permission, the Drush confirmation and a kernel test covering every status all came out of it. Thanks to vishal.kadam, avpaderno, anil_bhutia and klausi for their time.
The application was approved on 25 October, and 1.0.0 is covered by the security advisory policy. If you find a security problem, report it privately through the Security Team, using the “Report a security vulnerability” link on the project page, not in the public issue queue. The 1.0.0 release notes list every change since the beta.
Try it, and tell us what breaks
composer require drupal/migrate_redirect_map
drush en migrate_redirect_map
Start with a dry run on a copy of your migrated site and read the unresolved rows. Bug reports and ideas are welcome in the issue queue. Reports from multilingual sites, very large sites and unusual Drupal 7 alias setups are the most useful right now.
Migrate Redirect Map is maintained by Gspikes. If you’re planning a Drupal 7 migration, our Drupal 7 end-of-life migration guide covers the options, and our Drupal migration team can take it on for you.
Field Notes
Get the next one by email
Research on Drupal, WordPress and search. Measured, not guessed. New pieces straight to your inbox.
Put senior Drupal engineers on it
Migrations, custom modules, integrations, and support with real SLAs — with a fixed-price quote from a senior engineer within 3 days.
