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.

Link to a record without hardcoding its URL

Take a URL from the record with getFullPath(), never from a path typed into the template. Get the record from an author-picked area, from a query by a stable attribute, or from a route name for module pages.

Context

Context. A path typed into a template breaks when an editor renames a slug, moves a page or adds a custom URL. The record knows its own address, and getFullPath() follows all three. So the question is only how to get hold of the record, and there are three good answers: let an author pick it in a record area; fetch it by an attribute that will not change; or, for the platform's own screens, ask for the route by name.

Steps

  1. For a link an editor should control, add a record area (input type category record, pointing at pages) and read it with getAreaRecord().
  2. For a known record, fetch it with _query by a stable attribute: a custom entity record by slug within its definition, a page by id kept in site data.
  3. For a module screen (sign-in, the blog index, an order), ask _url for the path by route name; the prefix can change per site and the route name cannot.
  4. Guard every result: an unpublished or deleted record comes back as nothing.

Template code

Template code
{* 1. an author-picked page *}
{$target = $component->getAreaRecord('linkTarget')}
{if $target}<a href="{$target->getFullPath()}">{$target->getName()|escape}</a>{/if}

{* 2. a known record by a stable attribute *}
{$flagship = $_query->findCustomEntityRecordBy('store', ['slug' => 'richmond'])}
{if $flagship}<a href="{$flagship->getFullPath()}">Our flagship store</a>{/if}
{$about = $_query->findOneBy('Pages\Entities\Page', ['id' => $_siteData->get('aboutPageId')])}
{if $about}<a href="{$about->getFullPath()}">About us</a>{/if}

{* 3. a module screen by route name *}
<a href="{$_url->getPath('login')}">Sign in</a>
<a href="{$_url->getModulePath('Blogs')}">News</a>
{# 1. an author-picked page #}
{% set target = component.getAreaRecord('linkTarget') %}
{% if target %}<a href="{{ target.getFullPath() }}">{{ target.getName()|e }}</a>{% endif %}

{# 2. a known record by a stable attribute #}
{% set flagship = _query.findCustomEntityRecordBy('store', {'slug': 'richmond'}) %}
{% if flagship %}<a href="{{ flagship.getFullPath() }}">Our flagship store</a>{% endif %}
{% set about = _query.findOneBy('Pages\\Entities\\Page', {'id': _siteData.get('aboutPageId')}) %}
{% if about %}<a href="{{ about.getFullPath() }}">About us</a>{% endif %}

{# 3. a module screen by route name #}
<a href="{{ _url.getPath('login') }}">Sign in</a>
<a href="{{ _url.getModulePath('Blogs') }}">News</a>

Why this and not that

Not a slug: a page slug is unique only among its siblings, so a lookup by slug alone can return the wrong page, and the slug is not the URL anyway. Not getUrl(): on a page it is a protocol-relative address on the primary hostname, on a post it is the path; getFullPath() means the same on every record. Not an id typed into the template: the same id points at a different record on the development copy of the site, which is why the example keeps it in site data.

Go deeper

Was this page helpful?

Your answer goes to the docs team.