ElasticPress is the WordPress plugin that bridges WP_Query to Elasticsearch. The integration is a one-line flag ('ep_integrate' => true) but the surrounding plumbing matters: which post types are indexable, which fields get indexed and weighted, how to reindex after bulk imports without taking the site down, and the question almost no tutorial answers honestly: does Elasticsearch actually beat MySQL FULLTEXT for your data, or are you adding a moving part for no real benefit? Below: the full setup walkthrough, the production patterns that keep the index from rotting, faceted search with aggregations, and the cost/benefit honest answer.
How do I use ElasticPress with WP_Query?
Install ElasticPress (composer require 10up/elasticpress or via plugin upload), define EP_HOST in wp-config.php to point at your Elasticsearch endpoint, activate the plugin, and run wp elasticpress index --setup from WP-CLI to build the initial index. Then add 'ep_integrate' => true to any WP_Query arguments to route that query through Elasticsearch instead of MySQL. For just search forms, enable the "Search" feature in the ElasticPress admin and queries with a non-empty s parameter automatically use ES. The ep_indexable_post_types filter controls which post types get indexed; the ep_search_fields filter controls which fields are queried. For the underlying PHP runtime considerations, see PHP Memory Limit: How to Fix 'Allowed Memory Size Exhausted'. For bulk-import-and-reindex patterns, see wp_insert_post Consuming Large Amounts of Memory.
Set your Elasticsearch host, WordPress table prefix, and the post type you index most. WP-CLI and curl examples below update so you can copy and run.
Jump to:
- Install ElasticPress and an Elasticsearch host
- Configure EP_HOST and indexable post types
- Build the initial index with wp-cli
- Route queries through ES with ep_integrate
- Faceted search with aggregations
- Reindex after bulk imports
- Common errors and how to debug
- Cost vs benefit: does ES actually beat MySQL FULLTEXT?
- Common pitfalls
- What to do next
- FAQ
Install ElasticPress and an Elasticsearch host
Two install paths:
Plugin (the WordPress-native way):
Download from the ElasticPress plugin page or install from the admin Plugins screen.
Composer (for project-managed WordPress sites):
composer require 10up/elasticpressThen symlink or copy vendor/10up/elasticpress into wp-content/plugins/.
For the Elasticsearch host you have three sensible options in 2026:
| Host | Cost | Notes |
|---|---|---|
| Self-hosted Elasticsearch 8.x on the same box | free | Lowest latency but you're now operating a JVM in production |
| Elastic Cloud (managed) | $$ from ~$95/month | Zero ops, one-click upgrades |
| Bonsai (managed, ElasticPress-friendly) | $ from $10/month | Cheapest managed option, designed for ElasticPress |
ElasticPress officially supports Elasticsearch, not OpenSearch. If you are weighing OpenSearch or AWS OpenSearch Service for the backend, see Elasticsearch vs OpenSearch for ElasticPress for the real compatibility status before committing.
For a typical content site with under 100k posts, a single small managed Elasticsearch instance is plenty. For an e-commerce site with 500k+ products and faceted filtering, plan for at least a 3-node cluster with 4 to 8 GB RAM per node.
Configure EP_HOST and indexable post types
In wp-config.php:
define('EP_HOST', 'https://es-username:es-password@search.example.com:9243');For Elastic Cloud, the host is provided as a single URL with credentials embedded.
Configure which post types get indexed via a filter. By default ElasticPress indexes post and page:
// In a custom plugin or theme functions.php
add_filter('ep_indexable_post_types', function ($post_types) {
$post_types[] = 'product'; // WooCommerce
$post_types[] = 'event';
unset($post_types['attachment']); // exclude
return $post_types;
});Configure which fields get searched:
add_filter('ep_search_fields', function ($search_fields) {
return [
'post_title^10', // boost title matches 10x
'post_content',
'post_excerpt^3',
'meta.sku.value^5', // custom field with 5x boost
];
});The ^N boost syntax tells Elasticsearch to weight matches in that field N times more heavily. Title and SKU matches almost always deserve higher weight than body content.
For per-feature configuration (Protected Content, WooCommerce, Documents, Autosuggest, Synonyms, Facets), use the ElasticPress admin under Dashboard > ElasticPress > Features.
Build the initial index with wp-cli
wp elasticpress index --setup--setup deletes any existing index and rebuilds from scratch. Use it on the first run and after schema changes. On subsequent runs (after a post type change, for example), use --setup again to delete-and-rebuild; ElasticPress will not migrate the existing index.
For multisite installs:
wp elasticpress index --setup --network-wideFor incremental updates without a full rebuild:
wp elasticpress index --include=42,43,44 # specific post IDs
wp elasticpress index --post-type=:post_type # one post type onlyFor a large index (100k+ posts), the initial build can take 10 to 60 minutes depending on Elasticsearch resources and network. ElasticPress streams posts in batches of 350 by default; tune with --per-page=N. Smaller batches use less PHP memory but more HTTP overhead.
Run the indexer from a screen/tmux session OR via WP-CLI's --allow-root from a systemd timer if you're scheduling it. For the underlying loop and parallel patterns, see Bash For Loops.
Route queries through ES with ep_integrate
This is the part most people are really asking about: I would like to run custom WP_Query calls through ElasticPress, how do I do this? The answer is a single argument. Once the index exists, any WP_Query with 'ep_integrate' => true runs through Elasticsearch:
$args = [
'ep_integrate' => true,
'post_type' => 'product',
'posts_per_page' => 12,
'meta_query' => [
[
'key' => '_price',
'value' => 50,
'compare' => '<=',
],
[
'key' => '_stock_status',
'value' => 'instock',
'compare' => '=',
],
],
'orderby' => 'date',
'order' => 'DESC',
];
$query = new WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
the_title('<h2>', '</h2>');
}
}
wp_reset_postdata();The same $args work with get_posts(). Behavior is otherwise identical to a MySQL-backed query: pagination, wp_reset_postdata(), the_loop(), all work as usual.
For search forms specifically, ElasticPress's "Search" feature (admin toggle) auto-routes queries with a non-empty s parameter. You don't need to set ep_integrate on the front-end search form: when search is enabled, EVERY search runs through Elasticsearch.
For WP_Term_Query and WP_User_Query, the equivalents are 'ep_integrate' => true on those query classes too. ElasticPress supports terms and users via the Term/User indexable features.
Faceted search with aggregations
ElasticPress supports Elasticsearch's aggregations directly:
$args = [
'ep_integrate' => true,
'post_type' => 'product',
'posts_per_page' => 24,
'aggs' => [
'by_category' => [
'terms' => ['field' => 'terms.product_cat.slug', 'size' => 50],
],
'price_ranges' => [
'range' => [
'field' => 'meta._price.long',
'ranges' => [
['to' => 50],
['from' => 50, 'to' => 200],
['from' => 200],
],
],
],
],
];
$query = new WP_Query($args);
// Access aggregation results from the query object
$aggs = $query->query_vars['ep_aggregations'] ?? null;The aggs parameter is Elasticsearch's native syntax: counts, ranges, histograms, nested aggregations. Returned data lives on query_vars['ep_aggregations'] after the query runs.
For e-commerce, the canonical use is "show 20 products and the count per category/price-range/attribute". MySQL would need a separate query per facet AND the full product query, each scanning the entire products table. Elasticsearch does it all in a single index scan.
ElasticPress also has a "Facets" feature that exposes WordPress-style facet widgets without writing aggregation JSON. For custom UI or non-standard facets, write your own aggregations with the aggs parameter.
Reindex after bulk imports
After a wp_insert_post loop creates thousands of posts, ElasticPress's per-insert sync (via save_post) is either too slow or was disabled during the import. Reindex explicitly:
# Full rebuild (safest, slow)
wp elasticpress index --setup
# Specific post types (faster)
wp elasticpress index --setup --post-type=:post_type
# Single post (fast)
wp elasticpress index --include=12345If you disabled the ElasticPress sync hook during the import (see wp_insert_post Consuming Large Amounts of Memory), run wp elasticpress index --setup afterwards to bring the index back in sync.
For zero-downtime reindexing on a live site, use ElasticPress's index aliasing (the default behavior since 4.0): the indexer writes to a new index, then atomically swaps the alias when the build is complete. Read traffic continues to hit the old index until the swap.
Common errors and how to debug
"No index found" / 404 from Elasticsearch. The Elasticsearch index doesn't exist yet. Run wp elasticpress index --setup.
"Different schema versions" / mapping conflicts. After upgrading ElasticPress or changing indexed fields, the existing index has the old schema. Run wp elasticpress index --setup to rebuild with the new schema.
Connection refused / timeout. Wrong EP_HOST, firewall blocking outbound 9200/9243, or the cluster is overloaded. Test the cluster directly:
# Cluster health
curl -u user:pass :es_host/_cluster/health?pretty
# List indices, filtered to your WordPress prefix
curl -u user:pass ":es_host/_cat/indices/:index_prefix*?v"Search returns 0 results even though posts exist. The index is empty (run --setup), the post status filter excludes the posts (only publish is indexed by default), or your query has a meta_query/tax_query that doesn't match in ES (some compare operators behave subtly differently).
WP_Query falls back to MySQL silently. ElasticPress falls back to MySQL when it can't translate the query. Enable debug mode (define('EP_ENABLE_DEBUG_BAR', true) plus the Debug Bar plugin) to see which queries hit ES and which fell back.
Mapping size error. A field has more than 1000 unique values per document. Configure mapping limits in the Elasticsearch index settings, or restructure the data to avoid high-cardinality flat fields.
For the underlying MySQL data structure that ElasticPress indexes, see MySQL Field Types and Sizes.
Cost vs benefit: does ES actually beat MySQL FULLTEXT?
The honest answer most ElasticPress tutorials don't give:
| Site profile | Should you use ElasticPress? |
|---|---|
| Blog with 200 posts, default search | No. MySQL LIKE search is instant at that scale. |
| Blog with 5,000 posts, default search | Maybe. MySQL FULLTEXT (with MATCH...AGAINST) handles this well. |
| News site with 50,000 articles + filtering by category, author, date | Probably yes. Search relevance and facets are noticeably better. |
| E-commerce with 10,000 products + meta_query filtering | Yes. WooCommerce on MySQL hits N+1 wpostmeta JOINs at this scale and crawls. |
| E-commerce with 100,000+ products | Definitely yes. MySQL is no longer practical for faceted product search. |
| Custom search with synonyms, fuzzy matching, weighted fields | Yes. Out-of-scope for MySQL. |
ElasticPress costs you: operational complexity (one more service to monitor), $10 to $200/month for a managed host or 4 to 8 GB of self-hosted RAM, and the discipline to keep the index in sync after every schema change.
You get back: 10 to 100x faster filtered queries on large catalogs, relevance scoring that's actually relevance-scored, fuzzy matching and synonyms, faceted aggregations in a single query, and search-as-you-type via the Autosuggest feature.
The breakeven is usually around the point where your MySQL search starts taking >500 ms or where your meta_query/tax_query JOINs hit double-digit milliseconds. Below that, stay on MySQL. Above that, ElasticPress is worth the operational cost.
For tracking when queries cross that line, profile with Query Monitor and watch the slow-query log. See PHP Memory Limit: How to Fix 'Allowed Memory Size Exhausted' for what happens to memory when a slow search starts swapping under load.
Common pitfalls
Index goes stale after schema changes. Changing ep_indexable_post_types, ep_search_fields, or adding a new custom meta field requires a --setup rebuild. ElasticPress will not auto-migrate.
save_post indexing is synchronous by default. A page save blocks until ES confirms the index update. On a slow ES cluster this makes the admin sluggish. Use ElasticPress's async indexing feature (queues changes and processes via WP-Cron) for write-heavy sites.
Network calls during admin operations. Every post save triggers an ES network call. If the ES cluster is unreachable, post saves still succeed but the index falls behind. Monitor ES health.
MySQL FULLTEXT still runs for non-EP queries. If a query doesn't have ep_integrate => true and isn't a search query, it runs against MySQL. Mixing ES and MySQL search in the same page request is fine but counterintuitive when debugging.
Versions matter. ElasticPress targets Elasticsearch, and the required Elasticsearch version has moved up across ElasticPress releases. Match versions to the ElasticPress compatibility documentation before deploying.
Aggregation cardinality blow-up. Aggregating on a high-cardinality field (e.g., user IDs across 1M posts) can OOM the ES cluster. Use size limits on terms aggregations.
WP-CLI index times out. For huge sites, the indexer can run for hours. Use --per-page=350 and run via tmux to survive disconnects.
The reindex deletes data. wp elasticpress index --setup drops the existing index. If you're running it on production, ALL searches return 0 results until the rebuild finishes. ElasticPress 4.0+ uses index aliasing to avoid this; older versions don't.
What to do next
For surrounding infrastructure:
- PHP Memory Limit: How to Fix 'Allowed Memory Size Exhausted': the runtime knob that catches WordPress queries before they swap.
- wp_insert_post Consuming Large Amounts of Memory: bulk-import pattern; disable ElasticPress sync during the import then reindex after.
- How to Remove Empty Values from a PHP Array: sanitize search inputs and faceted-filter arrays before passing them to WP_Query.
- MySQL Field Types and Sizes: the schema reference for the underlying wp_posts/wp_postmeta tables that ElasticPress mirrors into Elasticsearch.
- Export or Backup All MySQL Databases: back up before any large reindex, especially on production.
- Elasticsearch Cheat Sheet: the raw Elasticsearch query DSL ElasticPress builds on top of.
- How to Optimize JPEG Images Using jpegoptim: paired image-pipeline content for media-heavy ElasticPress sites.





