Privacy
Every field on a member's profile has a visibility setting, and there are two of them: the ceiling the site sets on the form row, and the preference the member chooses.
The setting actually in force is the stricter of the two.
That is the whole rule, and combining them this way is what makes a member's choice safe to accept
from a form post. The worst a hostile value can do is tighten. A member posting
memberzPrivacy[fields][email]=public for a field the site keeps to members gets members — not
because the value was rejected, but because strictest(members, public) is members.
The four settings
| Who can see it | |
|---|---|
| Everyone | Anybody, signed in or not. |
| Signed-in members | Any signed-in user. |
| Only the account owner | The member themselves (and admins). |
| Nobody (write-only) | Nobody. The field is collected but never shown — a phone number kept for the site's records. |
Nobody is the site's to give. It is not offered to members, because a member choosing it would be choosing to hide a value from themselves.
Owners always see their own fields, and admins always see everything — they can already see it all in the control panel, and pretending otherwise on the front end only makes moderation harder.
One verdict
Everything that renders somebody else's data — the profile page, a directory card, the Twig API — asks the same function. That is deliberate. A second implementation of "can this be shown" is how a field ends up private on the profile page and printed on the directory card, which is a privacy setting that does not work and looks like one that does.
The consequence for your templates:
{# Goes through the check. Returns null if this visitor may not see it. #}
{{ craft.memberz.value(member, 'jobTitle') }}
{# Does NOT. This is Craft's own accessor and knows nothing about Memberz. #}
{{ member.jobTitle }}
And when looping a profile, loop what the plugin hands you:
{# Already filtered to what this visitor may see. #}
{% for row in craft.memberz.fields(member) %}
<dt>{{ row.displayLabel }}</dt>
<dd>{{ craft.memberz.value(member, row.handle) }}</dd>
{% endfor %}
Looping form.rows instead publishes every field regardless of who is looking.
Several profile forms
When more than one profile form names the same field, the strictest of them wins.
Otherwise the verdict would depend on which form happened to be created first, and adding a second, looser form could silently widen what a directory already publishes about every member on it.
Profile visibility, and being listed
Separate from field-level settings, a member controls two more things:
Who can see my profile — the profile page as a whole. A profile nobody may see answers 404, not 403. A 403 confirms the account exists, which turns the profile URL into a way of testing whether a username is registered — and for a member who set their profile to private, being discoverable is exactly what they asked not to be.
List me in member directories — whether they appear in listings at all. This is a different permission from profile visibility and members treat it as such: "you may read my profile if you find it" is not the same as "put me in the index". Members are filtered out of the directory in SQL rather than afterwards, so page two of a directory never holds fewer cards than page one.
An account that has never opened its settings has no stored preferences and is listed. The absence of a choice is not a choice.
What the profile URL still publishes
A profile lives at /members/<slug>, and by default that slug is built from the member's username.
So the URL carries the username even if the field is set to Only the account owner — a link to a
profile has to say which profile it is.
If that matters for your site, set Profile URLs are built from to A random string in the plugin settings. New members then get an opaque twelve-character slug. Slugs already issued are never rewritten by a change to that setting — a profile URL that stops working is a broken link on someone else's site.
Sequential user ids are never used for a slug under any setting: an id in a URL makes every member enumerable by counting, which is the thing having a slug is for.
Turning it off
Setting Let members set their own privacy off removes the controls and applies the form's ceilings alone. Preferences already stored are ignored rather than deleted, so switching it back on restores what members had chosen.