Forms
A form is an ordered list of rows plus what happens when it is submitted. Forms live in project config, so they deploy alongside the fields they render.
The row list is the allow-list
This is the load-bearing idea of the whole plugin, and it is worth being explicit about.
When a form is submitted, Memberz walks the form's rows and reads the one body parameter each
row names. It never iterates the POST body, and it never calls Craft's
setFieldValuesFromRequest(), which would take every posted field on the user layout.
So a submission carrying admin=1, groups[]=1, suspended=1 or a custom field the site
deliberately left off the form has no effect: those are not rows, so nothing reads them. There is
no list of forbidden parameters to keep in step with Craft, because nothing is read by default.
The practical consequence for you: adding an input to a copy of a Memberz template does nothing on its own. Add the row in the control panel instead.
The three types
Registration
Creates an account. Only ever runs when Craft's own public-registration setting is on.
A registration form needs an Email row, and a Username row unless Craft's
useEmailAsUsername is on. Memberz refuses to save one that cannot produce an account Craft would
accept.
Group membership comes from the form's own configuration — the Add new members to setting — plus whatever default user group Craft is configured with. A submission cannot ask for a group.
Profile
Edits the signed-in member's own fields. Never touches credentials.
This is the form a member's privacy controls hang off: each row can offer a visible to dropdown beside it.
Account settings
Changes email or password, and demands the current password first — the same bar Craft's own account screen sets. The demand comes from the form, not from what was posted, so leaving a box blank does not dodge it.
A username row does not trigger the demand. That is the same line Craft draws: email and password are the two that let somebody take an account, and gating a username change as well would make an ordinary profile save ask for a password, which teaches members to type it into whatever asks.
Rows
Each row is one of:
- A profile field — any custom field on the user field layout, held by UID, so renaming the field in the control panel leaves every form still pointing at it.
- An account attribute — Username, Email, Password, Full Name, First Name, Last Name, Photo.
- A heading or a block of HTML, for structure.
Each input row carries:
| Setting | What it does |
|---|---|
| Label, Instructions, Placeholder | Override what the field calls itself. Blank keeps the field's own. |
| Required | Adds a requirement. A field the layout already marks required stays required whatever this says — the form is a view onto the field layout, not a way around its validation. |
| Width | 100%, 50%, 33% or 25%. Presentational, and snapped server-side to exactly those four. |
| Visible to | The ceiling for who may see this field's value. See Privacy. |
| Members may change this | Whether the owner gets a visible to control for it. |
Fields Memberz can draw
Plain Text, Email, URL, Number, Range, Money, Dropdown, Radio Buttons, Checkboxes, Multi-select, Lightswitch, Date, Time, Color and Country.
Anything else — Matrix, Assets, Entries, Table, Link, Addresses — is reported rather than
approximated. Craft's own getInputHtml() is deliberately not used: control-panel inputs pull in
Craft's asset bundles and JavaScript, assume the control panel's stylesheet, and several do not
work outside it. A finite, visible list of supported types is a better bargain than a fallback
that silently renders a text box for a Matrix field and loses what the member typed.
Spam checks
Registration forms get two, both free and both involving no third party.
A honeypot — a hidden input a person never fills in and a naive bot always does. It is
positioned off-screen rather than display: none, because a bot that reads the stylesheet skips a
hidden field and fills in one it can see, and it is marked aria-hidden so nothing using a screen
reader is asked to complete it.
A minimum time on screen — the form carries a timestamp of when it was drawn, signed with your site's security key, so it cannot be back-dated. A submission faster than the configured number of seconds is refused.
Neither check tells the submitter which one it failed. A bot that is told is a bot that stops tripping it.
Rendering
{{ craft.memberz.form('register') }}
To draw the markup yourself, get the form and loop its rows:
{% set form = craft.memberz.getForm('register') %}
<form method="post" accept-charset="UTF-8">
{{ csrfInput() }}
{{ actionInput('memberz/account/register') }}
<input type="hidden" name="formHandle" value="{{ form.handle }}">
{% for row in form.usableRows %}
<label for="{{ row.inputId }}">{{ row.displayLabel }}</label>
{{ craft.memberz.input(row) }}
{% endfor %}
<button type="submit">{{ form.displaySubmitLabel }}</button>
</form>
A registration form written by hand also needs the honeypot input and the signed timestamp, or those checks will refuse every submission. Copying the shipped template is easier than reconstructing it — set the form's Template setting to your copy.