Meta Query Builder
Meta comparisons with the right compare operator and the right cast — so "9" does not come out greater than "10".
// 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 type — CHAR, 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.
- 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. - 02
Choose the comparison and the value
Pick from
=,!=, the four inequalities,LIKE,NOT LIKE,IN,NOT IN,BETWEEN,NOT BETWEEN,EXISTSandNOT EXISTS.INandBETWEENtake comma-separated values;EXISTStakes none. - 03
Set the cast
Leave it
CHARfor text equality. Switch toNUMERICorDECIMAL(10,2)for anything you compare with an inequality,DATEorDATETIMEfor stored dates,BINARYonly when you need case-sensitive matching such as a token. - 04
Set the relation and the surrounding query, then export
Choose
ANDorORbetween clauses, set the post type, page size, ordering andno_found_rows, clear the Checks tab, then copy the query, the args array or thepre_get_postsversion.
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.