Query block

WordPress query loop block with filters and AJAX loading.

Last modified: May 23, 2023

link Properties (t2.json)

link

features

link Style properties

link Screenshot

Block screenshot


link Introduction

This block allows you to list the results of a WP_Query, with extra features such as AJAX loading, taxonomy filtering and sorting logic, and search feature.

It can be inserted in a post content and configured using the block editor, or used inside a theme template and configured via attributes.

Contrary to the core Query Loop block, the results posts template, pagination and no-results blocks are automatically part of the block render output and are not needed as child inner blocks of this T2 Query block. Both results post template and no-results text can be customized via template files or filters (see below) to fit your needs.


link Usage

link In the editor

Simply add the Query loop with filters block to your page content and configure it using the sidebar settings.

link In a template or programmatically

Output this block using the <!-- wp:t2/query /--> directive and pass attributes to it. The complete list of attributes can be found below.

Example:

<!-- wp:t2/query { "inheritQuery": true, "id": "search", "enableSearchField": true, "postType": "any", "enablePostTypeFilter": true, "enableTaxonomyFilters": false } /-->

link Available attributes

Attribute Type Description Default value
id string An optional block identifier that can be used for specific customization, like loading highly-specific template files for a single block instance only. ""
query object An object representing the WP_Query for that block, if you want to configure the query more precisely than the few available settings.
inheritQuery boolean Whether to use the global WP_Query if the current page is a template page (archive, search, etc). false
postType string The post type to be queried. "post"
postsPerPage integer The number of results to display per page. 9
enablePagination boolean Whether to display a pagination after the list of results. true
enableSearchField boolean Whether to display a search input field before the filters. Useful on the site search page. false
enableTaxonomyFilters boolean Whether to display a list of filter dropdowns to filter results on specific taxonomies. true
allowMultipleValuesSelection boolean Controls whether multiple values can be selected in a taxonomy filter dropdown (<select multiple>). false
taxonomies string[] The list of taxonomies to use for the taxonomy filter dropdowns. ["category"]
enablePostTypeFilter boolean Whether to display a post type filter dropdown (useful on the search page to filter results per content type). false
enableSortByDropdown boolean Whether to display a "Sort by" dropdown. true
enableActiveFiltersPills boolean Whether to display the list of active taxonomy terms filters before the query results. true

link Template hierarchy for the query results

The following template files can be placed in your theme:

Path for block-based themes Path for PHP-templates themes
/parts/query/<block_id>/result-<post_type>.html /templates/query/<block_id>/result-<post_type>.php
/parts/query/<block_id>/result.html /templates/query/<block_id>/result.php
/parts/query/result-<post_type>.html /templates/query/result-<post_type>.php
/parts/query/result.html /templates/query/result.php

If none of these files exist, then the T2 Featured Content template (default, or specific to the queried post type) will be used instead.

By passing a unique id in a block attribute, you can display the block results differently on a specific page using one of the high-priority template files listed above. Note: in the block editor, you can use the native HTML Anchor setting field to set the id attribute.

For instance, if your search.html template uses the following block directive:

<!-- wp:t2/query { "id": "search", "enableSearchField": true, "postType": "any" } /-->

You can display a unique output for the search results page by creating a /parts/query/search/result.html template file. You can be even more specific by creating /parts/query/search/result-<post_type>.html to render different output depending on the result's post type.


link Hooks

link Rendering

The default Query block rendered markup will be output in the following order:

Component Hook in charge of component render
Search input field t2/query/search_html
Filters/sort-by form t2/query/filters_html
List of active filters pills t2/query/active_filters_html
Results items or no-results text t2/query/results_html
Pagination t2/query/pagination_html

Use the t2/query/render_hooks filter to:

/**
 * @param array    $order The component order.
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query.
 */
$hooks = apply_filters( 't2/query/render_hooks', [ 'search', 'filters', 'active_filters', 'results', 'pagination' ], $attributes, $query );

If you have injected a new component in that list, use the t2/query/{$component}_html action to output its content. For instance, if you've registered a new results_title item, you can output its markup with:

add_action( 't2/query/results_title_html', function( array $attributes, \WP_Query $query ): void {
	echo 'Results title goes here…';
} );

See a complete example below.

link Query modification

link Using a block attribute

You can customize the WP_Query being run in the block by passing a query attribute to the block directive.

<!-- wp:t2/query { "query": { "post_type": [ "post", "page" ], "meta_query": { … } } } /-->

Any valid WP_Query argument can be passed to the query object attribute. This query will serve as a "base query" during AJAX requests and extra parameters will be applied on top of it based on the user request (filtering, ordering, searching, changing page, etc.).

link Using filters

You can use the following two filters to programmatically alter the WP_Query arguments.

Alter the initial WP_Query on first page load

/**
 * @param array $args       The WP_Query arguments.
 * @param array $attributes The block attributes.
 */
$args = apply_filters( 't2/query/args', array $args, array $attributes );

Alter the WP_Query during AJAX requests (filtering, paginating, etc)

/**
 * @param array $args The WP_Query arguments.
 * @param array $data The AJAX request input data.
 */
$args = apply_filters( 't2/query/get_results/args', array $args, array $data );

Navigate to the /inc/integrations/event.php file to see how these two filters are used in action.

link A note on security

By default, this block only accepts a restricted list of WP_Query arguments. This is a security measure, as those arguments are passed from the frontend to the backend via an AJAX request.

Use the t2/query/secured_query_args filter to deliberately accept new query arguments.

/**
 * @param array $new_args The secured list of query arguments.
 * @param array $args The original query arguments.
 */
$args = apply_filters( 't2/query/secured_query_args', array $new_args, array $args );

The only arguments allowed by default are:

link AJAX response

Use the t2/query/get_results/response filter to inject extra data in the AJAX response sent to the frontend.

/**
 * @param array|WP_Error $response The AJAX response being sent back to the client.
 * @param array          $data The AJAX request input data.
 * @param WP_Query       $query The WP_Query which fetched the new results.
 */
$response = apply_filters( 't2/query/get_results/response', $response, $data, $query );

link Fragments

When an AJAX request runs to refresh the query results, you can inject new data on the frontend.
The AJAX response object has a fragments property where keys represent a DOM selector and values represent the HTML markup to be injected in those DOM elements.

Use the above t2/query/get_results/response filter to inject new fragments in the $response['fragments'] item.

See a complete example below.

link Filters form

link Single filter (<fieldset>) markup

Use the t2/query/filters filter to inject new sections/fields in the filters form.

/**
 * @param array    $filters An array where each item is a single filter HTML markup.
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query that run on page load.
 */
$filters = apply_filters( 't2/query/filters', $filters, $attributes, $query );

Each item in the $filters array contain the HTML output of a specific filter.
Browse the /inc/templating/filters.php file to see the default filters output or see a complete example below on how to inject a new field and alter the results query.

link Taxonomy filter label

You can use the t2/query/filters_taxonomy/label to alter the label displayed before a taxonomy dropdown. By default, the taxonomy singular name is used.

/**
 * @param string   $label The filter dropdown label.
 * @param string   $taxonomy The taxonomy slug.
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query that run on page load.
 */
$label = apply_filters( 't2/query/filters_taxonomy/label', $label, $taxonomy_slug, $attributes, $query );

link Post type filter options

Use the t2/query/filters_post_type/post_types filter to modify the list of post types displayed in the post type filter dropdown. By default, all public post types (except media attachments) are listed.

/**
 * @param array    $options The post type options (keys = post type slug, value = post type readable name).
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query that run on page load.
 */
$options  = apply_filters( 't2/query/filters_post_type/post_types', $options, $attributes, $query );

link Sort-by filter dropdown

Use the t2/query/filters_sortby/options filter to modify the options available in the "Sort by" dropdown.

/**
 * @param array    $options The sort-by dropdown options (keys = option value, value = option readable label).
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query that run on page load.
 */
$options  = apply_filters( 't2/query/filters_sortby/options', $options, $attributes, $query );

The default options are latest first, oldest first and alphabetically. If the query post type corresponds to an event or a product, more appropriate sort-by options are used instead.

Look at the /inc/integrations/event.php or /inc/integrations/woocommerce.php files to see how it is being used and how new values are being used in the WP_Query calls.

link No-results text

Use the t2/query/no_results_text filter to modify the text displayed if the query does not return any results.

/**
 * @param string   $text The "no results found" text.
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query.
 */
$text = apply_filters( 't2/query/no_results_text', esc_html__( 'No results found.', 't2' ), $attributes, $query );

link Pagination arguments

Use the t2/query/pagination_args filter to modify the arguments passed to the paginate_links() core pagination function used to display the pagination links after the list of results.

/**
 * @param string   $args The arguments passed to the paginate_links() function.
 * @param array    $attributes The block attributes.
 * @param WP_Query $query The WP_Query.
 */
$pagination_args = apply_filters( 't2/query/pagination_args', [
	'current'   => max( 1, $query->query['paged'] ?? 1 ),
	'total'     => $query->max_num_pages,
	'format'    => '?qp=%#%',
	'prev_text' => sprintf( '%2$s%1$s', esc_html__( 'Previous', 't2' ), T2\Icons\get_icon\get_icon( 'arrowBack' ) ),
	'next_text' => sprintf( '%1$s%2$s', esc_html__( 'Next', 't2' ), T2\Icons\get_icon\get_icon( 'arrowForward' ) ),
	'echo'      => false,
], $attributes, $query );

link Global frontend JSON data

Use the t2/query/frontend_json_data filter to modify the JSON object sent to the frontend containing global data vital to the plugin logic.

$data = apply_filters( 't2/query/frontend_json_data', [
	'api'      => [ 'root' => $root 'nonce' => $nonce ],
	'features' => [
		'useFancyFiltersDropdowns' => true,
	],
] );

See a complete example below on how to disable the fancy-dropdowns library.

link JavaScript hooks

link Block settings in the editor

In the editor sidebar, you can inject new fields after the default block settings.
Each tab (Query and Filters) provides a <SlotFillProvider> in which custom components can be added externally via your custom theme or plugin.

Query tab

Use the T2.Query.BlockSettings.QueryTab filter and <Fill> name to inject new settings fields in the Query tab. Your component will receive the attributes object and setAttributes function from the Query block.

Filters tab

Use the T2.Query.BlockSettings.FiltersTab filter and <Fill> name to inject new settings fields in the Filters tab. Your component will receive the attributes object and setAttributes function from the Query block.

See a complete example below.

link Block setting: post type options

Use the T2.Query.Block.PostTypesOptions to modify the options array used to populate the "Post type" query setting field. Each item in this array needs a value property (saved in the attribute value) and a readable label property.

See the /inc/integrations/woocommerce.js file to see how to implement a JavaScript filter.

link Block setting: taxonomy options

Use the T2.Query.Block.TaxonomiesOptions to modify the options array used to populate the "Taxonomy" filters setting fields. Each item in this array needs a value property (saved in the attribute value), a readable label property and a postTypes array property containing a list of valid post types for this taxonomy.

See the /inc/integrations/woocommerce.js file to see how to use this filter.

link Frontend: send extra parameters in the AJAX request

Use the T2.Query.RequestParams filter to modify the request parameters sent from the frontend to the backend after the user interacts with the block (for filtering, sorting, changing page, searching, etc).

/**
 * @param {object} requestParams The AJAX request parameters.
 * @param {Node} $block The Query block DOM element.
 */
requestParams = applyFilters( 'T2.Query.RequestParams', requestParams, $block );

See a complete example below.

link Frontend: run extra logic after user interaction

Use the T2.Query.ResultsRefreshed action to run new logic after the AJAX response has been (successfully) received.

/**
 * @param {Node} $block The Query block DOM element.
 * @param {object} response The AJAX response.
 */
doAction( 'T2.Query.ResultsRefreshed', $block, response );

See a complete example below.

link Frontend: modify the TomSelect library options on fancy select dropdowns

Use the T2.Query.TomSelectSettings filter to modify the settings used to initialize the TomSelect JS library on the filters select dropdowns. Visit the library docs page to get a list of available settings.

/**
 * @param {object} settings The settings object.
 * @param {Node} $select The filters select dropdown being enhanced by the TomSelect library.
 */
settings = applyFilters('T2.Query.TomSelectSettings', settings, $select);

link Integrations

link Dekode Events extension

For queries that are loading Event posts from the Dekode Events extension, the "Sort by" dropdown filter will only display the following options:

The posts will be sorted by start date instead of publication date.

For more technical details on this integration, look at the /inc/integrations/event.php file.

link WooCommerce

If WooCommerce is activated, products attributes can be used as taxonomy filters.
For queries that are loading WooCommerce products, the "Sort by" dropdown filter will display the WooCommerce catalog order-by options (popularity, rating, price, etc.).

For more technical details on this integration, look at the /inc/integrations/woocommerce.php file.

link How to…?

link Add extra fields in the Query block sidebar settings

import { Fill } from '@wordpress/components';
import { addFilter } from '@wordpress/hooks';

addFilter(
	'T2.Query.BlockSettings.QueryTab',
	'Theme.Query.BlockSettings.QueryTab',
	() => {
		return ({ attributes, setAttributes }) => {
			return (
				<>
					<Fill name="T2.Query.BlockSettings.QueryTab">
						<p>Add your own fields in here!</p>
					</Fill>
				</>
			);
		};
	},
);

link Add extra content in the block markup (on page load and after an AJAX request)

/**
 * Inject a new "results_title" hook before the results.
 */
function alter_hooks( array $variable ): array {
	return [ 'search', 'filters', 'active_filters', 'results_title', 'results', 'pagination' ];
}
add_filter( 't2/query/render_hooks', __NAMESPACE__ . '\\alter_hooks', 10, 1 );

/**
 * Add a title before the results (page load).
 */
function add_results_title( array $attributes, \WP_Query $query ): void {
	if ( ! $query->have_posts() ) {
		printf( '<h2 class="results-title">%s</h2>', esc_html__( 'No results', 't2' ) );
		return;
	}

	$posts_count = $query->found_posts;
	$posts_count = number_format_i18n( $posts_count );
	printf( '<h2 class="results-title">%s</h2>', esc_html( sprintf( _n( '%s result', '%s results', $posts_count, 't2' ), $posts_count ) ) );
}
add_action( 't2/query/results_title_html', __NAMESPACE__ . '\\add_results_title', 10, 2 );

/**
 * Inject the title before the results in a new AJAX fragment.
 */
function inject_results_title_fragment( array $response, array $data, \WP_Query $query ): array {
	if ( $query->have_posts() ) {
		$posts_count = $query->found_posts;
		$posts_count = number_format_i18n( $posts_count );
		$title       = sprintf( _n( '%s result', '%s results', $posts_count, 't2' ), $posts_count );

		$response['fragments']['.results-title'] = sprintf( '<h2 class="results-title">%s</h2>', esc_html( $title ) );
	} else {
		$response['fragments']['.results-title'] = '';
	}

	return $response;
}
add_filter( 't2/query/get_results/response', __NAMESPACE__ . '\\inject_results_title_fragment', 10, 3 );

link Add a new filter field on the frontend and modify the AJAX query

/**
 * Display a new arbitrary meta dropdown filter in the filters form.
 */
function add_new_meta_filter( array $filters, array $attributes, \WP_Query $query ): array {
	$filters[] = '<fieldset>
		<label for="custom-meta-filter">Meta filter</label>
		<select name="custom_meta_filter" id="custom-meta-filter">
			<option value="yes">Yes</option>
			<option value="no">No</option>
		</select>
	</fieldset>';

	return $filters;
}
add_filter( 't2/query/filters', __NAMESPACE__ . '\\add_new_meta_filter', 10, 3 );

/**
 * Change the WP_Query args based on our new meta filter.
 */
function modify_ajax_request_query_args( array $args, array $data ) {
	if ( isset( $data['filters']['custom_meta_filter'] ) ) {
		if ( ! isset( $args['meta_query'] ) ) {
			$args['meta_query'] = [];
		}

		$args['meta_query'][] = [
			'key'   => 'custom_meta_filter',
			'value' => sanitize_text_field( wp_unslash( $data['filters']['custom_meta_filter'] ) ),
		];
	}

	return $args;
}
add_filter( 't2/query/get_results/args', __NAMESPACE__ . '\\modify_ajax_request_query_args', 10, 2 );

/**
 * Todo: hook into the t2/query/args filter to alter the initial WP_Query that runs on page load.
 */

link Use the frontend JavaScript hooks

import { addAction, addFilter } from '@wordpress/hooks';

/**
 * Pass a new "extraParam" in the AJAX payload.
 */
addFilter(
	'T2.Query.RequestParams',
	'Theme.Query.RequestParams',
	(requestParams, $block) => {
		return {
			...requestParams,
			extraParam: 'someExtraValue',
		};
	},
);

/**
 * After the results have been refreshed, scroll to the top of the block.
 */
addAction(
	'T2.Query.ResultsRefreshed',
	'Theme.Query.ResultsRefreshed',
	($block, response) => {
		$block.scrollIntoView({ behavior: 'smooth' });
	},
);

/**
 * If the results refresh logic fails, display an alert message.
 */
addAction(
	'T2.Query.RefreshResultsFailed',
	'Theme.Query.RefreshResultsFailed',
	($block, ajaxResponse, requestParams) => {
		alert("Something went terribly wrong");
	},
);

link Disable the TomSelect fancy-dropdown library

function disable_fancy_dropdown( array $data ): array {
	$data['features']['useFancyFiltersDropdowns'] = false;
	return $data;
}
add_filter( 't2/query/frontend_json_data', __NAMESPACE__ . '\\disable_fancy_dropdown', 10, 1 );