Skip to content
ExponaBLOG
ELASTICSEARCH & SEARCH ENGINEERING · DAY 096 / 100

Boolean search syntax for users: lessons from production

Advanced search boxes.

Expona Admin6 min read

Why user-facing search feels different

Building search for internal tools is one thing; exposing a search box to millions of users is another. When you hand users a free-form query box, you're inviting them to experiment—and sometimes, to break things. This is where boolean search syntax for users: lessons from production becomes essential. In this article, we'll walk through the hard-won lessons from running user-facing search at scale, showing you how to let users express complex queries without exposing your Elasticsearch cluster to malformed requests or performance disasters.

The difference between a safe, user-friendly search experience and a fragile one often comes down to a single query type choice and a few strategic field restrictions.

simple_query_string is safe for user input

If you've been using query_string directly against user input, stop. The simple_query_string query is built for exactly this scenario: accepting raw user input and handling it gracefully.

Here's why simple_query_string wins:

  • It silently ignores syntax errors instead of throwing exceptions.
  • It treats unrecognized operators as literal text, preventing query injection.
  • It's designed to be lenient—users rarely want a 400 error for a typo.

Consider an e-commerce platform where users search products. When someone types laptop battery -, you don't want Elasticsearch to reject the query. Instead, you want it to find laptops and batteries, and treat the trailing dash as noise.

json
{
  "query": {
    "simple_query_string": {
      "query": "laptop battery -",
      "fields": ["title^2", "description"],
      "default_operator": "AND"
    }
  }
}

With simple_query_string, that query returns results. The trailing - is ignored. With query_string, you'd get a parsing error.

The default_operator choice matters too. Set it to AND for stricter matching (fewer, more relevant results) or OR for recall-focused search. Most production systems default to OR to avoid empty result sets, then rely on BM25 scoring to rank the most relevant matches first.

Support quotes, minus and OR

Even though simple_query_string is forgiving, we should still support the boolean operators users expect. The big three are quotes (phrase search), minus (negation), and OR (disjunction).

Phrase search with quotes

Let users wrap terms in double quotes to find exact phrases. This is intuitive and powerful.

json
{
  "query": {
    "simple_query_string": {
      "query": "\"machine learning\" tutorial",
      "fields": ["title", "content"],
      "default_operator": "OR"
    }
  }
}

That query finds documents mentioning the phrase "machine learning" or the word "tutorial". Phrases are indexed and searched using the standard analyzer, so they're language-aware (respecting stemming, stop words, etc.).

Negation with minus

Users often want to exclude results. The minus operator (-) does exactly that. It's especially useful when a term is overloaded.

Imagine a SaaS support search where users want articles about "export" but not about "export controls":

json
{
  "query": {
    "simple_query_string": {
      "query": "export -controls",
      "fields": ["title", "body"],
      "default_operator": "AND"
    }
  }
}

The minus operator is shorthand for a must_not clause, and it integrates seamlessly with other terms. Keep in mind: a query of only negations (e.g., -apple -orange) won't match anything useful. Always combine negation with at least one positive term.

OR for explicit disjunction

Sometimes users want to broaden their search across multiple terms. The OR operator (case-sensitive in simple_query_string) combines terms without requiring all of them to match.

json
{
  "query": {
    "simple_query_string": {
      "query": "python OR javascript OR go",
      "fields": ["language", "tags"],
      "default_operator": "AND"
    }
  }
}

This finds documents matching any of those three languages. In a travel app, you might use OR for flexible filtering: paris OR london OR berlin.

A production tip: OR (uppercase) is recognized; or (lowercase) is treated as a literal term. Always document this for users, or consider preprocessing user input to uppercase boolean operators before sending to Elasticsearch.

Avoid query_string with raw user input

The query_string query is powerful—it supports advanced syntax like wildcards, ranges, regex, and field-specific queries. It's also a liability when users can type freely.

Here's what goes wrong:

  • Syntax errors crash requests. A user types title:( and gets a 400 error instead of results.
  • Resource exhaustion. Wildcards like * or .* can trigger full index scans. A query like *tion on a text field is brutal.
  • Information leakage. If you allow field-specific queries (password:123), users can probe fields you didn't intend to expose.
  • Regex DoS. A malformed regex in query_string can consume CPU on every node.

We learned this the hard way in a CRM product. A well-meaning user typed a wildcard query, and the search cluster spiked to 95% CPU. The incident was brief, but it was a wake-up call.

If you absolutely need query_string—say, for an internal tool—whitelist the fields users can query and disable wildcards:

json
{
  "query": {
    "query_string": {
      "query": "user:john status:active",
      "fields": ["user", "status"],
      "allow_leading_wildcard": false,
      "analyze_wildcard": false
    }
  }
}

Even then, reserve it for authenticated, low-traffic environments. For public search, simple_query_string is the safer bet.

Limit allowed fields

By default, simple_query_string respects the fields parameter. This is your firewall. Always be explicit about which fields users can search.

Here's a production scenario from an e-commerce backend: you have a product index with fields like title, description, category, price, supplier_id, and internal_notes. You want users to search title, description, and category, but never supplier_id or internal_notes.

json
{
  "query": {
    "simple_query_string": {
      "query": "blue running shoe",
      "fields": ["title^3", "description^1", "category^2"],
      "default_operator": "OR"
    }
  }
}

The ^ boost syntax elevates certain fields. Here, title matches are 3x more relevant than description matches. This is both safe and effective: we've constrained the search scope and tuned relevance.

Also think about field types. Avoid searching across nested or object fields unless you're sure users understand the structure. Stick to keyword and text fields. If a field is analyzed (text), the query respects the field's analyzer. If it's a keyword, the query matches exactly (unless you set analyze_wildcard: true, which you shouldn't for user input).

Common mistakes

  • Using query_string for user input. We've said it, but it bears repeating. If you're building a user-facing search feature and your query uses query_string with raw user input, you're taking on unnecessary risk. Switch to simple_query_string or a combination of filters and structured queries.
  • Not documenting boolean syntax. Users won't know that OR must be uppercase, or that minus only works with at least one positive term. A short help tip ("Use OR to broaden search, minus (-) to exclude terms") goes a long way.
  • Forgetting to test edge cases. Empty queries, queries with only operators, very long queries, and special characters (unicode, emoji) should all be tested. simple_query_string is forgiving, but you should still validate input length on the application layer.
  • Exposing too many fields. If users can search a field via query_string with field-specific syntax, they can infer the schema. Limit the fields parameter strictly.
  • Setting default_operator incorrectly. AND is safer (more precise), but OR is more user-friendly (fewer empty results). Most production search systems use OR and rely on BM25 scoring and boost values to rank relevant results first.

Key takeaways

  • simple_query_string is your default for user input. It's forgiving, safe, and designed for this job.
  • Support quotes, minus, and OR. Users expect these boolean operators, and they're intuitive once documented.
  • Never use query_string with raw, unvalidated user input. The performance and security risks are real.
  • Always whitelist fields. Be explicit about what users can search. Use boost values to tune relevance without exposing schema details.
  • Test edge cases and document behavior. Empty queries, very long input, and special characters should all be handled gracefully.
  • Monitor search performance. Even with simple_query_string, unusual query patterns can spike latency. Keep an eye on slow logs and query counts by type.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles