Skip to content

Conversation

@oandregal
Copy link
Member

@oandregal oandregal commented Jan 7, 2026

What?

This PR adds support for the object field type in the Field API.

Example of an object field type:

{
  id: 'address',
  type: 'object',
  label: 'Address',
  properties: {
    street: { id: 'street', type: 'text', label: 'Street' },
    city: { id: 'city', type: 'text', label: 'City' },
    country: { id: 'country', type: 'text', label: 'Country' },
  },
}

Why?

The work for content-only has surfaced this as a priority, see #73374 (comment)

How?

  • Create a object field type.
  • Create a object dataform control.

Testing Instructions

  • Open the local storybook (npm install && npm run storybook:dev).
  • Visit to "DataViews > Field Types > Object".

There are two objects in the story, link and address fields:

{
		id: 'link',
		type: 'object',
		label: 'Link',
		description: 'Object field with URL, rel, and target properties.',
		properties: {
			url: { id: 'url', type: 'url', label: 'URL' },
			rel: {
				id: 'rel',
				type: 'text',
				label: 'Rel attribute',
				elements: [
					// https://html.spec.whatwg.org/multipage/links.html#linkTypes
				],
			},
			target: {
				id: 'target',
				type: 'text',
				label: 'Target',
				elements: [
					// https://html.spec.whatwg.org/multipage/document-sequences.html#navigable-target-names
				],
			},
		},
		render: ( { item } ) => {
			const {
				link: { url, rel, target },
			} = item;
			return (
				<a href={ url } rel={ rel } target={ target }>
					{ url }
				</a>
			);
		},
	},
        {
		id: 'address',
		type: 'object',
		label: 'Address',
		description: 'Object field with street, city, and country properties.',
		properties: {
			street: { id: 'street', type: 'text', label: 'Street' },
			city: { id: 'city', type: 'text', label: 'City' },
			country: { id: 'country', type: 'text', label: 'Country' },
		},
	},
Screen.Recording.2026-01-07.at.19.42.46.mov

TODO

  • Validate the approach with some content-only attributes.
  • Validation story: update to reflect object types.

@oandregal oandregal self-assigned this Jan 7, 2026
@github-actions github-actions bot added the [Package] DataViews /packages/dataviews label Jan 7, 2026
@github-actions
Copy link

github-actions bot commented Jan 7, 2026

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: oandregal <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@oandregal oandregal added [Type] Feature New feature to highlight in changelogs. [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond and removed [Package] DataViews /packages/dataviews labels Jan 7, 2026
@github-actions github-actions bot added the [Package] DataViews /packages/dataviews label Jan 7, 2026
@oandregal
Copy link
Member Author

@talldan @andrewserong what would be a good attribute that represents an object type? Is there any?

I looked at the current status for auto-generating forms from content-only blocks, specifically this link example. But then I realized link is not a block attribute, but rather a "virtual thing" that groups attributes. The DataForm way to do that is declaring form fields instead (see panel layout example, but all work the same):

Screen.Recording.2026-01-07.at.20.35.49.mov
With the following diff:
diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js
index fc65703a23c..be43ffeac5f 100644
--- a/packages/block-library/src/image/index.js
+++ b/packages/block-library/src/image/index.js
@@ -84,15 +84,14 @@ if ( window.__experimentalContentOnlyInspectorFields ) {
                        },
                },
                {
-                       id: 'link',
-                       label: __( 'Link' ),
-                       type: 'link',
-                       mapping: {
-                               url: 'href',
-                               rel: 'rel',
-                               linkTarget: 'linkTarget',
-                               destination: 'linkDestination',
-                       },
+                       id: 'url',
+                       label: __( 'URL' ),
+                       type: 'url',
+               },
+               {
+                       id: 'linkTarget',
+                       label: __( 'Link target' ),
+                       type: 'boolean',
                },
                {
                        id: 'caption',
@@ -106,7 +105,15 @@ if ( window.__experimentalContentOnlyInspectorFields ) {
                },
        ];
        settings[ formKey ] = {
-               fields: [ 'image' ],
+               fields: [
+                       'image',
+                       {
+                               id: 'link',
+                               label: __( 'Link' ),
+                               layout: { type: 'panel', labelPosition: 'top' },
+                               children: [ 'url', 'linkTarget' ],
+                       },
+               ],
        };
 }

I've got this UI:

Screen.Recording.2026-01-07.at.21.35.33.mov

Would you be able to look at that direction to remove mapping from content-only?

@oandregal oandregal added the [Status] In Progress Tracking issues with work in progress label Jan 7, 2026
@oandregal oandregal marked this pull request as draft January 7, 2026 20:43
@talldan
Copy link
Contributor

talldan commented Jan 8, 2026

The DataForm way to do that is declaring form fields instead (see panel layout example, but all work the same):

Interesting point, thanks for mentioning that!

@talldan @andrewserong what would be a good attribute that represents an object type? Is there any?

Media is the best example. For example, when adding a media library image to an image block it'll usually set id, src and caption in one action (not via separate controls).

As part of this we also need to look at aligning the media field used for blocks with the one @ntsekouras has been working on (#74336).

@talldan
Copy link
Contributor

talldan commented Jan 8, 2026

@oandregal I've attempted to use the object type for block fields in this PR - Block fields: Add object type support for media and link controls.

It does work, but there's still some mapping required from the field keys to the block attribute keys. It'd be interesting to know if it's possible to remove the need for any mapping.

Block attributes also don't tend to be in a nested structure like this:

{ image: { id, url, caption } }

They're instead all at the root:

{ id, url, caption }

So in setValues/getValues the values have to be flattened.

@youknowriad
Copy link
Contributor

This PR makes me wonder why "properties" is not named "fields". I guess to stay close to JSON Schema. This is good I think. But I wonder we should rename the "fields" prop of DataViews and DataForm to "schema". It feels like a better name and maybe offer a deprecation for some time. Just food for thoughts.

@oandregal oandregal force-pushed the add/field-type-object branch from 122b8ab to 3742e56 Compare January 8, 2026 20:18
@oandregal
Copy link
Member Author

At 3742e56 I've pushed an additional field type: group. Like the object field type, it admits properties. Unlike the object field type it works with flat data.

{
		id: 'imageGroup',
		type: 'group',
		label: 'Image',
		description: 'Group field with flat data (not nested).',
		properties: {
			imageId: { id: 'imageId', type: 'text', label: 'Image ID' },
			imageUrl: { id: 'imageUrl', type: 'url', label: 'URL' },
			imageCaption: { id: 'imageCaption', type: 'text', label: 'Caption' },
		},
	},

I'd like to think more about this but wanted to push anyway for you to play with, so timezones work to our favor.

What I like about having both is that it's easy to reason about object and group field types:

  • object represents nested fields and mimics JSON Schema (comparison).
  • group represents a group of fields and mimics ACF (group field).

object and group are very similar, but that's something that can be said as well of text, email, password, and url. Code-wise, the implementation can be improved, but, my main interest right now is to surface if this is enough API-wise, or we're missing anything. @talldan @andrewserong Would you give it a try with content-only and share your feedback?

@talldan
Copy link
Contributor

talldan commented Jan 9, 2026

Thanks for making that change @oandregal.

I think it works. Is the idea that the group Edit component shouldn't use setValue / getValue internally, or is that something you're looking to tidy up?

I think it'd be great if we can find a way to support something like setValue / getValue to be able to support different data structures from a single control.

So for example, the image block stores media like this - { id: 25, url: 'picture.jpg' }, but media-text stores it like this { mediaId: 25, mediaUrl: 'picture.jpg' }.

The Edit component will always receive the raw inconsistent data, it'd be great to be able to something like this to remove the inconsistencies of the external data:

const { id, url } = field.getValue( { item: { mediaId: 25, mediaUrl: 'picture.jpg' } } );
field.setValue( { item: data, value: { id: 26, url: 'test.jpg' } } ); // { mediaId: 26, mediaUrl: 'test.jpg' }

I thought it may be possible to achieve it with a field definition like this (the property key is the internal key the Edit component uses and the id is the key that exists in the data):

{
  id: 'media',
  type: 'group', // could also be 'object',
  properties: {
    id: { id: 'mediaId', type: 'number', label: __( 'Id' ) },
    url: { id: 'mediaUrl', type: 'url', label: __( 'Url' ) }
  }
}

Something else that comes to mind is whether property ids should have parity with regular field ids, and support the dot notation. That might be useful with a group, as it'd allow grouping values that are not colocated into a single field:

{
  id: 'media',
  type: 'group',
  properties: {
    id: { id: 'deeply.nested.media.id', type: 'number', label: __( 'Id' ) },
    url: { id: 'shallowUrl', type: 'url', label: __( 'Url' ) }
  }
}

However, one outcome of this is that it means group can completely implement object:

{
  id: 'media',
  type: 'group',
  properties: {
    id: { id: 'media.id', type: 'number', label: __( 'Id' ) },
    url: { id: 'media.url', type: 'url', label: __( 'Url' ) }
  }
}

So perhaps that hints at not needing two separate types for object and group.

@ntsekouras
Copy link
Contributor

Media is the best example. For example, when adding a media library image to an image block it'll usually set id, src and caption in one action (not via separate controls).

I guess this could be achieved by the field's setValue.

As part of this we also need to look at aligning the media field used for blocks with the one @ntsekouras has been working on (#74336).

@jameskoster right now the designs for MediaEdit have no concept of handling an external image. Has this been considered?

@youknowriad
Copy link
Contributor

I don't like the "group" type personally.

The schema/fields should be about defining the shape of the data (not defining something for the form to render). Which means if a property is top level, it shouldn't use the "object" type (or group), it should just be defined as a "top" level field.

The "group" field breaks that, and IMO this is a concern of the DataForm form property and not something the schema/fields needs to be concerned about.

@jameskoster
Copy link
Contributor

right now the designs for MediaEdit have no concept of handling an external image. Has this been considered?

It has not. Do you think it should be part of the same control? My initial reaction is that selecting from (and uploading to) the Media Library feels like a separate consideration to inserting an external image.

@ntsekouras
Copy link
Contributor

Do you think it should be part of the same control? My initial reaction is that selecting from (and uploading to) the Media Library feels like a separate consideration to inserting an external image.

Not sure yet. I agree that it feels like a separate consideration. I'll see to explore though in that area.

@talldan
Copy link
Contributor

talldan commented Jan 12, 2026

The schema/fields should be about defining the shape of the data

I don't really see it as all that different to the way the id already supports dot notation for accessing properties. In that fields already supports data that's a different structure to the field structure itself. Maybe the critique also applies to that.

edit: Also just to mention I'm happy enough to go with just object support for now. That alone would allow me to improve quite a lot of the BlockFields code, and I can work around any issues by using setValues/getValues.

Do you think it should be part of the same control? My initial reaction is that selecting from (and uploading to) the Media Library feels like a separate consideration to inserting an external image.

If it's two separate controls how might it work on a block like image? In just about every existing case for blocks, media controls are presented as a single control:
Screenshot 2026-01-12 at 10 52 07 am

I think even for the placeholder, the placeholder itself represents the item that will be replaced and it has subcontrols for how a user replaces that single item.

@youknowriad
Copy link
Contributor

I don't really see it as all that different to the way the id already supports dot notation for accessing properties. In that fields already supports data that's a different structure to the field structure itself. Maybe the critique also applies to that.

Yes it does :)

@oandregal
Copy link
Member Author

I've prepared #74575 to remove mapping and args from the content-only code with the existing DataForm API. We don't need anything new to do that.

I'd like to land that PR and then revisit the conversation on this one. By landing 74575 first, we have more clarity about the problem to solve in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Package] DataViews /packages/dataviews [Status] In Progress Tracking issues with work in progress [Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants