Guide 07 · Developer docs

Members, accounts and gated content

Building a member area on Core dna: who a member is in the data, the sign-in flows and their templates, single sign-on and passkeys, the my-account routing convention, members-only content, sessions and the page cache, and the security checks to run before launch.

L2
Unit 02 · Registration, sign-in and password recovery

Restyling the account screens

The account screens are restyled by writing templates at the paths the routes render, as one set: the pages, the shared input partials, the messages and the emails. The full list of files, a field partial that handles its own errors, and what differs on a Twig site.

Who this is for. The theme developer restyling sign-in, registration and the password screens as a set. Afterwards you have the list of files to write and a way to write each field once.

How overriding works

Each route renders a fixed template path. A file at that path in your site's templates/ folder is used instead of the framework's; where the framework has no file, yours is the only one (Template resolution order). Nothing is registered in the admin. The framework's own copies are plain Smarty, so they show the variables each screen has, not a design to keep.

The set of files

File under templates/For
users/login.htmlSign-in
users/register.htmlRegistration
users/forgot-password.html, users/reset-password.html, users/change-password.htmlThe password screens
users/profile.htmlThe member's own details
users/frontend/includes/account.htmlThe account menu the member screens share (The account menu)
users/inputs/input-field.html and the other input partialsThe fields, written once (below)
includes/frontend/messages/all.htmlThe messages a route shows after a redirect
users/emails/reset-password-smarty.html or -twig.htmlThe reset email (The reset email)
o-auth/frontend/remember-me.html or remember-me-twig.htmlThe remember-me prompt, where it is on (The prompt)
errors/oauth.htmlA failed provider sign-in (When the handshake fails)
errors/not-authorized.htmlA refused CSRF token (The refusal)

Give every screen the site's own base template, and print the messages on each: several routes report their result only as a message after a redirect.

Shared input partials

The framework's account templates build their fields from small partials in users/inputs/: input-field.html, a labelled input that shows its own error from _validation; select.html, for a list of options; and select-entity.html, for a list of records such as countries or address types. Keep the same idea in your site: one partial per kind of field, included from every screen, so a change to how fields look or report errors is made once.

Write your own copy at the same path, and escape the value and label in it, as the example below does. A Twig site writes its own in any case, since the framework's partials are Smarty.

Template code templates/users/inputs/input-field.html
{if !isset($type)}{$type = 'text'}{/if}
{if !isset($value)}{$value = ''}{/if}
{$hasError = isset($_validation) && $_validation->hasFieldError($fieldName)}
<div class="field{if $hasError} field--error{/if}">
  <label for="{$id}">{$label|escape}</label>
  <input id="{$id}" name="{$fieldName}" type="{$type}" value="{$value|escape}"{if !empty($isRequired)} required{/if}{if $hasError} aria-invalid="true" aria-describedby="{$id}-error"{/if}>
  {if $hasError}<p class="field__error" id="{$id}-error">{$_validation->getFieldErrorMessage($fieldName)}</p>{/if}
</div>
{% set type = type ?? 'text' %}
{% set value = value ?? '' %}
{% set hasError = _validation is defined and _validation.hasFieldError(fieldName) %}
<div class="field{{ hasError ? ' field--error' : '' }}">
  <label for="{{ id }}">{{ label|e }}</label>
  <input id="{{ id }}" name="{{ fieldName }}" type="{{ type }}" value="{{ value|e }}"{{ isRequired ?? false ? ' required' : '' }}{% if hasError %} aria-invalid="true" aria-describedby="{{ id }}-error"{% endif %}>
  {% if hasError %}<p class="field__error" id="{{ id }}-error">{{ _validation.getFieldErrorMessage(fieldName) }}</p>{% endif %}
</div>
Template code Using it in users/register.html
{include 'users/inputs/input-field.html' fieldName='person[email]' id='email' type='email' label='Email' isRequired=true value=$user->getPerson()->getEmail()}
{% include 'users/inputs/input-field.html' with {fieldName: 'person[email]', id: 'email', type: 'email', label: 'Email', isRequired: true, value: user.getPerson().getEmail()} %}

The error message comes from the platform and is printed as it is. An included file sees the including template's variables in both engines (a Twig include passes the current context by default), so _validation reaches the partial with no extra work.

On a Twig site

  • Write every account template, the sign-in page included: the framework's copies are Smarty and cannot render on a Twig site.
  • The remember-me prompt has a Twig version the framework ships, o-auth/frontend/remember-me-twig.html; override that name.
  • The provider error page is looked for as errors/oauth-twig.html and then errors/oauth.html, so either name works.
  • The reset email must be supplied as users/emails/reset-password-twig.html: the framework ships only the Smarty version.

Checking the set

Before launch, open each screen in its states: signed out and signed in; with each form submitted empty, with a wrong value and correctly; and on a narrow screen. A screen whose template is missing shows only "Template not found" on a Smarty site, so walking the list once confirms every screen has its template (When a template is missing).

Go deeper

Was this page helpful?

Your answer goes to the docs team.