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 04 · The my-account area and its routing convention
How the member area is routed
The my-account area and its routing conventionPage 22 of 36Updated
Every URL under /my-account goes through one dispatcher: named routes for the built-in screens, then a catch-all that turns a template in templates/my-account/ into a members-only page. The mapping rule, what wins when names collide, and what the template receives.
Who this is for. The theme developer adding a page to a member area, or working out why a template under templates/my-account/ renders or does not. Afterwards you can tell, from a URL alone, which template answers it.
One dispatcher, named routes and a catch-all
The member area is the MyAccount module. Its dispatcher holds a list of named routes, one for each built-in screen (the account home, addresses, orders, quotes, the wishlist, linked accounts, passkeys, authorised applications and current activity), and one last route, default, that accepts whatever path is left. A request is matched against the routes in that order, and the first match answers it. The built-in member screens lists the named routes with their templates.
Every route in the list, the catch-all included, requires someone to be signed in. A signed-out visitor is redirected to /login with the message "You have to log in to view this page", and the URL they asked for is kept in their session; once they sign in, they are sent back to it.
/my-account is the default prefix. The platform team can change it for a site, so link with the URL helper rather than writing the prefix into templates (below).
The catch-all
Any path under /my-account that no named route takes is mapped onto a template of the same name:
/my-account/<path> renders templates/my-account/<path>.html, from your site's templates folder.
URL
Template
/my-account/downloads
templates/my-account/downloads.html
/my-account/courses/certificates
templates/my-account/courses/certificates.html
The rest follows from the rule:
There is no index file./my-account/courses looks for my-account/courses.html, never my-account/courses/index.html. A file and a folder of the same name can sit side by side: courses.html answers /my-account/courses, and courses/certificates.html the longer path.
Only your site's folder counts. The controller looks for the file in the site's templates/ folder and nowhere else. A framework template at the same path is never used.
Case matters. The file is looked up on a case-sensitive file system, so /my-account/Downloads does not find downloads.html. Keep URLs and file names in lower case.
No full stops. The catch-all's pattern does not accept a dot, so /my-account/downloads.html matches no route at all and is a 404 before sign-in is even checked. Use letters, digits, hyphens, underscores and slashes.
Any .html file in that folder, at any depth, answers a URL for every signed-in member, partials included: my-account/includes/card.html is served on its own at /my-account/includes/card. Keep partials somewhere else, such as templates/includes/my-account/, and include them from there.
A named route always wins
The named routes are tried first, so a path one of them takes never reaches the catch-all. A template named after one of them never renders. The built-in screen answers instead or, if your site has not written that screen's template, a page that reads only Template not found: quotes/quotes/index.html (on a Smarty site; on a Twig site, an error). Either is the symptom of a collision.
These paths are taken. Do not use them, or a first folder of the same name, for your own pages:
Path after /my-account
Taken by
/
The account home
/addresses, /addresses/<one segment>
The address book; the segment is read as a page number
/address, /address/<number>
Adding, showing, editing and deleting an address
/orders, /orders/<one segment>
The orders list
/order/<number>
One order
/quotes, /quotes/<one segment>
The quotes list
/quote/<number>
One quote
/wishlist, /wishlist/<one segment>
The wishlist
/linked-accounts
Linked accounts
/passkeys
Passkeys
/applications
Authorised applications
/current-activity
Signed-in sessions
The list routes take any single segment as the page number, not only a number. /my-account/orders/archived is the orders list asked for page "archived", and my-account/orders/archived.html is never read.
A missing template is a 404
When the catch-all finds no file at the mapped path, it answers 404, rendered with your site's errors/not-found.html like any other missing page. It never renders a blank page. A typo in a link or a file name therefore shows on the first click, and a 404 under /my-account for a signed-in member means one thing: there is no file at templates/my-account/<path>.html on the server. Check the path, its case and whether the file was deployed. (A signed-out visitor never gets that far: they are sent to sign in first.)
The two failures look different on purpose. A missing catch-all template is a 404; a named route whose template is missing answers 200 with the "Template not found" line above.
What your template receives
One variable, user: the signed-in user's record, with getPerson() for their name and contact details. Every global is there as well (Global template variables), including _currentUser and _csrfToken.
The template needs no sign-in check. The route has already redirected anyone who is not signed in, so by the time the template runs, user is always set. A check of your own is redundant, not defensive: it can never fail.
Template codetemplates/my-account/welcome.html
{$person = $user->getPerson()}
<h1>Hello, {$person->getFirstName()|escape}</h1>
<p>You are signed in as {$user->getUsername()|escape}.</p>
<p><a href="{$_url->getPath('MyAccount', 'account')}">Back to your account</a></p>
{% set person = user.getPerson() %}
<h1>Hello, {{ person.getFirstName()|e }}</h1>
<p>You are signed in as {{ user.getUsername()|e }}.</p>
<p><a href="{{ _url.getPath('MyAccount', 'account') }}">Back to your account</a></p>
Wrap it in your site's base template as any page is (Adding a custom member page shows the whole file). Neither engine escapes output on this platform, so escape what a member typed.
The route passes nothing from the URL into the template. Read the query string with $_request->getParameter('tab') (_request.getParameter('tab') in Twig), and fetch records with $_query. Nothing rendered for a signed-in visitor is stored in the page cache, so a member page is always rendered fresh (Member pages and the page cache).
Linking into the member area
Link to a built-in screen by its route name in the MyAccount module, and to your own pages from the module's path, so the links follow the prefix if it is ever changed: