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

Next Steps

Continue to 07-index-patterns.md for connecting Kibana to your data with index patterns and data views.