SPL#
SPL, Splunk’s Search Processing Language, is the proprietary query DSL for the Splunk platform. Pipeline-shaped like KQL: each command transforms the result of the previous one. The standard for Splunk-based SOCs.
Pipeline Form#
index=security sourcetype=auth
| search EventID=4625
| stats count BY user, host
| where count > 10
| sort - count
| head 50
The first segment (index=security sourcetype=auth EventID=4625) is
an implicit search command; everything after a pipe is an explicit
command.
Common Commands#
The verbs an SPL author writes most weeks. Filter, project, compute, aggregate, sort, dedupe, look up enrichments, parse regex and JSON, group into transactions; the table below covers the workhorses. Most queries draw from a dozen commands and rarely reach further.
Command |
Action |
|---|---|
|
filter events |
|
filter on computed fields |
|
compute new fields |
|
rename fields |
|
keep / remove fields |
|
aggregate (avg, count, max, min, dc, values, list) |
|
aggregate over time, render |
|
aggregate, multi-axis |
|
most / least frequent values |
|
order results |
|
first / last N |
|
deduplicate by field |
|
group events into sessions |
|
combine searches |
|
enrich with a lookup table |
|
regex extract / match |
|
parse / select within JSON |
|
aggregates joined back to each row |
|
running aggregates |
stats, the Workhorse#
index=auth sourcetype=ssh action=failure
| stats
count AS attempts,
dc(src_ip) AS unique_src,
values(user) AS users,
earliest(_time) AS first,
latest(_time) AS last
BY host
| where attempts > 100
eval, Inline Functions#
... | eval
domain = lower(domain),
is_admin = if(role=="admin", 1, 0),
risk = case(
count > 100, "high",
count > 10, "medium",
true(), "low"),
ts = strftime(_time, "%Y-%m-%d %H:%M:%S")
rex, Regex Extraction#
... | rex field=raw "user=(?<user>[^\s]+)"
| rex "from\s+(?<src_ip>\d+\.\d+\.\d+\.\d+)"
Lookups#
Enrichment from CSV / KV / external sources.
... | lookup geoip src_ip OUTPUT country, city, lat, lon
| lookup user_directory user OUTPUT department, manager
Time#
Searches require a time range. In the search bar this is selected via the time picker; in saved searches you set it explicitly.
index=security earliest=-24h@h latest=now
| bucket _time span=1h
| stats count by _time, user
| timechart span=1h count by user
Subsearches#
A subsearch returns a list to the outer search.
index=auth status=success
[ search index=threatintel src_ip="*" | fields src_ip ]
| stats count by user, src_ip
Macros and Saved Searches#
Reusable SPL is exposed as macros (\``my_macro\``) or saved
searches referenced via lookups / inputlookup. Most SOC content lives
in apps that ship preconfigured macros.
Detection Workflow#
The lifecycle of a Splunk detection. Hypothesis from ATT&CK, historical search, false-positive tuning, save as alert with throttle and notable-event creation, and track in Enterprise Security or an ESCU app. Content flows from the Splunk research team, the Sigma project, and community repos.
A typical Splunk detection pipeline.
Hypothesis (often from MITRE ATT&CK).
Write SPL search; run on historical data.
Tune for false positives.
Save as alert / correlation search with throttle and notable-event creation.
Track in Enterprise Security or your own ESCU app.
Detection content distributed via.
Sigma → SPL conversion (
sigma-cli).Community blog / GitHub repos.
Tooling#
Splunk Web, the search head’s UI.
Splunk SDKs, Python / JS / Go / Java.
splunklib, run SPL programmatically.
VS Code Splunk extension, syntax highlighting.
Sigma → SPL via the standard Sigma converters.
Strengths#
The reasons SPL is still everywhere in 2026. Twenty years of SOC use means the language is mature; the pipeline form is expressive; the ecosystem of pre-built content is deep; and schema-on-read means raw events flow in without up-front parsing decisions.
Mature, 20 years of SOC use.
Powerful pipeline, expressive enough for nearly any analysis.
Huge content library, ESCU, Splunk apps, community blogs.
Schema-on-read, ingest first, parse later.
Weaknesses#
The flip side. The license model is steep and incentivizes ingest filtering that complicates later investigation; performance suffers on wide ranges or heavy subsearches; and the mental model of indexed versus extracted fields is something every Splunk operator eventually has to learn.
Proprietary, only runs on Splunk; license cost is famously steep.
License model based on data volume incentivizes filtering at ingest, which complicates investigation later.
Performance drops on very wide time ranges or many subsearches; understand “indexed fields” vs. “extracted fields”.
Sigma compatibility is good but not perfect; some constructs don’t translate cleanly.
SPL vs. KQL vs. EQL#
The major SIEM query languages an operator may meet. Each maps roughly to one vendor stack (Splunk, Microsoft, Elastic, IBM, Google), and they’re not portable between backends without translation. Sigma is the project that bridges authoring across all of them.
SPL (Splunk), the original mature pipeline language; commercial.
KQL (Microsoft Sentinel / Defender), Microsoft’s; cleaner syntax; tightly integrated with Azure.
EQL / ES|QL (Elastic), Elastic’s; ES|QL is the newer pipeline language replacing KQL-flavored search syntax.
AQL (IBM QRadar), SQL-shaped; closer to relational SQL than pipelines.
YARA-L (Google Chronicle), detection-focused.
If your organization runs Splunk, you write SPL. If it runs Sentinel, you write KQL. The Sigma project bridges the writing of detections, so the same source rule can target multiple SIEMs.
Pitfalls#
The traps that catch teams new to SPL. Search-time field
extraction is expensive; join is far slower than
stats with BY; subsearches silently truncate at fifty
thousand results; and saved searches need explicit time
ranges to schedule reliably.
Field extraction at search time vs. at index time has cost implications;
rexon every search is expensive.``join`` is slow, prefer
statswithBYorlookup.Subsearches have a default 50,000-result limit; overflow silently.
Time picker mismatch, saved searches need explicit
earliest/latest; relying on the picker breaks scheduled runs.