How to use Django forms: Creating, rendering and handling forms

Django forms are Python classes that define the structure, validation rules, and rendering behavior of HTML forms in Django web applications. For example, the Form class lets you create a form with custom logic, while the ModelForm enables you to easily include fields in your web application based on a model.
Rendering forms is also straightforward with helper functions, which use pre-configured wrappers to display these fields. In terms of logic, you can handle form submissions and validation gracefully using a simple workflow involving request.POST, form.is_valid(), and the cleaned_data property.
Moreover, you can set error handling method easily using the default template variables, and even customize them for a more personalized notification. This allows you to give more details and personalize the error message, which helps improve user experience.
A distinguishing feature of Django is formset, which lets you manage them from a single page. Learn more about Django forms in more detail, including how to properly handle submissions and errors.
How to create Django forms
You can create forms in Django using two main methods: by using the Form class to define fields manually or automatically generating them from Django models using ModelForm.
Use Form to define fields that don’t directly relate to a model or need custom logic. Meanwhile, use ModelForm to create forms based on an existing model, which helps minimize boilerplate code and ensures schema consistency.
To create a form in Django, define a form class in your forms.py file. This class specifies the form fields and validation rules to effectively process user input.
Creating forms manually with forms.Form
Here’s an example of creating a simple contact form manually:
# forms.py from django import forms class ContactForm(forms.Form): name = forms.CharField(max_length=100, label='Your Name') email = forms.EmailField(label='Your Email') message = forms.CharField(widget=forms.Textarea, label='Your Message')
This form includes three fields: name, email, and message. Each field is defined using Django’s form field classes:
- CharField for short text inputs like the name.
- EmailField for email validation.
- CharField with a Textarea widget for longer text inputs, such as messages.
Generating forms from models using ModelForm
In many cases, forms are tied to models. Django simplifies this by letting you create forms directly from models using ModelForm. This saves time by automatically generating form fields based on the model’s fields.
- Creating a ModelForm form:
If you have a Post model in your models.py file:
# models.py from django.db import models class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True)
You can create a form for this model as follows:
# forms.py from django import forms from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'content']
- Using the ModelForm form in a view:
Next, use the form in your Django view like this:
# views.py from django.shortcuts import render from .forms import PostForm def create_post(request): if request.method == 'POST': form = PostForm(request.POST) if form.is_valid(): form.save() # Saves the form data to the database return redirect('post_list') # Redirect to a list of posts else: form = PostForm() return render(request, 'create_post.html', {'form': form})
With ModelForm, Django automatically generates form fields based on the model’s fields and handles saving the form to the database with minimal boilerplate code.
Rendering forms in Django templates
Once a form is created, it must be rendered in a template to collect user input. This step is crucial for user experience, providing the flexibility to choose between quick prototyping with Django’s built-in shortcuts or full customization through manual rendering.
Django provides two approaches for rendering forms in templates: using built-in rendering shortcuts and manual rendering for full control.
Simple rendering with helper functions
Django provides built-in rendering tools called helper functions. Since these functions are pre-configured, you don’t need to write the HTML code to render forms in templates. This approach is suitable during prototyping because it’s easier and more efficient than manual rendering.
While simple, using helper functions is less flexible since you can only use three methods to render a form with different structures:
- form.as_p – wraps each form field in a <p> tag, ideal for quick and clean styling.
- form.as_table – displays form fields in an HTML table, useful for structured layouts.
- form.as_ul – wraps each field in <li> tags for list-style formatting.
Here’s an example of using form.as_table:
<form method="post"> {% csrf_token %} {{ form.as_table }} <button type="submit">Submit</button> </form>
- {% csrf_token %} – adds a CSRF token to protect against cross-site request forgery attacks. This is required for forms that use POST submission.
Custom rendering with HTML
Custom rendering Django forms with HTML means manually writing the code to access and render the fields. This approach gives you complete control of the forms’ appearance and behavior, making it suitable if you want more in-depth customization.
The custom rendering approach is also ideal when you need to integrate forms into a custom-designed HTML structure or apply advanced CSS styling.
To select and access a Django form to render, use its field name (e.g. form.name). Below is an example of a full custom HTML layout that renders the form.name Django form:
<form method="post" action="/submit-form/"> {% csrf_token %} <div class="form-group"> <label for="id_name">Name</label> <input type="text" id="id_name" name="name" class="form-control" value="{{ form.name.value|default:'' }}"> {% for error in form.name.errors %} <div class="error">{{ error }}</div> {% endfor %} </div> <div class="form-group"> <label for="id_email">Email</label> {{ form.email }} {% for error in form.email.errors %} <div class="error">{{ error }}</div> {% endfor %} </div> <div class="form-group"> <label for="id_message">Message</label> {{ form.message }} {% for error in form.message.errors %} <div class="error">{{ error }}</div> {% endfor %} </div> <button type="submit" class="btn btn-primary">Send Message</button> </form>
You can use Django widgets to add CSS classes or attributes to form fields directly in your forms.py, for instance:
from django import forms class ContactForm(forms.Form): name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'})) email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'})) message = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control'}))
Then, in your template:
<form method="post"> {% csrf_token %} {{ form.name }} {{ form.email }} {{ form.message }} <button type="submit">Submit</button> </form>
Handling form submissions in Django views
When a user submits a form in Django, the data is sent to the view through an HTTP request. The form instance is then populated with that data and checked for validity. A typical submission workflow looks like this:
- Form instance → populated with → request.POST
- form.is_valid() → returns → True/False
- cleaned_data → provides → validated input
If the form is valid, the cleaned data can be saved to the database using ModelForm.save() or passed to other parts of your application for processing.
Here’s a simple example that shows a view handling a form submission:
from django.shortcuts import render from .forms import ContactForm def contact_view(request): if request.method == "POST": form = ContactForm(request.POST) if form.is_valid(): # Access validated input name = form.cleaned_data["name"] email = form.cleaned_data["email"] message = form.cleaned_data["message"] # Here you could save data or trigger other logic # Example: contact.save() if using a ModelForm return render(request, "success.html") else: form = ContactForm() return render(request, "contact.html", {"form": form})
In this example:
- request.POST passes submitted data to the form.
- form.is_valid() runs Django’s built-in and custom validation rules.
- cleaned_data gives you access to the validated input.
This is where custom validation methods, like clean_name() in the form class, come into play. They raise errors when rules are broken, and Django automatically links those errors to the form fields.
Forms in Django connect the pieces of your application: they take input from templates, validate and process it in views, and can save it directly to models. This makes them a central part of how data flows through a project.
Current examples are correct but incomplete. Add the workflow triple: Form instance → populated with → request.POST form.is_valid() → returns → True/False cleaned_data → provides → validated input Add a note that validated data can be saved to the DB (via ModelForm.save()) or processed otherwise. Connect to broader context: forms link models ↔ templates ↔ views
Validating and processing forms
Validation ensures that submitted data is both secure and reliable. Without it, users could send incomplete, malformed, or even malicious data. Django makes this process easier by providing automatic checks and letting you define your own rules when needed.
Built-in validation
Django runs several checks as soon as a form is validated. Two of the most important are:
- Required fields. Requiring all form fields to have input by default, unless explicitly set to required=False. This ensures critical fields are not left blank.
- Field types. Validating input types based on the field definition. For example:
- EmailField checks for a valid email address.
- IntegerField ensures the input is a valid integer.
- URLField validates that the input is a properly formatted URL.
These checks happen automatically whenever you call form.is_valid() in your view.
Custom validation
For more specific rules, Django lets you define your own validation logic. There are two main approaches:
- Field-level validation. Using clean_<fieldname>() to set rules that apply to one field only. For example, you can block certain usernames like admin or test.
- Form-level validation. Overriding the clean() method for rules that involve multiple fields. For example, you can enforce that a start date is earlier than an end date, or ensure that two fields together are unique.
Here’s a simple example of field-level validation:
from django import forms from django.core.exceptions import ValidationError class ContactForm(forms.Form): name = forms.CharField(max_length=100, label='Your Name') email = forms.EmailField(label='Your Email') message = forms.CharField(widget=forms.Textarea, label='Your Message') def clean_name(self): name = self.cleaned_data.get('name') if "spam" in name.lower(): raise ValidationError("Invalid name: 'spam' is not allowed.") return name
In this form, the clean_name() method runs field-level validation. Since it only checks the name field‘s value, putting the logic inside clean_name() is the most direct and efficient approach. If the input contains the word spam (case-insensitive), Django raises a ValidationError. The error is then automatically linked to the name field and displayed when the form is re-rendered.
This is an ideal application for field-level validation because the restriction applies to one specific field. You’d use form-level validation, or clean(), when the rule depends on multiple fields together, such as when checking that a start date is earlier than an end date.
Customizing form errors
Error handling in Django forms ensures users understand and fix mistakes quickly. By default, Django attaches error messages to form fields, but developers can customize both how errors are displayed in templates and what the messages say.
Default error messages
Django forms generate default error messages whenever input fails validation. For example:
- This field is required. Appears when a required field is left blank.
- Enter a valid email address. Shows up when input doesn’t match an email format.
- Ensure this value has at most X characters. Displays when text exceeds max_length in CharField.
These defaults provide basic usability without extra configuration. You can display them in your templates like this:
<form method="post"> {% csrf_token %} <div> {{ form.name.label_tag }} {{ form.name }} {{ form.name.errors }} </div> <div> {{ form.email.label_tag }} {{ form.email }} {{ form.email.errors }} </div> <div> {{ form.message.label_tag }} {{ form.message }} {{ form.message.errors }} </div> <button type="submit">Send Message</button> </form>
Customizing error display in templates
Developers can override the default messages with the error_messages parameter when defining fields in the form class. This is especially useful for providing user-friendly language or instructions tailored to your audience, which helps improve overall user experience.
In the following example, the default error This field is required is replaced with clearer, more conversational text. For instance, We need your email address to contact you gives users context and feels friendlier than the default.
from django import forms class ContactForm(forms.Form): name = forms.CharField( max_length=100, error_messages={ 'required': 'Please enter your name.', 'max_length': 'Name must not exceed 100 characters.' } ) email = forms.EmailField( error_messages={ 'required': 'We need your email address to contact you.', 'invalid': 'Please enter a valid email address.' } ) message = forms.CharField( widget=forms.Textarea, error_messages={'required': 'Don’t forget to include a message!'} )
By default, error messages are rendered as plain text, but you can change their styling and placement. Django makes this possible by exposing errors for each field, so you can wrap them in your custom HTML:
<form method="post"> {% csrf_token %} <div> {{ form.name.label_tag }} {{ form.name }} {% if form.name.errors %} <div class="error">{{ form.name.errors }}</div> {% endif %} </div> <div> {{ form.email.label_tag }} {{ form.email }} {% if form.email.errors %} <div class="error">{{ form.email.errors }}</div> {% endif %} </div> <div> {{ form.message.label_tag }} {{ form.message }} {% if form.message.errors %} <div class="error">{{ form.message.errors }}</div> {% endif %} </div> <button type="submit">Send Message</button> </form>
This approach ensures errors appear directly next to the relevant fields, improving clarity and accessibility.
Using Django formsets
Formsets in Django allow you to manage multiple form instances from a single page. This abstraction layer helps you create, validate, and process several forms together as a group, rather than handling each one separately.
Formsets are useful for handling related data or dynamic forms, such as adding multiple entries to a database simultaneously. Django provides the formset_factory utility function to simplify the process of generating a formset class from any standard form class.
Creating a formset
To create a formset, define a base form in the forms.py file:
# forms.py from django import forms class ItemForm(forms.Form): name = forms.CharField(max_length=100) quantity = forms.IntegerField(min_value=1)
Then, generate a formset using formset_factory in views.py:
# views.py from django.shortcuts import render from django.forms import formset_factory from .forms import ItemForm def manage_items(request): ItemFormSet = formset_factory(ItemForm, extra=3) # Create 3 empty forms by default if request.method == "POST": formset = ItemFormSet(request.POST) if formset.is_valid(): for form in formset: # Process each form's cleaned data print(form.cleaned_data) return redirect('success') # Redirect after processing else: formset = ItemFormSet() return render(request, 'manage_items.html', {'formset': formset})
Using model formsets
The formset_factory utility will generate a formset from a standard form class. If you want to create a formset from a model, use Django’s modelformset_factory instead:
from django.forms import modelformset_factory from .models import Item def manage_items(request): ItemFormSet = modelformset_factory(Item, fields=('name', 'quantity'), extra=3) if request.method == "POST": formset = ItemFormSet(request.POST) if formset.is_valid(): formset.save() # Save all forms at once return redirect('success') else: formset = ItemFormSet() return render(request, 'manage_items.html', {'formset': formset})
What’s next after learning Django forms?
Django forms simplify handling user input with built-in validation, customizable error messages, and rendering flexibility. This is one of the more advanced features of the Python framework that helps streamline your web application development.
After understanding how forms work, it’s time to implement your knowledge by deploying a Django project. You can do this on a local development environment or using a remote system like Hostinger’s VPS, which is more convenient and customizable.
Meanwhile, we recommend checking our Django tutorial if you want to learn about other important concepts and features of this Python framework.

All of the tutorial content on this website is subject to Hostinger's rigorous editorial standards and values.