Back to Tips & Tricks

Real Estate Property Search Filter in JetSmartFilters and JetEngine

BoostMyCroco TeamPublished on Updated
Miniature houses on a teal surface beside slider and checkbox shapes, illustrating a JetSmartFilters property search

To build a real estate property search filter with JetSmartFilters, store each property detail in the right JetEngine field and pair it with the matching filter type. Use a Range filter for price, a Check Range Filter with a high max for "2+ / 3+ bedrooms", a Checkboxes list with Is Checkbox Meta Field turned on for amenities, and taxonomy filters for property type and location. Set every filter widget's This filter for to JetEngine so it targets your property Listing Grid. Then add an Apply Button with Apply Redirect to send a home page search to the results page.

Plan the property data so it can be filtered

Set up a property Custom Post Type like this (for the submission form and single listing template, see our guide to building a Crocoblock listing website):

  • Property type (house, apartment, villa) and location as taxonomies. Location can be hierarchical, for example City > Area.
  • Price, bedrooms, bathrooms and area as JetEngine Number meta fields holding plain numbers: 450000, not $450,000. The filter's display settings add the currency symbol and separators.
  • Amenities as a JetEngine Checkbox meta field with Save as array turned on. Give each option a short, stable value (pool, garage) and a readable label.

Turned on Save as array after listings already existed? A plain re-save is not enough. On each existing listing, uncheck the amenities and save with the field empty, then tick them again and save (see Crocoblock's indexer troubleshooting guide).

Property detailJetEngine fieldJetSmartFilters filterKey setting
PriceNumber meta fieldRangeQuery Variable = price field Name/ID
Bedrooms, bathroomsNumber meta fieldCheck Range FilterOptions 1–99, 2–99, 3–99
AreaNumber meta fieldRange or Check Range FilterValues suffix for the unit
AmenitiesCheckbox, Save as arrayCheckboxes listIs Checkbox Meta Field ON
Property typeTaxonomySelect or Checkboxes listTaxonomies source, Is Checkbox Meta Field OFF
LocationHierarchical taxonomySelect or Checkboxes listTaxonomies source, Is Checkbox Meta Field OFF

Copy each meta field's Name/ID from the meta box now. It goes into the filters' Query Variable, and a typo there makes the filter return nothing.

Price range slider (Range filter)

  1. Go to Smart Filters > Add New, enter a Filter Name such as "Price", and choose Range as the Filter Type in Filter Settings.
  2. Set Get min/max dynamically to Get from Post Meta by query meta key, so the slider runs from your cheapest to your most expensive listing. Dynamic min/max was fixed for CPT listing providers in JetSmartFilters 3.8.2.1 (June 2026), so update if the slider does not cover that span. To set the limits yourself, keep None and fill in Min Value and Max Value.
  3. Set Step to match your market, for example 10000 for sales or 50 for rentals.
  4. Add a Values prefix of $ or €. For area filters, use a Values suffix such as m².
  5. Turn on Inputs enabled so visitors can type an exact budget. Then turn on Inputs separators enabled, set Number of decimals to 0 and Thousands separator to a comma. For European formats, use a dot as Thousands separator and a comma as Decimal separator.
  6. In Query Variable (required), paste the price field's Name/ID, for example price.

Optional: turn on Use Default Filter Value to preselect a budget, written as min_max, for example 100000_500000. It is applied once through an Ajax request when the page loads. Full option list: Range Filter Overview.

1+ / 2+ / 3+ bedrooms (Check Range Filter)

Check Range options are min/max pairs, so a minimum-only option gets a deliberately high max.

  1. Create a new filter with Filter Type Check Range Filter.
  2. In Options List, click + Add New once per option: Min Value 1 / Max Value 99, then 2 / 99, then 3 / 99, then 4 / 99.
  3. Set Query Variable to the bedrooms field Name/ID, for example bedrooms.
  4. Repeat with a second filter for bathrooms.

Type an explicit max well above any real value instead of leaving Max Value blank, then check how your version treats the bounds: a listing with exactly 3 bedrooms should appear under 3+. Details: Check Range Filter Overview.

Amenities checklist (Checkboxes list + Is Checkbox Meta Field)

  1. Create a filter with Filter Type Checkboxes list.
  2. Pick a Data Source:
    • Manual Input: add each option with Add New (older versions: New Option), using the exact stored Value and Label from the meta field.
    • Custom Fields: enter the field name in Custom Field Key and turn on Get Choices From Field Data.
  3. Turn on Is Checkbox Meta Field (Jet Engine), because the Checkbox field is saved as an array.
  4. Set Query Variable to the amenities field Name/ID.
  5. The docs only document a Relational Operator for the Taxonomies source. If your version shows one here, tick Pool and Garage and check whether listings need both (Intersection) or either (Union).

Reference: How to Filter by JetEngine Checkbox Meta Field.

Property type and location (taxonomy filters)

  1. Create a Select filter for a single choice, or a Checkboxes list if visitors may pick several types or areas.
  2. Set Data Source to Taxonomies and pick the property type or location taxonomy.
  3. Keep Is Checkbox Meta Field OFF. With a taxonomy source it must stay off.
  4. On a Checkboxes list, set the Relational Operator. For type and location, Union is usually right: "House or Villa". See the Checkboxes Filter Overview.

Place the filters with the Listing Grid in Elementor

On the results page, add your property Listing Grid. Then add the matching widget for each filter (Range Filter, Check Range Filter, Checkboxes Filter and so on) and set:

  • Select filter: the filter you created.
  • This filter for: JetEngine. Any other provider leaves the grid untouched.
  • Apply type: AJAX updates results without a reload, Mixed doesn't reload but updates the URL, Page Reload reloads the page.
  • Apply on and Show apply button: for instant results, apply on value change with no button. With Page Reload, or with AJAX/Mixed applied on the button, turn on Show apply button.
  • Query ID: needed when the page has more than one Listing Grid. Put a unique name such as property-results in the grid's Advanced > CSS ID and the same name in every filter's Query ID.
  1. On the home page, add the search bar filters, for example location, type and price, each set to JetEngine.
  2. Add the Apply Button widget with the same provider.
  3. Turn on Apply Redirect and paste the results page's front-end URL into Redirect Path, for example https://example.com/properties/.
  4. Make sure the results page has the same filters and the Listing Grid with the same provider.
  5. Choose the Apply type. Page reload carries the filter values in the URL, so visitors can bookmark a search. AJAX keeps the URL clean. More: Redirect Path setup.

Property filters show no results or wrong results

  1. Provider: JetEngine on every filter and the Apply Button, and enabled in Crocoblock > JetPlugins Settings > JetSmartFilters > General Settings.
  2. Query Variable: the meta field's Name/ID, not its label.
  3. Query ID: matches the Listing Grid CSS ID. A grid duplicated for mobile with responsive hiding needs a CSS ID on both copies.
  4. Amenities: the settings from the amenities section, plus the Save as array clean-up for older listings.
  5. Type or location: Is Checkbox Meta Field off with a Taxonomies source.
  6. Apply settings and redirect: Apply type, Apply on and Show apply button match; the results page has the filters and provider.
  7. Options missing, disabled or with wrong counts: with Use Indexed Filters on (Indexer Settings tab, off by default), the indexer drives option counts and can show, hide or disable empty options. A stale or incomplete index can hide options that do have listings. Check that:
    • your property post type is switched on under Index Post Types;
    • each filter widget's own Apply Indexer option (Indexer Options tab) is on. The indexer is only available in the Checkboxes, Select, Check Range, Visual and Radio widgets;
    • you re-index after changing fields or importing listings, using the button under Smart Filters in the WordPress dashboard. Depending on version it is labelled Apply Indexer or Index Filters. Do this even with Use auto re-indexing on: it only runs when posts are saved in the dashboard, not for CSV or WP All Import imports, REST API or WP-CLI updates, or JetFormBuilder submissions.
  8. Still stuck: update JetSmartFilters and JetEngine, then work through Crocoblock's JetSmartFilters not working checklist. If your grid runs on a custom query, see our JetEngine Query Builder mistakes guide.

Clean up prices saved as text

If the price slider skips listings, check the raw values on a few properties. Text like $450,000 or 120 m² may not be compared reliably as numbers. The WP-CLI script below converts clean single amounts to plain numbers and lists everything else for manual fixing. Back up the database first. Save it as a standalone file outside your web root (for example ~/fix-prices.php), set your post type and meta keys at the top, and run it from your WordPress folder with wp eval-file ~/fix-prices.php. Do not paste it into functions.php or a snippets plugin:

Code

<?php
/**
 * BoostMyCroco: one-time clean-up for property numbers saved as text.
 * Converts single amounts like "$450000", "450000 USD" or "120 m²" into plain numbers.
 * With $bmc_skip_commas = false it also converts "$450,000" to 450000.
 * Anything unclear (ranges, "300k", "$1.2M", "450.000", "450 000", "From $300",
 * "3 beds 2 baths") is skipped and listed so you can fix it by hand.
 *
 * WARNING: not confirmed to work with JetEngine Custom Meta Storage.
 * If your post type uses Custom Meta Storage, test this on a staging copy first.
 *
 * Standalone WP-CLI file, never functions.php:  wp eval-file ~/fix-prices.php
 * Back up the database first. Keep $bmc_dry_run = true for the first run.
 */

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

$bmc_dry_run = true; // true = only list the changes. Set to false to save them.

// true (default) = list every value with a comma for manual fixing, because on comma-decimal
// sites "1,500" means 1.5. Sites that use a comma as thousands separator ("$450,000") can set
// this to false, run the dry run again and check every comma value before saving.
$bmc_skip_commas = true;

$bmc_post_type  = 'property';               // Your post type slug.
$bmc_meta_keys  = array( 'price', 'area' ); // Meta field Name/ID values from the meta box.
$bmc_batch_size = 200;

// Currency signs and units allowed before or after the number (lowercase).
$bmc_allowed_marks = array( '', '$', '€', '£', 'usd', 'eur', 'gbp', 'm²', 'ft²', 'sq ft', 'sqft' );

// Lowercase that is safe for multibyte marks such as m² and ft².
$bmc_lower = static function ( $text ) {
    $text = trim( $text );
    return function_exists( 'mb_strtolower' ) ? mb_strtolower( $text, 'UTF-8' ) : strtolower( $text );
};

// Every scalar value stored for a key on a post, as strings. Arrays are left alone.
$bmc_read = static function ( $post_id, $key ) {
    return array_map( 'strval', array_filter( get_post_meta( $post_id, $key, false ), 'is_scalar' ) );
};

// All statuses, including drafts and trash, but not auto-drafts.
$bmc_statuses = array_values( array_diff( array_keys( get_post_stati() ), array( 'auto-draft' ) ) );

$bmc_page    = 1;
$bmc_changed = 0;
$bmc_skipped = array();

do {
    $bmc_ids = get_posts(
        array(
            'post_type'        => $bmc_post_type,
            'post_status'      => $bmc_statuses,
            'fields'           => 'ids',
            'posts_per_page'   => $bmc_batch_size,
            'paged'            => $bmc_page,
            'orderby'          => 'ID',
            'order'            => 'ASC',
            'no_found_rows'    => true,
            'suppress_filters' => true,
        )
    );

    foreach ( $bmc_ids as $bmc_id ) {
        foreach ( $bmc_meta_keys as $bmc_key ) {
            // Every stored row for this key, not just the first one. Identical rows are handled once:
            // one update with the old value changes all rows holding it.
            $bmc_values = array_unique( $bmc_read( $bmc_id, $bmc_key ) );

            foreach ( $bmc_values as $bmc_raw ) {
                $bmc_value = trim( $bmc_raw );
                $bmc_label = sprintf( '%d %s: %s', $bmc_id, $bmc_key, $bmc_raw );

                if ( '' === $bmc_value ) {
                    continue; // Nothing saved.
                }

                if ( $bmc_skip_commas && false !== strpos( $bmc_value, ',' ) ) {
                    $bmc_skipped[] = $bmc_label;
                    continue;
                }

                // Exactly one amount: optional mark, digits (optionally with comma thousands groups),
                // optional decimals, optional mark. A second number anywhere means no match.
                if ( ! preg_match( '/^(\D*?)(\d{1,3}(?:,\d{3})+|\d+)(\.\d+)?(\D*)$/', $bmc_value, $bmc_m ) ) {
                    $bmc_skipped[] = $bmc_label;
                    continue;
                }

                $bmc_prefix   = $bmc_lower( $bmc_m[1] );
                $bmc_suffix   = $bmc_lower( $bmc_m[4] );
                $bmc_decimals = isset( $bmc_m[3] ) ? $bmc_m[3] : '';

                // Words such as "From", shorthand such as "k" or "M", or exactly three decimals like
                // ".000" (likely a thousands separator, not decimals): too risky to convert automatically.
                if (
                    ! in_array( $bmc_prefix, $bmc_allowed_marks, true )
                    || ! in_array( $bmc_suffix, $bmc_allowed_marks, true )
                    || 4 === strlen( $bmc_decimals )
                ) {
                    $bmc_skipped[] = $bmc_label;
                    continue;
                }

                $bmc_clean = str_replace( ',', '', $bmc_m[2] ) . $bmc_decimals;

                if ( $bmc_clean === $bmc_raw ) {
                    continue; // Already a plain number.
                }

                if ( $bmc_dry_run ) {
                    WP_CLI::log( $bmc_label . ' => ' . $bmc_clean );
                    $bmc_changed++;
                    continue;
                }

                // Pass the old value so only rows holding it are updated, not every row with this key.
                if ( false !== update_post_meta( $bmc_id, $bmc_key, $bmc_clean, $bmc_raw ) ) {
                    // A plugin hooked into the meta update can report success without saving, so count
                    // the change only if the new value is there and the old text value is gone.
                    $bmc_after = $bmc_read( $bmc_id, $bmc_key );
                    if ( in_array( $bmc_clean, $bmc_after, true ) && ! in_array( $bmc_raw, $bmc_after, true ) ) {
                        $bmc_changed++;
                    } else {
                        $bmc_skipped[] = $bmc_label . ' (reported saved but not found, check your meta storage)';
                    }
                    continue;
                }

                // Depending on the database collation, values that differ only by case or trailing
                // spaces ("120 SQFT" and "120 sqft ") can match the same update, so an earlier update
                // may already have converted this row. Report a failure only if the old value remains.
                if ( in_array( $bmc_raw, $bmc_read( $bmc_id, $bmc_key ), true ) ) {
                    $bmc_skipped[] = $bmc_label . ' (save failed)';
                }
            }
        }
    }

    // Free memory between batches.
    if ( function_exists( 'WP_CLI\Utils\wp_clear_object_cache' ) ) {
        \WP_CLI\Utils\wp_clear_object_cache();
    } elseif ( function_exists( 'wp_cache_flush_runtime' ) ) {
        wp_cache_flush_runtime();
    }

    $bmc_page++;
} while ( count( $bmc_ids ) === $bmc_batch_size );

if ( $bmc_dry_run ) {
    WP_CLI::success( 'Dry run, nothing saved. Values that would change: ' . $bmc_changed );
    WP_CLI::log( 'Check the list above, set $bmc_dry_run to false and run the file again.' );
} else {
    WP_CLI::success( 'Values updated: ' . $bmc_changed );
}

if ( $bmc_skipped ) {
    WP_CLI::warning( count( $bmc_skipped ) . ' values to check by hand (post ID, key, value):' );
    foreach ( $bmc_skipped as $bmc_item ) {
        WP_CLI::log( '  ' . $bmc_item );
    }
}

How the script works

It converts a value only when it holds one clean amount with an optional currency sign or unit, and lists everything else for you. The code comments explain each step.

Run it in two passes

  1. Run it with $bmc_dry_run = true. Each line shows ID key: old => new. Check that every new value is right.
  2. If your site writes thousands with a comma ($450,000), set $bmc_skip_commas to false, run the dry run again and check every comma value.
  3. Set $bmc_dry_run to false and run it again to save the changes.
  4. Fix the skipped listings by hand, then delete the file.

Keep the opening <?php line, because this is a standalone file run only with wp eval-file. Never paste it into functions.php or a snippets plugin: a second <?php line there causes a parse error, and the early return would stop the rest of functions.php from running.

Before you run it

  • Other currencies or units: if valid values are skipped because of a mark like chf, add it in lowercase to $bmc_allowed_marks.
  • Dot and space thousands such as 450.000 and 450 000 are skipped on purpose.
  • Add 'bedrooms' to the key list only if bedrooms were saved as text.

After you run it

  1. Use JetEngine Number fields for price and area so editors enter plain numbers. Save a new listing and run the dry run again: a clean value produces no line.
  2. If Use Indexed Filters is on, re-index as described in the troubleshooting list above. WP-CLI updates don't trigger auto re-indexing.
  3. Reload the results page. The price slider's min and max should match your cheapest and most expensive listings, and dragging it should hide properties outside the range.

With clean numeric fields, each filter matched to its field type and every widget pointed at JetEngine, your property search returns the listings visitors expect. If you'd rather have the whole search built and tested for you, post a request on BoostMyCroco.

Frequently Asked Questions

Why does the JetSmartFilters price slider not reach my most expensive property?
Set Get min/max dynamically to Get from Post Meta by query meta key and make sure the price field holds plain numbers. Dynamic min/max for CPT listing providers was fixed in JetSmartFilters 3.8.2.1, so update if the range still looks wrong. You can also choose None and type the Min Value and Max Value yourself.
Why does my JetSmartFilters amenities checkbox filter return no properties?
The usual causes are Is Checkbox Meta Field (Jet Engine) switched off, a JetEngine Checkbox field that doesn't save as an array, or filter options that use labels instead of stored values. Listings created before Save as array was enabled also need the clear-and-re-save step described above.
Can visitors combine price, bedrooms and location filters at the same time?
You can place several filters that all target the same Listing Grid with the JetEngine provider, so a visitor can pick a budget, 3+ bedrooms and a city in one search. After setup, test a combination and check that the remaining listings match every choice. If the page has more than one Listing Grid, each filter's Query ID must match the CSS ID of the grid it should control.
How do I make a property search URL that visitors can share?
Use Page reload as the Apply type, or Mixed on the results page, so the filter values are written into the URL. Visitors can then bookmark or send the link. AJAX keeps the URL clean, so the filtered view is not part of the link.

This guide answers a question from our community: Smart Filter Help

Sources

  1. Solving the Non-Working Indexer Problem — JetSmartFilters (crocoblock.com)
  2. Croco Educates June Issue 2026 - Crocoblock (crocoblock.com)
  3. Range Filter Overview — JetSmartFilters (crocoblock.com)
  4. Check Range Filter Overview — JetSmartFilters (crocoblock.com)
  5. How to Filter by JetEngine Checkbox Meta Field — JetSmartFilters (crocoblock.com)
  6. Checkboxes Filter Overview — JetSmartFilters (crocoblock.com)
  7. How to Set Up Redirect Path to Filter Results Page — JetSmartFilters | Crocoblock (crocoblock.com)
  8. 7 Quick Fixes to Try When JetSmartFilters Fail to Work (crocoblock.com)

Related tips