The @payloadcms/plugin-redirects package injects a full redirects collection into Payload, letting editors manage HTTP redirect rules from the Admin Panel instead of hardcoding them into next.config.js or an Nginx rewrite file. I added it during a WordPress-to-Payload migration where the legacy site carried 31 explicit 301 rules that had to survive the move without breaking search rankings. Tested with Payload 3.88.0 and the plugin at the matching version, in a multi-tenant project with bilingual content across en-CA and fr-CA.
The site being migrated had accumulated years of vanity URLs and merged taxonomy paths, tracked in WordPress through a legacy SEO plugin's redirect table. Moving that table's logic into Payload meant more than copying rows across. The project's existing redirects collection had been hand-built with plain source and destination text fields, which meant a destination page's slug could change and silently break every redirect pointing at it, with no relationship to catch the drift. The official plugin replaces that with a real relationship field, so a redirect can point at a living document instead of a hardcoded string. It also defaults to a single unique constraint on the source path, which conflicts directly with a multi-tenant, multi-locale project where the same relative path is legitimately reused across sites and languages. This guide covers the plugin's structure, the exact configuration used to fix that constraint, and how it fits alongside the multi-tenant plugin in the same config.
If you're setting up the multi-tenant foundation first, my production-ready multi-tenant setup guide covers the tenant isolation this plugin needs to sit on top of.
What the Redirects Plugin Actually Adds
@payloadcms/plugin-redirects is a first-party Payload package published in the same monorepo and version line as core, alongside plugin-nested-docs, plugin-seo, plugin-form-builder, and plugin-multi-tenant. Installing 3.88.0 alongside Payload 3.88.0 keeps it on the compatibility line it was built against.
Registering the plugin appends an entire new collection to the config, defaulting to the slug redirects. That collection ships with:
A from field (text, indexed, unique by default) holding the incoming path to match.
A to group field with a radio toggle between reference and custom. Choosing reference exposes a relationship picker scoped to whichever collections you list; choosing custom exposes a plain URL text field.
A type select field for the HTTP status code, populated from redirectTypes if you pass that option: 301, 302, 303, 307, or 308.
Built-in admin translations for English, French, and Spanish.
The plugin does not execute redirects at the HTTP layer on its own. It's a data store. Actually running redirects at request time is still the frontend's job, typically through Next.js middleware or an edge proxy that queries this collection.
collections limits which document types are selectable as redirect targets, in this project spanning ten collections including pages, blogs, recipes, and products. redirectTypes enables the status code dropdown, defaulted here to 308 since that preserves the original request method during migration-driven redirects rather than forcing every redirect to a GET. overrides.access wires the project's own RBAC functions into the collection instead of leaving it on Payload's default access rules. overrides.indexes and the fields override are what fix the uniqueness problem covered next.
Register the plugin before the multi-tenant plugin in the plugin array:
Order matters because redirects generates a brand-new collection at config time, and multiTenant needs to discover that collection to inject the site relationship into it. Registering multiTenant first means it runs before the redirects collection exists.
Fixing the Uniqueness Constraint for Multi-Tenant, Multi-Locale Sites
The plugin's default from field carries unique: true at the field level. That's a reasonable default for a single-site, single-locale project, where the same source path should only ever map to one destination. It breaks immediately on a multi-tenant, bilingual project, where /about is a legitimate redirect source on Site A, Site B, and again separately in the fr-CA locale. A single-field unique constraint has no way to know those are three different contexts.
The fix does two things: it strips the field-level uniqueness from from, and it replaces it with a compound index across site, locale, and from.
defaultFields gives you the plugin's own generated field array to map over, rather than replacing the whole set from scratch. Finding the from field by name and spreading it with unique: false removes the conflicting constraint. The compound index then enforces the uniqueness the project actually needs: one redirect rule per source path, per site, per locale, rather than one redirect rule per source path globally. The locale field itself isn't part of the plugin's default schema; it gets pushed into the returned array explicitly, since a bilingual redirects table needs to know which language a given rule applies to.
Wiring Redirects Into the Multi-Tenant Plugin
With the collection generated and the constraint fixed, the multi-tenant plugin needs to know it should scope this collection to tenants:
Because redirects is a plugin-generated collection with no static class or exported slug constant to reference, it gets registered by its literal string slug rather than an imported reference. Adding it to this map is what causes the multi-tenant plugin to auto-inject the site relationship field, which is the field the compound index above depends on.
Comparing Redirect Approaches
Approach
When to use
Trade-off
@payloadcms/plugin-redirects
Editorially managed redirects that need to stay in sync with living CMS content
Requires a frontend layer (middleware or edge proxy) to actually query and execute the redirect; adds nothing at the HTTP level by itself
Hand-rolled redirects collection with plain text fields
Very small, static redirect sets that rarely change
No relational binding to real documents, so a destination slug change breaks the redirect silently, and any relational or status-code logic has to be built and maintained by hand
Hardcoded next.config.js redirects array
A handful of permanent, code-level rules
Every new rule needs a code change and a deployment, which puts redirect management outside marketing or content editors' reach
Edge or reverse proxy rewrites (Nginx, Cloudflare)
High-volume, performance-critical rewrite rules
Fully disconnected from CMS content, unmanageable by non-engineering staff, and invisible to editors who need to add a rule
The migration case for this project ruled out the first two alternatives directly: the legacy hand-rolled collection had already shown its weakness by breaking silently on slug changes, and 31 rules were too many to justify hardcoding into the Next.js config given ongoing editorial ownership after launch.
FAQ
Does the redirects plugin actually perform the HTTP redirect?
No. The plugin only provides the data collection and the admin UI for managing rules. Executing a redirect at request time is the frontend's responsibility, typically through Next.js middleware or an edge proxy that queries this collection and issues the response.
Why does the default from field break on a multi-tenant project?
The plugin sets unique: true directly on the from field, which enforces global uniqueness across the entire collection. A multi-tenant or multi-locale project legitimately reuses the same source path across different sites or languages, which the field-level constraint has no way to distinguish. Replacing it with a compound index on site, locale, and from fixes this.
Can a redirect point to a document instead of a hardcoded URL?
Yes. The to group field exposes a radio choice between reference, which opens a relationship picker scoped to whichever collections you list in the plugin config, and custom, which takes a plain URL string. Using reference means the redirect stays valid even if the target document's slug changes later.
Which HTTP status codes does the plugin support?301, 302, 303, 307, and 308, exposed as a select field when you pass redirectTypes into the plugin config. Setting a defaultValue on that field controls what new redirect rules default to.
Where does the plugin need to sit relative to the multi-tenant plugin?
Before it. plugin-redirects generates the collection at config-evaluation time, and the multi-tenant plugin needs that collection to already exist so it can inject the site field into it. Registering multi-tenant first means the redirects collection isn't there yet to scope.
Conclusion
Migrating a legacy site's redirect rules into Payload is straightforward once the collection exists, but the plugin's defaults assume a single-site, single-locale project. A multi-tenant, bilingual site needs the uniqueness constraint rebuilt as a compound index rather than a single field, and the plugin's defaultFields override makes that a small, contained change rather than a fork. Combined with correct plugin ordering against the multi-tenant plugin, the result is an editorially managed redirects table that preserved all 31 legacy rules from the WordPress migration without a single hardcoded rewrite rule in the codebase.
Let me know in the comments if you have questions, and subscribe for more practical development guides.