# Validate a form on both sides

Source: https://zotonic.com/docs/1419/validate-a-form-on-both-sides

URI: https://zotonic.com/id/1419

Language: en

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:

```django
<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:

```erlang
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:

```erlang
-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 %}`:

```django
<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{}`.

License: 

Attribution: 

Related resources: https://zotonic.com/api/agent/related/1419?language=en


## API access

- [API help](<https://zotonic.com/api-help>)
- [OpenAPI specification](<https://zotonic.com/openapi.json>)
- [This resource as JSON](<https://zotonic.com/api/agent/resource/1419?language=en>)
