Directories
A directory describes a member listing: which accounts appear, what each card shows, and what a visitor may search, filter and sort by. Like forms, directories live in project config.
Two separate gates
Who can open this listing decides whether the directory renders at all — everyone, or signed-in members only.
Each field is still checked against the owner's privacy before its value is printed. A directory a member may open is not permission to read every field of every account in it. In practice this means one member's card can show fewer fields than the one beside it, because that member tightened something. That is correct, and it is why the check is per member and per field rather than per column.
The four lists
Each list is also an allow-list, and that is the point of them.
| List | What it does |
|---|---|
| Card fields | Shown on each card, in order, subject to privacy. |
| Searchable | What the keyword box looks in. |
| Filters | Offered as dropdowns. |
| Sort options | Offered in the sort menu. The first is the default. |
A request can send ?sort= and ?f[…]= for anything it likes. Memberz looks the name up in the
directory's own lists and ignores it if it is not there. ?sort=password does not become an
ORDER BY, and a filter on a field the directory never offered does not become a WHERE.
Page size is clamped too: a request may ask for a different perPage, but never for more than 100.
What can go in each list
Card fields, search and filters accept any user field plus Full Name, First Name, Last Name, Username, Email, Date Created and Last Login.
Sorting is account fields only. Custom fields cannot be ordered on in Craft 5 without a join it is not this plugin's place to write, so a custom-field sort entry produces no order clause rather than pretending to work.
Filters need a field with its own option list — a dropdown, radio buttons, checkboxes, a multi-select. A free text field produces no filter control. Building one would mean selecting the distinct values of that field across every account and printing them on the page, which publishes the contents of the field to everyone who loads it, privacy settings and all.
Searching custom fields
Custom-field search goes through Craft's search index, so the field has to be marked searchable in its own settings. Account columns are matched directly and need nothing.
Search terms are quoted before they reach the index, so *, - and OR arriving from a URL are
treated as part of a name rather than as index operators.
Rendering
{{ craft.memberz.directory('members') }}
The listing reads q, sort, f[…], group and page from the URL, so it works as a plain GET
form with JavaScript switched off. Adding the optional runtime upgrades it to filter in place:
{{ craft.memberz.directory('members') }}
{{ craft.memberz.js() }}
Nothing includes that script for you. A plugin that put JavaScript on every page of a site by default would be adding weight to pages that need none, and making itself the first suspect for every unrelated error in the console.
Drawing your own
craft.memberz.members() returns the directory's UserQuery, already narrowed by the directory's
configuration and by whatever of the request it recognises, or null if this visitor may not open
the directory at all:
{% set query = craft.memberz.members('members') %}
{% if query %}
{% paginate query.limit(24) as pageInfo, members %}
{% for member in members %}
<article>
{{ craft.memberz.avatar(member, 64) }}
<h3>{{ member.fullName }}</h3>
{% if craft.memberz.canSee(member, 'jobTitle') %}
<p>{{ craft.memberz.value(member, 'jobTitle') }}</p>
{% endif %}
</article>
{% endfor %}
{% endif %}
The canSee call is not optional politeness. The query narrows which members are listed; it does
not narrow which of their fields you may print.