How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Give a record a second URL with a custom URL

A custom URL is an extra address independent of the slug and the tree. Both addresses serve the record, links from the record switch to it, and it works as a prefix. Add a rewrite if the old address must go.

Context

Context. A campaign lands on /summer-sale while the page lives at /campaigns/2026/summer; a blog whose slug is main-blog should answer at /blog; a legacy path from an old site must keep working. A custom URL is that second address, set on the record, for pages, books, posts, blogs, FAQ pages, questions and custom entity records. It belongs to one site, or to the whole account when its site is left empty.

Steps

  1. On the record in the admin, set the Custom URL field to /summer-sale. Through the API it is the customUrl field with a url.
  2. Check both addresses signed out: the original path and the custom URL both serve the page, and neither redirects.
  3. Expect every link built from the record to switch, because getFullPath() returns the custom URL once one is set; getOriginalFullPath() still gives the tree path.
  4. If the original address must stop working, add an exact rewrite from it to the custom URL, a 301 for a permanent move.

Code

Template code
{* links follow the custom URL automatically *}
<a href="{$page->getFullPath()}">{$page->getName()|escape}</a>      {* /summer-sale *}
<link rel="canonical" href="{$_request->getSchemeAndHost()}{$page->getFullPath()}">
{* the tree address, if you ever need it *}
{$page->getOriginalFullPath()}                                       {* /campaigns/2026/summer *}
<a href="{{ page.getFullPath() }}">{{ page.getName()|e }}</a>
<link rel="canonical" href="{{ _request.getSchemeAndHost() }}{{ page.getFullPath() }}">
{{ page.getOriginalFullPath() }}
Shell
# confirm both addresses, then the redirect once a rewrite is added
curl -s -o /dev/null -w '%{http_code}\n' https://www.example.com/summer-sale
curl -s -o /dev/null -w '%{http_code}\n' https://www.example.com/campaigns/2026/summer
curl -sI https://www.example.com/campaigns/2026/summer | grep -i '^location'

Why this and not that

Not a rewrite alone: a rewrite maps one path to another, but the record's own links would still print the tree address; the custom URL changes what the record calls itself. Not moving the page to the root to shorten its URL: that changes its place in the page tree, taking it out of the section and off its breadcrumb; keep it in the section and give it a custom URL instead. Remember the prefix rule: /blog on the blog also resolves /blog/my-post, because matching drops path segments from the right until something matches.

Go deeper

Was this page helpful?

Your answer goes to the docs team.