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

search (implicit)

filter events

where

filter on computed fields

eval

compute new fields

rename

rename fields

fields / fields -

keep / remove fields

stats

aggregate (avg, count, max, min, dc, values, list)

timechart

aggregate over time, render

chart

aggregate, multi-axis

top / rare

most / least frequent values

sort

order results

head / tail

first / last N

dedup

deduplicate by field

transaction

group events into sessions

join / append

combine searches

lookup

enrich with a lookup table

rex / regex

regex extract / match

spath

parse / select within JSON

eventstats

aggregates joined back to each row

streamstats

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.

  1. Hypothesis (often from MITRE ATT&CK).

  2. Write SPL search; run on historical data.

  3. Tune for false positives.

  4. Save as alert / correlation search with throttle and notable-event creation.

  5. Track in Enterprise Security or your own ESCU app.

Detection content distributed via.

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; rex on every search is expensive.

  • ``join`` is slow, prefer stats with BY or lookup.

  • 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.

See Also#

  • KQL.

  • Sigma, vendor-neutral detection format.

  • detection.