Table of Contents
- Settings Registration Foundation
- register_setting with REST API
- Schema-Based Validation
- show_in_rest Configuration
- Sanitize and Validation Callbacks
- Type Checking and Coercion
- Complex Setting Structures
- Permission Control for Settings
- FAQ
Settings Registration Foundation
WordPress plugin settings with REST API support require proper registration using register_setting. Unlike older approaches that relied on options pages, modern plugins expose settings through both admin UI and REST API with unified validation.
Traditional WordPress plugin settings didn't expose configuration through REST API. Modern plugins use REST-aware settings that are accessible and validated consistently across both interfaces.
The WordPress plugin settings REST API pattern requires three components: registration with register_setting, schema validation, and proper REST exposure:
<?php
// Traditional settings: No REST support
add_option('my_plugin_api_key', '');
// Modern settings: REST-enabled with validation
register_setting('my_plugin_group', 'my_plugin_api_key', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'show_in_rest' => true,
'default' => '',
]);
This simple registration provides several capabilities: sanitization, REST API exposure, schema validation, and default values.
WP HealthKit audits plugin settings for proper validation, identifying unvalidated settings, missing type information, and REST API exposure risks.
register_setting with REST API
Properly configured register_setting calls expose plugin settings to REST API:
<?php
// Comprehensive register_setting example
register_setting('my_plugin_settings', 'my_plugin_config', [
// Data type
'type' => 'object',
// Default value
'default' => [
'api_key' => '',
'api_secret' => '',
'enable_feature' => false,
'max_retries' => 3,
],
// Sanitization callback
'sanitize_callback' => function($value) {
if (!is_array($value)) {
return [];
}
return [
'api_key' => sanitize_text_field($value['api_key'] ?? ''),
'api_secret' => sanitize_text_field($value['api_secret'] ?? ''),
'enable_feature' => rest_sanitize_boolean($value['enable_feature'] ?? false),
'max_retries' => rest_validate_request_arg(
$value['max_retries'] ?? 3,
['type' => 'integer', 'minimum' => 1, 'maximum' => 10]
),
];
},
// Validation callback
'validate_callback' => function($value) {
if (!is_array($value)) {
return new WP_Error('invalid_format', 'Settings must be an object');
}
if (strlen($value['api_key'] ?? '') < 10) {
return new WP_Error('invalid_api_key', 'API key must be at least 10 characters');
}
return true;
},
// Expose via REST API
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
'api_key' => [
'type' => 'string',
'description' => 'API key for service authentication',
'minLength' => 10,
'maxLength' => 255,
],
'api_secret' => [
'type' => 'string',
'description' => 'API secret for service authentication',
'minLength' => 20,
'maxLength' => 512,
],
'enable_feature' => [
'type' => 'boolean',
'description' => 'Enable optional feature',
'default' => false,
],
'max_retries' => [
'type' => 'integer',
'description' => 'Maximum retry attempts',
'minimum' => 1,
'maximum' => 10,
'default' => 3,
],
],
'required' => ['api_key'],
],
],
]);
Register with sanitize_callback and validate_callback:
<?php
add_action('admin_init', function() {
register_setting('my_plugin_group', 'my_plugin_api_key', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function($value) {
// Validate API key format
if (!preg_match('/^[A-Z0-9]{32}$/', $value)) {
return new WP_Error(
'invalid_api_key_format',
'API key must be 32 uppercase alphanumeric characters'
);
}
return true;
},
'show_in_rest' => true,
]);
register_setting('my_plugin_group', 'my_plugin_features', [
'type' => 'array',
'sanitize_callback' => function($value) {
return array_map('sanitize_text_field', (array) $value);
},
'show_in_rest' => [
'schema' => [
'type' => 'array',
'items' => [
'type' => 'string',
'enum' => ['feature_a', 'feature_b', 'feature_c'],
],
],
],
]);
});
Schema-Based Validation
Schemas define what data structures are acceptable. REST API uses JSON Schema format:
<?php
// Detailed schema for complex settings
register_setting('my_plugin_settings', 'my_plugin_advanced', [
'type' => 'object',
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
'database_host' => [
'type' => 'string',
'format' => 'hostname',
'description' => 'Database host address',
],
'database_port' => [
'type' => 'integer',
'minimum' => 1,
'maximum' => 65535,
'default' => 3306,
'description' => 'Database port number',
],
'database_name' => [
'type' => 'string',
'pattern' => '^[a-zA-Z_][a-zA-Z0-9_]*$',
'description' => 'Database name (valid identifier)',
],
'max_connections' => [
'type' => 'integer',
'minimum' => 1,
'maximum' => 1000,
'multipleOf' => 5, // Must be multiple of 5
'description' => 'Maximum concurrent connections',
],
'timeout_ms' => [
'type' => 'integer',
'minimum' => 100,
'maximum' => 30000,
'default' => 5000,
'description' => 'Connection timeout in milliseconds',
],
'enabled' => [
'type' => 'boolean',
'default' => true,
'description' => 'Enable this service',
],
'tags' => [
'type' => 'array',
'items' => [
'type' => 'string',
'minLength' => 1,
'maxLength' => 50,
],
'maxItems' => 10,
'uniqueItems' => true,
'description' => 'Service tags',
],
],
'required' => ['database_host', 'database_name'],
],
],
]);
Schemas validate complex nested structures:
<?php
// Nested object validation
register_setting('my_plugin_settings', 'notification_config', [
'type' => 'object',
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
'email' => [
'type' => 'object',
'properties' => [
'enabled' => ['type' => 'boolean'],
'from_address' => ['type' => 'string', 'format' => 'email'],
'recipients' => [
'type' => 'array',
'items' => ['type' => 'string', 'format' => 'email'],
],
],
],
'sms' => [
'type' => 'object',
'properties' => [
'enabled' => ['type' => 'boolean'],
'api_key' => ['type' => 'string', 'minLength' => 20],
'phone_numbers' => [
'type' => 'array',
'items' => [
'type' => 'string',
'pattern' => '^\+?[1-9]\d{1,14}$', // E.164 format
],
],
],
],
'webhook' => [
'type' => 'object',
'properties' => [
'enabled' => ['type' => 'boolean'],
'url' => ['type' => 'string', 'format' => 'uri'],
'secret' => ['type' => 'string'],
],
],
],
],
],
]);
show_in_rest Configuration
Control how settings appear in REST API:
<?php
// Basic REST exposure
register_setting('my_plugin', 'api_key', [
'show_in_rest' => true, // Expose with auto-generated schema
]);
// Custom REST exposure with schema
register_setting('my_plugin', 'advanced_config', [
'show_in_rest' => [
'name' => 'my_custom_name', // Use different name in REST
'schema' => [ /* schema here */ ],
],
]);
// Don't expose sensitive settings in REST
register_setting('my_plugin', 'internal_cache', [
'show_in_rest' => false, // Not exposed
]);
// Expose but with strict schema
register_setting('my_plugin', 'public_settings', [
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
// Only expose certain fields
'public_feature' => ['type' => 'boolean'],
'public_limit' => ['type' => 'integer', 'minimum' => 0],
],
],
],
]);
REST API exposes settings at wp-json/wp/v2/settings endpoint. Retrieve and update:
// Fetch settings via REST API
fetch('/wp-json/wp/v2/settings', {
headers: {
'X-WP-Nonce': wpNonce,
},
})
.then(r => r.json())
.then(settings => console.log(settings));
// Update settings via REST API
fetch('/wp-json/wp/v2/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpNonce,
},
body: JSON.stringify({
my_plugin_api_key: 'new_api_key',
my_plugin_enabled: true,
}),
})
.then(r => r.json())
.then(result => console.log('Updated:', result));
Sanitize and Validation Callbacks
Sanitization and validation are distinct but complementary. Understanding the difference is crucial for security and data integrity. Sanitization transforms potentially unsafe data into safe data. Validation checks that data meets requirements—it doesn't transform it.
For example, if a user enters "" as their name, sanitization would remove the script tags. Validation would then check that the name is between 1 and 100 characters. These are separate operations serving different purposes.
Always apply sanitization first, then validation. This ensures that validation operates on clean data. If you validate first, you might accept dangerous input that only becomes safe after sanitization.
<?php
// Correct order: sanitize first, then validate
// Sanitization: Clean data to expected format
'sanitize_callback' => function($value) {
// Remove dangerous characters, normalize format
return sanitize_text_field(trim($value));
},
// Validation: Check data meets requirements
'validate_callback' => function($value) {
// Ensure data is valid; if not, return WP_Error
// At this point, $value has already been sanitized
if (strlen($value) < 5) {
return new WP_Error('too_short', 'Value too short');
}
return true;
},
A critical security pattern: never store unsanitized data. WordPress sanitization functions are designed for specific contexts (HTML output, URLs, database queries, etc.). Choose the appropriate sanitization function for how your data will be used:
Advanced sanitization patterns:
<?php
// Sanitize URL
'sanitize_callback' => function($value) {
return esc_url_raw($value);
},
'validate_callback' => function($value) {
$parsed = parse_url($value);
if (!$parsed || !in_array($parsed['scheme'] ?? '', ['http', 'https'])) {
return new WP_Error('invalid_url', 'Must be valid HTTP/HTTPS URL');
}
return true;
},
// Sanitize JSON
'sanitize_callback' => function($value) {
if (is_string($value)) {
$value = json_decode($value, true);
}
return wp_json_encode($value);
},
// Sanitize with type coercion
'sanitize_callback' => function($value) {
if (is_bool($value)) {
return $value;
}
return rest_sanitize_boolean($value);
},
Custom Validation Rules
Beyond basic type checking, implement domain-specific validation that ensures settings make logical sense together. Settings rarely exist in isolation—they interact with each other and the broader system.
For example, if you have settings for min and max values, validation should ensure min <= max. If you have retry settings, ensure the retry count is reasonable and retry delay isn't too short. If you have rate limit settings, ensure they're consistent with your architecture.
Custom validation prevents configuration states that would cause runtime errors or unexpected behavior:
<?php
// Custom validation: Logical consistency between settings
register_setting('my_plugin', 'rate_limit_config', [
'type' => 'object',
'validate_callback' => function($value) {
// Ensure rate limits make sense
if ($value['max_requests'] <= 0) {
return new WP_Error('invalid_max_requests', 'Max requests must be positive');
}
if ($value['time_window'] <= 0) {
return new WP_Error('invalid_time_window', 'Time window must be positive');
}
// Logical consistency: ensure maximum is larger than minimum
if (isset($value['burst_limit']) && $value['burst_limit'] > $value['max_requests']) {
return new WP_Error(
'invalid_burst_limit',
'Burst limit cannot exceed max requests'
);
}
return true;
},
]);
Type Checking and Coercion
Proper type handling prevents subtle bugs:
<?php
// Type-safe register_setting
register_setting('my_plugin', 'settings_json', [
'type' => 'string',
'sanitize_callback' => function($value) {
if (is_array($value)) {
$value = wp_json_encode($value);
}
// Validate JSON
if (!is_string($value)) {
return '{}';
}
$decoded = json_decode($value, true);
if (!is_array($decoded)) {
return '{}';
}
return wp_json_encode($decoded);
},
'validate_callback' => function($value) {
$decoded = json_decode($value, true);
return $decoded !== null;
},
]);
// Type coercion for REST
register_setting('my_plugin', 'numeric_value', [
'type' => 'integer',
'sanitize_callback' => function($value) {
return (int) $value;
},
'validate_callback' => function($value) {
return is_int($value) && $value >= 0;
},
]);
Complex Setting Structures
Patterns for multi-field settings:
<?php
// Settings object with multiple properties
register_setting('my_plugin', 'service_config', [
'type' => 'object',
'default' => [
'primary_host' => '',
'secondary_host' => '',
'failover_enabled' => false,
'health_check_interval' => 300,
],
'sanitize_callback' => function($value) {
return [
'primary_host' => sanitize_text_field($value['primary_host'] ?? ''),
'secondary_host' => sanitize_text_field($value['secondary_host'] ?? ''),
'failover_enabled' => rest_sanitize_boolean($value['failover_enabled'] ?? false),
'health_check_interval' => max(60, (int) ($value['health_check_interval'] ?? 300)),
];
},
'validate_callback' => function($value) {
if (empty($value['primary_host'])) {
return new WP_Error('missing_host', 'Primary host is required');
}
if (!filter_var($value['primary_host'], FILTER_VALIDATE_URL)) {
return new WP_Error('invalid_host', 'Primary host must be valid URL');
}
return true;
},
]);
Permission Control for Settings
Settings require authorization to read/modify:
<?php
// Only admins can view/modify
register_setting('my_plugin', 'admin_only_setting', [
'type' => 'string',
'show_in_rest' => true,
'rest_api_access_control_callback' => function() {
return current_user_can('manage_options');
},
]);
// Custom permission logic
register_setting('my_plugin', 'team_settings', [
'type' => 'object',
'show_in_rest' => [
'schema' => [ /* schema */ ],
'rest_api_access_control_callback' => function() {
// Only team managers can modify
return current_user_can('manage_plugin_team');
},
],
]);
Implement fine-grained permissions:
<?php
// Different permissions for read vs write
add_filter('register_setting_args', function($args, $option_group, $option_name) {
if ($option_name === 'my_plugin_settings') {
$args['rest_api_access_control_callback'] = function($prepared, $object) {
$request = rest_get_server()->last_request;
// GET: Anyone can read public settings
if ($request->get_method() === 'GET') {
return true;
}
// POST/PUT: Only admins can modify
return current_user_can('manage_options');
};
}
return $args;
}, 10, 3);
FAQ
What's the difference between register_setting and register_rest_field?
register_setting is for plugin options and WordPress core settings. register_rest_field is for adding data to existing REST endpoints (posts, users, etc.). Use register_setting for plugin configuration.
How do I prevent settings from being exposed in REST API?
Set 'show_in_rest' => false in register_setting. This keeps the setting stored and functional but not exposed via REST endpoints.
Can I use custom data types in settings?
REST API uses JSON-serializable types (string, number, boolean, object, array). For complex data, store as JSON string or object and sanitize/validate accordingly.
What happens if validation fails during REST API call?
WordPress returns 400 Bad Request with error details. The setting is not updated. Client receives error message to display to user.
How do I test settings validation?
Use REST API clients (Postman, curl) to test endpoint with various inputs. Verify sanitization works and validation rejects invalid data correctly. Create unit tests using WP_UnitTestCase.
Can I deprecate a setting?
You can mark it as deprecated in documentation but still support it for backward compatibility. For REST API, continue accepting it in validation but document the replacement.
Broader Context and Best Practices
Security vulnerabilities in WordPress plugins don't exist in isolation. Each vulnerability represents a potential entry point that attackers chain together to achieve broader compromise. A seemingly minor issue like improper input validation can escalate when combined with a privilege escalation flaw, turning a low-severity finding into a critical breach. This interconnected nature of security weaknesses is why comprehensive auditing matters so much. Rather than checking individual items in isolation, modern security analysis examines how different components interact and where those interactions create unexpected attack surfaces that manual review would miss entirely.
The WordPress plugin ecosystem's open-source nature creates both strengths and challenges for security. Open code allows community review, which catches many issues early. However, it also means attackers can study source code to find exploitable patterns before patches are released. This asymmetry makes proactive security testing essential rather than reactive. Developers who integrate automated security scanning into their development workflow catch vulnerabilities during development, long before code reaches production. The cost of fixing a security issue during development is orders of magnitude lower than addressing it after a public disclosure or active exploitation.
Understanding the attacker's perspective transforms how developers approach security. Attackers don't think in terms of individual functions or classes. They think in terms of data flows, trust boundaries, and privilege transitions. When data crosses from an untrusted context like user input into a trusted context like a database query, that boundary is where vulnerabilities emerge. By mapping these trust boundaries in your plugin architecture, you can systematically identify where validation, sanitization, and authorization checks are needed.
WordPress powers over forty percent of the web, making it the single largest target for automated attacks. Plugin vulnerabilities are the primary vector for these attacks, with Patchstack reporting thousands of new plugin vulnerabilities each year. The scale of the WordPress ecosystem means that even a vulnerability affecting a relatively obscure plugin can impact hundreds of thousands of sites. This reality underscores why every plugin developer has a responsibility to take security seriously.
Broader Industry Context and Best Practices
Security hardening in WordPress extends beyond individual plugin fixes to encompass a holistic defense strategy. Organizations managing multiple WordPress installations benefit from centralized security policies that enforce consistent standards across all sites. This includes automated vulnerability scanning, real-time threat intelligence feeds, and coordinated patch management. WP HealthKit provides the automated scanning infrastructure that makes centralized security monitoring practical, giving teams visibility into vulnerabilities across their entire WordPress portfolio. Regular security assessments should evaluate not just known vulnerabilities but also configuration drift, where settings gradually deviate from security baselines over time, creating subtle but exploitable weaknesses.
The WordPress security landscape continues evolving as attackers develop increasingly sophisticated techniques. Supply chain attacks targeting plugin update mechanisms, zero-day exploits in popular themes, and credential stuffing campaigns against wp-admin endpoints represent growing threat vectors. Effective defense requires layered security controls: web application firewalls filter malicious requests, file integrity monitoring detects unauthorized changes, and behavioral analysis identifies anomalous patterns. WP HealthKit scans for these vulnerability patterns automatically, helping teams stay ahead of emerging threats. Security teams should also implement network segmentation to limit lateral movement if an attacker compromises a single WordPress instance within a larger infrastructure.
Compliance requirements add another dimension to WordPress security planning. Organizations in regulated industries must demonstrate that their WordPress deployments meet specific security standards, whether PCI DSS for payment processing, HIPAA for healthcare data, or SOC 2 for service providers. This means maintaining detailed audit trails, implementing access controls with principle of least privilege, and conducting regular penetration testing. WP HealthKit audit reports provide documentation that supports compliance evidence gathering, making it easier to demonstrate security due diligence during audits. Automated compliance checking reduces the manual effort required for audit preparation while ensuring continuous adherence to security requirements throughout the year.
Incident preparedness separates resilient WordPress deployments from vulnerable ones. Before a security incident occurs, teams should establish clear incident response procedures, including communication templates, escalation paths, and forensic preservation protocols. Regular tabletop exercises help teams practice their response procedures, identifying gaps before real incidents expose them. Post-incident reviews should analyze root causes systematically, implementing both immediate fixes and longer-term architectural improvements to prevent recurrence. WP HealthKit helps organizations maintain continuous security visibility, which is essential for rapid incident detection and response. Building a security-conscious culture where all team members understand their role in maintaining WordPress security creates the strongest defense against evolving threats.
Strategic Considerations and Implementation Patterns
WordPress security monitoring requires continuous vigilance rather than periodic assessments. Automated scanning tools should run on scheduled intervals, checking for newly disclosed vulnerabilities, configuration changes, and suspicious file modifications. Real-time alerting ensures security teams can respond quickly to emerging threats rather than discovering issues during scheduled reviews. WP HealthKit provides this continuous monitoring capability, scanning WordPress installations on configurable schedules and alerting administrators to new findings. Security operations centers that manage multiple WordPress sites benefit from centralized dashboards that aggregate findings across all installations, enabling pattern recognition and coordinated response to widespread threats.
Authentication and session management represent critical security boundaries that require careful implementation. WordPress default authentication mechanisms can be strengthened with multi-factor authentication, session timeout policies, and brute force protection. Custom authentication flows for REST API endpoints must validate tokens properly and handle edge cases like token expiration and refresh. WP HealthKit audits authentication configurations to identify weaknesses that could allow unauthorized access. Organizations should implement the principle of least privilege, ensuring that each user account has only the minimum permissions necessary for its intended function, reducing the potential impact of compromised credentials.
Frequently Asked Questions
How does WP HealthKit detect security vulnerabilities automatically?
WP HealthKit uses 62 verification layers including static analysis, pattern matching, and dependency scanning to identify vulnerabilities in WordPress plugins. The automated scanning catches issues that manual code review would miss, providing comprehensive security coverage across your entire codebase.
What are the most common WordPress plugin security vulnerabilities?
The most frequently discovered vulnerabilities include cross-site scripting through improper output escaping, SQL injection via unparameterized queries, cross-site request forgery from missing nonce verification, and privilege escalation through inadequate capability checks. These four categories account for over seventy percent of all reported plugin vulnerabilities.
How often should I audit my WordPress plugin for security issues?
Security audits should happen at every major release, after significant code changes, and on a regular quarterly schedule. Automated scanning through CI/CD pipelines provides continuous monitoring, while thorough manual reviews should complement automated testing at least twice per year.
Can automated tools replace manual security code review?
Automated tools like WP HealthKit catch the majority of common vulnerability patterns quickly and consistently, but they complement rather than replace manual review. Complex business logic vulnerabilities, architectural issues, and novel attack vectors still benefit from expert human analysis. The ideal approach combines both.
What should I do if a vulnerability is discovered in my plugin?
Follow responsible disclosure practices: verify the vulnerability, develop and test a fix, notify affected users through your update channel, and publish a security advisory. Coordinate with the WordPress security team if the vulnerability is severe. Speed matters — most attackers begin exploitation within days of public disclosure.
Conclusion
WordPress plugin settings with REST API support require careful configuration. Proper registration with schemas, validation, and sanitization ensures data integrity across both admin UI and API interfaces.
Key patterns: use register_setting with complete arguments, define JSON schemas for REST exposure, implement both sanitization and validation, and control permissions appropriately.
WP HealthKit audits plugin settings for validation robustness, identifying insufficiently validated settings, missing schema information, and REST API exposure risks.
Ready to audit your WordPress settings? Upload your plugins to WP HealthKit for detailed analysis of settings validation, REST API exposure, and configuration security.
Internal Links
- WordPress REST API Permissions: User Capability Checks
- WordPress Plugin Admin Pages: Routing and UI Framework