Homepage

[DEPRECATED] Creating a Sign-Up Form

Last edit: Sep 16, 2024

Warning

This article series promotes UserProfiles and Forms, which are deprecated. We decided to reduce the learning curve by promoting explicit implementation via Liquid, Pages and GraphQL, instead of built-in features, which add magic into the mix increasing the learning curve and making debugging harder. Please refer to our Get Started to read up-to date articles, including User Authentication

This guide will help you create sign-up forms for users.

Requirements

To follow the steps in this tutorial, you should be familiar with the required directory structure for your codebase, and understand the concept of users. You'll use the User Profiles created in a previous tutorial.

Steps

Creating a sign-up form is a two-step process:

Step 1: Create form files

Create a form to sign up developers first. Create a liquid file in the app/forms/developer/ directory, and name it developer_sign_up.liquid.

Then create a liquid file in the forms/client/client_sign_up.liquid directory named client_sign_up.liquid.

Step 2: Configure forms

Developer form

Add the following content to the developer_sign_up.liquid sign up form:


---
name: developer_sign_up
resource: User
resource_owner: anyone
redirect_to: /sign-in
fields:
  email:
  password:
  first_name:
    validation: { presence: true }
  properties:
    phone_number:
      validation:
        phone_number: true
  profiles:
    developer:
      properties:
        enabled:
          property_options:
            default: true
            readonly: true
      validation:
        presence: true
    validation:
      presence: true
---
{% form %}
  <label for="first_name">First name</label>
  <input name="{{ form.fields.first_name.name }}" value="{{ form.fields.first_name.value }}" id="first_name" type="text">
  {% if form.errors.first_name %}
    <p>{{ form.errors.first_name }}</p>
  {% endif %}

  <label for="email">Email</label>
  <input name="{{ form.fields.email.name }}" value="{{ form.fields.email.value }}" id="email" type="email">
  {% if form.errors.email %}
    <p>{{ form.errors.email }}</p>
  {% endif %}

  <label for="password">Password</label>
  <input name="{{ form.fields.password.name }}" id="password" type="password">
  {% if form.errors.password %}
    <p>{{ form.errors.password }}</p>
  {% endif %}

  <label for="first_name">Phone Number</label>
  <input name="{{ form.fields.properties.phone_number.name }}" value="{{ form.fields.properties.phone_number.value }}" id="phone_number" type="text">
  {% if form.errors.properties.phone_number %}
    <p>{{ form.errors.properties.phone_number }}</p>
  {% endif %}

  <input value="{{ form.fields.profiles.developer.properties.enabled.value }}" name="{{ form.fields.profiles.developer.properties.enabled.name }}" type="hidden">

  <button>Create</button>
{% endform %}

  • name: the name of the form, written in snake case. It is used to embed the form in a page.
  • resource: defines what is the root resource in this form. In our example it is User.
  • redirect_to: defines the url to which the user will be redirected after success. You can use liquid. In our example, you will redirect the user to /sign-in.
  • configuration: defines which fields should be acceptable by the form, and it's possible to define validation of each field. Depending on the resource, some fields are available without having to create Properties. Some of them have pre-defined validation due to the backend requirements. In this example, such fields are email, with validation that ensures uniqueness (no two users can sign up with the same email address), it has to be a valid email, and it can't be blank; and password, which has to be minimum 6 characters long (this is the default validation).
  • password: When a user signs up, and you pass the password to the password field, platformOS stores it as an encrypted password in the database. The encryption uses the bcrypt password hashing function. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

Configuration

  • first_name: marked as required.
  • properties: starts the nested form where you can decide which user properties defined in user.yml are accessible through this form. In this example, you added phone_number, defined in the previous tutorial.
  • profiles: this is a special abstraction, to easily create the UserProfile resource associated with the newly signed up User. You'll learn about a similar concept later on. Within profiles, you can specify names of the profiles created in user_profile_types directory. In our example, you have two choices: developer and client. Since you created the sign up for developer, this is what you used. Now, the developer is a UserProfile resource, so you have access to its field.
  • property_options: a special construction to add special behavior to the field. Here you used default, which provides the default value (i.e. the value that will be used if user does not fill in this field); readonly on the other hand ignores the user's input. This combination is useful if you want to set something in a background, and don't want the user to be able to override it. Because any input by the user will be ignored, the default value will always be used.

All of this might be a bit confusing at first sight, like what really is this enabled field. All should be clearer, once you realize that the last part of the configuration, which is

developer:
  properties:
    enabled:
      property_options:
        default: true
        readonly: true
  validation:
    presence: true

can be also represented as an independent form, with configuration like this:

...
resource: UserProfile
fields:
  properties:
    enabled:
      property_options:
        default: true
        readonly: true
...

In other words, forms are nested in each other – you can access them as 'nested forms'. In this example, you want to create a User and a corresponding UserProfile at the same time. This behavior is desired if in your UI you have a page with a button that says "Sign up as a Developer". User provides first name, email, password and that's it-the user registers as a developer.

Alternatively, the UI can split the process into two separate actions. It can first ask the user to register, so the button would be just 'Sign up', and only ask after registration if the user wants to become a developer or a client. In this scenario, the first form would look like this:

---
name: generic_sign_up
resource: User
resource_owner: anyone
redirect_to: /
fields:
  email:
  password:
  first_name:
    validation: { presence: true }
---

and the second one would be similar to:

---
name: become_developer_form
resource: UserProfile
redirect_to: /
fields:
  properties:
    enabled:
      validation: { presence: true }
      property_options:
        default: true
        readonly: true
---

The end result would be the same. The point is, that with platformOS, you have the power of creating multiple resources within one form submission-which might be more complex in terms of configuration, but it can greatly simplify UX.

Rendering inputs

The second part of the form is the actual HTML that will be presented to the user. There are few things that you will want to include in every form.

  • form tag: {% form %} form tag renders the <form> html tag with action set based on the resource provided in configuration. It also adds a bunch of hidden inputs. The most important are CSRF token (called authenticity-token) and _method - depending whether you want to create or update the resource, it will be either post or patch. In case you want to allow users to destroy their resources, you would need to manually pass method as argument to tag with value delete. Another set of hidden inputs are resource_id - the one that you passed in include_form if any, and slug, slug2, slug3 - in case of validation errors, you most likely will need those to be available. Please note: If you want to invoke our write API manually (for example via AJAX or even from third-party API) we recommend to take a look at full API Endpoints documentation.

Optional inputs

  • <input value="{{ page.id }}" type="hidden" name="page_id" />: page is used to notify the API which page should be rendered if submission of the form fails (most likely due to validation error). This allows to re-display user's input for all fields and display validation errors where applicable. By default, it renders the page from which the form was submitted.

Inputs

Proceeding with rendering proper inputs that the user will be expected to fill:

  • input: utility liquid tag that can save quite a bit of work-by default it is configured to generate HTML that is compatible with Bootstrap 3 framework. Moreover, in case of validation errors, it automatically adds 'error' CSS class to the input and renders a validation error message below the input. If bootstrap 3 is not the framework of your choice, in the How-To Guides section we explain how to create your own re-usable set of inputs.

Client form

Add the following content to the client_sign_up.liquid sign up form:


---
name: client_sign_up
resource: User
resource_owner: anyone
redirect_to: /sign-in
fields:
  email:
  password:
  first_name:
    validation:
      presence: true
  profiles:
    client:
      properties:
        enabled:
          property_options:
            default: true
            readonly: true
      validation:
        presence: true
    validation:
      presence: true
---

{% form %}
  <label for="first_name">First name</label>
  <input name="{{ form.fields.first_name.name }}" value="{{ form.fields.first_name.value }}" id="first_name" type="text">
  {% if form.errors.first_name %}
    <p>{{ form.errors.first_name }}</p>
  {% endif %}

  <label for="email">Email</label>
  <input name="{{ form.fields.email.name }}" value="{{ form.fields.email.value }}" id="email" type="email">
  {% if form.errors.email %}
    <p>{{ form.errors.email }}</p>
  {% endif %}

  <label for="password">Password</label>
  <input name="{{ form.fields.password.name }}" id="password" type="password">
  {% if form.errors.password %}
    <p>{{ form.errors.password }}</p>
  {% endif %}

  <input value="{{ form.fields.profiles.client.properties.enabled.value }}" name="{{ form.fields.profiles.client.properties.enabled.name }}" type="hidden">

  <button>Create</button>
{% endform %}

Next steps

Congratulations! You now know how to create sign-up forms for your users. Now you can embed the sign up form in a page:

Questions?

We are always happy to help with any questions you may have.

contact us