Validate a form on both sides

Use semantic form controls with labels and meaningful names. Add Zotonic validators for immediate feedback, then validate the submitted values again at the server boundary.

Test an empty form, invalid input, and a valid submission. Verify that a failed submission leaves the entered values available and explains which field needs attention.

Treat repeated submissions as a normal possibility. A disabled submit button helps interaction but cannot guarantee a write happens only once. Enforce any uniqueness or retry rules in the operation itself.

Start with a named, required email field:

<form id="garden-contact" method="post" action="postback">
    <label for="garden-email">{_ Email _}</label>
    <input id="garden-email" name="email" type="email">
    {% validate id="garden-email" name="email" type={presence} type={email} %}
    <button type="submit">{_ Check address _}</button>
</form>
{% wire id="garden-contact" type="submit" postback={check_email} delegate=`mod_garden` %}

Add this clause to the exported event/2 in mod_garden, using the header and declarations from the postback task. Broaden its spec to accept #postback{} | #submit{} and separate clauses with a semicolon:

event(#submit{message = {check_email, []}}, Context) ->
    case z_context:get_q_validated(<<"email">>, Context) of
        Email when is_binary(Email), Email =/= <<>> ->
            z_render:growl(?__("Address checked; nothing was sent.", Context), Context);
        _ ->
            z_render:growl_error(?__("Enter an email address.", Context), Context)
    end.

Zotonic's submit pipeline runs the attached validators before calling the handler. This example checks input and shows feedback; it does not store an address or send mail. Do not treat validation as permission to subscribe or email someone. Check missing, malformed, and valid addresses, and also reject invalid input in any API that calls the same domain operation.

Handle a postback on the server

Use the active Garden module as the delegate. Add this header, export, and callback to mod_garden.erl, merging with declarations and event clauses already present:

-include_lib("zotonic_core/include/zotonic.hrl").
-export([event/2]).

-spec event(Event, Context) -> z:context()
    when Event :: #postback{}, Context :: z:context().
event(#postback{message = {garden_hello, []}}, Context) ->
    z_render:growl(?__("Welcome to the garden", Context), Context).

In a page with the standard JavaScript includes, mod_wires, and a final {% script %}:

<button id="garden-hello" type="button">{_ Say hello _}</button>
{% wire id="garden-hello" postback={garden_hello} delegate=`mod_garden` %}

Compile and load the module, reload the page, and click the button. Expect the greeting notification. The returned context contains the queued browser response; returning the original context after another response call can lose that response.

This public example changes no stored data. For a write, read only supported binary query keys, validate input, and authorize with the supplied context before calling a model. A signed postback does not make arbitrary form values trustworthy. A form submit delivers #submit{}, not #postback{}.

In this section

  1. 1acceptance
  2. 2confirmation
  3. 3custom
  4. 4date
  5. 5email
  6. 6email_unique
  7. 7format
  8. 8length
  9. 9name_unique
  10. 10numericality
  11. 11postback
  12. 12presence
  13. 13username_unique

Also in: Developer guide

Referred by

Developer guide

Create a reusable module

Create apps_user/zotonic_mod_garden with the following files. This example adds a discoverable module without starting an extra process.

Validators

name_unique

A validator to check whether a resource’s name is unique:

Validators

acceptable_password

A validator to check whether a password conforms to the password secutiry requirements.

Validators

hasedge

A validator to check if a resource has a certain number of edges with a predicate.

Validators

page_path_unique

A validator to check whether a resource’s page path is unique: