<a id="panels-reference"></a>

# Panels

<a id="editing-api"></a>

## Built-in Fields and Choosers

Wagtail’s panel mechanism automatically recognizes Django model fields and provides them with an appropriate widget for input. You can use it by defining the field in your Django model as normal and passing the field name into
[`FieldPanel`](#wagtail.admin.panels.FieldPanel) (or a suitable panel type) when defining your panels.

Here are some built-in panel types that you can use in your panel definitions. These are all subclasses of the base [`Panel`](#wagtail.admin.panels.Panel) class, and unless otherwise noted, they accept all of `Panel`’s parameters in addition to their own.

<a id="field-panel"></a>

### FieldPanel

### *class* wagtail.admin.panels.FieldPanel(field_name, widget=None, disable_comments=None, permission=None, read_only=False, required_on_save=None, \*\*kwargs)

This is the panel to use for basic Django model field types. It provides a default icon and heading based on the model field definition, but they can be customized by passing additional arguments to the constructor. For more details, see [Panel customization](#customizing-panels).

#### field_name

This is the name of the class property used in your model definition.

#### widget(optional)

This parameter allows you to specify a [Django form widget](https://docs.djangoproject.com/en/stable/ref/forms/widgets/) to use instead of the default widget for this field type.

#### disable_comments(optional)

This allows you to prevent a field-level comment button from showing for this panel if set to `True`. See [Create and edit comments](https://guide.wagtail.org/en-latest/how-to-guides/manage-pages/#create-and-edit-comments).

#### permission(optional)

Allows a field to be selectively shown to users with sufficient permission. Accepts a permission codename such as `'myapp.change_blog_category'` - if the logged-in user does not have that permission, the field will be omitted from the form. See Django’s documentation on [custom permissions](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#custom-permissions) for details on how to set permissions up; alternatively, if you want to set a field as only available to superusers, you can use any arbitrary string (such as `'superuser'`) as the codename, since superusers automatically pass all permission tests.

#### read_only(optional)

Allows you to prevent a model field value from being set or updated by editors.

For most field types, the field value will be rendered in the form for editors to see (along with field’s label and help text), but no form inputs will be displayed, and the form will ignore attempts to change the value in POST data. For example by injecting a hidden input into the form HTML before submitting.

By default, field values from `StreamField` or `RichTextField` are redacted to prevent rendering of potentially insecure HTML mid-form. You can change this behavior for custom panel types by overriding `Panel.format_value_for_display()`.

#### required_on_save(optional)

Specifies whether required constraints should be enforced on this field when saving as draft.

For page models, and snippets using [DraftStateMixin](../topics/snippets/features.md#wagtailsnippets-saving-draft-changes-of-snippets), saving as draft will skip validation of required fields by default - this allows editors to save drafts of items while they are still incomplete. Validation of required fields will be applied when the page or snippet is published, scheduled, or submitted to a workflow. To override this behaviour, and enforce validation when saving as draft, set `required_on_save` to `True`. This can also be achieved by setting the attribute `required_on_save` on the model field:

```python
subtitle = models.CharField(max_length=255)
subtitle.required_on_save = True
```

Note that non-text-based fields (such as `IntegerField` and `DateField`) that have not been defined as `null=True` do not permit saving blank values at the database level, and so these will always be enforced as required fields when saving drafts.

#### attrs(optional)

Allows a dictionary containing HTML attributes to be set on the rendered panel. If you assign a value of `True` or `False` to an attribute, it will be rendered as an HTML5 boolean attribute.

#### NOTE
A plain string in a panel definition is equivalent to a FieldPanel with no arguments.

Use this:

```python
    content_panels = Page.content_panels + ["title", "body"]
```

Instead of

```python
    content_panels = Page.content_panels + [
        FieldPanel('title'),
        FieldPanel('body'),
    ]
```

<a id="multifieldpanel"></a>

### MultiFieldPanel

### *class* wagtail.admin.panels.MultiFieldPanel(children=(), \*args, permission=None, \*\*kwargs)

This panel condenses several [`FieldPanel`](#wagtail.admin.panels.FieldPanel) s or choosers, from a `list` or `tuple`, under a single `heading` string. To save space, you can [collapse the panel by default](#collapsible).

#### children

A `list` or `tuple` of child panels

#### permission(optional)

Allows a panel to be selectively shown to users with sufficient permission. Accepts a permission codename such as `'myapp.change_blog_category'` - if the logged-in user does not have that permission, the panel will be omitted from the form. Similar to [`FieldPanel.permission`](#wagtail.admin.panels.FieldPanel.permission).

#### attrs(optional)

Allows a dictionary containing HTML attributes to be set on the rendered panel. If you assign a value of `True` or `False` to an attribute, it will be rendered as an HTML5 boolean attribute.

<a id="inline-panels"></a>

### InlinePanel

### *class* wagtail.admin.panels.InlinePanel(relation_name, panels=None, label='', min_num=None, max_num=None, \*\*kwargs)

This panel allows for the creation of a “cluster” of related objects over a join to a separate model, such as a list of related links or slides to an image carousel. For a full explanation of the usage of `InlinePanel`, see [Inline models](../topics/pages.md#inline-models). To save space, you can [collapse the panel by default](#collapsible).

#### relation_name

The related_name label given to the cluster’s ParentalKey relation.

#### panels(optional)

The list of panels that will make up the child object’s form. If not specified here, the panels definition on the child model will be used.

#### label

Text for the add button and heading for child panels. Used as the heading when `heading` is not present.

#### min_num(optional)

Minimum number of forms a user must submit.

#### max_num(optional)

Maximum number of forms a user must submit.

#### attrs(optional)

Allows a dictionary containing HTML attributes to be set on the rendered panel. If you assign a value of `True` or `False` to an attribute, it will be rendered as an HTML5 boolean attribute.

#### NOTE
A plain string in a panel definition is equivalent to an InlinePanel with no arguments.

Use this:

```python
    content_panels = Page.content_panels + ["gallery_images"]
```

Instead of

```python
    content_panels = Page.content_panels + [
        InlinePanel('gallery_images'),
    ]
```

<a id="inline-panel-events"></a>

#### JavaScript DOM events

You may want to execute some JavaScript when `InlinePanel` items are ready, added or removed. The `w-formset:ready`, `w-formset:added` and `w-formset:removed` events allow this.

For example, given a child model that provides a relationship between Blog and Person on `BlogPage`.

```python
class CustomInlinePanel(InlinePanel):
    class BoundPanel(InlinePanel.BoundPanel):
        class Media:
            js = ["js/inline-panel.js"]


class BlogPage(Page):
        # .. fields

        content_panels = Page.content_panels + [
               CustomInlinePanel("blog_person_relationship"),
              # ... other panels
        ]
```

Using JavaScript is as follows.

```javascript
// static/js/inline-panel.js

document.addEventListener('w-formset:ready', function (event) {
    console.info('ready', event);
});

document.addEventListener('w-formset:added', function (event) {
    console.info('added', event);
});

document.addEventListener('w-formset:removed', function (event) {
    console.info('removed', event);
});
```

Events will be dispatched and can trigger custom JavaScript logic such as setting up a custom widget.

<a id="multiple-chooser-panel"></a>

### MultipleChooserPanel

### *class* wagtail.admin.panels.MultipleChooserPanel(relation_name, chooser_field_name=None, panels=None, label='', min_num=None, max_num=None, \*\*kwargs)

This panel is a variant of `InlinePanel` that can be used when the inline model includes a `ForeignKey` relation to a model that implements Wagtail’s chooser interface.
Wagtail images, documents, snippets, and pages all implement this interface, and other models may do so by [registering a custom ChooserViewSet](../extending/generic_views.md#chooserviewset).

Rather than the “Add” button inserting a new form to be filled in individually, it immediately opens up the chooser interface for that related object, in a mode that allows multiple items to be selected. The user is then returned to the main edit form with the appropriate number of child panels added and pre-filled.

`MultipleChooserPanel` accepts an additional required argument `chooser_field_name`, specifying the name of the `ForeignKey` relation that the chooser is linked to.

For example, given a child model that provides a gallery of images on `BlogPage`:

```python
class BlogPageGalleryImage(Orderable):
    page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images')
    image = models.ForeignKey(
        'wagtailimages.Image', on_delete=models.CASCADE, related_name='+'
    )
    caption = models.CharField(blank=True, max_length=250)

    panels = [
        FieldPanel('image'),
        FieldPanel('caption'),
    ]
```

The `MultipleChooserPanel` definition on `BlogPage` would be:

```python
        MultipleChooserPanel(
            'gallery_images', label="Gallery images", chooser_field_name="image"
        )
```

### FieldRowPanel

### *class* wagtail.admin.panels.FieldRowPanel(children=(), \*args, permission=None, \*\*kwargs)

This panel creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below.

The use of `FieldRowPanel` particularly helps reduce the “snow-blindness” effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example, if you created a model representing an “Event” which had a starting date and ending date, it may be intuitive to find the start and end date on the same “row”.

By default, the panel is divided into equal-width columns, but this can be overridden by adding `col*` class names to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is laid out using a grid system. Classes `col1`-`col12` can be applied to each child of a FieldRowPanel to define how many columns they span out of the total number of columns. When grid items add up to 12 columns, the class `col3` will ensure that field appears 3 columns wide or a quarter the width. `col4` would cause the field to be 4 columns wide, or a third the width.

#### children

A `list` or `tuple` of child panels to display on the row

#### permission(optional)

Allows a panel to be selectively shown to users with sufficient permission. Accepts a permission codename such as `'myapp.change_blog_category'` - if the logged-in user does not have that permission, the panel will be omitted from the form. Similar to [`FieldPanel.permission`](#wagtail.admin.panels.FieldPanel.permission).

#### attrs(optional)

Allows a dictionary containing HTML attributes to be set on the rendered panel. If you assign a value of `True` or `False` to an attribute, it will be rendered as an HTML5 boolean attribute.

### HelpPanel

### *class* wagtail.admin.panels.HelpPanel(content='', template='wagtailadmin/panels/help_panel.html', \*\*kwargs)

A panel to display helpful information to the user.

This panel does not support the `help_text` parameter.

#### content

HTML string that gets displayed in the panel.

#### template

Path to a template rendering the full panel HTML.

#### attrs(optional)

Allows a dictionary containing HTML attributes to be set on the rendered panel. If you assign a value of `True` or `False` to an attribute, it will be rendered as an HTML5 boolean attribute.

### PageChooserPanel

### *class* wagtail.admin.panels.PageChooserPanel(field_name, page_type=None, can_choose_root=False, \*\*kwargs)

While `FieldPanel` also supports `ForeignKey` to [`Page`](models.md#wagtail.models.Page) models, you can explicitly use `PageChooserPanel` to allow `Page`-specific customizations.

```python
from wagtail.models import Page
from wagtail.admin.panels import PageChooserPanel


class BookPage(Page):
    related_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    content_panels = Page.content_panels + [
        PageChooserPanel('related_page', 'demo.PublisherPage'),
    ]
```

`PageChooserPanel` takes one required argument, the field name. Optionally, specifying a page type (in the form of an `"appname.modelname"` string) will filter the chooser to display only pages of that type. A list or tuple of page types can also be passed in, to allow choosing a page that matches any of those page types:

```python
PageChooserPanel('related_page', ['demo.PublisherPage', 'demo.AuthorPage'])
```

Passing `can_choose_root=True` will allow the editor to choose the tree root as a page. Normally this would be undesirable since the tree root is never a usable page, but in some specialized cases it may be appropriate; for example, a page with an automatic “related articles” feed could use a `PageChooserPanel` to select which subsection articles will be taken from, with the root corresponding to ‘everywhere’.

### FormSubmissionsPanel

### *class* wagtail.contrib.forms.panels.FormSubmissionsPanel(\*\*kwargs)

This panel adds a single, read-only section in the edit interface for pages implementing the `wagtail.contrib.forms.models.AbstractForm` model.
It includes the number of total submissions for the given form and also a link to the listing of submissions.

```python
from wagtail.contrib.forms.models import AbstractForm
from wagtail.contrib.forms.panels import FormSubmissionsPanel

class ContactFormPage(AbstractForm):
    content_panels = [
        FormSubmissionsPanel(),
    ]
```

<a id="title-field-panel"></a>

### TitleFieldPanel

### *class* wagtail.admin.panels.TitleFieldPanel(\*args, apply_if_live=False, classname='title', placeholder=True, targets=None, \*\*kwargs)

Prepares the default widget attributes that are used on Page title fields.
Can be used outside of pages to easily enable the slug field sync functionality.

* **Parameters:**
  * **apply_if_live** – (optional) If `True`, the built in slug sync behaviour will apply irrespective of the published state.
    The default is `False`, where the slug sync will only apply when the instance is not live (or does not have a live property).
  * **classname** – (optional) A CSS class name to add to the panel’s HTML element. Default is `"title"`.
  * **placeholder** – (optional) If a value is provided, it will be used as the field’s placeholder, if `False` is provided no placeholder will be shown.
    If `True`, a placeholder value of `"Title*"` will be used or `"Page Title*"` if the model is a `Page` model.
    The default is `True`. If a widget is provided with a placeholder, the widget’s value will be used instead.
  * **targets** – (optional) This allows you to override the default target of the field named slug on the form.
    Accepts a list of field names, default is `["slug"]`.
    Note that the slugify/urlify behaviour relies on usage of the `wagtail.admin.widgets.slug` widget on the slug field.

This is the panel to use for Page title fields or main titles on other models. It provides a default classname, placeholder, and widget attributes to enable the automatic sync with the slug field in the form. Many of these defaults can be customized by passing additional arguments to the constructor. All the same FieldPanel arguments are supported including a custom widget. For more details, see [Panel customization](#customizing-panels).

<a id="customizing-panels"></a>

## Panel customization

By adding extra parameters to your panel/field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail’s page editing interface takes much of its behavior from Django’s admin, so you may find many options for customization covered there.
(See [Django model field reference](https://docs.djangoproject.com/en/stable/ref/models/fields/)).

<a id="customizing-panel-icons"></a>

### Icons

Use the `icon` argument to the panel constructor to override the icon to be displayed next to the panel’s heading. For a list of available icons, see [Available icons](../advanced_topics/icons.md#available-icons).

### Heading

Use the `heading` argument to the panel constructor to set the panel’s heading. This will be used for the input’s label and displayed on the content minimap. If left unset for `FieldPanel`s, it will be set automatically using the form field’s label (taken in turn from a model field’s `verbose_name`).

### CSS classes

Use the `classname` argument to the panel constructor to add CSS classes to the panel. The class will be applied to the HTML `<section>` element of the panel. This can be used to add extra styling to the panel or to control its behavior.

The `title` class can be used to make the input stand out with a bigger font size and weight.

<a id="collapsible"></a>

The `collapsed` class will load the editor page with the panel collapsed under its heading.

```python
    content_panels = [
        MultiFieldPanel(
            [
                FieldPanel('cover'),
                FieldPanel('book_file'),
                FieldPanel('publisher'),
            ],
            heading="Collection of Book Fields",
            classname="collapsed",
        ),
    ]
```

### Help text

Use the `help_text` argument to the panel constructor to customize the help text to be displayed above the input. If unset for `FieldPanel`s, it will be set automatically using the form field’s `help_text` (taken in turn from a model field’s `help_text`).

### Placeholder text

By default, Wagtail uses the field’s label as placeholder text. To change it, pass to the `FieldPanel` a widget with a placeholder attribute set to your desired text. You can select widgets from [Django’s form widgets](https://docs.djangoproject.com/en/stable/ref/forms/widgets/), or any of the Wagtail’s widgets found in `wagtail.admin.widgets`.

For example, to customize placeholders for a `Book` snippet model:

```python
# models.py
from django import forms            # the default Django widgets live here
from wagtail.admin import widgets   # to use Wagtail's special datetime widget

class Book(models.Model):
    title = models.CharField(max_length=256)
    release_date = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # You can create them separately
    title_widget = forms.TextInput(
        attrs = {
            'placeholder': 'Enter Full Title'
        }
    )
    # using the correct widget for your field type and desired effect
    date_widget = widgets.AdminDateInput(
        attrs = {
            'placeholder': 'dd-mm-yyyy'
        }
    )

    panels = [
        TitleFieldPanel('title', widget=title_widget), # then add them as a variable
        FieldPanel('release_date', widget=date_widget),
        FieldPanel('price', widget=forms.NumberInput(attrs={'placeholder': 'Retail price on release'})) # or directly inline
    ]
```

### Required fields

To make input or chooser selection mandatory for a field, add [`blank=False`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.Field.blank) to its model definition.

### Hiding fields

Without a top-level panel definition, a `FieldPanel` will be constructed for each field in your model. If you intend to hide a field on the Wagtail page editor, define the field with [`editable=False`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.Field.editable). If a field is not present in the panels definition, it will also be hidden.

<a id="panels-permissions"></a>

### Permissions

Most panels can accept a `permission` kwarg, allowing the set of panels or specific panels to be restricted to a set permissions.
See [Permissions](../topics/permissions.md#permissions-overview) for details about working with permissions in Wagtail.

In this example, ‘notes’ will be visible to all editors, ‘cost’ and ‘details’ will only be visible to those with the `submit` permission, ‘budget approval’ will be visible to super users only. Note that super users will have access to all fields.

```python
    content_panels = [
        FieldPanel("notes"),
        MultiFieldPanel(
            [
                FieldPanel("cost"),
                FieldPanel("details"),
            ],
            heading="Budget details",
            classname="collapsed",
            permission="submit"
        ),
        FieldPanel("budget_approval", permission="superuser"),
    ]
```

<a id="panels-attrs"></a>

### Additional HTML attributes

Use the `attrs` parameter to add custom attributes to the HTML element of the panel. This allows you to specify additional attributes, such as `data-*` attributes. The `attrs` parameter accepts a dictionary where the keys are the attribute names and these will be rendered in the same way as Django’s widget [`attrs`](https://docs.djangoproject.com/en/stable/ref/forms/widgets/#django.forms.Widget.attrs) where `True` and `False` will be treated as HTML5 boolean attributes.

For example, you can use the `attrs` parameter to integrate your Stimulus controller into the panel:

```python
    content_panels = [
        MultiFieldPanel(
            [
                FieldPanel('cover'),
                FieldPanel('book_file'),
                FieldPanel('publisher', attrs={'data-my-controller-target': 'myTarget'}),
            ],
            heading="Collection of Book Fields",
            classname="collapsed",
            attrs={'data-controller': 'my-controller'},
        ),
    ]
```

<a id="panels-api"></a>

## Panel API

This document describes the reference API for the base `Panel` and the `BoundPanel` classes that are used to render Wagtail’s panels. For available panel types and how to use them, see [Built-in Fields and Choosers](#editing-api).

### `Panel`

### *class* wagtail.admin.panels.Panel(heading='', classname='', help_text='', base_form_class=None, icon='', attrs=None)

Defines part (or all) of the edit form interface for pages and other models
within the Wagtail admin. Each model has an associated top-level panel definition
(also known as an edit handler), consisting of a nested structure of `Panel` objects.
This provides methods for obtaining a [`ModelForm`](https://docs.djangoproject.com/en/stable/topics/forms/modelforms/#django.forms.ModelForm) subclass,
with the field list and other parameters collated from all panels in the structure.
It then handles rendering that form as HTML.

The following parameters can be used to customize how the panel is displayed.
For more details, see [Panel customization](#customizing-panels).

* **Parameters:**
  * **heading** – The heading text to display for the panel.
  * **classname** – A CSS class name to add to the panel’s HTML element.
  * **help_text** – Help text to display within the panel.
  * **base_form_class** – The base form class to use for the panel. Defaults to the model’s `base_form_class`, before falling back to [`WagtailAdminModelForm`](../advanced_topics/customization/page_editing_interface.md#wagtail.admin.forms.WagtailAdminModelForm). This is only relevant for the top-level panel.
  * **icon** – The name of the icon to display next to the panel heading.
  * **attrs** – A dictionary of HTML attributes to add to the panel’s HTML element.

#### bind_to_model(model)

Create a clone of this panel definition with a `model` attribute pointing to the linked model class.

#### on_model_bound()

Called after the panel has been associated with a model class and the `self.model` attribute is available;
panels can override this method to perform additional initialisation related to the model.

#### clone()

Create a clone of this panel definition. By default, constructs a new instance, passing the
keyword arguments returned by `clone_kwargs`.

#### clone_kwargs()

Return a dictionary of keyword arguments that can be used to create a clone of this panel definition.

#### get_form_options()

Return a dictionary of attributes such as ‘fields’, ‘formsets’ and ‘widgets’
which should be incorporated into the form class definition to generate a form
that this panel can use.
This will only be called after binding to a model (i.e. self.model is available).

#### get_form_class()

Construct a form class that has all the fields and formsets named in
the children of this edit handler.

#### get_bound_panel(instance=None, request=None, form=None, prefix='panel')

Return a `BoundPanel` instance that can be rendered onto the template as a component. By default, this creates an instance
of the panel class’s inner `BoundPanel` class, which must inherit from `Panel.BoundPanel`.

#### *property* clean_name

A name for this panel, consisting only of ASCII alphanumerics and underscores, suitable for use in identifiers.
Usually generated from the panel heading. Note that this is not guaranteed to be unique or non-empty; anything
making use of this and requiring uniqueness should validate and modify the return value as needed.

### `BoundPanel`

### *class* wagtail.admin.panels.Panel.BoundPanel(panel, instance, request, form, prefix)

A template component for a panel that has been associated with a model instance, form, and request.

In addition to the standard template component functionality (see [Creating components](../extending/template_components.md#creating-template-components)), this provides the following attributes and methods:

#### panel

The panel definition corresponding to this bound panel

#### instance

The model instance associated with this panel

#### request

The request object associated with this panel

#### form

The form object associated with this panel

#### prefix

A unique prefix for this panel, for use in HTML IDs

#### id_for_label()

Returns an HTML ID to be used as the target for any label referencing this panel.

#### is_shown()

Whether this panel should be rendered; if false, it is skipped in the template output.
