All from unfold.widgets:
| Widget | Replaces | Notes |
|---|---|---|
UnfoldAdminTextInputWidget |
AdminTextInputWidget |
Supports prefix/suffix icons |
UnfoldAdminURLInputWidget |
AdminURLFieldWidget |
URL-specific styling |
UnfoldAdminEmailInputWidget |
AdminEmailInputWidget |
Email input |
UnfoldAdminColorInputWidget |
AdminTextInputWidget |
Color picker (type="color") |
UnfoldAdminUUIDInputWidget |
AdminUUIDInputWidget |
UUID input |
UnfoldAdminPasswordWidget |
PasswordInput |
Password with render_value param |
UnfoldAdminPasswordToggleWidget |
PasswordInput |
Password with visibility toggle |
UnfoldAdminIntegerRangeWidget |
MultiWidget |
Two-input range widget |
| Widget | Notes |
|---|---|
UnfoldAdminTextareaWidget |
Standard textarea |
UnfoldAdminExpandableTextareaWidget |
Starts at 2 rows, expands |
| Widget | Replaces |
|---|---|
UnfoldAdminIntegerFieldWidget |
AdminIntegerFieldWidget |
UnfoldAdminDecimalFieldWidget |
AdminIntegerFieldWidget |
UnfoldAdminBigIntegerFieldWidget |
AdminBigIntegerFieldWidget |
| Widget | Notes |
|---|---|
UnfoldAdminSelectWidget |
Basic styled select |
UnfoldAdminSelect2Widget |
Select2 with search (includes jQuery/Select2 JS) |
UnfoldAdminSelectMultipleWidget |
Multi-select |
UnfoldAdminSelect2MultipleWidget |
Select2 multi-select with search |
UnfoldAdminNullBooleanSelectWidget |
Yes/No/Unknown select |
UnfoldAdminRadioSelectWidget |
Radio buttons (VERTICAL default) |
UnfoldAdminCheckboxSelectMultiple |
Checkbox group |
| Widget | Notes |
|---|---|
UnfoldBooleanWidget |
Standard checkbox |
UnfoldBooleanSwitchWidget |
Toggle switch |
| Widget | Notes |
|---|---|
UnfoldAdminDateWidget |
Date picker |
UnfoldAdminSingleDateWidget |
Simple date input |
UnfoldAdminTimeWidget |
Time picker |
UnfoldAdminSingleTimeWidget |
Simple time input |
UnfoldAdminSplitDateTimeWidget |
Separate date + time inputs |
UnfoldAdminSplitDateTimeVerticalWidget |
Vertical layout with labels |
| Widget | Notes |
|---|---|
UnfoldAdminImageFieldWidget |
Image upload with preview |
UnfoldAdminFileFieldWidget |
File upload (compact) |
UnfoldAdminImageSmallFieldWidget |
Small image upload |
| Widget | Notes |
|---|---|
UnfoldRelatedFieldWidgetWrapper |
Styled related field wrapper |
UnfoldForeignKeyRawIdWidget |
Raw ID for ForeignKey |
UnfoldAdminAutocompleteWidget |
Autocomplete select |
UnfoldAdminMultipleAutocompleteWidget |
Autocomplete multi-select |
| Widget | Requires |
|---|---|
UnfoldAdminMoneyWidget |
django-money |
UnfoldAdminLocationWidget |
django-location-field |
For custom autocomplete views (beyond Django's built-in autocomplete_fields):
from unfold.fields import (
UnfoldAdminAutocompleteModelChoiceField, # single select
UnfoldAdminMultipleAutocompleteModelChoiceField, # multi select
)
These are form fields (not widgets) - use them in custom forms when you need autocomplete with a custom BaseAutocompleteView backend.
Unfold automatically applies styled widgets to all standard Django fields. You do NOT need formfield_overrides for basic field types - they're handled by default. This mapping shows what Unfold applies behind the scenes:
| Django Field | Unfold Widget (auto-applied) |
|---|---|
CharField |
UnfoldAdminTextInputWidget |
TextField |
UnfoldAdminTextareaWidget |
IntegerField |
UnfoldAdminIntegerFieldWidget |
DecimalField |
UnfoldAdminDecimalFieldWidget |
BigIntegerField |
UnfoldAdminBigIntegerFieldWidget |
BooleanField |
UnfoldBooleanWidget |
NullBooleanField |
UnfoldAdminNullBooleanSelectWidget |
DateField |
UnfoldAdminDateWidget |
TimeField |
UnfoldAdminTimeWidget |
DateTimeField |
UnfoldAdminSplitDateTimeWidget |
EmailField |
UnfoldAdminEmailInputWidget |
URLField |
UnfoldAdminURLInputWidget |
UUIDField |
UnfoldAdminUUIDInputWidget |
ForeignKey |
UnfoldAdminSelectWidget (or autocomplete) |
ManyToManyField |
UnfoldAdminSelectMultipleWidget |
FileField |
UnfoldAdminFileFieldWidget |
ImageField |
UnfoldAdminImageFieldWidget |
Only use formfield_overrides when you want to change from the default (e.g., BooleanField to UnfoldBooleanSwitchWidget, or TextField to WysiwygWidget).
Apply widgets to all fields of a type (overrides the defaults above):
from django.db import models
from unfold.widgets import (
UnfoldAdminTextInputWidget, UnfoldBooleanSwitchWidget,
UnfoldAdminImageFieldWidget, UnfoldAdminSelect2Widget,
)
from unfold.contrib.forms.widgets import WysiwygWidget
class MyAdmin(ModelAdmin):
formfield_overrides = {
models.TextField: {"widget": WysiwygWidget},
models.ImageField: {"widget": UnfoldAdminImageFieldWidget},
models.BooleanField: {"widget": UnfoldBooleanSwitchWidget},
}
Apply widgets to specific fields:
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
form.base_fields["color"].widget = UnfoldAdminColorInputWidget()
form.base_fields["name"].widget = UnfoldAdminTextInputWidget(attrs={
"prefix_icon": "search",
"suffix_icon": "check",
})
return form
Full control with a custom form class:
class MyModelForm(forms.ModelForm):
flags = forms.MultipleChoiceField(
choices=[("A", "Option A"), ("B", "Option B")],
widget=UnfoldAdminCheckboxSelectMultiple,
required=False,
)
custom_select = forms.ChoiceField(
choices=[("show", "Show"), ("hide", "Hide")],
widget=UnfoldAdminSelect2Widget,
)
class MyAdmin(ModelAdmin):
form = MyModelForm
UnfoldAdminTextInputWidget(attrs={
"prefix_icon": "search", # Material Symbols icon before input
"suffix_icon": "euro", # Material Symbols icon after input
})
Or set after init:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"].widget.attrs.update({
"prefix_icon": "person",
"suffix_icon": "verified",
})
Requires unfold.contrib.forms in INSTALLED_APPS:
from unfold.contrib.forms.widgets import WysiwygWidget
class MyAdmin(ModelAdmin):
formfield_overrides = {
models.TextField: {"widget": WysiwygWidget},
}
Uses the Trix editor. The field stores HTML content.
For PostgreSQL ArrayField:
from unfold.contrib.forms.widgets import ArrayWidget
class MyAdmin(ModelAdmin):
formfield_overrides = {
ArrayField: {"widget": ArrayWidget},
}
# With choices (requires get_form override)
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
form.base_fields["tags"].widget = ArrayWidget(choices=TagChoices)
return form
from unfold.admin import TabularInline, StackedInline, GenericStackedInline
from unfold.contrib.inlines.admin import NonrelatedStackedInline, NonrelatedTabularInline
class OrderItemInline(TabularInline):
model = OrderItem
fields = ["product", "quantity", "price"]
extra = 1
show_change_link = True
class OrderItemInline(TabularInline):
model = OrderItem
tab = True # renders as a tab instead of inline block
class CommentInline(StackedInline):
model = Comment
per_page = 10 # paginate after 10 items
class MenuItemInline(TabularInline):
model = MenuItem
ordering_field = "weight" # integer field for sort order
ordering = ["weight"]
The model needs an integer field (typically weight or order) that stores position.
class NoteInline(StackedInline):
model = Note
collapsible = True
classes = ["collapse"] # Django's built-in collapse also works
class StandingInline(StackedInline):
model = Standing
hide_title = True
Override the default inline title by adding get_inline_title() to the model:
# models.py
class RelatedModel(models.Model):
name = models.CharField(max_length=100)
def get_inline_title(self):
return f"Custom: {self.name}"
class OrderItemInline(TabularInline):
model = OrderItem
tab = True
per_page = 5
ordering_field = "weight"
hide_title = True
collapsible = True
autocomplete_fields = ["product"]
show_change_link = True
extra = 0
max_num = 20
Display models without a direct ForeignKey relationship:
from unfold.contrib.inlines.admin import NonrelatedStackedInline
class RecentOrdersInline(NonrelatedStackedInline):
model = Order
fields = ["id", "total", "status"]
extra = 0
tab = True
per_page = 10
def get_form_queryset(self, obj):
"""Return queryset for the inline based on parent obj."""
return self.model.objects.filter(customer__user=obj).order_by("-created_at")
def save_new_instance(self, parent, instance):
"""Define how to save new instances."""
instance.customer = parent.customer
Requires unfold.contrib.inlines in INSTALLED_APPS.
For GenericForeignKey relationships:
from unfold.admin import GenericStackedInline
class TagInline(GenericStackedInline):
model = Tag
| Attribute | Type | Default | Description |
|---|---|---|---|
tab |
bool | False | Render as tab |
per_page |
int | None | Items per page (pagination) |
ordering_field |
str | None | Field for drag-to-reorder |
hide_title |
bool | False | Hide inline heading |
collapsible |
bool | False | Allow collapse/expand |
| Standard Django inline attributes also apply |
Unfold provides styled versions of Django's user admin forms:
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
@admin.register(User)
class UserAdmin(BaseUserAdmin, ModelAdmin):
form = UserChangeForm
add_form = UserCreationForm
change_password_form = AdminPasswordChangeForm
Transform readonly field display using readonly_preprocess_fields:
class MyAdmin(ModelAdmin):
readonly_preprocess_fields = {
"description": "html", # render as HTML (not escaped)
"metadata": "json", # pretty-printed JSON
"avatar": "image", # render as image thumbnail
"document": "file", # render as file download link
"config": lambda content: f"<pre>{content}</pre>", # custom callable
}
Built-in preprocessors: "html", "json", "image", "file". Pass a callable for custom transformations.
Unfold supports django-crispy-forms for custom action forms and standalone pages. Create views that extend Unfold's base templates for consistent styling:
from django.views.generic import FormView
class CustomFormView(FormView):
template_name = "myapp/custom_form.html"
model_admin = None # set when registering URL
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.model_admin.admin_site.each_context(self.request))
return context
Register via get_urls():
def get_urls(self):
return super().get_urls() + [
path("custom/", self.admin_site.admin_view(
CustomFormView.as_view(model_admin=self)
), name="custom_form"),
]
Use UnfoldModelAdminViewMixin for class-based views that integrate with Unfold's layout:
from django.views.generic import TemplateView
from unfold.views import UnfoldModelAdminViewMixin
class MyCustomPage(UnfoldModelAdminViewMixin, TemplateView):
title = "Custom Page"
permission_required = () # tuple of required permissions
template_name = "myapp/custom_page.html"
Register via get_urls() with model_admin=self:
def get_urls(self):
view = self.admin_site.admin_view(
MyCustomPage.as_view(model_admin=self)
)
return super().get_urls() + [
path("custom-page/", view, name="custom_page"),
]
Override the default admin site for full control:
from unfold.sites import UnfoldAdminSite
class CustomAdminSite(UnfoldAdminSite):
pass
custom_site = CustomAdminSite(name="custom_admin")
Register models with site=:
@admin.register(MyModel, site=custom_site)
class MyModelAdmin(ModelAdmin):
pass
To override the default admin site globally, use BasicAppConfig:
# settings.py
INSTALLED_APPS = [
"unfold.apps.BasicAppConfig", # NOT just "unfold"
"django.contrib.admin",
]
# apps.py
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = "myproject.sites.CustomAdminSite"
Quick reference for all major Unfold imports:
# Core
from unfold.admin import ModelAdmin, StackedInline, TabularInline, GenericStackedInline
from unfold.sites import UnfoldAdminSite
from unfold.views import UnfoldModelAdminViewMixin
# Decorators and enums
from unfold.decorators import display, action
from unfold.enums import ActionVariant
# Forms
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
# Dashboard
from unfold.components import BaseComponent, register_component
from unfold.datasets import BaseDataset
from unfold.sections import TableSection, TemplateSection
from unfold.paginator import InfinitePaginator
# Command palette
from unfold.dataclasses import SearchResult
# Filters
from unfold.contrib.filters.admin import (
TextFilter, DropdownFilter, MultipleDropdownFilter,
ChoicesDropdownFilter, MultipleChoicesDropdownFilter,
RelatedDropdownFilter, MultipleRelatedDropdownFilter,
RelatedCheckboxFilter, ChoicesCheckboxFilter, AllValuesCheckboxFilter,
BooleanRadioFilter, CheckboxFilter, AutocompleteSelectMultipleFilter,
RangeNumericFilter, SingleNumericFilter, SliderNumericFilter,
RangeNumericListFilter, RangeDateFilter, RangeDateTimeFilter,
)
# Contrib widgets
from unfold.contrib.forms.widgets import ArrayWidget, WysiwygWidget
from unfold.contrib.inlines.admin import NonrelatedStackedInline, NonrelatedTabularInline
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm