Learn

Membership

Statamic’s membership functionality has been expanded to allow users to sign up, sign in, update their profiles, and more — all from the front-side of the website.

note We differentiate users from members. A member is a logged-in user of the site. A user is anyone that uses your site. Their relationship is a bit like squares and rectangles: all members are users, but not all users are members.

Configuring

Membership is packaged as a bundle, and comes with two config files:

  • _config/bundles/member/member.yaml holds a couple of settings for making membership work
  • _config/bundles/member/fields.yaml is a list of allowed fields that each member can have in their profile

Fields

The fields.yaml file is structured the same as fieldsets (if you’ve ever set up the Statamic Control Panel, you’ll be right at home). We’ve provided a default set of fields to get you started, but you can add or remove whichever ones you’d like with a couple of exceptions:

  • This fieldset allows you to define validation rules with the validate option; it uses the same validation library and settings that Raven Forms does
  • This fieldset is used in the Control Panel to power the edit form for members, this is where the type is used for each field; displaying these fields on the front-end will be completely up to you and the HTML that you bring
  • username and password are the fields used to log into the system, you should update the validation requirements and messages as you see fit, but you’ll need to have these fields for members to be able to log in
  • roles aren’t specifically required for use on the front-end, but users will need to have them to use the Control Panel (which requires the admin role)

For front-end use, this becomes the list of allowed fields in a form submission. When you save a member’s information, the member forms will loop through this list of fields as ones that should be saved. Fields not found in this list will be ignored, as will fields in this list that weren’t submitted with the form. This feature lets you control the fields that each member’s content file contains.

Checking Values Without Saving Them Added in v1.7.2

Fields can now set the save_value option (which is true by default). When this value is set to false, the value of this field will still be validated when submitted through a member form, but its value will not be stored in the member’s data.

As an example, it makes sense to set password_confirmation’s save_value to false, as we want to check its value, match it against password, but we don’t want to save it in the member’s profile.

The show_password Field

The list of default fields included with the member tag includes a show_password field. Part of the addition of front-end member functionality was updating how a member gets saved in the Control Panel. The show_password field is a helper for the Control Panel.

In the Control Panel, admins gets a full form of all fields that can be stored for a member, including the ability to reset a member’s password. The show_password field allows admins to save member information without having to reset the member’s password. In the Control Panel, this field is essentially a button that will show the password and password_confirmation field when pressed, which will then force the admin to enter a new password for the member.

For backwards-compatibility purposes, it’s a good idea to keep this field in the member-fields list. Feel free to set its save_value of false, but it’s needed if you’re going to be managing members through the control panel.

Member Login

Use the new {{ member:login_form }} tag-pair to create a form that will log a user in (assuming they type in valid credentials). A simple sample use of this tag looks like this:

{{ member:login_form }}
  <p>
    <label for="username">Username:</label>
    <input type="text" name="username" value="" id="username">
  </p>

  <p>
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">
  </p>

  <p>
    <input type="submit" name="submit" value="Log In">
  </p>
{{ /member:login_form }}

As you can see, we let you dictate the HTML here. The {{ member:login_form }} and its closing tag will be transformed into <form> and </form> tags respectively. (Note that you can pass an attribute string into the attr parameter to set a class and an ID on the form tag itself.) Inside, it’s all up to you.

This tag (and thus, form) is expecting you to submit two fields: username and password. They must be named this, and these will be the two variables used to determine if a member can sign in or not.

There are more tags and parameters that you can use to customize the form and display even further:

{{ member:login_form return="/member-home" allow_request_return="true" }}
  {{ if error }}
    <p class="error">{{ error }}</p>
  {{ endif }}

  <p>
    <label for="username">Username:</label>
    <input type="text" name="username" value="{{ old_values:username }}" id="username">
  </p>

  <p>
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">
  </p>

  <p>
    <input type="submit" name="submit" value="Log In">
  </p>
{{ /member:login_form }}

In this example we’re doing a couple of new things:

  • We’ve added the return parameter, this gives us a last-minute chance to customize where members may be sent once they successfully log in
  • We’ve also added the allow_request_return parameter and set it to true, this gives us another way to tell the tag where to redirect a successful log in, more on that below
  • The {{ error }} tag will be populated with a notice that something’s gone wrong if it has
  • The {{ old_values }} tag is a list containing all of the previously submitted information which can then be used in your form, here we’re using {{ old_values:username }} to get the submitted value of username in case something goes wrong, note that we’re not doing this for password as to not display the information in the source of the page in plain-text, however, use these how you want

On a Successful Login

There are four values that {{ member:login_form }} will use to determine where to send a successfully logged in member:

  1. If allow_request_return is set to true, it will look for the return GET variable in the URL string and send the user there; this will be automatically appended by Statamic’s protect functionality if a user attempts to visit a page that requires them to be logged in
  2. If that isn’t available, it will try to use the value you’ve set in the return parameter on the tag
  3. If that isn’t available, it will try to use the value set to member_home in your member config file
  4. If that isn’t available, it will use your site’s root

Note that allow_request_return is false by default.

Member Registration

Use the new {{ member:register_form }} tag-pair to create a form that will create a user in the system. An example of this tag looks like this:

{{ member:register_form }}
  {{ if error }}
    <p class="error">{{ error }}</p>
  {{ endif }}

  <p>
    <label for="username">Username:</label>
    <input type="text" name="username" value="{{ old_values:username }}" id="username">
    {{ if field_errors:username }}
      <br>
      <small class="error">{{ field_errors:username }}</small>
    {{ endif }}
  </p>

  <p>
    <label for="email">Email:</label>
    <input type="text" name="email" value="{{ old_values:email }}" id="email">
    {{ if field_errors:email }}
      <br>
      <small class="error">{{ field_errors:email }}</small>
    {{ endif }}
  </p>
  <p>
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">
    {{ if field_errors:password }}
      <br>
      <small class="error">{{ field_errors:password }}</small>
    {{ endif }}
  </p>
  <p>
    <label for="password_confirmation">Password Again:</label>
    <input type="password" name="password_confirmation" value="" id="password_confirmation">
    {{ if field_errors:password_confirmation }}
      <br>
      <small class="error">{{ field_errors:password_confirmation }}</small>
    {{ endif }}
  </p>

  <p>
    <input type="submit" name="Submit" value="Sign Me Up">
  </p>
{{ /member:register_form }}

Once again, we let you create your own HTML. The {{ member:register_form }} tag and its closing counterpart will create the <form> and </form> tag respectively, and once again you can use the attr parameter to set an attribute string to set the form’s class and ID.

Each of the field’s names within the form must match to a field in your fields.yaml file. These are the only fields that will be accepted and saved to the member’s profile.

Setting New Member Roles

Most of the time, new members will need some roles assigned to them so that they can do different things on your site. You get to choose these roles with the new_member_roles field in your member.yaml config file. When a user successfully registers as a member, their account will automatically be assigned the roles in this list.

Remember, the admin role can use the Control Panel. Assign that role carefully.

It’s best to remember that these are starting roles for the user. You can later either manually add roles to users in their files, update their account through the Control Panel, or have add-ons automatically add or remove roles as needed when members perform certain tasks.

If Errors Occur

If the user either misses a required field, or enters all of the fields but fills in some incorrectly (or any combination of these), they will be redirected back to this form and a couple of new tags will be available for use.

  • error will state any general form errors
  • field_errors is a list of field-specific errors and corresponding messages, which can be set in the fields.yaml file
  • old_values is a list of the values submitted by the user so that you can re-populate the form

Users can keep submitting this form until they get it all right.

On Successful Submission

Once the user fills in everything correctly, there are a couple of parameters that can be set to alter where they’re taken from here:

  • auto_login (which is true by default) will automatically log in the user as the member account they’ve just created
  • The same return system that {{ member:login_form }} uses

Once Logged In

Once a member is logged in, their attempts to view pages protected with _protect will resolve differently. In addition to using the built in protect scheme, you can look for a logged-in member in your templates with a couple of automatically-made variables:

  • logged_in will be true if the current user is logged in
  • current_member will be a list that contains the current member’s profile, including is_[role] checks

Update Member Profile

Providing a way to let members update their profile works similarly to how the {{ member:register_form }} works. An example of the {{ member:profile_form }}:

{{ member:profile_form }}
  {{ if error }}
    <p class="error">{{ error }}</p>
  {{ elseif success }}
    <p class="success">{{ success }}</p>
  {{ endif }}

  <p>
    Username: {{ old_values:username }}
  </p>

  <p>
    <label for="first_name">First Name:</label>
    <input type="text" name="first_name" value="" id="first_name">
    {{ if field_errors:first_name }}
      <br>
      <small class="error">{{ field_errors:first_name }}</small>
    {{ endif }}
  </p>

  <p>
    <label for="last_name">Last Name:</label>
    <input type="text" name="last_name" value="" id="last_name">
    {{ if field_errors:last_name }}
      <br>
      <small class="error">{{ field_errors:last_name }}</small>
    {{ endif }}
  </p>

  <p>
    <label for="email">Email:</label>
    <input type="text" name="email" value="{{ old_values:email }}" id="email">
    {{ if field_errors:email }}
      <br>
      <small class="error">{{ field_errors:email }}</small>
    {{ endif }}
  </p>

  <p>
    <input type="submit" name="Submit" value="Update Profile">
  </p>
{{ /member:profile_form }}

As you should be getting used to be now, we let you write your own HTML. The {{ member:profile_form }} tag and its closing counterpart will create the <form> and </form> tag respectively, and once again you can use the attr parameter to set an attribute string to set the form’s class and ID.

Each of the field’s names within the form must match to a field in your fields.yaml file. These are the only fields that will be accepted and saved to the member’s profile.

Using Old Values

This tag differs from the {{ member:register_form }} tag in that the {{ old_values }} tag will be available to you before the user submits anything. This will be filled with existing member data. As you can see in the example above, this lets us do two things:

  1. Pre-populate the form with the right information; if a user changes a field’s value to something and then attempts to save their profile, this tag will redirect them back to the form, and their invalid submission will now be in {{ old_values }}
  2. We can display information about the user without necessarily doing that with an input tag, as we’re doing with username

Field Errors

Once again, field errors will be available to you in the {{ field_errors }} tag. You can check for their existence the same way you did on the registration form.

On Success

Like the other tags, {{ member:profile_form }} has a return attribute, but it isn’t as flexible as it is with other tags. If you include it as a parameter, the user will be redirected to the URL you specify upon success. If you don’t include it, the user will be redirected back to the current page. There are no options to use the return GET variable, as updating a profile shouldn’t stand in the way of someone accessing a dynamic page.

You can use {{ success }} like you do with {{ error }} to display a success message above your form.

Displaying a Profile

If you’re just looking to display profile information and not edit it, you can continue to use the {{ member:profile }} tag. You specify the username parameter to say which member’s profile you want to look up, and between the tags you can use all of the fields in their stored profile. As of v1.8.1, you can alternatively specify the uid parameter with a member’s unique ID.

For example:

{{ member:profile username="{segment_2}" }}
  {{ if no_results }}
    <p class="error">Could not find a profile for member <em>{{ segment_2|sanitize }}</em>.</p>
  {{ else }}
    <dl class="profile">
      <dt>Username</dt>
      <dd>{{ segment_2|sanitize }}</dd>

      <dt>Full Name</dt>
      <dd>{{ first_name }} {{ last_name }}</dd>

      <dt>Email</dt>
      <dd><a href="mailto:{{ email }}">{{ email }}</a></dd>
    </dl>
  {{ endif }}
{{ /member:profile }}

Members, Unique IDs, and Add-ons

Members are now each automatically assigned a unique ID that is stored within their profile (a field called _uid). In your add-on files, you can access the unique ID from any Member object, like this:

<?php
class Plugin_sample extends Plugin
{
  public function index()
  {
    // check that someone is logged in
    if (!Auth::isLoggedIn()) {
      return false;
    }

    $member = Auth::getCurrentMember();
    $uid = $member->getUID();  // this is the UID
  }
}
?>

UID vs. Username

Add-ons that want to store information about members should always use the unique ID and never use the username.

A member’s username is the name of the YAML file that’s keeping that member’s information stored in the filesystem. Although this provides an easy way of making sure that all usernames will be unique (because you can’t have two files named the same thing), the username is too easy for admins (or members, if you let them) to change.

The problem with add-ons attaching information to a username is that once someone does change a member’s username, all of the connections linked to that username are broken. It would be impossible to automatically search through (third-party add-on) data to update the member’s username everywhere, and although we probably could fire a hook for such an event, this makes for a more complex machine than necessary. The more moving parts, the more chance for things to break. This is where the idea for the unique ID comes from.

A member’s _uid field is checked for on every login. If it’s not found in the member’s profile (which usually happens because either the user account was created either by hand or it was created in a version before v1.7), it will be added. When you create a member through the {{ member:register_form }} tag, the _uid field will be created at the same time.

Every profile save, every login, and every member registration will ensure that this field is there. This means that as of v1.7, you can rely on every logged-in member having a _uid field to which you can link things.

Resetting Passwords

As of 1.9, you can provide a way for your members to reset their own passwords.
Resetting a password is a 3 step process:

  1. Visit a forgot password page, where a member enters their username into a {{ member:forgot_password_form }}.
  2. They will receive an email containing a link to a reset password page.
  3. On this page, they can enter a new password into a {{ member:reset_password_form }}.

The member has a set amount of time (defaulting to 20 minutes) to click the link in their email and reset the password. Once this time has elapsed, the link will become void. To change this length, adjust the reset_password_age_limit variable in member.yaml.

Workflow setup

Create two URLs: one for the {{ member:forgot_password_form }} and one for the {{ member:reset_password_form }}. In your site navigation, direct users to the ‘forgot password’ template. You should never take your users directly to the ‘reset password’ page – they will access it through the link in the email.

You should specify the page containing the {{ member:reset_password_form }} in your member.yaml file using reset_password_url – it defaults to /member/reset-password.

In order for emails to be sent, you’ll need to set some email variables in your general site settings and your member.yaml file.

General settings:

  • _email_sender
  • _email_handler
  • _email_handler_key

Member settings:

  • reset_password_subject – The subject line of the email
  • reset_password_text_email – The template containing your text version.
  • reset_password_html_email – The template containing your HTML version, if you want one

In your email templates, the {{ reset_url }} variable is available to you.

note If your forgot_password_form renders nothing, you most likely haven’t set your template path.

This article was last updated on September 13th, 2016. Find an error? Please let us know!