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 05 · Members-only content
Restricting a record to a group
Members-only contentPage 27 of 36Updated
Pages, books, blog posts, FAQ, forms, products and custom entity records can each be made readable by one group only, and the platform then enforces it wherever the record is read. The settings, how a section inherits them, how to prove they apply, and when a group is the wrong tool.
Who this is for. The developer or content modeller asked to make part of a site members-only. Afterwards you can restrict a record or a whole section, show group content in a template, and prove the restriction holds for the people it is meant to stop.
Which records can be restricted
These record types carry permissions that the site enforces on every read:
pages, and books with their units and book pages, which are pages too;
blogs and blog posts;
FAQ pages and questions;
forms;
products and product categories;
custom entity records.
The mechanism is the same for all of them. What differs is the parent a record inherits from: a page's parent page, a book page's unit, a blog post's blog. A record with no parent falls back to the defaults for its entity type and module.
Two things are not on the list. A file is served to anyone who has its URL (Members-only files). And a menu element keeps the URL it was saved with and is shown whether or not the page it points at is restricted, so a visitor who may not read the page still sees the link, and gets a 404 when they follow it. Leave such links out of menus that everyone sees, or show them only to members in the template.
Restricting one record
A new record has a Permissions tab on its form; an existing one has its own permissions screen, opened from the record's row in the admin's list. On it:
Switch off Inherit from parent. The record's own permissions appear.
Under Default permissions, in All user's permissions, switch Read off. Visitors and members without a grant of their own can no longer read the record.
Choose Add group permissions, pick the group, and switch Read on for it.
Save.
"A grant of their own" includes one made higher up. A user or group given read on the whole entity type or module, in the admin's Users › Permissions, keeps reading the record: on the site, a grant to the visitor's user or groups on the entity type outranks the record's default. Before restricting records, check that no group your members belong to holds read on the entity type or module (The order permissions are resolved in).
The site's read lookups are rebuilt by a database task that runs five seconds after the save, so a test in the first seconds can still show the old behaviour. Wait, then test.
Administrators, super admins and developers are never held to these settings, and an administrator signed in on the site sees the record regardless. That is why the check below is done signed out and as members.
Restricting a whole section
Restrict the section's top page. Its children follow as long as their own Inherit from parent is on, which it is for a new record. A child that already has permissions of its own keeps them, and a child given its own permissions later stops following the parent. Check each child once, signed out, after restricting a section someone else built.
To restrict every record of a kind, such as all the records of a custom entity type, set the defaults for the entity type instead, in the permissions screen of the admin's Users section. Records that inherit, and records with no parent, then take those defaults. The order the platform resolves a permission in is on Groups and permissions.
Confirming the restriction applies
Test the restriction from the outside, not signed in as yourself, since as an administrator you pass every check:
Signed out, in a private window, request the URL. Expect a 404, or the sign-in redirect if the site's Access Denied setting asks for it (What a refused visitor sees).
As a member outside the group, signed in with a test member account, expect the same. A super admin or developer can also render a page as another user by adding ?overrideUser= and that user's id to the URL.
As a member in the group, expect a 200.
Repeat the first two for every other place the record appears: the listings that would show it, a search for its title, /sitemap.xml, and the URL requested as JSON (Checking a gated section).
Restricting the record is the gate. A template sometimes also needs to know the group, to show a "Members" badge or offer a link only partners can follow. Membership belongs to the person, not the user, so go through getPerson(), and compare group ids rather than names, which an editor can change:
{% do _response.preventCaching() %}
{% set groupIds = _currentUser.isLoggedIn()
? _currentUser.getPerson().getGroups().toList('id', 'id') : [] %}
{% if 12 in groupIds %}<a href="/partners/price-list">Partner price list</a>{% endif %}
Three things to know about that check:
It needs preventCaching() on a public page, because its output depends on who is looking. Without it the first visitor's version is stored and served to everyone (Member pages and the page cache). On a page under /my-account it is not needed.
Groups are permission-controlled records too. A member's getGroups() lists only the groups members may read, so on a site that has never granted read on groups it can come back empty for a member while the same code works for you as an administrator. Test it with a real member account.
It protects nothing. The price list page must itself be restricted to the group; the template only decides whether to show the link.
When a group is the wrong tool
A group answers "what kind of member is this?": staff, partner, premium subscriber. It is right when everyone in the group may see the same set of content, and that set changes rarely.
It is wrong when access is per record: the course a member bought, the event they booked, the documents for their own account. A group per course or per customer multiplies without end, has to be maintained by hand, and cannot say when access started, when it ends or what was paid. That is an entitlement, a record of its own (Entitlements: access per record). A useful test: if granting access is something a person does in the admin once a year, use a group; if it happens on a purchase, a booking or an approval, use an entitlement.