{"result":{"depiction_url":null,"edges":{"haspart":{"objects":[{"created":"2020-05-30T05:47:42Z","object_id":{"id":1743,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_acceptance","title":"acceptance","uri":"https:\/\/zotonic.com\/id\/1743"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1744,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_confirmation","title":"confirmation","uri":"https:\/\/zotonic.com\/id\/1744"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1418,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_custom","title":"custom","uri":"https:\/\/zotonic.com\/id\/1418"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1745,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_date","title":"date","uri":"https:\/\/zotonic.com\/id\/1745"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1746,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_email","title":"email","uri":"https:\/\/zotonic.com\/id\/1746"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1433,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_email_unique","title":"email_unique","uri":"https:\/\/zotonic.com\/id\/1433"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1747,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_format","title":"format","uri":"https:\/\/zotonic.com\/id\/1747"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1730,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_length","title":"length","uri":"https:\/\/zotonic.com\/id\/1730"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1748,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_name_unique","title":"name_unique","uri":"https:\/\/zotonic.com\/id\/1748"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1749,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_numericality","title":"numericality","uri":"https:\/\/zotonic.com\/id\/1749"},"seq":1000000},{"created":"2020-05-30T05:47:43Z","object_id":{"id":1750,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_postback","title":"postback","uri":"https:\/\/zotonic.com\/id\/1750"},"seq":1000000},{"created":"2020-05-30T05:47:43Z","object_id":{"id":1751,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_presence","title":"presence","uri":"https:\/\/zotonic.com\/id\/1751"},"seq":1000000},{"created":"2020-05-30T05:47:43Z","object_id":{"id":1432,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_username_unique","title":"username_unique","uri":"https:\/\/zotonic.com\/id\/1432"},"seq":1000000}],"predicate":{"id":310,"is_a":["meta","predicate"],"name":"haspart","title":{"_type":"trans","tr":{"en":"Contains"}},"uri":"http:\/\/purl.org\/dc\/terms\/hasPart"}},"hasreference":{"objects":[{"created":"2026-10-07T11:13:47Z","object_id":{"id":1402,"is_a":["text","documentation","reference","template_scomp"],"name":"doc_template_scomp_scomp_validate","title":"validate","uri":"https:\/\/zotonic.com\/id\/1402"},"seq":1},{"created":"2026-10-07T11:13:47Z","object_id":{"id":1539,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_wires","title":{"_type":"trans","tr":{"en":"Handle a browser event with a wire"}},"uri":"https:\/\/zotonic.com\/id\/1539"},"seq":2}],"predicate":{"id":2774,"is_a":["meta","predicate"],"name":"hasreference","title":{"_type":"trans","tr":{"en":"Documentation reference"}},"uri":"https:\/\/zotonic.com\/id\/hasreference"}},"references":{"objects":[{"created":"2020-05-30T05:47:42Z","object_id":{"id":1751,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_presence","title":"presence","uri":"https:\/\/zotonic.com\/id\/1751"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1418,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_custom","title":"custom","uri":"https:\/\/zotonic.com\/id\/1418"},"seq":1000000},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1750,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_postback","title":"postback","uri":"https:\/\/zotonic.com\/id\/1750"},"seq":1000000}],"predicate":{"id":332,"is_a":["meta","predicate"],"name":"references","title":{"_type":"trans","tr":{"en":"References"}},"uri":"https:\/\/zotonic.com\/id\/references"}},"relation":{"objects":[{"created":"2026-10-07T11:13:47Z","object_id":{"id":1539,"is_a":["text","documentation","developerguide"],"name":"doc_developerguide_wires","title":{"_type":"trans","tr":{"en":"Handle a browser event with a wire"}},"uri":"https:\/\/zotonic.com\/id\/1539"},"seq":1},{"created":"2026-10-07T11:13:47Z","object_id":{"id":2986,"is_a":["text","documentation","developerguide"],"name":"developer_testing_permissions","title":{"_type":"trans","tr":{"en":"Test with different users"}},"uri":"https:\/\/zotonic.com\/id\/2986"},"seq":2},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1402,"is_a":["text","documentation","reference","template_scomp"],"name":"doc_template_scomp_scomp_validate","title":"validate","uri":"https:\/\/zotonic.com\/id\/1402"},"seq":3},{"created":"2020-05-30T05:47:42Z","object_id":{"id":1750,"is_a":["text","documentation","reference","template_validator"],"name":"doc_template_validator_validator_postback","title":"postback","uri":"https:\/\/zotonic.com\/id\/1750"},"seq":4}],"predicate":{"id":303,"is_a":["meta","predicate"],"name":"relation","title":{"_type":"trans","tr":{"nl":"Relatie","en":"Relation"}},"uri":"http:\/\/purl.org\/dc\/terms\/relation"}},"subject":{"objects":[{"created":"2026-10-07T11:13:47Z","object_id":{"id":2554,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_how_to_guide","title":"How-to guide","uri":"https:\/\/zotonic.com\/id\/2554"},"seq":1},{"created":"2026-10-07T11:13:47Z","object_id":{"id":2563,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_frontend_developer","title":"Frontend developer","uri":"https:\/\/zotonic.com\/id\/2563"},"seq":2},{"created":"2026-10-07T11:13:47Z","object_id":{"id":2582,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_forms","title":"Forms","uri":"https:\/\/zotonic.com\/id\/2582"},"seq":3},{"created":"2026-10-07T11:13:47Z","object_id":{"id":2622,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_template_validator","title":"Template validator","uri":"https:\/\/zotonic.com\/id\/2622"},"seq":4},{"created":"2026-10-07T11:13:47Z","object_id":{"id":2640,"is_a":["categorization","keyword","keyword_task"],"name":"zotonic_topic_validate","title":"Validate","uri":"https:\/\/zotonic.com\/id\/2640"},"seq":5}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":1419,"is_a":["text","documentation","developerguide"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1419"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_developerguide_forms_and_validation","page_url":{"en":"https:\/\/zotonic.com\/docs\/1419\/validate-a-form-on-both-sides","x-default":"https:\/\/zotonic.com\/docs\/1419\/validate-a-form-on-both-sides"},"preview_url":null,"resource":{"body":{"_type":"trans","tr":{"en":"<p>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.<\/p>\n<p>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.<\/p>\n<p>Start with a named, required email field:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">&lt;form id=&quot;garden-contact&quot; method=&quot;post&quot; action=&quot;postback&quot;&gt;\n    &lt;label for=&quot;garden-email&quot;&gt;{_ Email _}&lt;\/label&gt;\n    &lt;input id=&quot;garden-email&quot; name=&quot;email&quot; type=&quot;email&quot;&gt;\n    {% validate id=&quot;garden-email&quot; name=&quot;email&quot; type={presence} type={email} %}\n    &lt;button type=&quot;submit&quot;&gt;{_ Check address _}&lt;\/button&gt;\n&lt;\/form&gt;\n{% wire id=&quot;garden-contact&quot; type=&quot;submit&quot; postback={check_email} delegate=`mod_garden` %}\n<\/code><\/pre>\n<p>Add this clause to the exported <code>event\/2<\/code> in <code>mod_garden<\/code>, using the header and declarations from the postback task. Broaden its spec to accept <code>#postback{} | #submit{}<\/code> and separate clauses with a semicolon:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">event(#submit{message = {check_email, []}}, Context) -&gt;\n    case z_context:get_q_validated(&lt;&lt;&quot;email&quot;&gt;&gt;, Context) of\n        Email when is_binary(Email), Email =\/= &lt;&lt;&gt;&gt; -&gt;\n            z_render:growl(?__(&quot;Address checked; nothing was sent.&quot;, Context), Context);\n        _ -&gt;\n            z_render:growl_error(?__(&quot;Enter an email address.&quot;, Context), Context)\n    end.\n<\/code><\/pre>\n<p>Zotonic&#39;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.<\/p>\n<h2>Handle a postback on the server<\/h2>\n<p>Use the active Garden module as the delegate. Add this header, export, and callback to <code>mod_garden.erl<\/code>, merging with declarations and event clauses already present:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">-include_lib(&quot;zotonic_core\/include\/zotonic.hrl&quot;).\n-export([event\/2]).\n\n-spec event(Event, Context) -&gt; z:context()\n    when Event :: #postback{}, Context :: z:context().\nevent(#postback{message = {garden_hello, []}}, Context) -&gt;\n    z_render:growl(?__(&quot;Welcome to the garden&quot;, Context), Context).\n<\/code><\/pre>\n<p>In a page with the standard JavaScript includes, <code>mod_wires<\/code>, and a final <code>{% script %}<\/code>:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">&lt;button id=&quot;garden-hello&quot; type=&quot;button&quot;&gt;{_ Say hello _}&lt;\/button&gt;\n{% wire id=&quot;garden-hello&quot; postback={garden_hello} delegate=`mod_garden` %}\n<\/code><\/pre>\n<p>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.<\/p>\n<p>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 <code>#submit{}<\/code>, not <code>#postback{}<\/code>.<\/p>"}},"category_id":{"id":317,"is_a":["meta","category"],"name":"developerguide","title":"Developer guide","uri":"https:\/\/zotonic.com\/id\/developerguide"},"content_group_id":{"id":339,"is_a":["meta","content_group"],"name":"default_content_group","title":{"_type":"trans","tr":{"en":"Default Content Group"}},"uri":"https:\/\/zotonic.com\/id\/default_content_group"},"created":"2020-05-30T05:47:21Z","creator_id":{"id":336,"is_a":["person","robot"],"name":"gitbot","title":"Git","uri":"https:\/\/zotonic.com\/id\/336"},"doc_editorial_bundle":true,"doc_editorial_edges":{"haspart":[],"hasreference":[1402,1539],"relation":[1539,2986],"subject":[2554,2563,2582,2622,2640]},"github_url":"https:\/\/github.com\/zotonic\/zotonic\/tree\/master\/doc\/developer-guide\/forms-and-validation.rst","is_authoritative":true,"is_dependent":false,"is_featured":false,"is_protected":false,"is_published":true,"is_unfindable":false,"language":["en"],"modified":"2026-10-07T20:45:37Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"name":"doc_developerguide_forms_and_validation","pivot_geocode":null,"pivot_location_lat":null,"pivot_location_lng":null,"privacy":0,"publication_end":"9999-06-01T00:00:00Z","publication_start":"2022-02-15T10:01:19Z","slug":"validate-a-form-on-both-sides","summary":{"_type":"trans","tr":{"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."}},"title":{"_type":"trans","tr":{"en":"Validate a form on both sides"}},"title_slug":{"_type":"trans","tr":{"en":"validate-a-form-on-both-sides"}},"tz":"UTC","uri":null,"version":44,"visible_for":0},"uri":"https:\/\/zotonic.com\/id\/1419","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/1419"}},"status":"ok"}