Skip to content

DataForm: support registering 3rd party controls#74942

Open
oandregal wants to merge 3 commits intotrunkfrom
add/dataform-control-registration
Open

DataForm: support registering 3rd party controls#74942
oandregal wants to merge 3 commits intotrunkfrom
add/dataform-control-registration

Conversation

@oandregal
Copy link
Member

@oandregal oandregal commented Jan 26, 2026

Closes #74856

What?

Adds support for registering custom controls in DataForm via a new settings prop.

  // Register custom controls via settings prop                                                                                                                                                                                                                          
  <DataForm                                                                                                                                                                                                                                                              
      data={ product }                                                                                                                                                                                                                                                   
      fields={ fields }                                                                                                                                                                                                                                                  
      form={ form }                                                                                                                                                                                                                                                      
      onChange={ setProduct }                                                                                                                                                                                                                                            
      settings={ {                                                                                                                                                                                                                                                       
          controls: {                                                                                                                                                                                                                                                    
              starRating: StarRatingControl,                                                                                                                                                                                                                             
          },                                                                                                                                                                                                                                                             
      } }                                                                                                                                                                                                                                                                
  />                                                                                                                                                                                                                                                                     

Custom controls can be referenced by string name:

  {                                                                                                                                                                                                                                                                      
      id: 'rating',                                                                                                                                                                                                                                                      
      label: 'Rating',                                                                                                                                                                                                                                                   
      type: 'integer',                                                                                                                                                                                                                                                   
      Edit: 'starRating',                                                                                                                                                                                                                                                
  }                                                                                                                                                                                                                                                                      

Or with additional configuration via EditConfig:

  {                                                                                                                                                                                                                                                                      
      id: 'rating',                                                                                                                                                                                                                                                      
      label: 'Rating (3 stars)',                                                                                                                                                                                                                                         
      type: 'integer',                                                                                                                                                                                                                                                   
      Edit: {                                                                                                                                                                                                                                                            
          control: 'starRating',                                                                                                                                                                                                                                         
          starCount: 3,                                                                                                                                                                                                                                                  
      },                                                                                                                                                                                                                                                                 
  }                                                                                                                                                                                                                                                                      

Why?

Field authors can already declare custom Edit controls:

  {                                                                                                                                                                                                                                                                      
      id: 'rating',                                                                                                                                                                                                                                                      
      label: 'Rating (3 stars)',                                                                                                                                                                                                                                         
      type: 'integer',                                                                                                                                                                                                                                                   
      Edit: ( { data, field, onChange } ) => { /* custom control */ } 
  }                                                                                                                                                                                                                                                                      

However, this is not serializable as so cannot be used in places like JSON or PHP. By allowing 3rd parties to register their own custom controls as if they were core ones, we unlock serialization for them.

How?

  • Added a new settings prop to DataForm with a controls object that maps control names to React components.
  • Custom controls take precedence over built-in controls, allowing overrides.
  • Custom controls can be referenced by string name (Edit: 'starRating') or with config (Edit: { control: 'starRating', starCount: 3 }).
  • Added new "Register Controls" story to highlight this use case.

Testing Instructions

  1. Run the Storybook: npm run storybook:dev
  2. Navigate to DataViews → DataForm → Register Controls
  3. Verify the star rating controls render correctly (support label and required/custom validation).

@oandregal oandregal self-assigned this Jan 26, 2026
@github-actions
Copy link

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 <oandregal@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>

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

@github-actions github-actions bot added the [Package] DataViews /packages/dataviews label Jan 26, 2026
@oandregal oandregal added [Type] Enhancement A suggestion for improvement. [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond and removed [Package] DataViews /packages/dataviews labels Jan 26, 2026
@oandregal oandregal changed the title Add/dataform control registration DataForm: support registering 3rd party controls Jan 26, 2026
@github-actions
Copy link

Flaky tests detected in 394688b.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/21358235925
📝 Reported issues:

};

export interface DataFormControls {
[ key: string ]: ComponentType< DataFormControlProps< any > >;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just reuse this interface in FormControls in dataform-controls/index.tsx:30-32 and CustomControls in field-types/index.tsx:72-74?

customControls?: FormControls
) {
// Check custom controls first (they take precedence)
if ( customControls && Object.keys( customControls ).includes( type ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just do customControls && type in customControls to avoid creating a keys array just to check existence?

() => normalizeFields( fields ),
[ fields ]
() => normalizeFields( fields, settings?.controls ),
[ fields, settings?.controls ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This memoization is useless if consumers just do:
settings={ {
controls: {
starRating: StarRatingControl,
},
} }
As we did on our story packages/dataviews/src/dataform/stories/register-controls.tsx. Not sure about what the correct solution should be but I guess we shgould update our story to use an object reference that does not chnages on rerender. We may also document that because of performance settings should not change on every rerender.

* Allows any control name and additional configuration properties.
*/
export type EditConfigCustom = {
control: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be: control: Exclude<string, FieldTypeName | 'textarea' | 'text'>;
So on "config.control === 'textarea'", TypeScript narrows to EditConfigTextarea instead of EditConfigTextarea | EditConfigCustom.

Suggested change
control: string;
control: Exclude<string, FieldTypeName | 'textarea' | 'text'>;

config,
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > & { validity?: FieldValidity } ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need & { validity?: FieldValidity } here? It seems DataFormControlProps already includes it.

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 [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Field API: support registering custom field controls

2 participants