Working with Forms in Django

This article explains how web forms work, how HTML forms send data, the difference between GET and POST methods, and how Django simplifies building, rendering, and processing forms. It also introduces Django’s Form class and its role in managing user input securely and efficiently.

Django formsHTML formGET and POST

~13 min read • Updated Mar 14, 2026

Introduction

In web development, a form is one of the most essential tools for collecting user input. If you plan to build websites that do more than simply display content, understanding forms is crucial. Django provides a powerful set of tools and libraries to help you build, render, and process forms securely and efficiently.


HTML Forms

In HTML, a form is a collection of elements inside the <form> tag that allows users to enter text, choose options, upload files, or interact with controls, and then send that data to the server. Some elements like text input or checkbox are built into HTML, while more advanced components like a date picker or slider rely on JavaScript and CSS.


Essential Form Attributes

Every form must define two key attributes:

  • action: the URL where the submitted data should be sent.
  • method: the HTTP method used to send the data.

For example, the Django admin login form includes several input elements and sends its data via the POST method to the /admin/ URL.


GET and POST Methods

Forms typically use only two HTTP methods: GET and POST. The POST method packages the form data and sends it to the server, while GET appends the data as a query string in the URL.


When to Use GET

  • For requests that do not modify server data.
  • For actions like search forms that can be bookmarked or shared.

When to Use POST

  • For actions that change the system state, such as database updates.
  • For sensitive data like password fields.
  • For safer handling of user input, especially with CSRF protection.

Django’s Role in Handling Forms

Managing forms can be complex: preparing data, rendering HTML, validating input, and saving results. Django simplifies this process and handles three major tasks:

  • Preparing and structuring data for display.
  • Generating HTML forms.
  • Receiving and processing submitted data.

Although you can handle all of this manually, Django provides tools that make the process more secure and efficient.


Forms in Django

In a web application, the term form may refer to the HTML form, the Django Form class that generates it, the submitted data, or the entire workflow. At the center of this system is Django’s Form class.


The Django Form Class

The Form class works similarly to a model: it defines the structure, behavior, and representation of user input. Each field in a form corresponds to an HTML input element. A ModelForm maps model fields to form fields, which is how the Django admin interface is built.


Widgets

Each form field is displayed using a widget, which is a user interface component. Widgets can be customized or replaced as needed.


Instantiating, Processing, and Rendering Forms

Rendering a form in a template is similar to rendering any other object, but with some differences. Unlike models, which usually need data to be useful, forms can be rendered empty because their purpose is to collect user input.


Using Forms in a View

In a view, you typically instantiate a form rather than retrieve it from the database. A form can be:

  • Empty.
  • Prepopulated with data from a model instance.
  • Filled with data submitted by the user.

Example: Creating a Simple Form


from django import forms

class ContactForm(forms.Form):
    name = forms.CharField()
    email = forms.EmailField()

Conclusion

Forms are a fundamental part of user interaction on the web, and Django provides powerful tools to build, render, and process them securely. With the Form class and its related features, developers can create structured, safe, and efficient form-handling workflows.

Introduction

Creating a form is one of the most essential tasks in web development. Whether you want to collect a user’s name or process complex multi-step input, forms are the foundation of user interaction. Django provides a powerful and structured way to build, validate, and process forms, reducing the amount of manual work required.


Building a Basic HTML Form

Suppose you want to create a simple form that collects a user’s name. In your template, you might write:


<form action="/your-name/" method="post">
    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" value="{{ current_name }}">
    <input type="submit" value="OK">
</form>

This form sends data to /your-name/ using the POST method. The current_name variable, if present in the template context, pre-fills the input field. You will need a view to render this form and provide the current_name value when necessary.


Handling Form Submission

When the form is submitted, the browser sends a POST request containing the form data. You need a view that receives this data, extracts the key/value pairs, and processes them. While this example is simple, real-world forms may contain many fields, require prepopulation, or involve multiple edit-submit cycles.


At this point, letting Django handle most of the work becomes much easier.


Building a Form in Django

The Form Class

To build the same form in Django, start by defining a Form class:


from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label="Your name", max_length=100)

This creates a form with a single field, your_name. The label defines the text shown in the HTML <label> tag. The max_length attribute sets both the browser’s maxlength and Django’s server-side validation.


A form instance includes an is_valid() method. When called, it:

  • returns True if all fields contain valid data
  • stores validated data in cleaned_data

When rendered for the first time, the form looks like:


<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100" required>

Note that Django does not include the <form> tag or submit button; you must add those in the template.


The View

Form data sent to a Django website is processed by a view, usually the same one that displays the form. This allows reuse of logic for both displaying and processing the form.


Here is an example view:


from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import NameForm

def get_name(request):
    if request.method == "POST":
        form = NameForm(request.POST)
        if form.is_valid():
            return HttpResponseRedirect("/thanks/")
    else:
        form = NameForm()

    return render(request, "name.html", {"form": form})

If the request is GET, Django creates an empty form. If it is POST, Django binds the submitted data to the form. A bound form is validated using is_valid(). If validation fails, the form is re-rendered with the submitted data for correction.


The Template

The template for rendering the form is simple:


<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit">
</form>

Django expands {{ form }} into the appropriate HTML markup for all fields.


CSRF Protection

Django includes built-in protection against Cross Site Request Forgery. When submitting a form via POST, you must include the {% csrf_token %} tag. Although this tag is not shown in all examples, it is required for secure form submission.


HTML5 Input Types and Browser Validation

If your form includes fields like URLField, EmailField, or integer fields, Django will use HTML5 input types such as url, email, and number. Browsers may apply their own validation, which can be stricter than Django’s. To disable browser validation, you can add novalidate to the form tag or use a different widget.


More About Django Form Classes

All form classes inherit from django.forms.Form or django.forms.ModelForm. ModelForm is especially useful when your form is tied directly to a model, as it automatically generates fields and validation rules based on the model definition.


Conclusion

You now have a working web form built with Django, processed by a view, and rendered as an HTML <form>. This foundation prepares you to explore more advanced features of Django’s form system, including custom validation, widgets, and ModelForm integration.

Introduction

In Django’s form system, understanding the difference between bound and unbound forms is essential. This distinction determines whether a form contains submitted data, whether it can be validated, and how it should be rendered. Django also provides powerful tools for managing fields, widgets, validated data, and advanced rendering techniques.


Bound and Unbound Form Instances

An unbound form has no associated data. When rendered, it appears empty or contains default values.
A bound form contains submitted data and can be validated. If a bound form is invalid, it can display inline error messages next to the fields.


The is_bound attribute indicates whether a form has data bound to it.


Working with Form Fields

Consider a more practical form, such as a contact form:


from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

This form includes four fields: subject, message, sender, and cc_myself. Django provides many field types, including CharField, EmailField, and BooleanField.


Widgets

Each form field uses a widget to determine how it is rendered in HTML. For example, CharField uses TextInput by default, producing an <input type="text">. To use a textarea, you must specify Textarea as the widget, as shown in the message field.


Field Data

After calling is_valid() and receiving True, validated data is stored in cleaned_data. Django converts values into appropriate Python types. For example, cc_myself becomes a boolean.


Here is how the data might be processed in a view:


from django.core.mail import send_mail

if form.is_valid():
    subject = form.cleaned_data["subject"]
    message = form.cleaned_data["message"]
    sender = form.cleaned_data["sender"]
    cc_myself = form.cleaned_data["cc_myself"]

    recipients = ["[email protected]"]
    if cc_myself:
        recipients.append(sender)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect("/thanks/")

Working with Form Templates

To render a form in a template, simply pass the form instance into the context. Using {{ form }} automatically renders all labels and input elements.


Important Note

A form’s output does not include the surrounding <form> tag or the submit button. These must be added manually.


Reusable Form Templates

Django renders forms using templates. You can customize this by creating a custom template and configuring FORM_RENDERER. For example:


{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

Reusable Field Group Templates

Each field can be rendered using as_field_group(), which outputs the label, widget, errors, and help text together.


Example:


{{ form.non_field_errors }}
<div class="fieldWrapper">
  {{ form.subject.as_field_group }}
</div>
<div class="fieldWrapper">
  {{ form.message.as_field_group }}
</div>

Manual Field Rendering

For full control, fields can be rendered manually:


{{ form.non_field_errors }}
<div class="fieldWrapper">
    {{ form.subject.errors }}
    <label for="{{ form.subject.id_for_label }}">Email subject:</label>
    {{ form.subject }}
</div>

Rendering Form Error Messages

Field-specific errors are displayed using {{ form.field_name.errors }}, which renders an unordered list with the class errorlist.


Example:


<ul class="errorlist">
    <li>Sender is required.</li>
</ul>

Conclusion

Understanding bound and unbound forms, working with fields and widgets, processing validated data, and customizing form rendering are essential skills when building forms in Django. These tools allow you to create flexible, powerful, and fully customizable form-handling workflows.

Introduction

Django provides powerful tools for rendering form fields dynamically in templates. Looping through form fields helps eliminate repetitive HTML and makes templates more flexible. Django’s BoundField objects expose useful attributes that allow full control over how each field is displayed, including labels, errors, help text, and widget behavior.


Looping Over Form Fields

If each field in your form uses similar HTML, you can loop through them using a {% for %} tag:


{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
          <p class="help" id="{{ field.auto_id }}_helptext">
            {{ field.help_text|safe }}
          </p>
        {% endif %}
    </div>
{% endfor %}

Useful BoundField Attributes

Each field in the loop is a BoundField instance with several helpful attributes:

  • field.errors: Renders a <ul class="errorlist"> containing validation errors.
  • field.field: The underlying Field instance, useful for accessing attributes like max_length.
  • field.help_text: Displays any help text associated with the field.
  • field.html_name: The name used in the HTML name attribute.
  • field.id_for_label: The HTML id used for the field, useful for custom labels.
  • field.is_hidden: Indicates whether the field is hidden.
  • field.label: The field’s label text.
  • field.label_tag: The label wrapped in a <label> tag.
  • field.legend_tag: Similar to label_tag but uses <legend> for multi-input widgets.
  • field.use_fieldset: Indicates whether the field should be wrapped in a <fieldset>.
  • field.value: The current value of the field.

Using Fieldsets for Multi-Input Widgets

Some widgets contain multiple inputs and should be grouped inside a fieldset:


{% if field.use_fieldset %}
  <fieldset>
  {% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %}
  {% if field.label %}{{ field.label_tag }}{% endif %}
{% endif %}
{{ field }}
{% if field.use_fieldset %}</fieldset>{% endif %}

Looping Over Hidden and Visible Fields

When manually laying out a form, you may want to treat hidden fields differently. Django provides two methods:

  • hidden_fields()
  • visible_fields()

Example:


{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}

{% for field in form.visible_fields %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

Manual Field Rendering

For full control over layout, fields can be rendered manually:


{{ form.non_field_errors }}
<div class="fieldWrapper">
    {{ form.subject.errors }}
    <label for="{{ form.subject.id_for_label }}">Email subject:</label>
    {{ form.subject }}
</div>

Rendering Form Error Messages

Field errors appear as an unordered list with the class errorlist:


<ul class="errorlist">
    <li>Sender is required.</li>
</ul>

To customize error display:


{% if form.subject.errors %}
    <ol>
    {% for error in form.subject.errors %}
        <li><strong>{{ error|escape }}</strong></li>
    {% endfor %}
    </ol>
{% endif %}

Conclusion

Looping through form fields, managing hidden and visible fields, using BoundField attributes, and customizing field rendering are essential techniques for building flexible and powerful form templates in Django. These tools allow developers to create clean, reusable, and fully customized form layouts.

Written & researched by Dr. Shahin Siami