Guide 05 · Developer docs

Content types and custom entities

Which built-in type fits which purpose, how categories, tags, slugs, custom URLs and SEO settings behave across them, how revisions and publishing gate what the public sees, how to model a domain the platform has no type for, and where uploaded files live.

L1–L3
Unit 02 · Taxonomy, URLs and SEO

Rewrites, redirects and which address wins

A rewrite maps an incoming path to another: a redirect the browser sees, or an internal rewrite it does not. The order in which rewrites, custom URLs and slugs are consulted, which explains most rules that never fire, and how to load and test redirects in bulk.

Who this is for. The site operator mapping old addresses at a migration or a restructure, and anyone whose redirect does nothing. Afterwards you can predict which rule answers a request and test a list of redirects in one go.

What a rewrite is

Rewrites are managed in the admin under System, Rewrites. Each belongs to one site and has these fields:

FieldValues
SourceThe path to match, with a leading slash: /old/page, or a regular expression
Match typeExact match or Regular expression
ActionRedirect: the browser is sent to the target and sees it in the address bar. Rewrite: the site serves the target's content at the requested address
TargetA path on the site, or a full URL for a redirect elsewhere. A regular expression's captures are $1, $2
Redirect typePermanent (301) or Temporary (302)
ActiveWhether the rule is applied at all

Two from this site: an exact redirect from /developer-docs/api-documentation to /developer-docs/api-reference, and a regular-expression redirect from ^/developer-docs/api-documentation/(.*)$ to /developer-docs/api-reference/$1 for everything below it.

The order a request is resolved in

  1. Exact rewrites of the current site, in position order; a trailing slash on the request is ignored. The first match is used, and regular expressions are not tried.
  2. Regular-expression rewrites of the current site, in position order. The first pattern that matches is used. Patterns are not anchored for you: /old/ matches anywhere in the path, ^/old/ only at the start.
  3. A matching redirect ends the request there, with the incoming query string carried to the target. A matching rewrite sets the path to serve and the request carries on.
  4. Custom URLs, matched against the path as it was requested, longest first, the current site's before account-wide ones.
  5. The module routes: custom entity paths, then the modules' prefixes (blogs, faq, books and the rest), and finally the page tree, slug by slug.

Rewrites run before anyone is signed in, so a rule cannot depend on who is asking.

HTTP A regular-expression redirect on this site, signed out
GET /developer-docs/api-documentation/pages?ref=old HTTP/1.1

HTTP/1.1 301 Moved Permanently
Location: /developer-docs/api-reference/pages?ref=old

When a rule never fires

  • Another rule matched first. An exact rule always beats a regular expression, and among regular expressions the lower position wins. A broad pattern at a low position swallows everything below it.
  • The pattern matches more, or less, than you think. Without ^ and $, /news also matches /blogs/news/spring. Test with the real path.
  • The rule is not active, or belongs to another site of the account: rewrites are per site.
  • A custom URL wins over an internal rewrite. An internal rewrite only sets the path to serve; a custom URL that matches the requested path then replaces it. A redirect is not affected, because it ends the request first.

Loading redirects in bulk, and testing them

There is no import screen for rewrites. A migration's redirect list is loaded through the API, one request per rule: POST /cdna-api/rewrites/add-rewrite, with GET, PUT and DELETE on /cdna-api/rewrites/rewrites/{id} and the list at /cdna-api/rewrites/rewrites (Rewrites in the API reference). Give each rule an exact source where the old site's paths are known, and keep regular expressions for whole sections that moved together.

Test the result the same way, with a loop over the old paths that compares each status and Location with what the list says:

Shell check-redirects.sh: old path and expected target, one pair per line
while read -r old expected; do
  location=$(curl -s -o /dev/null -w '%{redirect_url}' "https://www.example.com$old")
  status=$(curl -s -o /dev/null -w '%{http_code}' "https://www.example.com$old")
  if [ "$status" != "301" ] || [ "$location" != "https://www.example.com$expected" ]; then
    echo "FAIL $old -> $status $location (expected $expected)"
  fi
done < redirects.txt

curl resolves a relative Location against the request's address, which is why the comparison adds the hostname. Run it before go-live against the new site's hostname, and again after.

Go deeper

Was this page helpful?

Your answer goes to the docs team.