View Config: Add versioning handling#79809
Conversation
| ## Server-side view configuration filter | ||
|
|
||
| DataViews-powered screens (such as the Pages list and its Quick Edit form) build their configuration on the server. A dynamic filter, `get_entity_view_config_{$kind}_{$name}`, lets you customize that configuration for a specific entity, where the dynamic portions are the entity kind (e.g. `postType`) and name (e.g. `page`). | ||
|
|
||
| The configuration has four keys: `default_view`, `default_layouts`, `view_list` (the saved views shown in the list), and `form` (the DataForm used by consumers like Quick Edit). | ||
|
|
||
| In the following example, a custom saved view is added to the `page` list and the Trash view is removed from it, the `grid` layout option is unset, and `slug` and `author` fields are removed from the form. | ||
|
|
||
| ```php | ||
| function example_filter_page_view_config( $data ) { | ||
| // Merge a partial configuration: add a saved view to the list. | ||
| $data->update_with( | ||
| array( | ||
| 'view_list' => array( | ||
| array( | ||
| 'title' => __( 'My drafts', 'example' ), | ||
| 'slug' => 'my-drafts', | ||
| 'view' => array( | ||
| 'filters' => array( | ||
| array( | ||
| 'field' => 'status', | ||
| 'operator' => 'isAny', | ||
| 'value' => 'draft', | ||
| 'isLocked' => true, | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| 1 | ||
| ); | ||
|
|
||
| // Unset a nested value with null: drop the grid layout option. | ||
| $data->update_with( | ||
| array( 'default_layouts' => array( 'grid' => null ) ), | ||
| 1 | ||
| ); | ||
|
|
||
| // Remove a view from the list by its slug. | ||
| $data->remove_view_list_items( 'trash' ); | ||
|
|
||
| // Remove some fields from the form by their identity. | ||
| $data->remove_fields( array( 'slug', 'author' ) ); | ||
|
|
||
| return $data; | ||
| } | ||
| add_filter( 'get_entity_view_config_postType_page', 'example_filter_page_view_config' ); | ||
| ``` | ||
|
|
||
| The filter receives an object holding the entity's view configuration. Contribute through its methods and return it: | ||
|
|
||
| - `update_with( $patch, $version )` merges a partial configuration. Collection members are matched by identity — views by `slug` and form fields by `id` — so a matching member is merged in place and an unknown one is appended to the end. A value of `null` deletes that key — the way to unset a nested value such as a `default_layouts` entry, a nested layout property, or a field's `label` — and passing `null` for a whole top-level key resets it to its default. Null never deletes list content: use the remove helpers below for that. A schema version is required so the contribution can be migrated forward if the configuration shape changes. | ||
| - `remove_view_list_items( $slugs )` drops one or more `view_list` entries by `slug`. | ||
| - `remove_fields( $ids )` drops one or more `form` fields by `id`, including fields nested inside a group. Both remove helpers accept a single identity or an array. |
There was a problem hiding this comment.
This is the added part. The rest is auto-format.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Some surface notes, it's been ages since I've worked on the PHP-side APIs 😅
|
|
Flaky tests detected in e65c604. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28583296410
|
I'm assuming you mean a single filter to update all entities for my reply. TBH I don't think so - at least from the start.. Even current core usages have quite a few differences and I'm not sure how updating something for everything makes sense.
I think that this class will stay forever in GB to always override like |
What?
Part of: #76544
This PR adds versioning to the server-side view configuration API (
gutenberg_get_entity_view_config()and the dynamicget_entity_view_config_{$kind}_{$name}filter). Until now callbacks received and returned a raw config array.Now callbacks receive a
Gutenberg_View_Config_Datacontainer and contribute through its methods —set()for an entity's base definition,update_with( $patch, $version )for versioned, identity-aware patches, andremove_view_list_items()/remove_fields()for removals — instead of mutating the array directly.Why?
Versioned from the start
update_with( $patch, $version )requires the schema version the patch was authored against, even though there is only version1today and no migrations exist.This is because without the container, callbacks would customize by walking the raw payload (find the right index,
foreach,unset), and imperative array manipulation can never be migrated. A later shape change could break existing callbacks in the wild. The explicit$versionargument keeps each patch unambiguous about the shape it assumed. This mirrorstheme.json, where documents declare aversionandWP_Theme_JSON_Data->update_with()migrates older data forward.update_with()— describe intent, not array surgeryupdate_with( $patch, $version )is the main contribution verb: it merges a partial configuration onto the current one, so a patch supplies only what it changes. Object-shaped keys (default_view,default_layouts) merge recursively; the identity-keyed lists merge by identity —view_listentries byslug,formfields byid— so a contribution names the member it targets and core walks its own current structure to apply it: a matching member merges in place, an unknown one appends. Anullpatch value unsets a nested key, andnullfor a whole top-level key resets it to its default.The
remove_*helpersThis applies to the identity-keyed lists —
view_listentries (keyed byslug) andformfields (keyed byid). For them, removal can't be expressed as a merge: a patch only adds or modifies identity-matched members, andnullnever deletes list content (unlike map keys, wherenullunsets). So removal gets its own verbs:remove_view_list_items( $slugs )andremove_fields( $ids )name identities and core drops them from its own current structure — including fields nested inside a group'schildren— keeping removals version-safe and independent of ordering.set()— base definitions, not customizationsset( $key, $value )replaces a whole top-level key. Because it replaces rather than merges, a contribution made with it stops inheriting core's future changes to that key — a freeze — so it should not be the default choice. It exists for authoritative base definitions — e.g. a CPT that doesn't want the default post form at all and provides its own from scratch:Examples
Noting that the below examples are called for
pages(get_entity_view_config_postType_page). You can test different post types with the respective filter.Testing Instructions
Use of AI Tools
Opus 4.8 and Fable 5 with direction, changes and review