Back to Blog
Lesson 8 of the Intermediate WordPress Plugins: REST API & React Admin course
WordPressJune 25, 20264 min read

Validating and Sanitizing API Arguments in WordPress REST API

Master API security by defining argument schemas in WordPress. Learn to validate and sanitize incoming REST API requests to ensure robust data integrity.

WordPressREST APISecurityPHPSanitizationValidationplugin-development

Previously in this course, we covered Implementing REST API Permission Callbacks for Secure Plugins, which ensured that only authorized users could hit our endpoints. While permission checks handle who can access your API, they don't solve the problem of what they send you.

In this lesson, we are shifting our focus to Validation, Sanitization, and API Security. Even if a user has permission to call your endpoint, they might send malicious, malformed, or unexpected data. Accepting unvalidated input is the fastest way to corrupt your database or introduce security vulnerabilities.

Defining Argument Schemas

When you register a route using register_rest_route, WordPress allows you to define an args array. This array acts as a contract between your client and your server. By explicitly declaring what your endpoint expects, you offload the burden of manual validation to the WordPress REST API infrastructure.

Each parameter in the args array can define:

  • required: A boolean indicating if the field is mandatory.
  • validate_callback: A function to check if the value is valid.
  • sanitize_callback: A function to clean the value before it reaches your handler.
  • type: The expected data type (string, integer, boolean).

Here is how you define a schema for a hypothetical update-kb-title endpoint:

PHP
add_action('rest_api_init', function () {
    register_rest_route('kb/v1', '/update-title', [
        'methods' => 'POST',
        'callback' => 'handle_kb_title_update',
        'permission_callback' => 'is_user_logged_in',
        'args' => [
            'post_id' => [
                'required' => true,
                'type' => 'integer',
                'validate_callback' => function($param) {
                    return is_numeric($param);
                }
            ],
            'new_title' => [
                'required' => true,
                'sanitize_callback' => 'sanitize_text_field',
            ]
        ]
    ]);
});

Sanitization vs. Validation

It is vital to distinguish between these two concepts. As we explored in Sanitizing User Input: Secure Your WordPress Database, sanitization is about cleaning data, while validation is about verifying its structure.

  • Validation: Use this to reject bad data. If a user provides an invalid post_id, your validate_callback should return false or a WP_Error object. WordPress will automatically halt the request and return a 400 Bad Request response.
  • Sanitization: Use this to strip dangerous tags or trim whitespace. sanitize_text_field is our go-to for standard strings; it removes line breaks, tabs, and invalid UTF-8 characters. For integers, use absint().

Handling Invalid Request Parameters

If your validate_callback returns a WP_Error, the REST API automatically transforms that into a JSON error response. This is significantly cleaner than manually checking if (empty($request['title'])) inside your main callback function.

Consider this robust approach to your handler:

PHP
function handle_kb_title_update($request) {
    #6A9955">// The arguments are already validated and sanitized by the time we get here!
    $post_id = $request['post_id'];
    $title = $request['new_title'];

    $result = wp_update_post([
        'ID' => $post_id,
        'post_title' => $title
    ]);

    if (is_wp_error($result)) {
        return new WP_Error('kb_update_failed', 'Could not update post', ['status' => 500]);
    }

    return ['success' => true];
}

By using the args schema, your main logic remains clean. You don't have to worry about whether new_title contains script tags or if post_id is actually an integer.

Hands-on Exercise

For our Knowledge Base plugin, we need an endpoint that accepts a category_id to filter posts.

  1. Open your plugin's main file where you register routes.
  2. Add a GET route for /kb/v1/posts.
  3. Define an args array for this route that includes a category_id parameter.
  4. Set required to false (as it's an optional filter).
  5. Use a sanitize_callback to ensure the value is treated as an integer (hint: use absint).
  6. Test the endpoint by passing a non-integer string in the URL parameters and verify that your sanitization forces it to an integer or that your validation rejects it.

Common Pitfalls

  • Ignoring the type property: If you define type => 'integer', WordPress will attempt to cast the input. Don't rely on this alone for security; always pair it with a validate_callback.
  • Over-sanitizing: Don't use sanitize_text_field on data that needs to contain HTML (like post content). Use wp_kses_post for content fields instead.
  • Assuming trust: Never assume the client (even your own React app) has correctly formatted the data. The REST API is a public interface; treat every request as potentially malicious.
  • Forgetting WP_Error: When validation fails, returning false will return a generic error. Returning new WP_Error allows you to provide specific, helpful feedback to the user.

Recap

Securing your API starts at the entry point. By using argument schemas within register_rest_route, you enforce data integrity before your application logic even runs. Remember: Validate the structure to ensure data is correct, and Sanitize the content to ensure it is safe. For broader context on how these practices fit into your overall plugin architecture, refer to Validating Settings: Secure Your WordPress Plugin Data.

Up next: We will put these skills to work by building the POST endpoints required for our Knowledge Base plugin to save new data to the database.

Similar Posts