# Wagtail 6.4 release notes

_February 3, 2025_

```{contents}
---
local:
depth: 1
---
```

## What's new

### Support for background tasks using `django-tasks`

Wagtail now integrates with the [django-tasks](https://github.com/realOrangeOne/django-tasks) library to allow moving computationally-intensive tasks out of the request-response cycle, and improving the performance of the Wagtail admin interface. These tasks include:

 * Updating the [search index](wagtailsearch_indexing)
 * Updating or creating entries for the [reference index](managing_the_reference_index)
 * Calculating [image focal points](image_focal_points)
 * Purging [frontend caching](frontend_cache_purging) URLs
 * Deleting image and document files when the model is deleted

In the default configuration, these tasks are executed immediately within the request-response cycle, as they were in previous versions of Wagtail. However, by configuring the `TASKS` setting with an appropriate backend [as per the django-tasks documentation](https://github.com/realOrangeOne/django-tasks?tab=readme-ov-file#installation), these tasks can be deferred to a background worker process.

This feature was developed by Jake Howard.

### Previews for StreamField blocks

You can now set up previews for StreamField blocks. The preview will be shown in the block picker, along with a description for the block. This feature can help users choose the right block when writing content inside a StreamField. To enable this feature, see [](configuring_block_previews).

This feature was developed by Sage Abdullah and Thibaud Colas.

### Headless documentation

The new [](headless) documentation page curates important information about Wagtail’s headless capabilities, directly within the developer documentation. This is the foundation for a [headless improvements roadmap](https://github.com/wagtail/rfcs/pull/100) for Wagtail.

Thank you to Sævar Öfjörð Magnússon and Alex Fulcher for creating this new page at the Wagtail Space NL 2024 sprint.

### Alt text improvements

Building upon improvements in past versions, this release comes with further enhancements to alt text management:

 * The [content modeling accessibility considerations](content_modeling) now reflect the latest capabilities for alt text.
 * The new `ImageBlock` alt text is now populated from the image’s default alt text when selecting a new image.
 * The `ImageBlock` alt text field is also populated from the image’s default alt text when converting from an ImageChooserBlock.
 * Alt text quality is now checked by default, as was intended with the `alt-text-quality` content check.

Thank you to Matt Westcott, Thibaud Colas, and Cynthia Kiser for their work on these improvements.

### Shorthand for `FieldPanel` and `InlinePanel`

Plain strings can now be used in panel definitions as a substitute for `FieldPanel` and `InlinePanel`, avoiding the need to import these classes from `wagtail.admin.panels`:

```python
class MyPage(Page):
    body = RichTextField()
    content_panels = [
        'body',
    ]
```

This feature was developed by Matt Westcott.

### Drag-and-drop support for StreamField and InlinePanel

The StreamField and InlinePanel interfaces now support drag-and-drop reordering of items within a field. This makes it faster to rearrange content within these fields, particularly when there is a lot of content.

This feature was developed by Thibaud Colas and Sage Abdullah, thanks to a sponsorship by Lyst.

### Search terms report

For users of [promoted search results](editors_picks), a new search terms report is available in the admin interface.
This report shows terms that website users have searched for, and how many times they have been searched.
This can help you understand what your users are looking for, and adjust your search promotions accordingly.

This feature was developed by Noah van der Meer and Sage Abdullah.

### Performance optimizations

Following from a recent audit, this release comes with performance improvements focused on the user interface.

 * Prevent main menu from re-rendering when clicking outside while the menu is closed (Sage Abdullah)
 * Skip loading of unused JavaScript to speed up 404 page rendering (Sage Abdullah)
 * Remove support for Safari 15 (Thibaud Colas)
 * Limit tags autocompletion to 10 items and add delay to avoid performance issues with large number of matching tags (Aayushman Singh)

### Other features

 * Add the ability to apply basic Page QuerySet optimizations to `specific()` sub-queries using `select_related` & `prefetch_related`, see [](../reference/pages/queryset_reference.md) (Andy Babic)
 * Increase `DATA_UPLOAD_MAX_NUMBER_FIELDS` in project template (Matt Westcott)
 * Stop invalid Site hostname records from breaking preview (Matt Westcott)
 * Set sensible defaults for InlinePanel heading and label (Matt Westcott)
 * Add the ability to restrict what types of requests a Pages supports via [`allowed_http_methods`](#wagtail.models.Page.allowed_http_methods) (Andy Babic)
 * Only allow selection of valid new parents within the copy Page view (Mauro Soche)
 * Add [`on_serve_page`](on_serve_page) hook to modify the serving chain of pages (Krystian Magdziarz, Dawid Bugajewski)
 * Add support for [`WAGTAIL_GRAVATAR_PROVIDER_URL`](wagtail_gravatar_provider_url) URLs with query string parameters (Ayaan Qadri, Guilhem Saurel)
 * Add [`get_avatar_url`](get_avatar_url) hook to customise user avatars (James Harrington)
 * Set content security policy (CSP) headers to block embedded content when serving images and documents (Jake Howard, with thanks to Ali İltizar for the initial report)
 * Add `page` as a third parameter to the [`construct_wagtail_userbar`](construct_wagtail_userbar) hook (claudobahn)
 * Enable breadcrumbs in revisions compare view (Sage Abdullah)
 * Replace l18n library with JavaScript Intl API for time zone options in Account view (Sage Abdullah)
 * Use explicit label for defaulting to server language in account settings (Sage Abdullah)
 * Add support for specifying an operator on `Fuzzy` queries (Tom Usher)
 * Make sure typing text at the bottom of the page editor always scrolls enough to keep the text into view (Jatin Bhardwaj)

### Bug fixes

 * Improve handling of translations for bulk page action confirmation messages (Matt Westcott)
 * Ensure custom rich text feature icons are correctly handled when provided as a list of SVG paths (Temidayo Azeez, Joel William, LB (Ben) Johnston)
 * Prevent error on lazily loading StreamField blocks after the stream has been modified (Stefan Hammer)
 * Fix sub-menus within the main menu cannot be closed on mobile (Bojan Mihelac)
 * Fix animation overflow transition when navigating through subpages in the sidebar page explorer (manu)
 * Ensure form builder supports custom admin form validation (John-Scott Atlakson, LB (Ben) Johnston)
 * Ensure form builder correctly checks for duplicate field names when using a custom related name (John-Scott Atlakson, LB (Ben) Johnston)
 * Normalize `StreamField.get_default()` to prevent creation forms from breaking (Matt Westcott)
 * Prevent out-of-order migrations from skipping creation of image/document choose permissions (Matt Westcott)
 * Use correct connections on multi-database setups in database search backends (Jake Howard)
 * Ensure CloudFront cache invalidation is called with a list, for compatibility with current botocore versions (Jake Howard)
 * Show the correct privacy status in the sidebar when creating a new page (Joel William)
 * Prevent generic model edit view from unquoting non-integer primary keys multiple times (Matt Westcott)
 * Ensure comments are functional when editing Page models with `read_only` `FieldPanel`s in use (Strapchay)
 * Ensure the accessible labels and tooltips reflect the correct private/public status on the live link button within pages after changing the privacy (Ayaan Qadri)
 * Fix empty `th` (table heading) elements that are not compliant with accessibility standards (Jai Vignesh J)
 * Ensure `MultipleChooserPanel` using images or documents work when nested within an `InlinePanel` when no other choosers are in use within the model (Elhussein Almasri)
 * Ensure `MultipleChooserPanel` works after doing a search in the page chooser modal (Matt Westcott)
 * Ensure new `ListBlock` instances get created with unique IDs in the admin client for accessibility and mini-map element references (Srishti Jaiswal)
 * Return never-cache HTTP headers when serving pages and documents with view restrictions (Krystian Magdziarz, Dawid Bugajewski)
 * Implement `get_block_by_content_path` on `ImageBlock` to prevent errors on commenting (Matt Westcott)
 * Add `aria-expanded` attribute to new column button on `TypedTableBlock` to reflect menu state (Ayaan Qadri, Scott Cranfill)
 * Allow page models to extend base `Page` panel definitions without importing `wagtail.admin` (Matt Westcott)
 * Fix crash when loading the dashboard with only the "unlock" or "bulk delete" page permissions (Unyime Emmanuel Udoh, Sage Abdullah)
 * Improve deprecation warning for `WidgetWithScript` by raising it with `stacklevel=3` (Joren Hammudoglu)
 * Correctly place comment buttons next to date / datetime / time fields. (Srishti Jaiswal)
 * Add missing `FilterField("created_at")` to `AbstractDocument` to fix ordering by `created_at` after searching in the documents index view (Srishti Jaiswal)
 * Add missing heading and breadcrumbs in Account view (Sage Abdullah)
 * Reduce confusing spacing below StreamField blocks help text (Rishabh Sharma)
 * Prevent redundant calls to `Site.find_for_request()` from `Page.get_url_parts()` (Andy Babic)
 * Prevent error on listings when searching and filtering by locale (Matt Westcott, Sage Abdullah)
 * Add missing space in panels check warning message (Stéphane Blondon)
 * Prevent `StreamChildrenToListBlockOperation` from duplicating data across multiple StreamField instances (Joshua Munn)
 * Prevent database error when calling permission_order.register on app ready (Daniel Kirkham, Matt Westcott)
 * Prevent syntax error on MySQL search when query includes symbols (Matt Westcott)

### Documentation

 * Move the [model reference page](../reference/models.md) from reference/pages to the references section as it covers all Wagtail core models (Srishti Jaiswal)
 * Move the [panels reference page](../reference/panels.md) from references/pages to the references section as panels are available for any model editing, merge panels API into this page (Srishti Jaiswal)
 * Move the tags documentation to standalone [advanced topic for tagging](../advanced_topics/tags.md), instead of being inside the reference/pages section (Srishti Jaiswal)
 * Refine the [](adding_reports) page so that common (page/non-page) class references are at the top and the full page only example has correct heading nesting (Alessandro Chitarrini)
 * Add the [`wagtail start`](wagtail_start) command to the management commands reference page (Damilola Oladele)
 * Refine the [](project_templates_reference) page sections and document common issues encountered when creating custom templates (Damilola Oladele)
 * Refine titles, references and URLS to better align with the documentation style guide, including US spelling (Srishti Jaiswal)
 * Recommend a larger `DATA_UPLOAD_MAX_NUMBER_FIELDS` when [integrating Wagtail into Django](../getting_started/integrating_into_django.md) (Matt Westcott)
 * Improve code highlighting and formatting for Python docstrings in core models (Srishti Jaiswal)
 * Update all JavaScript inline scripts & some CSS inline style tags to a CSP compliant approach by using external scripts/styles (Aayushman Singh)
 * Update usage of `mark_safe` to `format_html` for any script inclusions, to better avoid XSS issues from example code (Aayushman Singh)
 * Update documentation writing guidelines to [encourage better considerations](documentation_code_example_considerations) of security, accessibility and good practice when writing code examples (Aayushman Singh, LB (Ben) Johnston)
 * Update documentation guidelines on writing links and API reference (Sage Abdullah)
 * Replace absolute URLs with intersphinx links where possible to avoid broken links (Sage Abdullah)
 * Update upgrading guide and release process to better reflect the current practices (Sage Abdullah)
 * Document usage of [Custom validation for admin form pages](form_builder_custom_admin_validation) when using `AbstractEmailForm` or `AbstractForm` pages (John-Scott Atlakson, LB (Ben) Johnston)
 * Reword `BlogTagIndexPage` example for clarity (Clifford Gama)
 * Change title of blog index page in tutorial to avoid slug issues (Thibaud Colas)
 * Fix `wagtailcache` and `wagtailpagecache` examples to not use quotes for the `fragment_name` (Shiv)
 * Update tutorial to reflect the move of the "Add child page" action to a top-level button in the header as a '+' icon (Clifford Gama)
 * Fix link to `HTTPMethod` in `Page.handle_options_request()` docs (Sage Abdullah)
 * Improve [](pages_theory) with added & more consistent section headings and admonitions to aid in readability (Clifford Gama)
 * Fix non-functional link to the community guidelines in the [Your first contribution](../contributing/first_contribution_guide.md) page (Ankit Kumar)
 * Introduce tags and filters by name in the [](writing_templates) docs (Clifford Gama)
 * Fix Django HTML syntax formatting issue on the [documents overview](documents_overview) page (LB (Ben) Johnston)
 * Separate virtual environment creation and activation steps in tutorial (Ankit Kumar)
 * Update tutorial to use plain strings in place of `FieldPanel` / `InlinePanel` where appropriate (Unyime Emmanuel Udoh)
 * Update example for customizing ["p-as-heading" accessibility check](built_in_accessibility_checker) without overriding built-in checks (Cynthia Kiser)
 * Document [`get_template` method on StreamField blocks](streamfield_get_template) (Matt Westcott)
 * Revert incorrect example of appending a `RichTextBlock` to a `StreamField` (Matt Westcott)

### Maintenance

 * Close open files when reading within utils/setup.py (Ataf Fazledin Ahamed)
 * Avoid redundant `ALLOWED_HOSTS` check in `Site.find_for_request` (Jake Howard)
 * Update `CloneController` to ensure that `added`/`cleared` events are not dispatched as cancelable (LB (Ben) Johnston)
 * Remove unused `uuid` UMD module as all code is now using the NPM module (LB (Ben) Johnston)
 * Clean up JS comments throughout codebase to be aligned to JSDoc where practical (LB (Ben) Johnston)
 * Replace `eslint-disable no-undef` linter directives with `global` comments (LB (Ben) Johnston)
 * Upgrade Node tooling to active LTS version 22 (LB (Ben) Johnston)
 * Remove defunct oEmbed providers (Rahul Samant)
 * Remove obsolete non-upsert-based code for Postgres search indexing (Jake Howard)
 * Remove unused Rangy JS library (LB (Ben) Johnston)
 * Update `PreviewController` usage to leverage Stimulus actions instead of calling `preventDefault` manually (Ayaan Qadri)
 * Various performance optimizations to page publishing (Jake Howard)
 * Remove unnecessary DOM canvas.toBlob polyfill (LB (Ben) Johnston)
 * Ensure Storybook core files are correctly running through Eslint (LB (Ben) Johnston)
 * Add a new Stimulus `ZoneController` (`w-zone`) to support dynamic class name changes & event handling on container elements (Ayaan Qadri)
 * Migrate jQuery class toggling & drag/drop event handling within the multiple upload views to the Stimulus `ZoneController` usage (Ayaan Qadri)
 * Test project template for warnings when run against Django pre-release versions (Sage Abdullah)
 * Refactor redirects create/delete views to use generic views (Sage Abdullah)
 * Add JSDoc description, adopt linting recommendations, and add more unit tests for `ModalWorkflow` (LB (Ben) Johnston)
 * Add support for for a `delay` value in `TagController` to debounce async autocomplete tag fetch requests (Aayushman Singh)
 * Add unit tests, Storybook stories & JSDoc items for the Stimulus `DrilldownController` (Srishti Jaiswal)
 * Enhance sidebar preview performance by eliminating duplicate asset loads on preview error (Sage Abdullah)
 * Remove unused `LinkController` (`w-link`) (Sage Abdullah)
 * Refactor settings `EditView` to make better use of generic `EditView` (Sage Abdullah)
 * Add a new Stimulus `RulesController` (`w-rules`) to support declarative conditional field enabling from other field values in a form (LB (Ben) Johnston)
 * Migrate the conditional enabling of fields in the image URL builder view away from ad-hoc jQuery to use the `RulesController` (`w-rules`) approach (LB (Ben) Johnston)
 * Enhance the Stimulus `ZoneController` (`w-zone`) to support inactive class and a mechanism to switch the mode based on data within events (Ayaan Qadri)
 * Use the Stimulus `ZoneController` (`w-zone`) to remove ad-hoc jQuery for the privacy switch when toggling visibility of private/public elements (Ayaan Qadri)
 * Remove unused `is_active` & `active_menu_items` from `wagtail.admin.menu.MenuItem` (Srishti Jaiswal)
 * Only call `openpyxl` at runtime to improve performance for projects that do not use `ReportView`, `SpreadsheetExportMixin` and `wagtail.contrib.redirects` (Sébastien Corbin)
 * Adopt the update value `mp` instead of `mm` for 'mystery person' as the default Gravatar if no avatar found (Harsh Dange)
 * Refactor search promotions views to use generic views and templates (Sage Abdullah, Rohit Sharma)
 * Use built-in `venv` instead of `pipenv` in CircleCI (Sage Abdullah)
 * Add a new Stimulus `FormsetController` (`w-formset`) to support dynamic formset insertion/deletion behavior (LB (Ben) Johnston)
 * Enable breadcrumbs by default on admin views using generic templates (Sage Abdullah)
 * Refactor pages `revisions_revert` view to be a subclass of `EditView` (Sage Abdullah)
 * Move images and documents `get_usage().count()` call to view code (Sage Abdullah)
 * Upgrade sass-loader to resolve Sass deprecation warnings (Ayaan Qadri)

## Upgrade considerations - changes affecting all projects

### `DATA_UPLOAD_MAX_NUMBER_FIELDS` update

It's recommended that all projects set the `DATA_UPLOAD_MAX_NUMBER_FIELDS` setting to 10000 or higher.

This specifies the maximum number of fields allowed in a form submission, and it is recommended to increase this from Django's default of 1000, as particularly complex page models can exceed this limit within Wagtail's page editor:

```python
# settings.py
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
```

### Form builder - validation of fields with custom `related_name`

On previous releases, form page models (defined through `AbstractEmailForm`, `AbstractForm`, `FormMixin` or `EmailFormMixin`) did not validate form fields where the `related_name` was different to the default value of `form_fields`. As a result, it was possible to create forms with duplicate field names - in this case, only a single field is displayed and captured in the resulting form.

In this new release, validation is now applied on these fields, existing forms will continue to behave as before and no data will be lost. However, editing them will now raise a validation error and users may need to delete any duplicated fields that they had previously missed.

### Background tasks run at end of current transaction

In the default configuration, tasks managed by `django-tasks` (see above) run during the request-response cycle, as before. However, they are now deferred until the current transaction (if any) is committed. If [`ATOMIC_REQUESTS`](inv:django:std:setting#DATABASE-ATOMIC_REQUESTS) is set to `True`, this will be at the end of the request. This may lead to a change of behaviour on views that expect to see the results of these tasks immediately, such as a view that creates a page and then performs a search query to retrieve it. To restore the previous behaviour, set `"ENQUEUE_ON_COMMIT": False` in the `TASKS` setting:

```python
# settings.py
TASKS = {
    "default": {
        "BACKEND": "django_tasks.backends.immediate.ImmediateBackend",
        "ENQUEUE_ON_COMMIT": False,
    }
}
```

Django's test framework typically runs tests inside transactions, and so this situation is also likely to arise in tests that perform database updates and then - within the same test - expect these changes to be immediately reflected in search queries, object usage counts, and other processes that are now handled with background tasks. This can be addressed by setting `ENQUEUE_ON_COMMIT` to `False` as above in the test settings, or by wrapping the database updates in `with self.captureOnCommitCallbacks(execute=True)` to ensure that these tasks are completed before the test continues:

```python
def test_search(self):
    home_page = Page.objects.get(slug="home")

    with self.captureOnCommitCallbacks(execute=True):  # Added
        home_page.add_child(instance=EventPage(title="Christmas party"))

    response = self.client.get("/search/?q=Christmas")
    self.assertContains(response, "Christmas party")
```

## Upgrade considerations - changes affecting Wagtail customizations

### Added `page` as a third parameter to the `construct_wagtail_userbar` hook

In previous releases, implementations of the [`construct_wagtail_userbar`](construct_wagtail_userbar) hook were expected to accept two arguments, whereas now `page` is passed in as a third argument.

**Old**

```python
@hooks.register('construct_wagtail_userbar')
def construct_wagtail_userbar(request, items):
    pass
```

**New**

```python
@hooks.register('construct_wagtail_userbar')
def construct_wagtail_userbar(request, items, page):
    pass
```

The old style will now produce a deprecation warning.

## Upgrade considerations - changes to undocumented internals

### Changes to `content_panels`, `promote_panels` and `settings_panels` values on base `Page` model

Previously, the `content_panels`, `promote_panels` and `settings_panels` attributes on the base `Page` model were defined as lists of `Panel` instances. These lists now contain instances of `wagtail.models.PanelPlaceholder` instead, which are resolved to `Panel` instances at runtime. Panel definitions that simply extend these lists (such as `content_panels = Page.content_panels + [...]`) are unaffected; however, any logic that inspects these lists (for example, finding a panel in the list to insert a new one immediately after it) will need to be updated to handle the new object types.

### Removal of unused Rangy JS library

The unused JavaScript include `wagtailadmin/js/vendor/rangy-core.js` has been removed from the editor interface, and functions such as `window.rangy.getSelection()` are no longer available. Any code relying on this should now either supply its own copy of the [Rangy library](https://github.com/timdown/rangy), or be migrated to the official [`Document.createRange()`](https://developer.mozilla.org/en-US/docs/Web/API/Range) browser API.

### Deprecation of `window.buildExpandingFormset` global function

The undocumented global function `window.buildExpandingFormset` to attach JavaScript insertion / deletion behavior for Django formsets has been deprecated and will be removed in a future release.

Within the Wagtail admin this only impacts a small set of basic expanding formsets in use across Workflow and Group view editing. `InlinePanel` is not affected.

Previously these expanding formsets required a mix of specific id attribute structures and inline scripts to instantiate with callbacks for handling deletion. User code implementing this functionality through `buildExpandingFormset` should be updated - the following data attributes can be used to emulate the same behavior. These are likely to change and should not be considered official documentation.

| Element                                      | Attribute(s)                              |
| -------------------------------------------- | ----------------------------------------- |
| Containing element                           | `data-controller="w-formset"`             |
| Element to append new child forms            | `data-w-formset-target="forms"`           |
| Child form element                           | `data-w-formset-target="child"`           |
| Deleted form element                         | `data-w-formset-target="deleted" hidden`  |
| `template` element for blank form            | `data-w-formset-target="template"`        |
| Management field (total forms)               | `data-w-formset-target="totalFormsInput"` |
| Management field (min forms)                 | `data-w-formset-target="minFormsInput"`   |
| Management field (max forms)                 | `data-w-formset-target="maxFormsInput"`   |
| Management field (Delete, within child form) | `data-w-formset-target="deleteInput"`     |
| Add child `button`                           | `data-action="w-formset#add"`             |
| Delete child `button` (within child form)    | `data-action="w-formset#delete"`          |

Usage of nested id structures are no longer required but can be left in place for easier debugging.
