Kibana Query Language (KQL)

KQL is Kibana's built-in query language for filtering data across Discover, dashboards, and visualizations. It is simpler than Lucene and designed specifically for Kibana users who need fast, readable queries.

KQL vs Lucene

Kibana supports two query languages. KQL is the default since Kibana 7.x.

FeatureKQLLucene
Default in Kibana 8.xYesNo (opt-in)
AutocompleteFull supportLimited
Nested field queriesYesNo
Scripted fieldsNoNo
Regular expressionsNoYes
Proximity searchNoYes
BoostingNoYes
Fuzzy matchingNoYes
Syntax complexitySimpleMore complex

Switching Between KQL and Lucene

  1. Click the "KQL" button to the left of the search bar
  2. Toggle to "Lucene" if needed
  3. The button label shows which language is active

Rule of thumb: Use KQL for everyday work. Switch to Lucene only when you need regex, fuzzy matching, or proximity searches.

Basic Syntax

Field:Value Pairs

The core of KQL is field: value:

status: active
response: 200
customer_first_name: "Eddie"

Quoting rules:

  • Strings with spaces require quotes: city: "New York"
  • Single words don't need quotes: city: London
  • Numbers don't need quotes: response: 200
  • Booleans don't need quotes: is_active: true

Search across all fields by omitting the field name:

error

This searches every text field for the word "error". It is convenient but slower than field-specific queries.

Operators

Comparison Operators

# Equals
response: 200

# Greater than
bytes: > 1000

# Greater than or equal
response_time: >= 500

# Less than
age: < 30

# Less than or equal
price: <= 99.99

Boolean Operators

AND

Both conditions must be true:

status: "error" AND service: "api-gateway"
response: 500 AND method: POST

OR

Either condition can be true:

status: "error" OR status: "warning"
response: 404 OR response: 500

NOT

Exclude matches:

NOT status: "healthy"
status: "error" AND NOT service: "legacy-app"

Operator Precedence

KQL evaluates in this order:

  1. NOT (highest)
  2. AND
  3. OR (lowest)
# This query:
status: error OR status: warning AND service: api

# Is interpreted as:
status: error OR (status: warning AND service: api)

# To override, use parentheses:
(status: error OR status: warning) AND service: api

Grouping with Parentheses

Always use parentheses to make intent clear:

# Errors or warnings from the API service
(status: "error" OR status: "warning") AND service: "api-gateway"

# 500s from either the API or the web frontend
response: 500 AND (service: "api" OR service: "web")

# Complex multi-condition
(response: 500 OR response: 503) AND (method: POST OR method: PUT) AND NOT path: "/healthcheck"

Wildcards

Asterisk (*) - Any Characters

Match zero or more characters:

# Starts with
host: prod-*
# Matches: prod-web-01, prod-api-02, prod-db-01

# Ends with
filename: *.log
# Matches: app.log, error.log, access.log

# Contains
message: *timeout*
# Matches: "connection timeout", "request timeout occurred"

# Multiple wildcards
path: /api/*/users/*
# Matches: /api/v1/users/123, /api/v2/users/profile

Field Existence

Check if a field exists (has any value):

# Field exists
error.message: *

# Field does not exist
NOT error.message: *

This is equivalent to an exists filter but usable in the query bar.

Working with Data Types

Strings

# Exact match (keyword field)
status.keyword: "Active"

# Text search (analyzed text field)
message: "connection refused"

# Note: text fields are analyzed, so searching for
# "Connection Refused" matches "connection refused"
# keyword fields are case-sensitive

Numbers

# Exact match
response: 200

# Range
bytes: > 5000
response_time: >= 100 AND response_time: <= 500

# Combine ranges
cpu_percent: > 80 AND cpu_percent: < 100

Dates

KQL uses date math in date fields:

# Exact date (less common)
@timestamp: "2024-01-15"

# Usually you use the time picker for dates instead of KQL
# But date fields work with comparison operators:
order_date: > "2024-01-01" AND order_date: < "2024-02-01"

Tip: For date filtering, use the time picker or visual filters instead of KQL. They are faster and support date math like now-7d.

Booleans

is_active: true
is_deleted: false

IP Addresses

KQL supports CIDR notation:

# Exact IP
clientip: "192.168.1.1"

# CIDR range
clientip: "192.168.1.0/24"
# Matches: 192.168.1.0 through 192.168.1.255

# Private ranges
source.ip: "10.0.0.0/8"
source.ip: "172.16.0.0/12"
source.ip: "192.168.0.0/16"

Nested Field Queries

KQL can query nested objects, which Lucene cannot.

What Are Nested Fields?

Some Elasticsearch documents contain arrays of objects:

{
  "order_id": "12345",
  "products": [
    { "name": "Shirt", "price": 45.00, "quantity": 2 },
    { "name": "Pants", "price": 65.00, "quantity": 1 }
  ]
}

Querying Nested Fields

Without nested syntax, queries match across different objects in the array:

# This could match an order where ONE product is "Shirt"
# and a DIFFERENT product has quantity > 1
products.name: "Shirt" AND products.quantity: > 1

With nested syntax, conditions apply to the same object:

# This matches only if the SAME product is "Shirt" AND quantity > 1
products:{ name: "Shirt" AND quantity: > 1 }

Nested Query Examples

# Find orders where a single product costs more than $100 and has quantity > 2
products:{ price: > 100 AND quantity: > 2 }

# Find orders with a specific product in a specific category
products:{ name: "Premium Shirt" AND category: "Men's Clothing" }

# Combine nested with regular fields
customer_name: "Eddie" AND products:{ price: > 50 }

Autocomplete

KQL provides intelligent autocomplete in the search bar.

How Autocomplete Works

  1. Start typing a field name → Kibana suggests matching fields
  2. Type : after field → Kibana suggests operators
  3. Start typing a value → Kibana suggests matching values from your data
Step 1: Type "res"
  Suggestions: response, response_time, response.keyword

Step 2: Select "response" and type ":"
  Suggestions: 200, 301, 404, 500 (actual values from your data)

Step 3: Select "200"
  Result: response: 200

Autocomplete Tips

  • Press Tab to accept a suggestion
  • Press Escape to dismiss suggestions
  • Autocomplete only works for fields in the selected data view
  • Values shown are sampled from recent data (not exhaustive)

Common Query Patterns

Log Analysis

# Find errors in a specific service
level: "error" AND service.name: "payment-service"

# Find slow requests
response_time: > 5000 AND method: "POST"

# Find 5xx errors excluding health checks
response: >= 500 AND NOT url.path: "/health"

# Search log messages
message: *exception* AND NOT message: *expected*

Security Investigation

# Failed login attempts
event.action: "authentication_failed" AND source.ip: *

# Suspicious activity from external IPs
NOT source.ip: "10.0.0.0/8" AND event.action: "login"

# Privilege escalation
event.action: "role_change" AND user.roles: "admin"

# Multiple failed attempts from one IP
event.action: "authentication_failed" AND source.ip: "203.0.113.50"

E-commerce Analysis

# High-value orders
taxful_total_price: > 200

# Orders from specific region
geoip.country_iso_code: "US" AND geoip.region_name: "California"

# Specific product category with high spend
category.keyword: "Men's Shoes" AND taxful_total_price: > 100

# Customer search
customer_full_name: "Eddie*"

Infrastructure Monitoring

# High CPU servers
system.cpu.total.pct: > 0.90

# Disk space warnings
system.filesystem.used.pct: > 0.85

# Memory pressure
system.memory.actual.used.pct: > 0.80 AND host.name: prod-*

# Container issues
container.status: "OOMKilled" OR container.status: "CrashLoopBackOff"

KQL in Different Contexts

In Discover

The search bar in Discover uses KQL by default. Combine with the time picker and visual filters:

Query bar:  status: "error" AND service: "api"
Time:       Last 24 hours
Filter:     environment is "production"

In Dashboards

The dashboard query bar applies KQL across all panels:

# Filter entire dashboard to production errors
environment: "production" AND level: "error"

In Visualizations

Individual visualizations can have their own KQL filters:

# Visualization: "Production Error Count"
# Built-in filter: environment: "production" AND level: "error"
# This filter persists regardless of dashboard-level queries

In Alerting Rules

KQL is used to define alert conditions:

# Alert when error rate spikes
level: "error" AND service: "payment-*"

# Alert for specific error messages
message: *"database connection refused"*

Performance Tips

Query Efficiency

✅ Use field-specific queries
   response: 500

❌ Use free-text search
   500
   (searches every field - much slower)
✅ Use keyword fields for exact matches
   status.keyword: "Active"

❌ Use text fields for exact matches
   status: "Active"
   (text fields are analyzed, may produce unexpected results)
✅ Combine with time range
   Set time picker to "Last 1 hour" + status: error

❌ Query without time constraint
   status: error (over entire index - slow)

Field Selection

✅ Know your field types
   - .keyword fields: exact match, case-sensitive, aggregatable
   - text fields: full-text search, analyzed, not aggregatable

✅ Use autocomplete to discover fields
   Start typing and let Kibana help

❌ Guess field names
   Field names are case-sensitive: Status ≠ status

Query Complexity

✅ Start simple, then refine
   1. status: error
   2. status: error AND service: api
   3. status: error AND service: api AND NOT path: /health

❌ Write complex queries from scratch
   Harder to debug when results are unexpected

Lucene Fallback

When KQL can't do what you need, switch to Lucene:

Regular Expressions (Lucene Only)

# Match pattern
status: /err.*/

# IP range with regex
clientip: /192\.168\.1\..*/

Fuzzy Matching (Lucene Only)

# Fuzzy search (handles typos)
customer_name: eddie~2
# Matches: Eddie, Eddy, Edd1e (within edit distance 2)

Proximity Search (Lucene Only)

# Words within N positions of each other
message: "connection timeout"~5
# Matches: "connection was refused due to timeout"

Range Syntax (Lucene)

# Inclusive range
response_time: [100 TO 500]

# Exclusive range
response_time: {100 TO 500}

# Open-ended
bytes: [1000 TO *]

Common Mistakes

Mistake 1: Missing Quotes

❌ city: New York
   (Interpreted as: city: New AND York)

✅ city: "New York"
   (Correct: city equals "New York")

Mistake 2: Wrong Field Type

❌ category: "Men's Clothing"
   (If category is a text field, this does full-text search)

✅ category.keyword: "Men's Clothing"
   (Keyword field does exact match)

Mistake 3: Case Sensitivity

# Keyword fields are case-sensitive
❌ status.keyword: "active"    (if stored as "Active")
✅ status.keyword: "Active"

# Text fields are NOT case-sensitive (they're analyzed)
✅ message: "Error"            (matches "error", "ERROR", etc.)

Mistake 4: Operator Case

❌ status: error and service: api
   (Lowercase "and" is treated as a search term)

✅ status: error AND service: api
   (Uppercase AND is the boolean operator)

Mistake 5: Escaping Special Characters

# Parentheses, colons, and quotes need escaping in values
❌ message: error (timeout)
✅ message: "error (timeout)"

# Backslash for literal special characters
url.path: /api\/v1\/users

Quick Reference

Syntax Cheat Sheet

BASIC
  field: value                  Exact match
  field: "value with spaces"    Quoted value
  value                         Free-text (all fields)

COMPARISON
  field: > 100                  Greater than
  field: >= 100                 Greater or equal
  field: < 100                  Less than
  field: <= 100                 Less or equal

BOOLEAN
  expr1 AND expr2               Both must match
  expr1 OR expr2                Either can match
  NOT expr                      Must not match
  (expr1 OR expr2) AND expr3    Grouping

WILDCARDS
  field: val*                   Starts with
  field: *val                   Ends with
  field: *val*                  Contains
  field: *                      Field exists
  NOT field: *                  Field missing

NESTED
  nested_field:{ a: 1 AND b: 2 }   Same nested object

IP
  ip_field: "10.0.0.0/8"       CIDR notation

Summary

In this chapter, you learned:

  • ✅ KQL syntax fundamentals: fields, values, operators
  • ✅ Boolean logic with AND, OR, NOT and parentheses
  • ✅ Wildcards for pattern matching and existence checks
  • ✅ Handling different data types (strings, numbers, IPs, dates)
  • ✅ Nested field queries for array objects
  • ✅ Practical query patterns for logs, security, and monitoring
  • ✅ Performance tips for writing efficient queries
  • ✅ When and how to fall back to Lucene syntax

Next: Understanding index patterns and data views for connecting Kibana to your data!