Title: WP_REST_Server::serve_request
Published: December 9, 2015
Last modified: February 24, 2026

---

# WP_REST_Server::serve_request( string|null $path = null ): null|false

## In this article

 * [Description](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#description)
    - [See also](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#see-also)
 * [Parameters](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#parameters)
 * [Return](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#return)
 * [Source](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#source)
 * [Hooks](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#hooks)
 * [Related](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#changelog)

[ Back to top](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#wp--skip-link--target)

Handles serving a REST API request.

## 󠀁[Description](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#description)󠁿

Matches the current server URI to a route and runs the first matching callback then
outputs a JSON representation of the returned value.

### 󠀁[See also](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#see-also)󠁿

 * [WP_REST_Server::dispatch()](https://developer.wordpress.org/reference/classes/WP_REST_Server/dispatch/)

## 󠀁[Parameters](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#parameters)󠁿

 `$path`string|nulloptional

The request route. If not set, `$_SERVER['PATH_INFO']` will be used.

Default:`null`

## 󠀁[Return](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#return)󠁿

 null|false Null if not served and a HEAD request, false otherwise.

## 󠀁[Source](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#source)󠁿

    ```php
    public function serve_request( $path = null ) {
    	/* @var WP_User|null $current_user */
    	global $current_user;

    	if ( $current_user instanceof WP_User && ! $current_user->exists() ) {
    		/*
    		 * If there is no current user authenticated via other means, clear
    		 * the cached lack of user, so that an authenticate check can set it
    		 * properly.
    		 *
    		 * This is done because for authentications such as Application
    		 * Passwords, we don't want it to be accepted unless the current HTTP
    		 * request is a REST API request, which can't always be identified early
    		 * enough in evaluation.
    		 */
    		$current_user = null;
    	}

    	/**
    	 * Filters whether JSONP is enabled for the REST API.
    	 *
    	 * @since 4.4.0
    	 *
    	 * @param bool $jsonp_enabled Whether JSONP is enabled. Default true.
    	 */
    	$jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );

    	$jsonp_callback = false;
    	if ( isset( $_GET['_jsonp'] ) ) {
    		$jsonp_callback = $_GET['_jsonp'];
    	}

    	$content_type = ( $jsonp_callback && $jsonp_enabled ) ? 'application/javascript' : 'application/json';
    	$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
    	$this->send_header( 'X-Robots-Tag', 'noindex' );

    	$api_root = get_rest_url();
    	if ( ! empty( $api_root ) ) {
    		$this->send_header( 'Link', '<' . sanitize_url( $api_root ) . '>; rel="https://api.w.org/"' );
    	}

    	/*
    	 * Mitigate possible JSONP Flash attacks.
    	 *
    	 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
    	 */
    	$this->send_header( 'X-Content-Type-Options', 'nosniff' );

    	/**
    	 * Filters whether the REST API is enabled.
    	 *
    	 * @since 4.4.0
    	 * @deprecated 4.7.0 Use the 'rest_authentication_errors' filter to
    	 *                   restrict access to the REST API.
    	 *
    	 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
    	 */
    	apply_filters_deprecated(
    		'rest_enabled',
    		array( true ),
    		'4.7.0',
    		'rest_authentication_errors',
    		sprintf(
    			/* translators: %s: rest_authentication_errors */
    			__( 'The REST API can no longer be completely disabled, the %s filter can be used to restrict access to the API, instead.' ),
    			'rest_authentication_errors'
    		)
    	);

    	if ( $jsonp_callback ) {
    		if ( ! $jsonp_enabled ) {
    			echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
    			return false;
    		}

    		if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) {
    			echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 );
    			return false;
    		}
    	}

    	if ( empty( $path ) ) {
    		if ( isset( $_SERVER['PATH_INFO'] ) ) {
    			$path = $_SERVER['PATH_INFO'];
    		} else {
    			$path = '/';
    		}
    	}

    	$request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );

    	$request->set_query_params( wp_unslash( $_GET ) );
    	$request->set_body_params( wp_unslash( $_POST ) );
    	$request->set_file_params( $_FILES );
    	$request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
    	$request->set_body( self::get_raw_data() );

    	/*
    	 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
    	 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
    	 * header.
    	 */
    	$method_overridden = false;
    	if ( isset( $_GET['_method'] ) ) {
    		$request->set_method( $_GET['_method'] );
    	} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
    		$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
    		$method_overridden = true;
    	}

    	$expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );

    	/**
    	 * Filters the list of response headers that are exposed to REST API CORS requests.
    	 *
    	 * @since 5.5.0
    	 * @since 6.3.0 The `$request` parameter was added.
    	 *
    	 * @param string[]        $expose_headers The list of response headers to expose.
    	 * @param WP_REST_Request $request        The request in context.
    	 */
    	$expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers, $request );

    	$this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );

    	$allow_headers = array(
    		'Authorization',
    		'X-WP-Nonce',
    		'Content-Disposition',
    		'Content-MD5',
    		'Content-Type',
    	);

    	/**
    	 * Filters the list of request headers that are allowed for REST API CORS requests.
    	 *
    	 * The allowed headers are passed to the browser to specify which
    	 * headers can be passed to the REST API. By default, we allow the
    	 * Content-* headers needed to upload files to the media endpoints.
    	 * As well as the Authorization and Nonce headers for allowing authentication.
    	 *
    	 * @since 5.5.0
    	 * @since 6.3.0 The `$request` parameter was added.
    	 *
    	 * @param string[]        $allow_headers The list of request headers to allow.
    	 * @param WP_REST_Request $request       The request in context.
    	 */
    	$allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers, $request );

    	$this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );

    	$result = $this->check_authentication();

    	if ( ! is_wp_error( $result ) ) {
    		$result = $this->dispatch( $request );
    	}

    	// Normalize to either WP_Error or WP_REST_Response...
    	$result = rest_ensure_response( $result );

    	// ...then convert WP_Error across.
    	if ( is_wp_error( $result ) ) {
    		$result = $this->error_to_response( $result );
    	}

    	/**
    	 * Filters the REST API response.
    	 *
    	 * Allows modification of the response before returning.
    	 *
    	 * @since 4.4.0
    	 * @since 4.5.0 Applied to embedded responses.
    	 *
    	 * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
    	 * @param WP_REST_Server   $server  Server instance.
    	 * @param WP_REST_Request  $request Request used to generate the response.
    	 */
    	$result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );

    	// Wrap the response in an envelope if asked for.
    	if ( isset( $_GET['_envelope'] ) ) {
    		$embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
    		$result = $this->envelope_response( $result, $embed );
    	}

    	// Send extra data from response objects.
    	$headers = $result->get_headers();
    	$this->send_headers( $headers );

    	$code = $result->get_status();
    	$this->set_status( $code );

    	/**
    	 * Filters whether to send no-cache headers on a REST API request.
    	 *
    	 * @since 4.4.0
    	 * @since 6.3.2 Moved the block to catch the filter added on rest_cookie_check_errors() from wp-includes/rest-api.php.
    	 *
    	 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
    	 */
    	$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );

    	/*
    	 * Send no-cache headers if $send_no_cache_headers is true,
    	 * OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code.
    	 */
    	if ( $send_no_cache_headers || ( true === $method_overridden && str_starts_with( $code, '4' ) ) ) {
    		foreach ( wp_get_nocache_headers() as $header => $header_value ) {
    			if ( empty( $header_value ) ) {
    				$this->remove_header( $header );
    			} else {
    				$this->send_header( $header, $header_value );
    			}
    		}
    	}

    	/**
    	 * Filters whether the REST API request has already been served.
    	 *
    	 * Allow sending the request manually - by returning true, the API result
    	 * will not be sent to the client.
    	 *
    	 * @since 4.4.0
    	 *
    	 * @param bool             $served  Whether the request has already been served.
    	 *                                           Default false.
    	 * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
    	 * @param WP_REST_Request  $request Request used to generate the response.
    	 * @param WP_REST_Server   $server  Server instance.
    	 */
    	$served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );

    	if ( ! $served ) {
    		if ( 'HEAD' === $request->get_method() ) {
    			return null;
    		}

    		// Embed links inside the request.
    		$embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
    		$result = $this->response_to_data( $result, $embed );

    		/**
    		 * Filters the REST API response.
    		 *
    		 * Allows modification of the response data after inserting
    		 * embedded data (if any) and before echoing the response data.
    		 *
    		 * @since 4.8.1
    		 *
    		 * @param array            $result  Response data to send to the client.
    		 * @param WP_REST_Server   $server  Server instance.
    		 * @param WP_REST_Request  $request Request used to generate the response.
    		 */
    		$result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );

    		// The 204 response shouldn't have a body.
    		if ( 204 === $code || null === $result ) {
    			return null;
    		}

    		$result = wp_json_encode( $result, $this->get_json_encode_options( $request ) );

    		$json_error_message = $this->get_json_last_error();

    		if ( $json_error_message ) {
    			$this->set_status( 500 );
    			$json_error_obj = new WP_Error(
    				'rest_encode_error',
    				$json_error_message,
    				array( 'status' => 500 )
    			);

    			$result = $this->error_to_response( $json_error_obj );
    			$result = wp_json_encode( $result->data, $this->get_json_encode_options( $request ) );
    		}

    		if ( $jsonp_callback ) {
    			// Prepend '/**/' to mitigate possible JSONP Flash attacks.
    			// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
    			echo '/**/' . $jsonp_callback . '(' . $result . ')';
    		} else {
    			echo $result;
    		}
    	}

    	return null;
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/rest-api/class-wp-rest-server.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/6.9.4/src/wp-includes/rest-api/class-wp-rest-server.php#L285)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/6.9.4/src/wp-includes/rest-api/class-wp-rest-server.php#L285-L571)

## 󠀁[Hooks](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#hooks)󠁿

 [apply_filters( ‘rest_allowed_cors_headers’, string[] $allow_headers, WP_REST_Request $request )](https://developer.wordpress.org/reference/hooks/rest_allowed_cors_headers/)

Filters the list of request headers that are allowed for REST API CORS requests.

 [apply_filters_deprecated( ‘rest_enabled’, bool $rest_enabled )](https://developer.wordpress.org/reference/hooks/rest_enabled/)

Filters whether the REST API is enabled.

 [apply_filters( ‘rest_exposed_cors_headers’, string[] $expose_headers, WP_REST_Request $request )](https://developer.wordpress.org/reference/hooks/rest_exposed_cors_headers/)

Filters the list of response headers that are exposed to REST API CORS requests.

 [apply_filters( ‘rest_jsonp_enabled’, bool $jsonp_enabled )](https://developer.wordpress.org/reference/hooks/rest_jsonp_enabled/)

Filters whether JSONP is enabled for the REST API.

 [apply_filters( ‘rest_post_dispatch’, WP_HTTP_Response $result, WP_REST_Server $server, WP_REST_Request $request )](https://developer.wordpress.org/reference/hooks/rest_post_dispatch/)

Filters the REST API response.

 [apply_filters( ‘rest_pre_echo_response’, array $result, WP_REST_Server $server, WP_REST_Request $request )](https://developer.wordpress.org/reference/hooks/rest_pre_echo_response/)

Filters the REST API response.

 [apply_filters( ‘rest_pre_serve_request’, bool $served, WP_HTTP_Response $result, WP_REST_Request $request, WP_REST_Server $server )](https://developer.wordpress.org/reference/hooks/rest_pre_serve_request/)

Filters whether the REST API request has already been served.

 [apply_filters( ‘rest_send_nocache_headers’, bool $rest_send_nocache_headers )](https://developer.wordpress.org/reference/hooks/rest_send_nocache_headers/)

Filters whether to send no-cache headers on a REST API request.

## 󠀁[Related](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#related)󠁿

| Uses | Description | 
| [WP_REST_Server::get_json_encode_options()](https://developer.wordpress.org/reference/classes/wp_rest_server/get_json_encode_options/)`wp-includes/rest-api/class-wp-rest-server.php` |

Gets the encoding options passed to wp_json_encode.

  | 
| [rest_parse_embed_param()](https://developer.wordpress.org/reference/functions/rest_parse_embed_param/)`wp-includes/rest-api.php` |

Parses the “_embed” parameter into the list of resources to embed.

  | 
| [WP_REST_Server::remove_header()](https://developer.wordpress.org/reference/classes/wp_rest_server/remove_header/)`wp-includes/rest-api/class-wp-rest-server.php` |

Removes an HTTP header from the current response.

  | 
| [wp_check_jsonp_callback()](https://developer.wordpress.org/reference/functions/wp_check_jsonp_callback/)`wp-includes/functions.php` |

Checks that a JSONP callback is a valid JavaScript callback name.

  | 
| [apply_filters_deprecated()](https://developer.wordpress.org/reference/functions/apply_filters_deprecated/)`wp-includes/plugin.php` |

Fires functions attached to a deprecated filter hook.

  | 
| [get_rest_url()](https://developer.wordpress.org/reference/functions/get_rest_url/)`wp-includes/rest-api.php` |

Retrieves the URL to a REST endpoint on a site.

  | 
| [WP_REST_Request::__construct()](https://developer.wordpress.org/reference/classes/wp_rest_request/__construct/)`wp-includes/rest-api/class-wp-rest-request.php` |

Constructor.

  | 
| [WP_REST_Server::get_headers()](https://developer.wordpress.org/reference/classes/wp_rest_server/get_headers/)`wp-includes/rest-api/class-wp-rest-server.php` |

Extracts headers from a PHP-style $_SERVER array.

  | 
| [WP_REST_Server::send_header()](https://developer.wordpress.org/reference/classes/wp_rest_server/send_header/)`wp-includes/rest-api/class-wp-rest-server.php` |

Sends an HTTP header.

  | 
| [WP_REST_Server::get_raw_data()](https://developer.wordpress.org/reference/classes/wp_rest_server/get_raw_data/)`wp-includes/rest-api/class-wp-rest-server.php` |

Retrieves the raw request entity (body).

  | 
| [WP_REST_Server::send_headers()](https://developer.wordpress.org/reference/classes/wp_rest_server/send_headers/)`wp-includes/rest-api/class-wp-rest-server.php` |

Sends multiple HTTP headers.

  | 
| [WP_REST_Server::set_status()](https://developer.wordpress.org/reference/classes/wp_rest_server/set_status/)`wp-includes/rest-api/class-wp-rest-server.php` |

Sends an HTTP status code.

  | 
| [WP_REST_Server::dispatch()](https://developer.wordpress.org/reference/classes/wp_rest_server/dispatch/)`wp-includes/rest-api/class-wp-rest-server.php` |

Matches the request to a callback and call it.

  | 
| [WP_REST_Server::get_json_last_error()](https://developer.wordpress.org/reference/classes/wp_rest_server/get_json_last_error/)`wp-includes/rest-api/class-wp-rest-server.php` |

Returns if an error occurred during most recent JSON encode/decode.

  | 
| [WP_REST_Server::envelope_response()](https://developer.wordpress.org/reference/classes/wp_rest_server/envelope_response/)`wp-includes/rest-api/class-wp-rest-server.php` |

Wraps the response in an envelope.

  | 
| [WP_REST_Server::response_to_data()](https://developer.wordpress.org/reference/classes/wp_rest_server/response_to_data/)`wp-includes/rest-api/class-wp-rest-server.php` |

Converts a response to data to send.

  | 
| [WP_REST_Server::json_error()](https://developer.wordpress.org/reference/classes/wp_rest_server/json_error/)`wp-includes/rest-api/class-wp-rest-server.php` |

Retrieves an appropriate error representation in JSON.

  | 
| [WP_REST_Server::check_authentication()](https://developer.wordpress.org/reference/classes/wp_rest_server/check_authentication/)`wp-includes/rest-api/class-wp-rest-server.php` |

Checks the authentication headers if supplied.

  | 
| [WP_REST_Server::error_to_response()](https://developer.wordpress.org/reference/classes/wp_rest_server/error_to_response/)`wp-includes/rest-api/class-wp-rest-server.php` |

Converts an error to a response object.

  | 
| [wp_get_nocache_headers()](https://developer.wordpress.org/reference/functions/wp_get_nocache_headers/)`wp-includes/functions.php` |

Gets the HTTP header information to prevent caching.

  | 
| [rest_ensure_response()](https://developer.wordpress.org/reference/functions/rest_ensure_response/)`wp-includes/rest-api.php` |

Ensures a REST response is a response object (for consistency).

  | 
| [wp_json_encode()](https://developer.wordpress.org/reference/functions/wp_json_encode/)`wp-includes/functions.php` |

Encodes a variable into JSON, with some confidence checks.

  | 
| [__()](https://developer.wordpress.org/reference/functions/__/)`wp-includes/l10n.php` |

Retrieves the translation of $text.

  | 
| [wp_unslash()](https://developer.wordpress.org/reference/functions/wp_unslash/)`wp-includes/formatting.php` |

Removes slashes from a string or recursively removes slashes from strings within an array.

  | 
| [is_user_logged_in()](https://developer.wordpress.org/reference/functions/is_user_logged_in/)`wp-includes/pluggable.php` |

Determines whether the current visitor is a logged in user.

  | 
| [sanitize_url()](https://developer.wordpress.org/reference/functions/sanitize_url/)`wp-includes/formatting.php` |

Sanitizes a URL for database or redirect usage.

  | 
| [apply_filters()](https://developer.wordpress.org/reference/functions/apply_filters/)`wp-includes/plugin.php` |

Calls the callback functions that have been added to a filter hook.

  | 
| [get_option()](https://developer.wordpress.org/reference/functions/get_option/)`wp-includes/option.php` |

Retrieves an option value based on an option name.

  | 
| [is_wp_error()](https://developer.wordpress.org/reference/functions/is_wp_error/)`wp-includes/load.php` |

Checks whether the given variable is a WordPress Error.

  | 
| [WP_Error::__construct()](https://developer.wordpress.org/reference/classes/wp_error/__construct/)`wp-includes/class-wp-error.php` |

Initializes the error.

  |

[Show 25 more](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#)

## 󠀁[Changelog](https://developer.wordpress.org/reference/classes/wp_rest_server/serve_request/?output_format=md#changelog)󠁿

| Version | Description | 
| [4.4.0](https://developer.wordpress.org/reference/since/4.4.0/) | Introduced. |

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Fclasses%2Fwp_rest_server%2Fserve_request%2F)
before being able to contribute a note or feedback.