Django has no built-in nested formset. You build one by attaching a child inlineformset_factory formset to every form of a parent inline formset, giving each child formset a unique prefix (parent prefix + index), then validating and saving every level in the right order — parents first, children second.
This guide modernizes the classic recipe for Django 5.x: correct inlineformset_factory usage, per-row management_form handling, prefix collisions, validation, and the save order that keeps foreign keys intact.
Key takeaways
- A nested formset is an inline formset whose every form contains its own child inline formset (e.g. Parent -> Child -> Address).
- Django ships
formset_factory,modelformset_factoryandinlineformset_factory, but not a nested formset — you wire it by hand or use thedjango-nested-formsetpackage. - The whole trick is overriding
BaseInlineFormSet.add_fields()to hang a child formset on each form with a uniqueprefix. - You must override
is_valid()andsave()so the nested level is validated and persisted too. - Save parents before children: the parent formset must create primary keys before the nested formset can set its foreign key.
- The number-one bug is a missing or duplicated
management_form/ prefix collision, which throwsManagementForm data is missing or has been tampered with.
New to formsets? Read Django model formsets in detail first — nested formsets build directly on that foundation, and a quick refresher on Django forms basics helps too.
What is a nested formset in Django?
A formset manages multiple copies of the same form in one view: it tracks how many forms existed initially, which were changed, and which should be deleted. A model formset does the same for instances of a model, and an inline formset (inlineformset_factory) handles a set of child objects that share one foreign key — for example, all children of a parent.
A nested formset goes one level deeper: each form inside an inline formset carries its own inline formset. Think Parent -> Child -> Address, or Order -> LineItem -> Adjustment. Django does not provide this out of the box, so you assemble it from inline formsets.
Consider these models (note Django 5.x requires on_delete on every ForeignKey):
# models.py
from django.db import models
class Parent(models.Model):
name = models.CharField(max_length=255)
class Child(models.Model):
parent = models.ForeignKey(
Parent, on_delete=models.CASCADE, related_name="children"
)
name = models.CharField(max_length=255)
class Address(models.Model):
child = models.ForeignKey(
Child, on_delete=models.CASCADE, related_name="addresses"
)
country = models.CharField(max_length=255)
state = models.CharField(max_length=255)
address = models.CharField(max_length=255)Formset, model formset, inline formset or nested formset?
Pick the lightest tool that fits. Only reach for a nested formset when you genuinely need to edit two levels of children on one page.
| Tool | What it manages | Foreign-key aware? | Built into Django? |
|---|---|---|---|
formset_factory |
N copies of a plain Form |
No | Yes |
modelformset_factory |
N rows of a single model | No | Yes |
inlineformset_factory |
child rows tied to one parent via FK | Yes — one level | Yes |
| Nested formset | children that each own grandchildren | Yes — two levels | No — you wire it |
If a single inline formset covers your screen, stop there. Nested formsets add real complexity around prefixes, validation and save order, so use them deliberately.
Step 1: Start with two inline formsets
With the models above you can edit all children of a parent on one page, and all addresses of a child on another. In Django 5.x, inlineformset_factory requires you to declare fields (or exclude):
# forms.py
from django.forms.models import inlineformset_factory
from .models import Address, Child, Parent
ChildrenFormset = inlineformset_factory(
Parent, Child, fields=["name"], extra=1, can_delete=True
)
AddressFormset = inlineformset_factory(
Child, Address, fields=["country", "state", "address"], extra=1, can_delete=True
)To add or edit every child and its addresses on a single page, each child form needs a full AddressFormset attached to it. That is the nested formset. The cleanest place to wire it up is a custom BaseInlineFormSet.
Step 2: Subclass BaseInlineFormSet
BaseInlineFormSet exposes an add_fields(form, index) hook that runs once per form in the formset. Override it to build and attach a nested formset for every parent form. Wire the subclass in through the factory's formset= argument:
# forms.py
from django.forms.models import BaseInlineFormSet, inlineformset_factory
from .models import Address, Child, Parent
AddressFormset = inlineformset_factory(
Child, Address, fields=["country", "state", "address"], extra=1, can_delete=True
)
class BaseChildrenFormset(BaseInlineFormSet):
pass
ChildrenFormset = inlineformset_factory(
Parent, Child, formset=BaseChildrenFormset, fields=["name"], extra=1, can_delete=True
)Step 3: Attach a nested formset with a unique prefix
Inside add_fields(), instantiate an AddressFormset bound to the child form's instance and store it on form.nested. The critical detail is the prefix: it must be unique for every child row, or the rendered field names and management_form of different rows will collide. Combining the parent form's own prefix with the formset's default prefix guarantees uniqueness:
# forms.py
class BaseChildrenFormset(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
# Build one Address formset per child form and store it on the form.
# The prefix mixes the child form's prefix with the address formset's
# default prefix, so no two rows ever share field names or a
# management_form.
form.nested = AddressFormset(
instance=form.instance,
data=form.data if self.is_bound else None,
files=form.files if self.is_bound else None,
prefix="address-%s-%s"
% (form.prefix, AddressFormset.get_default_prefix()),
)Note: extra, can_delete and similar options belong on the factory, not on the formset instance — passing extra=1 to AddressFormset(...) here raises TypeError. Set them once in inlineformset_factory.
Step 4: Validate the nested formsets
Calling formset.is_valid() only validates the parent (children) forms. Override is_valid() so every form.nested is validated too — and so the whole submission fails if any address is invalid:
# forms.py
class BaseChildrenFormset(BaseInlineFormSet):
# ... add_fields as above ...
def is_valid(self):
result = super().is_valid()
if self.is_bound:
for form in self.forms:
if hasattr(form, "nested"):
result = result and form.nested.is_valid()
return resultStep 5: Save parents before children
Order matters. The parent (children) formset must save first so each Child gets a primary key; only then can the nested AddressFormset write a valid child_id foreign key. Override save() to run the parent save, then loop the surviving rows and save their nested formsets. Skip any row flagged for deletion via _should_delete_form() so you don't persist orphans:
# forms.py
class BaseChildrenFormset(BaseInlineFormSet):
# ... add_fields and is_valid as above ...
def save(self, commit=True):
# Saving the parent formset first gives every Child a primary key.
result = super().save(commit=commit)
for form in self.forms:
if hasattr(form, "nested"):
if not self._should_delete_form(form):
# Now each address has a real child to point its FK at.
form.nested.save(commit=commit)
return resultThe save order, step by step
| Step | Action | Why it matters |
|---|---|---|
| 1 | Validate parent formset and every nested formset | a child isn't valid if its addresses aren't |
| 2 | super().save() the children formset |
every Child gets a primary key |
| 3 | For each surviving child, call form.nested.save() |
each Address gets a real child_id FK |
| 4 | Skip nested save when the row is marked for deletion | avoids saving orphans about to be removed |
If you need a single all-or-nothing write, wrap the save in transaction.atomic() so a later failure rolls back the children created earlier.
Step 6: Wire it into the view
The view stays almost identical to a normal inline formset — your overridden is_valid() and save() handle the nested level automatically. Remember to pass request.FILES if any nested form has file uploads:
# views.py
from django.shortcuts import get_object_or_404, redirect, render
from . import forms, models
def manage_children(request, parent_id):
"""Edit children and their addresses for one parent on a single page."""
parent = get_object_or_404(models.Parent, id=parent_id)
if request.method == "POST":
formset = forms.ChildrenFormset(
request.POST, request.FILES, instance=parent
)
if formset.is_valid():
formset.save()
return redirect("parent_view", parent_id=parent.id)
else:
formset = forms.ChildrenFormset(instance=parent)
return render(
request,
"manage_children.html",
{"parent": parent, "children_formset": formset},
)Step 7: Render every management_form in the template
Each formset level needs its own management_form. Render the parent formset's management_form, then inside the loop render each nested formset's management_form before its rows. Miss one and Django raises ManagementForm data is missing:
{# manage_children.html — formset section only #}
<form method="post">
{% csrf_token %}
{{ children_formset.management_form }}
{{ children_formset.non_form_errors }}
{% for child_form in children_formset.forms %}
{{ child_form.as_p }}
{% if child_form.nested %}
{{ child_form.nested.management_form }}
{{ child_form.nested.non_form_errors }}
{% for address_form in child_form.nested.forms %}
{{ address_form.as_p }}
{% endfor %}
{% endif %}
{% endfor %}
<button type="submit">Save</button>
</form>Adding rows dynamically with JavaScript? Clone the nested formset's empty_form and bump its prefix index so the new block has a unique name. A small custom template tag can keep that markup tidy — see Django custom template tags and filters.
Should you use the django-nested-formset package?
The community django-nested-formset package wraps this exact pattern behind a nestedformset_factory helper, which is handy for a quick two-level form. The trade-offs:
- Use the package when you want a fast, conventional two-level form and don't need custom validation or save logic.
- Build it manually (the approach above) when you need three levels, conditional nesting, custom save order, transactions, or tight control over prefixes and error handling.
Either way the underlying mechanics are identical — add_fields, unique prefixes, per-level management_form, and parents-before-children saving — so understanding the manual version makes the package easy to debug.
Common nested formset pitfalls
- Missing per-row
management_form— every nested formset needs its own; the parent's is not enough. This is the classicManagementForm data is missing or has been tampered witherror. - Prefix collisions — reusing the same prefix across rows merges their POST data; always derive the prefix from
form.prefix. - Saving children before parents — leads to
IntegrityErrororNoneforeign keys; save the parent formset first. - Forgetting deletion checks — saving a nested formset on a row marked for deletion recreates rows you meant to remove; guard with
_should_delete_form(). - Setting
extra/can_deleteon the instance — those are factory arguments; passing them to the formset constructor raisesTypeError.
Building complex Django forms? We can help
Multi-level forms, dynamic formsets and large data-entry screens are exactly the kind of fiddly Django work we have shipped across 50+ projects since 2014. If your team is wrestling with nested formsets, wizard flows, or migrating legacy Django form code to 5.x, our Django development services can design, build and harden them with you — and we use AI-assisted workflows to deliver in days to weeks, not months.
Frequently Asked Questions
Does Django have a built-in nested formset?
No. Django provides formset_factory, modelformset_factory and inlineformset_factory, but there is no built-in nested formset. You create one by attaching a child inline formset to each form of a parent inline formset, or by using the third-party django-nested-formset package.
Why do I get "ManagementForm data is missing or has been tampered with"?
Each formset level needs its own management_form. With nested formsets you must render the parent formset's management_form and every nested formset's management_form separately. A missing nested management_form — or a prefix collision between rows — triggers this error.
How do unique prefixes prevent formset collisions?
A formset's prefix namespaces all of its field names and its management_form. If two nested formsets share a prefix, Django can't tell their POST data apart. Deriving each prefix from the parent form's form.prefix (e.g. "address-%s-%s" % (form.prefix, default_prefix)) makes every row's fields unique.
What order should I save parents and children in?
Always save parents first. The parent (children) formset must run super().save() so each child gets a primary key; only then can the nested formset set a valid foreign key. Saving children before parents causes IntegrityError or null foreign keys.
Should I use the django-nested-formset package or build it myself?
Use the django-nested-formset package for a quick, conventional two-level form. Build it manually when you need three or more levels, custom validation, transactional saves, conditional nesting, or fine control over prefixes — the mechanics are the same either way.
Can nested formsets go three levels deep?
Yes, but each extra level multiplies the complexity. You repeat the same pattern — override add_fields at each level, give each formset a unique prefix, render its own management_form, and save from the top down — and wrap everything in transaction.atomic() so a deep failure rolls back cleanly. Beyond two levels, consider splitting the workflow across pages instead.