Title: WP_REST_Posts_Controller::get_items
Published: December 6, 2016
Last modified: May 20, 2026

---

# WP_REST_Posts_Controller::get_items( WP_REST_Request $request ): 󠀁[WP_REST_Response](https://developer.wordpress.org/reference/classes/wp_rest_response/)󠁿|󠀁[WP_Error](https://developer.wordpress.org/reference/classes/wp_error/)󠁿

## In this article

 * [Parameters](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#parameters)
 * [Return](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#return)
 * [Source](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#source)
 * [Hooks](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#hooks)
 * [Related](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#changelog)

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

Retrieves a collection of posts.

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

 `$request`[WP_REST_Request](https://developer.wordpress.org/reference/classes/wp_rest_request/)
required

Full details about the request.

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

 [WP_REST_Response](https://developer.wordpress.org/reference/classes/wp_rest_response/)
|[WP_Error](https://developer.wordpress.org/reference/classes/wp_error/) Response
object on success, or [WP_Error](https://developer.wordpress.org/reference/classes/wp_error/)
object on failure.

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

    ```php
    public function get_items( $request ) {

    	// Ensure a search string is set in case the orderby is set to 'relevance'.
    	if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
    		return new WP_Error(
    			'rest_no_search_term_defined',
    			__( 'You need to define a search term to order by relevance.' ),
    			array( 'status' => 400 )
    		);
    	}

    	// Ensure an include parameter is set in case the orderby is set to 'include'.
    	if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
    		return new WP_Error(
    			'rest_orderby_include_missing_include',
    			__( 'You need to define an include parameter to order by include.' ),
    			array( 'status' => 400 )
    		);
    	}

    	// Retrieve the list of registered collection query parameters.
    	$registered = $this->get_collection_params();
    	$args       = array();

    	/*
    	 * This array defines mappings between public API query parameters whose
    	 * values are accepted as-passed, and their internal WP_Query parameter
    	 * name equivalents (some are the same). Only values which are also
    	 * present in $registered will be set.
    	 */
    	$parameter_mappings = array(
    		'author'         => 'author__in',
    		'author_exclude' => 'author__not_in',
    		'exclude'        => 'post__not_in',
    		'include'        => 'post__in',
    		'ignore_sticky'  => 'ignore_sticky_posts',
    		'menu_order'     => 'menu_order',
    		'offset'         => 'offset',
    		'order'          => 'order',
    		'orderby'        => 'orderby',
    		'page'           => 'paged',
    		'parent'         => 'post_parent__in',
    		'parent_exclude' => 'post_parent__not_in',
    		'search'         => 's',
    		'search_columns' => 'search_columns',
    		'slug'           => 'post_name__in',
    		'status'         => 'post_status',
    	);

    	/*
    	 * For each known parameter which is both registered and present in the request,
    	 * set the parameter's value on the query $args.
    	 */
    	foreach ( $parameter_mappings as $api_param => $wp_param ) {
    		if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
    			$args[ $wp_param ] = $request[ $api_param ];
    		}
    	}

    	// Check for & assign any parameters which require special handling or setting.
    	$args['date_query'] = array();

    	if ( isset( $registered['before'], $request['before'] ) ) {
    		$args['date_query'][] = array(
    			'before' => $request['before'],
    			'column' => 'post_date',
    		);
    	}

    	if ( isset( $registered['modified_before'], $request['modified_before'] ) ) {
    		$args['date_query'][] = array(
    			'before' => $request['modified_before'],
    			'column' => 'post_modified',
    		);
    	}

    	if ( isset( $registered['after'], $request['after'] ) ) {
    		$args['date_query'][] = array(
    			'after'  => $request['after'],
    			'column' => 'post_date',
    		);
    	}

    	if ( isset( $registered['modified_after'], $request['modified_after'] ) ) {
    		$args['date_query'][] = array(
    			'after'  => $request['modified_after'],
    			'column' => 'post_modified',
    		);
    	}

    	// Ensure our per_page parameter overrides any provided posts_per_page filter.
    	if ( isset( $registered['per_page'] ) ) {
    		$args['posts_per_page'] = $request['per_page'];
    	}

    	if ( isset( $registered['sticky'], $request['sticky'] ) ) {
    		$sticky_posts = get_option( 'sticky_posts', array() );
    		if ( ! is_array( $sticky_posts ) ) {
    			$sticky_posts = array();
    		}
    		if ( $request['sticky'] ) {
    			/*
    			 * As post__in will be used to only get sticky posts,
    			 * we have to support the case where post__in was already
    			 * specified.
    			 */
    			$args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts;

    			/*
    			 * If we intersected, but there are no post IDs in common,
    			 * WP_Query won't return "no posts" for post__in = array()
    			 * so we have to fake it a bit.
    			 */
    			if ( ! $args['post__in'] ) {
    				$args['post__in'] = array( 0 );
    			}
    		} elseif ( $sticky_posts ) {
    			/*
    			 * As post___not_in will be used to only get posts that
    			 * are not sticky, we have to support the case where post__not_in
    			 * was already specified.
    			 */
    			$args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts );
    		}
    	}

    	/*
    	 * Honor the original REST API `post__in` behavior. Don't prepend sticky posts
    	 * when `post__in` has been specified.
    	 */
    	if ( ! empty( $args['post__in'] ) ) {
    		unset( $args['ignore_sticky_posts'] );
    	}

    	if (
    		isset( $registered['search_semantics'], $request['search_semantics'] )
    		&& 'exact' === $request['search_semantics']
    	) {
    		$args['exact'] = true;
    	}

    	$args = $this->prepare_tax_query( $args, $request );

    	if ( isset( $registered['format'], $request['format'] ) ) {
    		$formats = $request['format'];
    		/*
    		 * The relation needs to be set to `OR` since the request can contain
    		 * two separate conditions. The user may be querying for items that have
    		 * either the `standard` format or a specific format.
    		 */
    		$formats_query = array( 'relation' => 'OR' );

    		/*
    		 * The default post format, `standard`, is not stored in the database.
    		 * If `standard` is part of the request, the query needs to exclude all post items that
    		 * have a format assigned.
    		 */
    		if ( in_array( 'standard', $formats, true ) ) {
    			$formats_query[] = array(
    				'taxonomy' => 'post_format',
    				'field'    => 'slug',
    				'operator' => 'NOT EXISTS',
    			);
    			// Remove the `standard` format, since it cannot be queried.
    			unset( $formats[ array_search( 'standard', $formats, true ) ] );
    		}

    		// Add any remaining formats to the formats query.
    		if ( ! empty( $formats ) ) {
    			// Add the `post-format-` prefix.
    			$terms = array_map(
    				static function ( $format ) {
    					return "post-format-$format";
    				},
    				$formats
    			);

    			$formats_query[] = array(
    				'taxonomy' => 'post_format',
    				'field'    => 'slug',
    				'terms'    => $terms,
    				'operator' => 'IN',
    			);
    		}

    		// Enable filtering by both post formats and other taxonomies by combining them with `AND`.
    		if ( isset( $args['tax_query'] ) ) {
    			$args['tax_query'][] = array(
    				'relation' => 'AND',
    				$formats_query,
    			);
    		} else {
    			$args['tax_query'] = $formats_query;
    		}
    	}

    	// Force the post_type argument, since it's not a user input variable.
    	$args['post_type'] = $this->post_type;

    	$is_head_request = $request->is_method( 'HEAD' );
    	if ( $is_head_request ) {
    		// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
    		$args['fields'] = 'ids';
    		// Disable priming post meta for HEAD requests to improve performance.
    		$args['update_post_term_cache'] = false;
    		$args['update_post_meta_cache'] = false;
    	}

    	/**
    	 * Filters WP_Query arguments when querying posts via the REST API.
    	 *
    	 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
    	 *
    	 * Possible hook names include:
    	 *
    	 *  - `rest_post_query`
    	 *  - `rest_page_query`
    	 *  - `rest_attachment_query`
    	 *
    	 * Enables adding extra arguments or setting defaults for a post collection request.
    	 *
    	 * @since 4.7.0
    	 * @since 5.7.0 Moved after the `tax_query` query arg is generated.
    	 *
    	 * @link https://developer.wordpress.org/reference/classes/wp_query/
    	 *
    	 * @param array           $args    Array of arguments for WP_Query.
    	 * @param WP_REST_Request $request The REST API request.
    	 */
    	$args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
    	if ( ! is_array( $args ) ) {
    		$args = array();
    	}
    	$query_args = $this->prepare_items_query( $args, $request );

    	$posts_query  = new WP_Query();
    	$query_result = $posts_query->query( $query_args );

    	// Allow access to all password protected posts if the context is edit.
    	if ( 'edit' === $request['context'] ) {
    		add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
    	}

    	if ( ! $is_head_request ) {
    		$posts = array();

    		update_post_author_caches( $query_result );
    		update_post_parent_caches( $query_result );

    		if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
    			update_post_thumbnail_cache( $posts_query );
    		}

    		foreach ( $query_result as $post ) {
    			if ( 'edit' === $request['context'] ) {
    				$permission = $this->check_update_permission( $post );
    			} else {
    				$permission = $this->check_read_permission( $post );
    			}

    			if ( ! $permission ) {
    				continue;
    			}

    			$data    = $this->prepare_item_for_response( $post, $request );
    			$posts[] = $this->prepare_response_for_collection( $data );
    		}
    	}

    	// Reset filter.
    	if ( 'edit' === $request['context'] ) {
    		remove_filter( 'post_password_required', array( $this, 'check_password_required' ) );
    	}

    	$page        = (int) ( $query_args['paged'] ?? 0 );
    	$total_posts = $posts_query->found_posts;

    	if ( $total_posts < 1 && $page > 1 ) {
    		// Out-of-bounds, run the query without pagination/offset to get the total count.
    		unset( $query_args['paged'] );

    		$count_query                          = new WP_Query();
    		$query_args['fields']                 = 'ids';
    		$query_args['posts_per_page']         = 1;
    		$query_args['update_post_meta_cache'] = false;
    		$query_args['update_post_term_cache'] = false;

    		$count_query->query( $query_args );
    		$total_posts = $count_query->found_posts;
    	}

    	$max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] );

    	if ( $page > $max_pages && $total_posts > 0 ) {
    		return new WP_Error(
    			'rest_post_invalid_page_number',
    			__( 'The page number requested is larger than the number of pages available.' ),
    			array( 'status' => 400 )
    		);
    	}

    	$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $posts );

    	$response->header( 'X-WP-Total', (int) $total_posts );
    	$response->header( 'X-WP-TotalPages', (int) $max_pages );

    	$request_params = $request->get_query_params();
    	$collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) );
    	$base           = add_query_arg( urlencode_deep( $request_params ), $collection_url );

    	if ( $page > 1 ) {
    		$prev_page = $page - 1;

    		if ( $prev_page > $max_pages ) {
    			$prev_page = $max_pages;
    		}

    		$prev_link = add_query_arg( 'page', $prev_page, $base );
    		$response->link_header( 'prev', $prev_link );
    	}
    	if ( $max_pages > $page ) {
    		$next_page = $page + 1;
    		$next_link = add_query_arg( 'page', $next_page, $base );

    		$response->link_header( 'next', $next_link );
    	}

    	return $response;
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/7.0/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L215)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/7.0/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L215-L543)

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

 [apply_filters( “rest_{$this->post_type}_query”, array $args, WP_REST_Request $request )](https://developer.wordpress.org/reference/hooks/rest_this-post_type_query/)

Filters [WP_Query](https://developer.wordpress.org/reference/classes/wp_query/) 
arguments when querying posts via the REST API.

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

| Uses | Description | 
| [update_post_parent_caches()](https://developer.wordpress.org/reference/functions/update_post_parent_caches/)`wp-includes/post.php` |

Updates parent post caches for a list of post objects.

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

Updates post author user caches for a list of post objects.

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

Gets the REST API route for a post type.

  | 
| [WP_REST_Posts_Controller::prepare_tax_query()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/prepare_tax_query/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Prepares the ‘tax_query’ for a collection of posts.

  | 
| [WP_REST_Posts_Controller::get_collection_params()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/get_collection_params/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Retrieves the query params for the posts collection.

  | 
| [WP_REST_Posts_Controller::check_update_permission()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/check_update_permission/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Checks if a post can be edited.

  | 
| [WP_REST_Posts_Controller::check_read_permission()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/check_read_permission/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Checks if a post can be read.

  | 
| [WP_REST_Posts_Controller::prepare_item_for_response()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/prepare_item_for_response/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Prepares a single post output for response.

  | 
| [WP_REST_Posts_Controller::prepare_items_query()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/prepare_items_query/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Determines the allowed query_vars for a get_items() response and prepares them for [WP_Query](https://developer.wordpress.org/reference/classes/wp_query/).

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

Navigates through an array, object, or scalar, and encodes the values to be used in a URL.

  | 
| [WP_Query::__construct()](https://developer.wordpress.org/reference/classes/wp_query/__construct/)`wp-includes/class-wp-query.php` |

Constructor.

  | 
| [update_post_thumbnail_cache()](https://developer.wordpress.org/reference/functions/update_post_thumbnail_cache/)`wp-includes/post-thumbnail-template.php` |

Updates cache for thumbnails in the current loop.

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

Checks a post type’s support for a given feature.

  | 
| [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).

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

Retrieves the URL to a REST endpoint.

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

Retrieves the translation of $text.

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

Retrieves a modified URL query string.

  | 
| [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.

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

Adds a callback function to a filter hook.

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

Removes a callback function from 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.

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

Initializes the error.

  |

[Show 17 more](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/classes/WP_REST_Posts_Controller/get_items/?output_format=md#)

| Used by | Description | 
| [WP_REST_Font_Faces_Controller::get_items()](https://developer.wordpress.org/reference/classes/wp_rest_font_faces_controller/get_items/)`wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php` |

Retrieves a collection of font faces within the parent font family.

  |

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

| Version | Description | 
| [4.7.0](https://developer.wordpress.org/reference/since/4.7.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_posts_controller%2Fget_items%2F)
before being able to contribute a note or feedback.