# Wagtail 5.1 release notes

_August 1, 2023_

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

## What's new

### Read-only panels

FieldPanels can now be marked as read-only with the `read_only=True` keyword argument, so that they are displayed in the admin but cannot be edited. This feature was developed by Andy Babic.

### Wagtail tutorial improvements

As part of Google Season of Docs 2023, we worked with technical writer Damilola Oladele to make improvements to Wagtail’s "Getting started" tutorial. Here are the specific changes made as part of this project:

 * Revamp the start of the getting started section, with a separate quick install page
 * Move the tutorial’s snippets section to come before tags
 * Rewrite the getting started tutorial to address identified friction points
 * Switch the Getting started tutorial’s snippets example to be more understandable

Thank you to Damilola for his work, and to Google for sponsoring this project.

### Custom template support for `wagtail start`

The `wagtail start` command now supports an optional `--template` argument that allows you to specify a custom project template to use. This is useful if you want to use a custom template that includes additional features or customizations. For more details, see [the project template reference](/reference/project_template). This feature was developed by Thibaud Colas.

### Search query boosting on Elasticsearch 6 and above

The `boost` option on `SearchField`, to increase the ranking of search results that match on the specified field, is now respected by Elasticsearch 6 and above. This was previously only supported up to Elasticsearch 5, due to a change in Elasticsearch's API. This feature was developed by Shohan Dutta Roy.

### Elasticsearch 8 support

This release adds support for Elasticsearch 8. This can be set up by installing a version 8.x release of the `elasticsearch` Python package, and setting `wagtail.search.backends.elasticsearch8` as the search backend. Compatibility updates were contributed by Matt Westcott and Wesley van Lee.

### Extend Stimulus adoption

As part of tackling Wagtail’s technical debt and improving [CSP compatibility](https://github.com/wagtail/wagtail/issues/1288), we have continued extending our usage of Stimulus, based on the plans laid out in [RFC 78: Adopt Stimulus](https://github.com/wagtail/rfcs/pull/78).

* Add support for `attrs` on `FieldPanel` and other panels to aid in custom Stimulus usage (Aman Pandey, Antoni Martyniuk, LB (Ben) Johnston)
* Migrate Tagit initialization to a Stimulus Controller (LB (Ben) Johnston)
* Migrate legacy dropdown implementation to a Stimulus controller (Thibaud Colas)
* Migrate async header search and search with the Task chooser modal to `w-swap`, a Stimulus controller (LB (Ben) Johnston)
* Replace Bootstrap tooltips with a new `w-tooltip` Stimulus controller (LB (Ben) Johnston)
* Migrate dialog instantiation to a new `w-dialog` Stimulus controller (Loveth Omokaro, LB (Ben) Johnston)
* Support dialog template cloning using a new `w-teleport` Stimulus controller (Loveth Omokaro, LB (Ben) Johnston)

### AVIF image support

Wagtail now supports [AVIF](https://en.wikipedia.org/wiki/AVIF), a modern image format. We encourage all site implementers to consider using it to improve the performance of the sites and reduce their carbon footprint. For further details, see [image file format](image_file_formats), [output image format](output_image_format) and [image quality](image_quality).

This feature was developed by Aman Pandey as part of the Google Summer of Code program and a [partnership with the Green Web Foundation](https://www.thegreenwebfoundation.org/news/working-with-the-wagtail-community-on-the-summer-of-code/) and Green Coding Berlin, with support from Dan Braghis, Thibaud Colas, Sage Abdullah, Arne Tarara (Green Coding Berlin), and Chris Adams (Green Web Foundation).

### Permissions consolidation

This release includes several changes to permissions, to make them easier to use and maintain, as well as to improve performance.

* Add initial implementation of `PagePermissionPolicy` (Sage Abdullah)
* Refactor `UserPagePermissionsProxy` and `PagePermissionTester` to use `PagePermissionPolicy` (Sage Abdullah, Tidiane Dia)
* Optimise queries in collection permission policies using cache on the user object (Sage Abdullah)
* Prevent 'choose' permission from being ignored when looking up 'choose', 'edit' and 'delete' permissions in combination (Sage Abdullah)
* Take user's permissions into account for image / document counts on the admin dashboard (Sage Abdullah)
* Deprecate `UserPagePermissionsProxy` (Sage Abdullah)
* Refactor GroupPagePermission to use Django's Permission model (Sage Abdullah)

### Snippet enhancements

We have made several improvements to snippets as part of [RFC 85: Snippets parity with ModelAdmin](https://github.com/wagtail/rfcs/pull/85), ahead of the deprecation of ModelAdmin contrib app.

* Add the ability to export snippets listing via `SnippetViewSet.list_export` (Sage Abdullah)
* Add Inspect view to snippets (Sage Abdullah)
* Reorganise snippets documentation to cover customizations and optional features (Sage Abdullah)
* Add docs for migrating from ModelAdmin to Snippets (Sage Abdullah)
* Purge revisions of non-page models in `purge_revisions` command (Sage Abdullah)

### Other features

 * Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
 * Return filters from `parse_query_string` as a `QueryDict` to support multiple values (Aman Pandey)
 * Explicitly specify `MenuItem.name` for all admin menu and submenu items (Justin Koestinger)
 * Add oEmbed provider patterns for YouTube Shorts (e.g. [https://www.youtube.com/shorts/nX84KctJtG0](https://www.youtube.com/shorts/nX84KctJtG0)) and YouTube Live URLs (valnuro, Fabien Le Frapper)
 * Add a predictable default ordering of the "Object/Other permissions" in the Group Editing view, allow this [ordering to be customized](customizing_group_views_permissions_order) (Daniel Kirkham)
 * Implement a new design for chooser buttons with better accessibility (Thibaud Colas)
 * Add [`AbstractImage.get_renditions()`](image_renditions_multiple) for efficient generation of multiple renditions (Andy Babic)
 * Phone numbers entered via a link chooser will now have any spaces stripped out, ensuring a valid `href="tel:..."` attribute (Sahil Jangra)
 * Auto-select the `StreamField` block when only one block type is declared (Sébastien Corbin)
 * Add support for more [advanced Draftail customization APIs](extending_the_draftail_editor_advanced) (Thibaud Colas)
 * Add support for adding [HTML `attrs`](panels_attrs) on `FieldPanel`, `FieldRowPanel`, `MultiFieldPanel`, and others (Aman Pandey, Antoni Martyniuk, LB (Ben) Johnston)
 * Change to always cache renditions (Jake Howard)
 * Update link/document rich text tooltips for consistency with the inline toolbar (Albina Starykova)
 * Increase the contrast between the rich text / StreamField block picker and the page in dark mode (Albina Starykova)
 * Change the default WebP quality to 80 to match AVIF (Aman Pandey)
 * Adopt optimized Wagtail logo in the admin interface (Albina Starykova)
 * Add support for presenting the userbar (Wagtail button) in dark mode (Albina Starykova)

### Bug fixes

 * Prevent choosers from failing when initial value is an unrecognized ID such as when moving a page from a location where `parent_page_types` would disallow it (Dan Braghis)
 * Move comment notifications toggle to the comments side panel (Sage Abdullah)
 * Remove comment button on InlinePanel fields (Sage Abdullah)
 * Fix missing link to `UsageView` from `EditView` for snippets (Christer Jensen)
 * Prevent lowercase conversions of IndexView column headers (Virag Jain)
 * Ensure that `RichText` objects with the same values compare as equal (NikilTn)
 * Use `gettext_lazy` on generic model views so that language settings are correctly used (Matt Westcott)
 * Prevent JS error when reverting the spinner on a submit button after a validation error (LB (Ben) Johnston)
 * Prevent crash when comparing page revisions that include `MultipleChooserPanel` (Matt Westcott)
 * Ensure that title and slug continue syncing after entering non-URL-safe characters (LB (Ben) Johnston)
 * Ensure that title and slug are synced on keypress, not just on blur (LB (Ben) Johnston)
 * Add a more visible active state for side panel toggle buttons (Thibaud Colas)
 * Debounce and optimize live preview panel to prevent excessive requests (Sage Abdullah)
 * Page listings actions under the "More" dropdown are now accessible for screen reader and keyboard users (Thibaud Colas)
 * Bulk actions under the "More" dropdown are now accessible for screen reader and keyboard users (Thibaud Colas)
 * Navigation to translations via the locale dropdown is now accessible for screen reader and keyboard users (Thibaud Colas)
 * Make it possible for speech recognition users to reveal chooser buttons (Thibaud Colas)
 * Use constant-time comparison for image serve URL signatures (Jake Howard)
 * Ensure taggit field type-ahead options show correctly in the dark mode theme (Sage Abdullah)
 * Fix the lock description message missing the model_name variable when locked only by system (Sébastien Corbin)
 * Fix empty blocks created in migration operations (Sandil Ranasinghe)
 * Ensure that `gettext_lazy` works correctly when using `verbose_name` on a generic Settings models (Sébastien Corbin)
 * Remove unnecessary usage of `innerHTML` when modifying DOM content (LB (Ben) Johnston)
 * Avoid `ValueError` when extending `PagesAPIViewSet` and setting `meta_fields` to an empty list (Henry Harutyunyan, Alex Morega)
 * Improve accessibility for header search, remove autofocus on page load, advise screen readers that content has changed when results update (LB (Ben) Johnston)
 * Fix incorrect override of `PagePermissionHelper.user_can_unpublish_obj()` in ModelAdmin (Sébastien Corbin)
 * Prevent memory exhaustion when updating a large number of image renditions (Jake Howard)
 * Add missing Time Zone conversions and date formatting throughout the admin (Stefan Hammer)
 * Ensure that audit logs and revisions consistently use UTC and add migration for existing entries (Stefan Hammer)
 * Make sure "critical" buttons have enough color contrast in dark mode (Albina Starykova)
 * Improve visibility of scheduled publishing errors in status side panel (Sage Abdullah)
 * Avoid N+1 queries in users index view (Tidiane Dia)
 * Use a theme-agnostic color token for read-only panels support in dark mode (Thibaud Colas)
 * Ensure collapsible StreamBlocks expand as necessary to show validation errors (Storm Heg)
 * Ensure userbar dialog can sit above other website content (LB (Ben) Johnston)
 * Fix preview panel loading issues (Sage Abdullah)
 * Fix `search_promotions` `0004_copy_queries` migration for long-lived Wagtail instances (Sage Abdullah)
 * Guard against `TypeError` in `0088_fix_log_entry_json_timestamps` migration (Sage Abdullah)
 * Add migration to replace JSON null values with empty objects in log entries' data (Sage Abdullah)
 * Fix typo in the `page_header_buttons` template tag when accessing the context's request object (Robert Rollins)

### Documentation

 * Document how to add non-ModelAdmin views to a `ModelAdminGroup` (Onno Timmerman)
 * Document how to add StructBlock data to a StreamField (Ramon Wenger)
 * Update ReadTheDocs settings to v2 to resolve urllib3 issue in linkcheck extension (Thibaud Colas)
 * Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)
 * Update color customization guidance to include theme-agnostic options (Thibaud Colas)
 * Mark LTS releases in release note page titles (Thiago C. S. Tioma)
 * Revise main Getting started tutorial for clarity (Kevin Chung (kev-odin))
 * Update the [deployment documentation](deployment_guide) page and remove outdated information (Jake Howard)
 * Add more items to performance page regarding pre-fetching images and frontend caching (Jake Howard)
 * Add docs for managing stored queries in `searchpromotions` (Scott Foster)

### Maintenance

 * Removed support for Python 3.7 (Dan Braghis)
 * Switch to ruff for flake8 / isort code checking (Oliver Parker)
 * Deprecate `insert_editor_css` in favour of `insert_global_admin_css` (Ester Beltrami)
 * Optimise use of `specific` on Task and TaskState (Matt Westcott)
 * Use table UI component for workflow task index view (Matt Westcott)
 * Make header search available on generic index view (Matt Westcott)
 * Update pagination behavior to reject out-of-range / invalid page numbers (Matt Westcott)
 * Remove color tokens which are duplicates / unused (Thibaud Colas)
 * Add tests to help with maintenance of theme color tokens (Thibaud Colas)
 * Split out a base listing view from generic index view (Matt Westcott)
 * Update type hints in admin/ui/components.py so that `parent_context` is mutable (Andreas Nüßlein)
 * Optimise the Settings context processor to avoid redundantly finding a Site to improve cache ratios (Jake Howard)
 * Convert page listing to a class-based view (Matt Westcott)
 * Clean up page reports and type usage views to be independent of page listing views (Matt Westcott)
 * Refactor "More" dropdowns, locale selector, "Switch locales", page actions, to use the same dropdown component (Thibaud Colas)
 * Convert the `CONTRIBUTORS` file to Markdown (Dan Braghis)
 * Move `django-filter` version upper bound to v23 (Yuekui)
 * Update Pillow dependency to allow 10.x, only include support for >= 9.1.0 (Yuekui)
 * Replace ModelAdmin history header human readable date template tag (LB (Ben) Johnston)
 * Update uuid to v9 and Jest to v29, with `jest-environment-jsdom` and new snapshot format (LB (Ben) Johnston)
 * Update test cases producing undesirable console output due to missing mocks, uncaught errors, warnings (LB (Ben) Johnston)
 * Remove unused snippets _header_with_history.html template (Thibaud Colas)
 * Migrate away from using the `"wagtailadmin/shared/field_as_li.html"` template include (Storm Heg)
 * Upgrade documentation theme `sphinx_wagtail_theme` to v6.1.1 which includes multiple styling fixes and always visible code copy buttons (LB (Ben) Johnston)
 * Don't update the reference index while deleting it (Andy Chosak)

## Upgrade considerations - changes affecting all projects

### Search within chooser interfaces requires `AutocompleteField` for full functionality

In Wagtail 4.2, the search bar within snippet chooser interfaces (and custom choosers created via `ChooserViewSet`) returned results for partial word matches - for example, a search for "wagt" would return results containing "Wagtail" - if this was supported by the search backend in use, and at least one `AutocompleteField` was present in the model's `search_fields` definition. Otherwise, it would fall back to only matching on complete words. In Wagtail 5.0, this fallback behavior was removed, and consequently a model with no `AutocompleteField`s in place would return no results.

As of Wagtail 5.1.2, the fallback behavior has been restored. Nevertheless, it is strongly recommended that you add `AutocompleteField` to your models' `search_fields` definitions, to ensure that users can receive search results continuously as they type. For example:

```python
from wagtail.search import index
# ... other imports

@register_snippet
class MySnippet(index.Indexed, models.Model):
     search_fields = [
         index.SearchField("name"),
         index.AutocompleteField("name"),
     ]
```

### `GroupPagePermission` now uses Django's `Permission` model

The `GroupPagePermission` model that is responsible for assigning page permissions to groups now uses Django's `Permission` model instead of a custom string. This means that the `permission_type` `CharField` has been deprecated and replaced with a `permission` `ForeignKey` to the `Permission` model.

In addition to this, "edit" permissions now use the term `change` within the code. As a result, `GroupPagePermission`s that were previously recorded with `permission_type="edit"` are now recorded with a `Permission` object that has the `codename="change_page"` and a `content_type` that points to the `Page` model. Any permission checks that are done using `PagePermissionPolicy` should also use `change` instead of `edit`.

If you have any fixtures for the `GroupPagePermission` model, you will need to update them to use the new `Permission` model. For example, if you have a fixture that looks like this:

```json
{
  "pk": 11,
  "model": "wagtailcore.grouppagepermission",
  "fields": {
    "group": ["Event moderators"],
    "page": 12,
    "permission_type": "edit"
  }
}
```

Update it to use a natural key for the `permission` field instead of the `permission_type` field:

```json
{
  "pk": 11,
  "model": "wagtailcore.grouppagepermission",
  "fields": {
    "group": ["Event moderators"],
    "page": 12,
    "permission": ["change_page", "wagtailcore", "page"]
  }
}
```

If you have any code that creates `GroupPagePermission` objects, you will need to update it to use the `Permission` model instead of the `permission_type` string. For example, if you have code that looks like this:

```python
from wagtail.models import GroupPagePermission

permission = GroupPagePermission(group=group, page=page, permission_type="edit")
permission.save()
```

Update it to use the `Permission` model instead:

```python
from django.contrib.auth.models import Permission
from wagtail.models import GroupPagePermission

permission = GroupPagePermission(
    group=group,
    page=page,
    permission=Permission.objects.get(content_type__app_label="wagtailcore", codename="change_page"),
)
permission.save()
```

During the deprecation period, the `permission_type` field will still be available on the `GroupPagePermission` model and is used to automatically populate empty `permission` field as part of a system check. The `permission_type` field will be removed in Wagtail 6.0.

### The default ordering of Group Editing Permissions models has changed

The ordering for "Object permissions" and "Other permissions" now follows a predictable order equivalent to Django's default `Model` ordering.
This will be different to the previous indeterminate ordering.

The default ordering is now `["content_type__app_label", "content_type__model"]`. See [](customizing_group_views_permissions_order) for details on how to customize this order.

### JSON-timestamps stored in `ModelLogEntry` and `PageLogEntry` are now ISO-formatted and UTC

Previously, timestamps stored in the "data"-`JSONField` of `ModelLogEntry` and `PageLogEntry` have used the custom python format `%d %b %Y %H:%M`. Additionally, the `"go_live_at"` timestamp had been stored with the configured local timezone, instead of UTC.

This has now been fixed, all timestamps are now stored as UTC, and because the "data"-`JSONField` now uses Django's `DjangoJSONEncoder`, those `datetime` objects are now automatically converted to the ISO format. This release contains a new migration `0088_fix_log_entry_json_timestamps` which converts all existing timestamps used by Wagtail to the new format.

If you've developed your own subclasses of `ModelLogEntry`, `PageLogEntry` or `BaseLogEntry`, or used those existing models to create custom log entries, and you've stored timestamps similarly to Wagtail's old implementation (using `strftime("%d %b %Y %H:%M")`). You may want to adapt the storage of those timestamps to a consistent format too.

There are probably three places in your code, which have to be changed:

1. Creation: Instead of using `strftime("%d %b %Y %H:%M")`, you can now store the datetime directly in the "data" field. We've implemented a new helper `wagtail.utils.timestamps.ensure_utc()`, which ensures the correct timezone (UTC).
2. Display: To display the timestamp in the user's timezone and format with a `LogFormatter`, we've created utils to parse (`wagtail.utils.timestamps.parse_datetime_localized()`) and render (`wagtail.utils.timestamps.render_timestamp()`) those timestamps. Look at the existing formatters [here](https://github.com/wagtail/wagtail/blob/main/wagtail/wagtail_hooks.py).
3. Migration: You can use the code of the above migration ([source](https://github.com/wagtail/wagtail/blob/main/wagtail/migrations/0088_fix_log_entry_json_timestamps.py)) as a guideline to migrate your existing timestamps in the database.

### Image Renditions are now cached by default

Wagtail will try to use the cache called "renditions". If no such cache exists, it will fall back to using the default cache.
You can [configure the "renditions" cache](custom_image_renditions_cache) to use a different cache backend or to provide
additional configuration parameters.

## Upgrade considerations - deprecation of old functionality

### Removed support for Python 3.7

Python 3.7 is no longer supported as of this release; please upgrade to Python 3.8 or above before upgrading Wagtail.

### Pillow dependency update

Wagtail no longer supports Pillow versions below `9.1.0`.

### Elasticsearch 5 and 6 backends are deprecated

The Elasticsearch 5 and 6 search backends are deprecated and will be removed in a future release; please upgrade to Elasticsearch 7 or above.

### `insert_editor_css` hook is deprecated

The `insert_editor_css` hook has been deprecated. The `insert_global_admin_css` hook has the same functionality, and all uses of `insert_editor_css` should be changed to `insert_global_admin_css`.

### `wagtail.contrib.modeladmin` is deprecated

As part of the [RFC 85: Snippets parity with ModelAdmin](https://github.com/wagtail/rfcs/pull/85) implementation, the `wagtail.contrib.modeladmin` app is deprecated. To manage non-page models in Wagtail, use [`wagtail.snippets`](snippets) instead.

If you still rely on ModelAdmin, use the separate [wagtail-modeladmin](https://github.com/wagtail-nest/wagtail-modeladmin) package. The `wagtail.contrib.modeladmin` module will be removed in a future release.

### `UserPagePermissionsProxy` is deprecated

The undocumented `wagtail.models.UserPagePermissionsProxy` class is deprecated.

If you use the `.for_page(page)` method of the class to get a `PagePermissionTester` instance, you can replace it with `page.permissions_for_user(user)`.

If you use the other methods, they can be replaced via the `wagtail.permission_policies.pages.PagePermissionPolicy` class. The following is a list of the `PagePermissionPolicy` equivalent of each method:

```python
from wagtail.models import UserPagePermissionsProxy
from wagtail.permission_policies.pages import PagePermissionPolicy

# proxy = UserPagePermissionsProxy(user)
permission_policy = PagePermissionPolicy()

# proxy.revisions_for_moderation()
permission_policy.revisions_for_moderation(user)

# proxy.explorable_pages()
permission_policy.explorable_instances(user)

# proxy.editable_pages()
permission_policy.instances_user_has_permission_for(user, "change")

# proxy.can_edit_pages()
permission_policy.instances_user_has_permission_for(user, "change").exists()

# proxy.publishable_pages()
permission_policy.instances_user_has_permission_for(user, "publish")

# proxy.can_publish_pages()
permission_policy.instances_user_has_permission_for(user, "publish").exists()

# proxy.can_remove_locks()
permission_policy.user_has_permission(user, "unlock")
```

The `UserPagePermissionsProxy` object that is available in page's `ActionMenuItem` context as `user_page_permissions` (which might be used as part of a `register_page_action_menu_item` hook) has been deprecated. In cases where the page object is available (e.g. the page edit view), the `PagePermissionTester` object stored as the `user_page_permissions_tester` context variable can still be used.

The `UserPagePermissionsProxy` object that is available in the template context as `user_page_permissions` as a side-effect of the `page_permissions` template tag has also been deprecated.

If you use the `user_page_permissions` context variable or use the `UserPagePermissionsProxy` class directly, make sure to replace it either with the `PagePermissionTester` or the `PagePermissionPolicy` equivalent.

### `get_pages_with_direct_explore_permission`, `get_explorable_root_page`, and `users_with_page_permission` are deprecated

The undocumented `get_pages_with_direct_explore_permission` and `get_explorable_root_page` functions in `wagtail.admin.navigation` are deprecated. They can be replaced with `PagePermissionPolicy().instances_with_direct_explore_permission(user)` and `PagePermissionPolicy().explorable_root_instance(user)`, respectively.

The undocumented `users_with_page_permission` function in `wagtail.admin.auth` is also deprecated. It can be replaced with `PagePermissionPolicy().users_with_permission_for_instance(action, page, include_superusers)`.

### Shared include `wagtailadmin/shared/last_updated.html` is no longer available

The undocumented shared include `wagtailadmin/shared/last_updated.html` is no longer available as it used the legacy Bootstrap tooltips and was not accessible. If you need to achieve a similar output, an element that shows a simple date with a tooltip for the full date, use the `human_readable_date` template tag instead.

#### Before

```html+django
{% include "wagtailadmin/shared/last_updated.html" with last_updated=my_model.timestamp %}
```

#### After

```html+django
{% load wagtailadmin_tags %}

<!-- ... -->
{% human_readable_date my_model.timestamp %}
```

### Shared include `field_as_li.html` will be removed

The documented include `"wagtailadmin/shared/field_as_li.html"` will be removed in a future release, if being used it will need to be replaced with `"wagtailadmin/shared/field.html"` wrapped within `li` tags.

#### Before

```html+django
{% include "wagtailadmin/shared/field_as_li.html" %}
```

#### After

```html+django
<li>
    {% include "wagtailadmin/shared/field.html" %}
</li>
```

## Upgrade considerations - changes affecting Wagtail customizations

### Tag (Tagit) field usage now relies on data attributes

The `AdminTagWidget` widget has now been migrated to a Stimulus controller, if using this widget in Python, no changes are needed to adopt the new approach.

If the widget is being instantiated in JavaScript or HTML with the global util `window.initTagField`, this undocumented util should be replaced with the new `data-*` attributes approach. Additionally, any direct usage of the jQuery widget in JavaScript (e.g. `$('#my-element).tagit()`) should be removed.

The global util will be removed in a future release. It is recommended that the documented `AdminTagWidget` be used. However, if you need to use the JavaScript approach you can do this with the following example.

#### Old syntax

```html
<input id="id_tags" type="text" value="popular, technology" hidden />
<script>
    window.initTagField('id_tags', 'path/to/url', { autocompleteOnly: true });
</script>
```

#### New syntax

```html
<input
    id="id_tags"
    type="text"
    value="popular, technology"
    hidden
    data-controller="w-tag"
    data-w-tag-options-value='{"autocompleteOnly": true}'
    data-w-tag-url-value="/path/to/url"
/>
```

Note: The `data-w-tag-options-value` is a JSON object serialized into string. Django's HTML escaping will handle it automatically when you use the `AdminTagWidget`, but if you are manually writing the attributes, be sure to use quotation marks correctly.

### Header searching now relies on data attributes

Previously the header search relied on inline scripts and a `window.headerSearch` global to activate the behavior. This has now changed to a data attributes approach and the window global usage will be removed in a future major release.

If you are using the documented Wagtail [viewsets](viewsets_reference), Snippets or `ModelAdmin` approaches to building custom admin views, there should be no change required.

If you are using the shared header template include for a custom search integration, here's how to adopt the new approach.

#### Header include before

```html+django
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% block extra_js %}
    {{ block.super }}
    <script>
        window.headerSearch = {
            url: "{% url 'myapp:search_results' %}",
            termInput: '#id_q',
            targetOutput: '#my-results',
        };
    </script>
{% endblock %}
{% block content %}
    {% include "wagtailadmin/shared/header.html" with title="my title" search_url="myapp:index" %}
    ... other content
{% endblock %}
```

#### Header include after

Note: No need for `extra_js` usage at all.

```html+django
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% block content %}
    {% url 'myapp:search_results' as search_results_url %}
    {% include "wagtailadmin/shared/header.html" with title="my title" search_url="myapp:index" search_results_url=search_results_url search_target="#my-results" %}
    ... other content
{% endblock %}
```

Alternatively, if you have customizations that manually declare or override `window.headerSearch`, here's how to adopt the new approach.

#### Manual usage before

```html
<script>
    window.headerSearch = {
        url: '{{ my_async_results_url }}',
        termInput: '#id_q',
        targetOutput: '#some-results',
    };
</script>
<form role="search">
    <input type="text" name="q" id="id_q" />
</form>
<div id="some-results"></div>
```

#### Manual usage after

```html
<form
    role="search"
    data-controller="w-swap"
    data-action="change->w-swap#searchLazy input->w-swap#searchLazy"
    data-w-swap-src-value="{{ my_async_results_url }}"
    data-w-swap-target-value="#some-results"
>
    <input type="text" name="q" id="id_q" data-w-swap-target="input" />
</form>
<div id="some-results"></div>
```

### Tooltips now rely on new data attributes

The undocumented Bootstrap jQuery tooltip widget is no longer in use, you will need to update any HTML that is using these attributes to the new syntax.

```html
<!-- Old attributes: -->
<span data-wagtail-tooltip="Tooltip content here">Label</span>
<!-- New attributes: -->
<span data-controller="w-tooltip" data-w-tooltip-content-value="Tooltip content here">Label</span>
```

### Dialog hide/show custom events name change

The undocumented client-side Custom Event handling for dialog showing & hiding will change in a future release.

| Action | Old event      | New event       |
| ------ | -------------- | --------------- |
| Show   | `wagtail:show` | `w-dialog:show` |
| Hide   | `wagtail:hide` | `w-dialog:hide` |

Additionally, two new events will be dispatched when the dialog visibility changes.

| Action | Event name        |
| ------ | ----------------- |
| Show   | `w-dialog:shown`  |
| Hide   | `w-dialog:hidden` |

### Shared template `.../tables/attrs.html` has been renamed to `.../shared/attrs.html`

The undocumented shared template for rendering a dict of `attrs` to HTML, similar to Django form widgets, has been renamed.

|     | Template location                                         | Usage with `include`                                                   |
| --- | --------------------------------------------------------- | ---------------------------------------------------------------------- |
| Old | `wagtail/admin/templates/wagtailadmin/tables/attrs.html ` | `{% include "wagtailadmin/tables/attrs.html" with attrs=link_attrs %}` |
| New | `wagtail/admin/templates/wagtailadmin/shared/attrs.html`  | `{% include "wagtailadmin/shared/attrs.html" with attrs=link_attrs %}` |
