Templates
Documentation / Templates / Forms, context & pitfalls

Forms, context & pitfalls

Form error handling

Automatic display (via {% form.xxx %})

When using {% form.signup_form %}, validation errors are automatically rendered under each relevant field.

Manual display of global errors

{% if signup_form.errors %}
    <div class="alert alert-warning">
        <ul>
            {% for field_name, error_msg in signup_form.errors %}
                <li><strong>{{ field_name }}:</strong> {{ error_msg }}</li>
            {% endfor %}
        </ul>
    </div>
{% endif %}

Complete example: page with a form

// Rust handler
pub async fn signup(mut request: Request) -> AppResult<Response> {
    let form: SignupForm = request.form("signup");

    let validated = match ValidationForm::try_form(form, &request).await {
        Ok(validated) => validated,
        Err(form) => {
            // Not sent (first display): blank form, no flash.
            // Sent but invalid: re-render with the error flash.
            if !form.is_submitted() {
                context_update!(request => {
                    "title" => "Sign Up",
                    "signup_form" => &form,
                });
            } else {
                context_update!(request => {
                    "title" => "Validation Error",
                    "signup_form" => &form,
                    "messages" => flash_now!(error => "Please fix the errors"),
                });
            }
            return request.render("signup.html");
        }
    };

    match validated.save(&request.engine.db).await {
        Ok(_) => {
            success!(request.notices => "Welcome!");
            Ok(Redirect::to("/").into_response())
        }
        Err(form) => {
            context_update!(request => {
                "title" => "Validation Error",
                "signup_form" => &form,
                "messages" => flash_now!(error => "Please fix the errors"),
            });
            request.render("signup.html")
        }
    }
}
{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
    <h1>{{ title }}</h1>
    {% messages %}
    <form method="post" action="/signup">
        {% form.signup_form %}
        <button type="submit" class="btn btn-primary">Sign up</button>
    </form>
{% endblock %}

Auto-injected variables

These variables are automatically available in all templates:

VariableTypeDescription
csrf_tokenStringSession CSRF token
csp_nonceStringCSP nonce for inline scripts/styles
messagesVec<FlashMessage>Flash messages from the previous session
debugboolDebug mode status

Common pitfall: variable name collision

When using {% form.user %}, the variable user must be a form (extracted via request.form("name")):

// ❌ ERROR: "user" is a SeaORM Model, not a form
context_update!(request => {
    "user" => &db_user,  // the form filter will crash!
});

// ✅ CORRECT: separate form and DB entity
context_update!(request => {
    "user" => &form,           // form → {% form.user %} works
    "found_user" => &db_user,  // Model → {{ found_user.email }}
});