GeneratorsQueryMeta Query Builder

Meta Query Builder

Meta comparisons with the right compare operator and the right cast — so "9" does not come out greater than "10".

product-meta-query.php
Saved just now
Clauses
2 clauses · 2 JOINs
Matchevery clause must match
price<=
meta_value cast to NUMERIC <= 5000
in_stock=
meta_value = 1
The query around it
no_found_rows
Skips the COUNT query — right whenever you are not paginating.
A secondary query with its own loop and wp_reset_postdata().
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
$args = array(
	'post_type'      => 'product',
	'posts_per_page' => 12,
	'orderby'        => 'meta_value_num',
	'order'          => 'ASC',
	'meta_key'       => 'price',
	'meta_query'     => array(
		'relation' => 'AND',
		array(
			'key'     => 'price',
			'value'   => 5000,
			'compare' => '<=',
			'type'    => 'NUMERIC',
		),
		array(
			'key'   => 'in_stock',
			'value' => '1',
		),
	),
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
	echo '<ul>';

	while ( $query->have_posts() ) {
		$query->the_post();
		printf(
			'<li><a href="%1$s">%2$s</a> %3$s</li>',
			esc_url( get_permalink() ),
			esc_html( get_the_title() ),
			esc_html( get_post_meta( get_the_ID(), 'price', true ) )
		);
	}

	echo '</ul>';
	wp_reset_postdata();
} else {
	esc_html_e( 'Nothing matched.', 'mytheme' );
}

About this generator

Meta Query Generator Online

Filter posts by custom field with the right comparison and the right cast. This WordPress meta_query generator builds each clause from a key, a compare, a value and a typeCHAR, NUMERIC, DECIMAL(10,2), DATE, DATETIME, TIME or BINARY — sets the relation between clauses, and hands back a WP_Query, a bare args array or a pre_get_posts callback.

A Reference tab restates the filter as a sentence, documents every clause key, and lists which comparisons need a numeric or date cast to behave. Free to use, no account, and everything runs in your browser.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why the meta_query builder beats a hand-written clause array

Post meta is stored as text. Every numeric filter you write is really a CAST instruction to MySQL, and if you leave the type at its CHAR default a price of 9 sorts above a price of 10 and your "under £50" filter returns the £500 items. The generator treats that as an error rather than a footnote, and checks five more failures beside it.

The CHAR-versus-NUMERIC trap is an error

Comparing with >, >=, <, <=, BETWEEN or NOT BETWEEN while the cast is CHAR is flagged by clause number with the reason spelled out — MySQL compares text, so "9" comes out greater than "10" — and a one-click "Cast to NUMERIC" fix.

BETWEEN is checked for exactly two values

BETWEEN and NOT BETWEEN need two comma-separated values. One or three is an error naming the count you actually supplied, not a query that silently matches nothing.

Date casts validated against the value

A clause cast to DATE or DATETIME whose value is not shaped Y-m-d is an error, because MySQL casts anything else to NULL and the clause then matches no rows at all.

Sorting kept consistent with filtering

Casting a key to a number for filtering while ordering by meta_value sorts it as text — flagged with a "Sort numerically" fix that switches to meta_value_num. Ordering by any meta key also gets a reminder that posts without that key are dropped entirely by the JOIN.

Comparisons matched to intent

A plain = with several comma-separated values gets a one-click "Use IN". EXISTS and NOT EXISTS ignore the value field, so filling it in raises a warning. LIKE gets a note that WordPress adds the % wildcards for you, and that a leading wildcard means no index can be used.

JOIN cost stated per clause

Each clause is a JOIN against wp_postmeta. The generator says so, and tells you when the total is worth timing rather than assuming. no_found_rows is offered for the short unpaginated lists that do not need a count query.

How does the meta query generator work?

Clauses first, then the query that carries them. The plain-English restatement updates as you type, so "price is at most 5000, and in_stock is 1" is readable before the PHP is.

  1. 01

    Add a clause per custom field

    Enter the meta key exactly as it is stored — _price, event_date, featured — one clause per field you want to filter on.

  2. 02

    Choose the comparison and the value

    Pick from =, !=, the four inequalities, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, EXISTS and NOT EXISTS. IN and BETWEEN take comma-separated values; EXISTS takes none.

  3. 03

    Set the cast

    Leave it CHAR for text equality. Switch to NUMERIC or DECIMAL(10,2) for anything you compare with an inequality, DATE or DATETIME for stored dates, BINARY only when you need case-sensitive matching such as a token.

  4. 04

    Set the relation and the surrounding query, then export

    Choose AND or OR between clauses, set the post type, page size, ordering and no_found_rows, clear the Checks tab, then copy the query, the args array or the pre_get_posts version.

Worked example — products under 5000, in stock, cheapest first

Two clauses joined with AND. The price clause is cast to NUMERIC because it uses <=; the stock flag stays CHAR because it is an exact match. Sorting uses meta_value_num so 10 comes after 9.

$args = array(
	'post_type'      => 'product',
	'posts_per_page' => 12,
	'orderby'        => 'meta_value_num',
	'order'          => 'ASC',
	'meta_key'       => 'price',
	'meta_query'     => array(
		'relation' => 'AND',
		array(
			'key'     => 'price',
			'value'   => 5000,
			'compare' => '<=',
			'type'    => 'NUMERIC',
		),
		array(
			'key'   => 'in_stock',
			'value' => '1',
		),
	),
);

Note what is missing: the second clause emits no compare and no type, because = and CHAR are the defaults. Ordering by a meta key also requires the separate top-level meta_key argument, which the generator adds for you.

meta_query — frequently asked questions

The recurring questions when a custom-field filter returns the wrong rows.

Why does my meta_query think 9 is greater than 10?

Because wp_postmeta.meta_value is a LONGTEXT column and the default cast is CHAR, so MySQL compares the values as strings — and as strings, "9" sorts after "10". Add 'type' => 'NUMERIC' to the clause (or DECIMAL(10,2) for money) and MySQL casts before comparing. The same applies to sorting: use orderby => meta_value_num, not meta_value.

Why do posts disappear when I order by a meta key?

Ordering by meta_value or meta_value_num requires the top-level meta_key argument, and that adds an inner JOIN — so any post without that meta key is excluded from the results entirely, not just sorted last. If you need every post regardless, either backfill the key on save, or add a relation => OR meta_query containing your real clause plus a NOT EXISTS clause for the same key.

How do I query posts that do not have a meta key at all?

Use 'compare' => 'NOT EXISTS' and omit the value. WordPress writes a LEFT JOIN with an IS NULL test. Note that a key which exists with an empty string is not the same as a missing key — NOT EXISTS will not match it, so if your save routine writes empty values you need 'compare' => '=' against '' as well.

Can I query a serialised array stored in post meta?

Not reliably. A serialised array is one text blob, so the only tool available is LIKE against a fragment of the serialisation, which breaks as soon as a value is a substring of another or the array shape changes. The correct fix is to store one row per value — call add_post_meta() repeatedly with the same key — or move the values into a taxonomy, which is indexed and joins properly.

How do I combine AND and OR in a meta_query?

Nest clause groups, supported since WordPress 4.1. An entry in meta_query can itself be an array with its own relation, so "featured is 1 AND (colour is red OR colour is blue)" is an outer relation => AND containing the featured clause and an inner array with relation => OR and the two colour clauses. A flat list only supports a single relation across all clauses.

Why is my meta_query so slow?

wp_postmeta is indexed on meta_key and post_id, but not on meta_value — the column is too long to index usefully. So filtering by key is cheap and filtering by value is a scan of every row carrying that key, once per clause, each one a separate JOIN. Two clauses is normal; four is a query worth profiling with Query Monitor. If a value is genuinely filterable content, a taxonomy term is the indexed alternative.

Where do I paste the code from the WP_Meta_Query generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other Query generators in the library, plus the WordPress tools most often used alongside this one.