Back to Tips & Tricks

JetSmartFilters: Filter Users by a Related CCT Field and Fixed Status

BoostMyCroco TeamPublished on Updated
Abstract illustration of user profiles linked to data records through a filter funnel with a fixed gate, for a users query

To filter users by a related CCT field with JetSmartFilters, build an SQL/AI Query in the JetEngine Query Builder that returns only the IDs of matching users. Pass those IDs into a regular Users query with the Query Results macro. The fixed condition (for example status = approved) is a permanent Where clause in the SQL query. The visitor's choice reaches that query through one extra Where clause whose value is the JetSmartFilters Query macro, and the filter itself targets the Users query.

This follows the pattern Crocoblock documents for filters that combine data from different sources. A second option, filtering the SQL/AI query directly through Columns for filters, is covered near the end. Treat that one as something to test in your version.

Why the filter ignores your second field

A radio or select filter sends one value. If the listing only knows about users, it has no way to check two fields on the same related CCT item. So put the fixed conditions (status = approved, the relation ID) inside an SQL query that joins the relation and CCT tables, where they apply on every request. Let the filter supply only the visitor's choice, as one more clause in that same query. Because every clause is checked against the same joined CCT row, a user matches only when one of their items is approved and belongs to the selected organization.

Check your table and column names

Each CCT has its own table, named wp_jet_cct_ plus the CCT slug (for example wp_jet_cct_memberships). Its ID column is _ID, and it usually includes a system column cct_status that marks items as published or draft. Relations without their own table share one default table, usually jet_rel_default, which stores ID pairs in columns such as rel_id, parent_object_id and child_object_id. If Register separate DB table is enabled for the relation, its rows live in the relation's own table instead. Confirm all of this in phpMyAdmin or in the From Table dropdown of an SQL/AI Query:

  1. The table that holds your relation's rows, and the exact CCT table name.
  2. The column names of your organization and status fields, and the exact value cct_status stores for published items.
  3. Which side of the relation the user is on. Here the user is the parent and the CCT item is the child. If yours is reversed, swap the parent and child columns everywhere.
  4. The relation ID. You may see it in the relation's edit URL; confirm it against the rel_id values in the relations table.
  5. The values your select fields store (for example org_acme), not the labels visitors see (Acme Ltd).

Step 1: Build the ID-source SQL query

  1. Go to WordPress Dashboard > JetEngine > JetEngine > Query Builder, add a query, set the type to SQL/AI Query and leave Advanced/AI mode off.
  2. In From Table, choose the relations table you confirmed.
  3. Turn on Use Join and add a row: Join Type Inner Join, Join Table = your CCT table, When current table column = child_object_id, Is equal to other table column = _ID.
  4. Add a second row: Inner Join, Join Table = the users table, parent_object_id equal to ID.
  5. Under Where (query clauses), add the fixed conditions:
    • Your status column, Compare Equal, Value approved, Type Char.
    • If the relation uses the shared table: rel_id, Compare Equal, Value = your relation ID, Type Numeric.
    • cct_status, Compare Equal, Value = the published value you found, Type Char. This stops draft items marked approved from qualifying a user.
  6. Add one more Where clause on the organization column, Compare Equal, Type Char. Click the database icon next to Value and choose JetSmartFilters Query. Set Type to Plain Query Var, Variable Name to org_field, and Result Format to Plain (the two choices are Plain and Prepare for SQL). Leave Fallback empty, then turn on Exclude this clause from the query if dynamic value is empty, a toggle that appears only for dynamic values. The JetSmartFilters Macros Guide doesn't describe how either format escapes values, so keep the macro in this builder-generated clause, never type it into hand-written SQL, and test the result.
  7. In Include columns, pick only the user ID column. Left empty, the query returns all possible columns, which may include columns from every joined table, possibly sensitive users-table columns such as user_pass. Check the preview to see what you get, and write down the exact name the preview shows for the user ID column. With joined tables it may carry a table prefix or alias, and you need that exact name in Step 2.
  8. Turn on Group Results and choose the user ID column in Group By, so each user appears once. If Include columns also contained a CCT or relation column, servers with ONLY_FULL_GROUP_BY in sql_mode may throw an error.
  9. Leave Show/Per Page Limit, Total Query Limit and Offset empty on this query. Show/Per Page Limit or Total Query Limit (or Offset together with Total Query Limit) can cut the ID list; Offset on its own does nothing. A limit here may mean Query Results passes only part of your user IDs, so leaving all three empty avoids the risk. Pagination belongs on the Users query.
  10. Keep Register Rest API Endpoint off, because it makes the query results publicly accessible.
  11. Turn off Cache Query while testing, save, and note the query's ID from its edit URL.

The preview should show one user ID per approved user. With no request variable present, the organization clause is excluded, so the fixed conditions alone decide the result.

Step 2: Feed the IDs into a Users query

  1. In the Query Builder, add a query with the type Users Query.
  2. Find the Include field (it sits on the Include/Exclude tab; check the tab label in your version). Open Dynamic Tags and choose Query Results. Pick it from the list rather than typing it, because typed macros aren't supported in the Query Builder. Select your SQL query, use the selected format, and as the property pick the user ID column name exactly as the SQL query preview showed it. With a column named ID and an SQL query with the ID 12, the result looks like %query_results|12|selected|ID%.
  3. Set the macro's Fallback to 2147483647, a user ID that cannot exist on your site. Don't use 0: WordPress skips the Include setting when its value is empty, and in PHP the string "0" counts as empty, so a no-match state could list every user, admins included. Never use a negative number either, because WordPress converts it to a positive ID (-1 becomes user 1). You will confirm the fallback in the no-match test below.
  4. Set how many users show per page on this query, give it a Custom Query ID such as approved_members, and save.

Step 3: Connect the Listing Grid and the filter

Run JetSmartFilters 3.8.3.1 or newer. Versions up to 3.8.3 have an unauthenticated SQL injection (CVE-2026-56067), and this setup sends visitor values into a query.

  1. Create a listing item for users and place it in a Listing Grid that uses the Users query.
  2. In Elementor, enter the query ID approved_members in the Listing Grid's CSS ID field so the filter can find the listing. This tip covers Elementor; in another builder, set the listing's query ID in that builder's own listing settings.
  3. Add a Radio or Select filter. Set its This filter for option to JetEngine, and set its Query ID to exactly the Users query's Custom Query ID.
  4. In the filter's Query Variable, click the dynamic tag icon and look for the plain query variable option, spelled Plane Query Variable in the UI, then enter org_field. The field should then store _plain_query::org_field. Crocoblock documents this option for the Search filter, so confirm that it shows up in the Query Variable of a Radio or Select filter in your version. If it doesn't, the direct route below is the other option.
  5. Make every option value match a stored select value. Our tip on filtering a text field with a select filter covers the same values-versus-labels issue.

Test every filter state

  • No option selected: every approved user, each listed once.
  • An option selected: only users with an approved item from that organization.
  • An option with no matching approved items: an empty listing, because the Include field receives the fallback ID 2147483647, which matches no user. If every user appears instead, the fallback isn't reaching the Users query. Re-check the Fallback value and the selected property name, and until it works, limit the filter's options to organizations that have approved items.

If visitors really need to choose the status, remove its fixed Where clause, add a second JetSmartFilters Query clause for the status column with its own variable name, and add a second filter with the same Query ID.

Alternative: filter the SQL query directly

You can also point the listing and the filter at the SQL/AI query itself. The closest official example filters a joined relation-meta column, not a joined CCT field, and the Macros Guide notes that JetSmartFilters sometimes struggles to filter advanced SQL queries directly. Test every state above before you rely on it.

  1. Build the query as in Step 1, but skip the JetSmartFilters Query clause.
  2. In Include columns, pick only the users-table columns your listing shows.
  3. In Columns for filters, select the organization column and copy its name exactly as the list shows it. Check that joined CCT columns appear there in your version.
  4. Set Cast result to instance of object to User, set Show/Per Page Limit for the listing, and add a Custom Query ID.
  5. Point the Listing Grid (including its CSS ID in Elementor) and the filter's Query ID at this query, set This filter for to JetEngine, and paste the column name into the filter's Query Variable.

Our tip on a custom visual filter built on an SQL query covers related ground, and the SQL/AI Query documentation lists every setting used here.

Troubleshooting

  • The filter doesn't change the listing. The filter's This filter for option isn't set to JetEngine, the Listing Grid's CSS ID doesn't hold the query ID, the filter's Query ID doesn't match the Custom Query ID exactly, or the Query Variable doesn't store _plain_query::org_field with the same name as the macro's Variable Name.
  • All approved users show whatever you pick. The JetSmartFilters Query macro is empty, so its clause is excluded. Re-check the plain query variable tag and the option values.
  • Every user on the site appears, admins included. The Include field received an empty value. Check that the Query Results fallback is 2147483647, not 0, and that the selected property matches the column name in the SQL preview. Official docs don't say whether an empty result triggers the macro's Fallback, so if it never applies on your site, limit the filter's options to organizations that have approved items.
  • Empty listing, no error. You're joining the shared relations table when the relation has its own table, the relation ID is wrong, the user sits on the other side of the relation, the selected property name is wrong, or the status or cct_status value doesn't match what's stored.
  • Some approved users never appear. Show/Per Page Limit or Total Query Limit is set on the ID-source SQL query (Offset only counts together with Total Query Limit), so it may pass only part of the IDs. Clear all three and paginate the Users query.
  • The same user appears several times. Group Results is off, or Group By isn't set to the user ID column.
  • SQL error mentioning GROUP BY. Include columns contains a CCT or relation column. Keep only the user ID (or users-table columns). Our list of common JetEngine Query Builder mistakes covers more checks.
  • Results look stale. Turn off Cache Query while editing. This matters most with an object cache such as Redis or Memcached.
  • A range filter does nothing. The %jsf_filter_query% macro has been reported not to work with the range filter. The reply in that issue links a Crocoblock gist that uses --range, --range1 and --range2 suffixes. Check the gist for how to install it and test it against your version.

Once every filter state returns the right users, turn Cache Query back on for both queries. If you'd rather have this built and tested on your own site, post a request on BoostMyCroco.

Frequently Asked Questions

Can JetSmartFilters filter a JetEngine users listing by fields stored in a CCT?
Yes, with an extra query in between. CCT fields live in their own table, so an SQL/AI Query joins the relation and CCT tables and returns matching user IDs, and a Users query receives them through the Query Results macro. The filter targets the Users query and passes its value to the SQL query through the JetSmartFilters Query macro.
Do I need to know SQL to set this up?
You don't have to write SQL for the main method, because joins, Where clauses and macros are all set in the Query Builder UI. You do need your exact table and column names and a basic sense of which rows a join returns. The direct method, filtering the SQL query through Columns for filters, isn't officially documented for joined CCT columns, so test it before relying on it.
Why does my filter show all approved users no matter which option I pick?
The JetSmartFilters Query macro is probably empty, so its Where clause is excluded. Set the filter's Query Variable with the plain query variable dynamic tag (spelled Plane Query Variable in the UI) so it stores _plain_query::your_variable, using the same name as the macro's Variable Name. Crocoblock documents that option for the Search filter, so confirm it appears for your Radio or Select filter, and check that the option values match the values stored in the CCT.
Why are some matching users missing from the listing?
Check Show/Per Page Limit and Total Query Limit on the SQL query that supplies the user IDs (Offset only matters together with Total Query Limit). Either can cut the list of IDs that Query Results passes to the Users query. Leave all three empty and handle pagination on the Users query.
Will this work with a checkbox or range filter instead of a radio?
The setup compares a single value, so it fits radio and select filters. The JetSmartFilters Query macro has been reported not to work with the range filter, and the reply in GitHub issue #7379 links a Crocoblock gist using --range, --range1 and --range2 suffixes; check that gist for how to install it. Test multi-value checkboxes separately, because they send more than one value.

This guide answers a question from our community: Help needed with SQL query for JetSmartFilters

Sources

  1. Macros Guide — JetSmartFilters (crocoblock.com)
  2. SQL/AI Query Type of Query Builder — JetEngine (crocoblock.com)
  3. Jetengine Jetsmartfilter macro %jsf_filter_query% · Issue #7379 · Crocoblock/suggestions · GitHub (github.com)

Related tips