Query posts by field value
4 min read
Because field values live in real, typed, indexed columns, you can filter and sort posts by them directly in WP_Query or get_posts() without meta_query joins.
$cheap = new WP_Query( [
'post_type' => 'product',
'wpmcf_query' => [
[ 'field' => 'product_details.price', 'compare' => '<=', 'value' => 200 ],
],
'orderby' => [ 'wpmcf:product_details.price' => 'ASC' ],
] );Behind the scenes the group's table is LEFT JOINed once on mid and mtype, and the conditions become normal SQL on its columns. Every value is passed through $wpdb->prepare().
Field references
A field is written group_key.field_key. For a field stored as JSON (Group, Repeater, checkbox lists, ...) add keys or list positions after it:
Reference | Points at |
|---|---|
| The column |
| The sub-field |
| The first entry of a list value. |
Segments may contain lowercase letters, numbers and underscores. The group must have location rules (it needs the mid/mtype columns) and the field must be a field that stores a value.
Conditions: wpmcf_query
wpmcf_query is a list of clauses, optionally with a relation (AND default, or OR), and clauses can be nested.
Clause key | Meaning |
|---|---|
| The field reference. |
| The value to compare. A list value defaults |
| One of the operators below. |
| A cast applied to the column before comparing (optional). |
Operators
| SQL meaning |
|---|---|
| Standard comparison. |
| Contains the text ( |
| In a list of values. An empty |
| Needs exactly two values: |
| The value is not |
| For list fields (checkboxes, relationships, taxonomy and user fields): the list holds the item. For plain text columns: substring match. |
Casts (type)
NUMERIC (signed integer), DECIMAL (DECIMAL(65,10)), DECIMAL(10,2) and similar, SIGNED, UNSIGNED, CHAR, BINARY, DATE, DATETIME, TIME. Use a cast for values read out of JSON (which come back as text):
'wpmcf_query' => [
[ 'field' => 'product_details.dimensions.weight', 'compare' => '>', 'value' => 2, 'type' => 'NUMERIC' ],
]Combining
'wpmcf_query' => [
'relation' => 'AND',
[ 'field' => 'product_details.color', 'value' => 'red' ],
[
'relation' => 'OR',
[ 'field' => 'product_details.price', 'compare' => '>', 'value' => 150 ],
[ 'field' => 'product_details.title_x', 'value' => 'A' ],
],
],Limits: nesting up to 5 levels and at most 30 conditions per query.
Ordering: wpmcf:
Put wpmcf: in front of the field reference as an orderby key. You can mix field ordering with WordPress's own keys; field ordering comes first.
'orderby' => [
'wpmcf:product_details.price' => 'DESC',
'date' => 'DESC',
],- Posts with an empty value are sorted last, in both directions.
- Password fields cannot be used for ordering.
- At most 5
wpmcf:keys per query. - A
typecast is not available for ordering: sort on a typed column, or make the column the right type.
Distance search: wpmcf_distance
For a Map field, find posts within a radius of a point and sort by distance:
$near = wpmcf_query_posts( [
'post_type' => 'place',
'posts_per_page' => 20,
'wpmcf_distance' => [
'field' => 'places.location',
'lat' => 40.71,
'lng' => -74.01,
'radius' => 25, // optional
'unit' => 'km', // km (default) or mi
],
'orderby' => [ 'wpmcf_distance' => 'ASC' ],
] );
foreach ( $near as $post ) {
printf( '%s - %.1f km', esc_html( $post->post_title ), $post->wpmcf_distance );
}Each result carries $post->wpmcf_distance. The distance is the great-circle distance. With a radius the query first limits by a bounding box (so an index on the Map field, Add index, helps), then by the exact distance. Without a radius every post with coordinates is returned. Posts without coordinates are left out.
wpmcf_query_posts()
get_posts() suppresses query filters by default, so wpmcf_query has no effect there. Use wpmcf_query_posts(), which is get_posts() with suppress_filters set to false and wrapped so that a failure is logged (under Settings › Health) and an empty array is returned instead of breaking the page:
$posts = wpmcf_query_posts( [
'post_type' => 'product',
'posts_per_page' => -1,
'fields' => 'ids',
'wpmcf_query' => [ [ 'field' => 'product_details.price', 'compare' => 'BETWEEN', 'value' => [ 50, 350 ] ] ],
'orderby' => [ 'wpmcf:product_details.price' => 'ASC' ],
] );WP_Query needs nothing special.
Mistakes match nothing
An argument that cannot be used (unknown group or field, unknown compare or cast, a non-scalar value, a group without location rules, a Password field used for ordering) makes the whole query return no posts and, with WP_DEBUG on, a _doing_it_wrong() notice naming the problem. A typo never lists every post.
Speed
- Add an index (Add index) to fields you filter or sort by often; a regular index helps
=, ranges,INand ordering. - Conditions on values inside JSON columns cannot use an index.
LIKE '%text%'scans the column.CONTAINSon list fields usesJSON_CONTAINS.
You can also join the table yourself:
SELECT p.ID, d.price
FROM wp_posts p
JOIN wp_wpmcf_fg_product_details d ON d.mid = p.ID AND d.mtype = 'posts'
WHERE p.post_status = 'publish' AND d.price BETWEEN 50 AND 350
ORDER BY d.price;Other objects
wpmcf_query filters posts. For users, terms and comments, join the group table in your own SQL with mtype set to users, terms or comments. Pro admin columns add field filters to list screens, see Admin columns. The Pro page builder post grid accepts the same wpmcf_query and wpmcf: orderby arguments; see Builders.
Troubleshooting
Problem | Fix |
|---|---|
No posts come back | A reference or operator is wrong, the group has no location rules, or the cast is unknown. Turn on |
The condition does nothing with | Use |
Numbers inside a Group sort as text | Add a |
| By design on the main query; use the |
Slow on a large site | Add an index on the filtered column and avoid JSON paths in the hot path. |
Still stuck? Contact support