Wagtail 6.1 release notes - IN DEVELOPMENT


What’s new

Universal listings continued

Continuing work on the Universal Listings project, this release rolls out universal listing styles for the following views:

  • Image listings

  • Document listings

  • Site and locale listings

  • Page and snippet history views

  • Form builder submissions

  • Collections listings

  • Groups

  • Users

  • Workflow and task views

Universal listing components like header buttons have also been tweaked to improve usability. This feature was developed by Ben Enright and Sage Abdullah.

Information-dense admin interface

Wagtail now provides a way for CMS users to control the information density of the admin interface, via their user profile preferences. The new setting allows switching between the “default” density and a new “snug” mode, which reduces the spacing and size of UI elements.

It’s also possible for site implementers to customize this for the needs of their project – see Custom UI information density.

This feature was developed by Ben Enright and Thibaud Colas.

Custom per-page-type listings

A new viewset class PageListingViewSet has been introduced, allowing developers to create custom page listings for specific page types similar to those previously provided by the Modeladmin package - see Custom page listings. This feature was developed by Matt Westcott.

Keyboard shortcuts overview

A new dialog is available from the help menu, providing an overview of keyboard shortcuts available in the Wagtail admin. This feature was developed by Karthik Ayangar and Rohit Sharma.

Other features

  • Refine wording of page & collection privacy using password is a shared password and should not be used for secure content (Rohit Sharma, Jake Howard)

  • Add RelatedObjectsColumn to the table UI framework (Matt Westcott)

  • Reduce memory usage when rebuilding search indexes (Jake Howard)

  • Support creating images in .ico format (Jake Howard)

  • Add the ability to disable the usage of a shared password for enhanced security for the private pages and collections (documents) feature (Salvo Polizzi, Jake Howard)

  • Add system checks to ensure that WAGTAIL_DATE_FORMAT, WAGTAIL_DATETIME_FORMAT, WAGTAIL_TIME_FORMAT are correctly configured (Rohit Sharma, Coen van der Kamp)

  • Allow custom permissions with the same prefix as built-in permissions (Sage Abdullah)

  • Allow displaying permissions linked to the Admin model’s content type (Sage Abdullah)

  • Add support for Draftail’s JavaScript to use chooserUrls provided by entity options & for the Draftail widget to encode lazy URLs/ translations (Elhussein Almasri)

  • Reimplement search promotions IndexView using the generic.IndexView (Rohit Sharma, Sage Abdullah, Storm Heg)

  • Reimplement redirects IndexView using the generic.IndexView (Rohit Sharma, Sage Abdullah, Temidayo Azeez)

  • Add ChooseParentView to PageListingViewSet to allow creating pages from custom page listings (Abdelrahman Hamada, Sage Abdullah)

  • Added AbstractGroupApprovalTask to simplify customizing behavior of custom Task models (John-Scott Atlakson)

  • Add ability to bulk toggle permissions in the user group editing view, including shift+click for multiple selections (LB (Ben) Johnston, Kalob Taulien)

  • Update the minimum version of djangorestframework to 3.15.1 (Sage Abdullah)

  • Add support for related fields in generic IndexView.list_display (Abdelrahman Hamada)

  • Improve page fetching logic and cache route results per request. You can now use Page.route_for_request() to find the page route, and Page.find_for_request() to find the page given a request object and a URL path, see Page. Results are cached on request._wagtail_route_for_request (Gordon Pendleton)

  • Optimise rewriting of links / embeds in rich text using bulk database lookups (Andy Chosak)

  • Add normalization mechanism to StreamField so that assignments and defaults can be passed in a wider range of data types (Joshua Munn, Matt Westcott)

  • Allow specifying a STORAGES alias name for WAGTAILIMAGES_RENDITION_STORAGE (Alec Baron)

  • Update PASSWORD_REQUIRED_TEMPLATE setting to WAGTAIL_PASSWORD_REQUIRED_TEMPLATE with deprecation of previous naming (Saksham Misra, LB (Ben) Johnston)

  • Update DOCUMENT_PASSWORD_REQUIRED_TEMPLATE setting to WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE with deprecation of previous naming (Saksham Misra, LB (Ben) Johnston)

  • When editing settings (contrib) use the same icon in the editing view that was declared when registering the setting (Vince Salvino, Rohit Sharma)

  • Populate django-treebeard cache during page routing to improve performance of get_parent (Nigel van Keulen)

  • Add additional field types to Elasticsearch mapping (scott-8)

Bug fixes

  • Fix typo in __str__ for MySQL search index (Jake Howard)

  • Ensure that unit tests correctly check for migrations in all core Wagtail apps (Matt Westcott)

  • Correctly handle date objects on human_readable_date template tag (Jhonatan Lopes)

  • Ensure re-ordering buttons work correctly when using a nested InlinePanel (Adrien Hamraoui)

  • Consistently remove model’s verbose_name in group edit view when listing custom permissions (Sage Abdullah, Neeraj Yetheendran, Omkar Jadhav)

  • Resolve issue local development of docs when running make livehtml (Sage Abdullah)

  • Resolve issue with unwanted padding in chooser modal listings (Sage Abdullah)

  • Ensure form builder emails that have date or datetime fields correctly localize dates based on the configured LANGUAGE_CODE (Mark Niehues)

  • Ensure the Stimulus UnsavedController checks for nested removal/additions of inputs so that the unsaved warning shows in more valid cases when editing a page (Karthik Ayangar)

  • Ensure get_add_url() is always used to re-render the add button when the listing is refreshed in viewsets (Sage Abdullah)

  • Ensure dropdown content cannot get higher than the viewport and add scrolling within content if needed (Chiemezuo Akujobi)

  • Prevent snippets model index view from crashing when a model does not have an objects manager (Jhonatan Lopes)

  • Fix get_dummy_request’s resulting host name when running tests with ALLOWED_HOSTS = ["*"] (David Buxton)

  • Fix timezone handling in the timesince_last_update template tag (Matt Westcott)

  • Fix Postgres phrase search to respect the language set in settings (Ihar Marhitych)

  • Retain query parameters when switching between locales in the page chooser (Abdelrahman Hamada, Sage Abdullah)

  • Add w-kbd-scope-value with support for global so that specific keyboard shortcuts (e.g. ctrl+s/cmd+s) trigger consistently even when focused on fields (Neeraj Yetheendran)

  • Improve exception handling when generating image renditions concurrently (Andy Babic)

  • Respect WAGTAIL_ALLOW_UNICODE_SLUGS setting when auto-generating slugs (LB (Ben) Johnston)

  • Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)


  • Add contributing development documentation on how to work with a fork of Wagtail (Nix Asteri, Dan Braghis)

  • Make sure the settings panel is listed in tabbed interface examples (Tibor Leupold)

  • Update content and page names to their US spelling instead of UK spelling (Victoria Poromon)

  • Update broken and incorrect links throughout the documentation (EK303)

  • Fix formatting of --purge-only in wagtail_update_image_renditions management command section (Pranith Beeram)

  • Update template components documentation to better explain the usage of the Laces library (Tibor Leupold)

  • Update Sphinx theme to 6.3.0 with a fix for the missing favicon (Sage Abdullah)

  • Document risk of XSS attacks on document upload in WAGTAILDOCS_SERVE_METHOD and example settings (Matt Westcott, with thanks to Georgios Roumeliotis of TwelveSec for the original report)

  • Add clarity to how custom StreamField validation works (Tibor Leupold)

  • Add additional reference to the wagtail_update_image_renditions management command on the using images page (LB (Ben) Johnston)

  • Correct information about line endings in Window development docs (Sage Abdullah)

  • Improve code snippets for “Create a footer for all pages” tutorial section (Drikus Roor)

  • Update list of third-party tutorials (LB (Ben) Johnston)

  • Update Integrating into Django documentation to emphasise creating page models (Matt Westcott)


  • Move RichText HTML whitelist parser to use the faster, built in html.parser (Jake Howard)

  • Remove duplicate ‘path’ in default_exclude_fields_in_copy (Ramchandra Shahi Thakuri)

  • Update unit tests to always use the faster, built in html.parser & remove html5lib dependency (Jake Howard)

  • Adjust Eslint rules for TypeScript files (Karthik Ayangar)

  • Rename the React Button that only renders links (a element) to Link and remove unused prop & behaviors that was non-compliant for aria role usage (Advik Kabra)

  • Set up an wagtail.models.AbstractWorkflow model to support future customizations around workflows (Hossein)

  • Improve classnames template tag to handle nested lists of strings, use template tag for admin body element (LB (Ben) Johnston)

  • Merge UploadedDocument and UploadedImage into new UploadedFile model for easier shared code usage (Advik Kabra, Karl Hobley)

  • Optimize queries in dashboard panels (Sage Abdullah)

  • Optimize queries in group create/edit view (Sage Abdullah)

  • Move modal-workflow.js script usage to base admin template instead of ad-hoc imports (Elhussein Almasri)

  • Update all Draftail chooserUrls to be passed in via the Entity options instead of using window.chooserUrls globals, removing the need for inline scripts (Elhussein Almasri)

  • Enhance w-init (InitController) to support a detail value to be dispatched on events (Chiemezuo Akujobi)

  • Remove usage of inline scripts and instead use event dispatching to instantiate standalone Draftail editor instances (Chiemezuo Akujobi)

  • Refactor page_breadcrumbs tag to use shared breadcrumbs.html template (Sage Abdullah)

  • Add keyboard icon to admin icon set (Rohit Sharma)

  • Remove dead code in the minimap when elements are not found (LB (Ben) Johnston)

  • Ensure untrusted data sources are logged correctly in the Stimulus SwapController (LB (Ben) Johnston)

  • Update Wagtail logo in admin sidebar & favicon plus documentation to the latest version (Osaf AliSayed, Albina Starykova, LB (Ben) Johnston)

  • Remove usage of inline scripts and instead use a new Stimulus controller (w-block/BlockController) to instantiate StreamField blocks (Karthik Ayangar)

  • Update NPM Babel, TypeScript and Webpack packages (Neeraj Yetheendran)

  • Replace ad-hoc JavaScript and vendor Mousetrap usage to a new Stimulus controller (w-kbd/KeyboardController) (Neeraj Yetheendran)

  • Update django-filter to 24.x (Sebastian Muthwill)

  • Remove jQuery usage in telepath widget classes (Matt Westcott)

  • Remove xregexp (IE11 polyfill) along with window.XRegExp global util (LB (Ben) Johnston)

  • Refactor the Django port of urlify to use TypeScript, officially deprecate window.URLify global util (LB (Ben) Johnston)

Upgrade considerations - changes affecting Wagtail customizations

Changes to SubmissionsListView class

As part of the Universal Listings project, the SubmissionsListView for listing form submissions in the wagtail.contrib.forms app has been refactored to become a subclass of wagtail.admin.views.generic.base.BaseListingView. As a result, the class has undergone a number of changes, including the following:

  • The filtering mechanism has been reimplemented to use django-filter.

  • The ordering attribute has been renamed to default_ordering.

  • The template used to render the view has been significantly refactored to use the new universal listings UI.

register_user_listing_buttons hook signature changed

The function signature for the register_user_listing_buttons hook was updated to accept a request_user argument instead of context. If you use this hook, make sure to update your function signature to match the new one. The old signature with context will continue to work for now, but the context only contains the request object. Support for the old signature will be removed in a future release.


The setting PASSWORD_REQUIRED_TEMPLATE has been deprecated, it will continue to work until a future release but the new name for this same setting will be WAGTAIL_PASSWORD_REQUIRED_TEMPLATE to align with other settings naming conventions.

See Frontend authentication.


The setting DOCUMENT_PASSWORD_REQUIRED_TEMPLATE has been deprecated, it will continue to work until a future release but the new name for this same setting will be WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE to align with other settings naming conventions.

See Frontend authentication.

Upgrade considerations - changes to undocumented internals

Deprecation of user_listing_buttons template tag

The undocumented user_listing_buttons template tag has been deprecated and will be removed in a future release.

Deprecation of wagtailusers_groups:users URL pattern

The undocumented wagtailusers_groups:users URL pattern has been deprecated and will be removed in a future release. If you are using reverse with this URL pattern name, you should update your code to use the wagtailusers_users:index URL pattern name and the group ID as the group query parameter. For example:

reverse('wagtailusers_groups:users', args=[group.id])

should be updated to:

reverse('wagtailusers_users:index') + f'?group={group.id}'

The corresponding wagtailusers_groups:users_results URL pattern has been removed as part of this change.

A redirect from the old URL pattern to the new one has been added to ensure that existing URLs continue to work. This redirect will be removed in a future release.

Deprecation of window.chooserUrls within Draftail choosers

The undocumented usage of the JavaScript window.chooserUrls within Draftail choosers will be removed in a future release.

The following chooserUrl object values will be impacted.

  • anchorLinkChooser

  • documentChooser

  • emailLinkChooser

  • embedsChooser

  • externalLinkChooser

  • imageChooser

  • pageChooser

Overriding these inner values on the global window.chooserUrls object will still override their usage in the Draftail choosers for now but this capability will be removed in a future release.


It’s recommended that usage of this global is removed in any customizations or third party packages and instead a custom Draftail Entity be used instead. See example below.

# .../wagtail_hooks.py

def editor_js():
    return format_html(
            window.chooserUrls.myCustomChooser = '{0}';

Remove the insert_editor_js hook usage and instead pass the data needed via the Entity’s data.

# .../wagtail_hooks.py

from django.urls import reverse_lazy

def register_my_custom_feature(features):
    # features.register_link_type...

                "type": "CUSTOM_ITEM",
                "icon": "doc-full-inverse",
                "description": gettext_lazy("Item"),
                "chooserUrls": {
                    # Important: `reverse_lazy` must be used unless the URL path is hard-coded
                    "myChooser": reverse_lazy("myapp_chooser:choose")

Overriding existing chooserUrls values

To override existing chooser Entities’ chooserUrls values, here is an example of an unsupported monkey patch can achieve a similar goal.

However, it’s recommended that a custom Entity be created to be registered as a replacement feature for Draftail customizations as per the documentation.

# .../wagtail_hooks.py
from django.urls import reverse_lazy

from wagtail import hooks

def override_embed_feature_url(features):
    features.plugins_by_editor["draftail"]["embed"].data["chooserUrls"]["embedsChooser"] = reverse_lazy("my_embeds:chooser")

Deprecation of window.initBlockWidget to initialize a StreamField block

The undocumented global function window.initBlockWidget has now been deprecated and will be removed in a future release.

Any complex customizations that have re-implemented parts of this functionality will need to be modified to adopt the new approach that uses Stimulus and avoids inline scripts.

The usage of this new approach is still unofficial and could change in the future, it’s recommended that the documented BlockWidget and StreamField approaches be used instead.

However, for comparison, here is the old and new approach below.


Assuming we are using Django’s format_html to prepare the HTML output with JSON.dumps strings for the block data values.

The old approach would call the window.initBlockWidget global function with an inline script as follows:



In the new approach, we no longer need to attach an inline script but instead use the Stimulus data attributes to attach the behavior with the w-block identifier as follows:


Removal of jQuery from base client-side Widget and BoundWidget classes

The JavaScript base classes Widget and BoundWidget that provide client-side access to form widgets (see Form widget client-side API) no longer use jQuery. The element argument passed to the BoundWidget constructor, and the input property of BoundWidget, are now native DOM elements rather than jQuery collections. User code that extends these classes should be updated accordingly.

window.URLify deprecated

The undocumented client-side global util window.URLify is now deprecated and will be removed in a future release.

If this was required for slug field behavior, it’s recommended that the SlugInput widget be used instead. This will automatically convert values entered into a suitable slug in the browser while respecting the global configuration WAGTAIL_ALLOW_UNICODE_SLUGS.

from wagtail.admin.widgets.slug import SlugInput
# ... other imports

class MyPage(Page):
    promote_panels = [
        FieldPanel("slug", widget=SlugInput),
        # ... other panels

If you require this for custom JavaScript functionality, it’s recommended you either include your own implementation from the original Django URLify source. Alternatively, the slugify or parameterize (a Django URLify port) NPM packages might be suitable.

window.XRegExp polyfill removed

The legacy window.XRegExp global polyfill util has been removed and will throw an error if called.

Instead, any usage of this should be updated to the well supported browser native Regex implementation.

// old
const newStr = XRegExp.replace(originalStr, XRegExp('[ab+c]', 'g'), '')

// new (with RegExp)
const newStr = originalStr.replace(new RegExp('[ab+c]', 'g'), '')
// OR
const newStr = originalStr.replace(/[ab+c]/g, '')