SPL Cheat Sheet: Splunk Search Processing Language
SPL — Search Processing Language — is how you ask Splunk questions. A search reads left to right, piping results from one command into the next with |. Learn the dozen commands here and you can investigate in any Splunk-based SOC. Want a Splunk to run these against? Stand one up free with the Splunk Docker lab.
Almost every search has the same shape: pick the data, filter it down, then transform it into a number or a table. Get that rhythm and the rest is vocabulary.
The shape of every search
Start by choosing an index and sourcetype, then pipe into commands. Filtering terms go right after the search, before the first pipe — that is what keeps a search fast.
index=main sourcetype=access_combined status=500
Pick data and filter in one step: the main index, web access logs, only HTTP 500s. Bare terms after the search are ANDed together.
index=main sourcetype=access_combined status=500 | stats count by clientip | sort - count
The full arc — pick, filter, transform, order. Each | hands its results to the next command. This counts 500s per client IP, worst first.
Filter and choose fields
| Command | What it does | Example |
|---|---|---|
| search | Add more filters mid-pipeline | ... | search status>=400 |
| where | Filter with expressions and functions | ... | where bytes > 1000000 |
| fields | Keep or drop fields (keep speeds things up) | ... | fields clientip, status |
| dedup | Remove duplicate events by a field | ... | dedup user |
| sort | Order results; a − means descending | ... | sort - count |
| head / tail | Keep the first / last N results | ... | head 20 |
Turn events into numbers
These transforming commands are where investigation happens — they collapse thousands of events into a count, a trend, or a table.
| Command | What it does | Example |
|---|---|---|
| stats | Aggregate: count, sum, avg, values, dc (distinct count) | ... | stats count by user |
| count | The workhorse metric, often with by | ... | stats count by src, dest |
| timechart | stats over time — draws a trend | ... | timechart span=1h count |
| chart | stats split across two dimensions | ... | chart count over status by host |
| top | The most common values of a field | ... | top limit=10 uri_path |
| rare | The least common values — great for outliers | ... | rare process_name |
index=main sourcetype=access_combined | timechart span=15m count by status
A classic dashboard query: request volume every 15 minutes, split by HTTP status. A spike of 401s or 500s jumps out immediately.
Compute and reshape
| Command | What it does | Example |
|---|---|---|
| eval | Create or modify a field with an expression | ... | eval mb=round(bytes/1024/1024,2) |
| if / case | Conditional values inside eval | ... | eval risk=if(status=200,"ok","check") |
| rename | Give a field a readable name | ... | rename clientip AS "Source IP" |
| table | Pick exact columns, in order, as a table | ... | table _time, user, action |
| fillnull | Replace missing values | ... | fillnull value=0 count |
Extract and enrich
Real data is messy. rex pulls fields out of raw text with a regex; lookups join your events to reference tables (asset owners, threat lists, geolocation).
index=main sourcetype=syslog | rex field=_raw "user=(?<username>\w+)" | stats count by username
Extract a username field from raw syslog with a named capture group, then count by it. Named groups (?<name>...) become fields.
index=main sourcetype=firewall | lookup threat_ips ip AS src_ip OUTPUT threat_level | where isnotnull(threat_level)
Join firewall events to a threat_ips lookup table on the IP, pull in a threat_level, and keep only the matches. This is how watchlists become alerts.
SOC searches you’ll actually run
Copy these, change the index and field names to match your data, and go:
index=* (EventCode=4625 OR "authentication failure") | stats count by user, src | where count > 5 | sort - count
Brute-force detection — users with more than five failed logins from a source. Windows logs failed auth as EventCode 4625; Linux says “authentication failure”.
index=* sourcetype=WinEventLog:Security EventCode=4624 | stats dc(ComputerName) AS hosts values(ComputerName) by user | where hosts > 3
Lateral movement hint — one account logging in successfully (4624) to more than three hosts in the window. dc() is distinct count.
index=* sourcetype=firewall action=allowed | stats sum(bytes_out) AS total by src_ip | sort - total | head 10
Top talkers / possible exfiltration — the ten internal IPs sending the most data out. A quiet host suddenly at the top is worth a look.
index=* sourcetype=sysmon EventCode=1 | rare limit=20 Image
Rare process hunt — the least-common executables launched (Sysmon EventCode 1). Malware and living-off-the-land tools often surface here.
Speed and gotchas
- Filter early, filter hard. Always name an index= and sourcetype=, and put keyword filters before the first pipe.
- Avoid leading wildcards. *error forces a full scan; error* is fine.
- Use fields early to drop what you don’t need — less data down the pipe is faster.
- For huge datasets, learn tstats — it queries indexed metadata and is dramatically faster than stats on raw events.
- Pick a real time range. “All time” is the most common reason a search crawls.
Where to go next
- Run every query above in the free Splunk Docker lab.
- Deciding whether Splunk is even the SIEM for you? See which SIEM to learn.
- Prefer a fully free stack? The Wazuh lab gets you a SIEM at $0.
- Map what your searches find to attacker steps with the Cyber Kill Chain.
A working analyst’s SPL starter kit, verified Sep 2026. SPL is stable, but if a command’s changed or you want one added, tell me.