Skip to content

Conversation

@jorgefilipecosta
Copy link
Member

@jorgefilipecosta jorgefilipecosta commented Feb 9, 2026

Part of: WordPress/ai#40
Inspired by the work on https://github.com/galatanovidiu/mcp-adapter-implementation-example/tree/experiment/layerd-mcp-tools/includes/Abilities by @galatanovidiu.
Ticket: https://core.trac.wordpress.org/ticket/64616

This PR adds a core/update-settings ability to the WordPress Abilities API. This ability allows updating WordPress settings that have show_in_abilities = true. It complements the existing core/get-settings ability by providing a symmetric API for reading and writing settings.

The input and output structures are designed for symmetry with core/get-settings:

  • Input accepts settings grouped by registration group (same structure returned by core/get-settings)
  • Output returns updated_settings (with updated values) and validation_errors (with error messages) in the same grouped structure

Organization

Following the pattern established in #10747, this PR extends the WP_Settings_Abilities class in src/wp-includes/abilities/class-wp-settings-abilities.php. The implementation maximizes code reuse:

  • Schema Reuse: Uses a permissive input schema that allows the execute callback to handle validation
  • Shared Methods: Reuses get_allowed_settings(), check_manage_options(), and cast_value() from the existing implementation
  • Registration: register_update_settings() registers the ability with appropriate annotations (readonly: false, destructive: false, idempotent: true)
  • Execution: execute_update_settings() validates, sanitizes, and updates settings, returning both successful updates and validation errors

Test plan

  • Open /wp-admin/post-new.php
  • Open the browser console and run the following examples:
// Update a single setting
await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/update-settings/run',
  method: 'POST',
  data: {
    input: {
      settings: {
        general: {
          blogname: 'New Site Title'
        }
      }
    }
  }
});
  • Verify the setting is updated in the database
// Update multiple settings across groups
await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/update-settings/run',
  method: 'POST',
  data: {
    input: {
      settings: {
        general: {
          blogname: 'New Site Title',
          blogdescription: 'New Tagline'
        },
        reading: {
          posts_per_page: 15
        }
      }
    }
  }
});
  • Verify multiple settings across different groups are updated
// Check the ability schema
await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/update-settings'
});
  • Verify the input schema documents the settings structure
  • Verify the output schema documents updated_settings and validation_errors
// Workflow: Get settings, modify, update (symmetry test)
const settings = await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/get-settings/run'
});
settings.general.blogname = 'Modified Title';
await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/update-settings/run',
  method: 'POST',
  data: { input: { settings } }
});
  • Verify the get -> modify -> update workflow works seamlessly
// Test partial success with invalid settings
await wp.apiFetch({
  path: '/wp-abilities/v1/abilities/core/update-settings/run',
  method: 'POST',
  data: {
    input: {
      settings: {
        general: {
          blogname: 'Valid Title',
          unknown_setting: 'should fail'
        }
      }
    }
  }
});
  • Verify blogname is in updated_settings and unknown_setting is in validation_errors

  • Test permission check: Verify non-admin users cannot access the ability (requires manage_options capability)

  • Verify settings without show_in_abilities cannot be modified

  • Verify settings placed in wrong groups are rejected with appropriate error messages

Test plan with Gutenberg

  • Open /wp-admin/post-new.php
  • Open the browser console

Set abilitiesAPI:

const abilitiesAPI = (await import( '@wordpress/abilities' ) );

Run the following examples:

// Update a single setting
await abilitiesAPI.executeAbility('core/update-settings', {
  settings: {
    general: {
      blogname: 'New Site Title'
    }
  }
});
  • Verify the setting is updated in the database
// Update multiple settings across groups
await abilitiesAPI.executeAbility('core/update-settings', {
  settings: {
    general: {
      blogname: 'New Site Title',
      blogdescription: 'New Tagline'
    },
    reading: {
      posts_per_page: 15
    }
  }
});
  • Verify multiple settings across different groups are updated
// Workflow: Get settings, modify, update (symmetry test)
const settings = await abilitiesAPI.executeAbility('core/get-settings');
settings.general.blogname = 'Modified Title';
await abilitiesAPI.executeAbility('core/update-settings', { settings });
  • Verify the get -> modify -> update workflow works seamlessly
await abilitiesAPI.executeAbility('core/update-settings', {
  settings: {
    general: {
      blogname: 'Valid Title',
      unknown_setting: 'should fail'
    }
  }
});
  • Verify there is an invalid schema error.

@github-actions
Copy link

github-actions bot commented Feb 9, 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.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jorgefilipecosta.

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

@jorgefilipecosta jorgefilipecosta changed the title initial version Add: WordPress Core update settings ability Feb 9, 2026
@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

* @var array
*/
private static $output_schema;
private static $settings_schema;
Copy link
Member

Choose a reason for hiding this comment

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

Thanks to PHP 7.4:

Suggested change
private static $settings_schema;
private static array $settings_schema;

* Schema for settings grouped by registration group.
*
* @since 7.0.0
* @var array
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @var array
* @var array<string, mixed>

* @since 7.0.0
* @var string[]
*/
private static $available_slugs;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private static array $available_slugs;

* @since 7.0.0
*
* @return array JSON Schema for the output.
* @return array JSON Schema for settings.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @return array JSON Schema for settings.
* @return array<string, mixed> JSON Schema for settings.

Comment on lines +261 to +262
*
* @return void
Copy link
Member

Choose a reason for hiding this comment

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

Core doesn't use @return void generally. (cf. r61599)

Suggested change
*
* @return void

}

return array(
'updated_settings' => ! empty( $updated_settings ) ? $updated_settings : (object) array(),
Copy link
Member

Choose a reason for hiding this comment

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

Is (object) array() correct given that $updated_settings is an array? I suppose this forces JSON to be encoded as {} instead of []?

*
* @type array $settings Settings to update, grouped by registration group.
* }
* @return array|WP_Error Updated settings on success, WP_Error on failure.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @return array|WP_Error Updated settings on success, WP_Error on failure.
* @return array<string, array<string, mixed>|object>|WP_Error Updated settings on success, WP_Error on failure.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants