Labsco
axiomhq logo

spl-to-apl

10

by axiomhq · part of axiomhq/skills

Translates Splunk SPL queries to Axiom APL. Provides command mappings, function equivalents, and syntax transformations. Use when migrating from Splunk,…

🔥🔥🔥✓ VerifiedFreeQuick setup
🧩 One of 6 skills in the axiomhq/skills package — works on its own, and pairs well with its siblings.

Translates Splunk SPL queries to Axiom APL. Provides command mappings, function equivalents, and syntax transformations. Use when migrating from Splunk,…

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates — shown here so you can audit it before installing. You don't need to read it to use the skill.

by axiomhq

Translates Splunk SPL queries to Axiom APL. Provides command mappings, function equivalents, and syntax transformations. Use when migrating from Splunk,… npx skills add https://github.com/axiomhq/skills --skill spl-to-apl Download ZIPGitHub10

SPL to APL Translator

Type safety: Fields like status are often stored as strings. Always cast before numeric comparison: toint(status) >= 500, not status >= 500.

Critical Differences

  • Time is explicit in APL: SPL time pickers don't translate — add where _time between (ago(1h) .. now())

  • Structure: SPL index=... | command → APL ['dataset'] | operator

  • Join is preview: limited to 50k rows, inner/innerunique/leftouter only

  • cidrmatch args reversed: SPL cidrmatch(cidr, ip) → APL ipv4_is_in_range(ip, cidr)

Core Command Mappings

SPL APL Notes search index=... ['dataset'] Dataset replaces index search field=value where field == "value" Explicit where where where Same stats summarize Different aggregation syntax eval extend Create/modify fields table / fields project Select columns fields - project-away Remove columns rename x as y project-rename y = x Rename sort / sort - order by ... asc/desc Sort head N take N Limit rows top N field summarize count() by field | top N by count_ Two-step dedup field summarize arg_max(_time, *) by field Keep latest rex parse or extract() Regex extraction join join Preview feature append union Combine datasets mvexpand mv-expand Expand arrays timechart span=X summarize ... by bin(_time, X) Manual binning rare N field summarize count() by field | order by count_ asc | take N Bottom N spath parse_json() or json['path'] JSON access transaction No direct equivalent Use summarize + make_list

Complete mappings: reference/command-mapping.md

Stats → Summarize

Copy & paste — that's it
# SPL
| stats count by status

# APL 
| summarize count() by status

Key function mappings

SPL APL count count() count(field) countif(isnotnull(field)) dc(field) dcount(field) avg/sum/min/max Same median(field) percentile(field, 50) perc95(field) percentile(field, 95) first/last arg_min/arg_max(_time, field) list(field) make_list(field) values(field) make_set(field)

Conditional count pattern

Copy & paste — that's it
# SPL
| stats count(eval(status>=500)) as errors by host

# APL
| summarize errors = countif(status >= 500) by host

Complete function list: reference/function-mapping.md

Eval → Extend

Copy & paste — that's it
# SPL
| eval new_field = old_field * 2

# APL
| extend new_field = old_field * 2

Key function mappings

SPL APL Notes if(c, t, f) iff(c, t, f) Double 'f' case(c1,v1,...) case(c1,v1,...,default) Requires default len(str) strlen(str) lower/upper tolower/toupper substr substring 0-indexed in APL replace replace_string tonumber toint/tolong/toreal Explicit types match(s,r) s matches regex "r" Operator split(s, d) split(s, d) Same mvjoin(mv, d) strcat_array(arr, d) Join array mvcount(mv) array_length(arr) Array length

Case statement pattern

Copy & paste — that's it
# SPL
| eval level = case(
 status >= 500, "error",
 status >= 400, "warning",
 1==1, "ok"
 )

# APL 
| extend level = case(
 status >= 500, "error",
 status >= 400, "warning",
 "ok"
 )

Note: SPL's 1==1 catch-all becomes implicit default in APL.

Rex → Parse/Extract

Copy & paste — that's it
# SPL
| rex field=message "user=(? \w+)"

# APL - parse with regex
| parse kind=regex message with @"user=(?P \w+)"

# APL - extract function 
| extend username = extract("user=(\\w+)", 1, message)

Simple pattern (non-regex)

Copy & paste — that's it
# SPL
| rex field=uri "^/api/(? v\d+)/(? \w+)"

# APL
| parse uri with "/api/" version "/" endpoint

Time Handling

SPL time pickers don't translate. Always add explicit time range:

Copy & paste — that's it
# SPL (time picker: Last 24 hours)
index=logs

# APL
['logs'] | where _time between (ago(24h) .. now())

Timechart translation

Copy & paste — that's it
# SPL
| timechart span=5m count by status

# APL
| summarize count() by bin(_time, 5m), status

Common Patterns

Error rate calculation

Copy & paste — that's it
# SPL
| stats count(eval(status>=500)) as errors, count as total by host
| eval error_rate = errors/total*100

# APL
| summarize errors = countif(status >= 500), total = count() by host
| extend error_rate = toreal(errors) / total * 100

Subquery (subsearch)

Copy & paste — that's it
# SPL
index=logs [search index=errors | fields user_id | format]

# APL
let error_users = ['errors'] | where _time between (ago(1h) .. now()) | distinct user_id;
['logs']
| where _time between (ago(1h) .. now())
| where user_id in (error_users)

Join datasets

Copy & paste — that's it
# SPL
| join user_id [search index=users | fields user_id, name]

# APL
| join kind=inner (['users'] | project user_id, name) on user_id

Transaction-like grouping

Copy & paste — that's it
# SPL
| transaction session_id maxspan=30m

# APL (no direct equivalent — reconstruct with summarize)
| summarize 
 start_time = min(_time),
 end_time = max(_time),
 events = make_list(pack("time", _time, "action", action)),
 duration = max(_time) - min(_time)
 by session_id
| where duration

## String Matching Performance

SPL APL Speed 
 `field="value"` `field == "value"` **Fastest** 
 `field="*value*"` `field contains "value"` Moderate 
 `field="value*"` `field startswith "value"` Fast 
 `match(field, regex)` `field matches regex "..."` **Slowest** 
 

 Prefer `has` over `contains` (word-boundary matching is faster). Use `_cs` variants for case-sensitive (faster).

## Reference

- `reference/command-mapping.md` — complete command list 

- `reference/function-mapping.md` — complete function list 

- `reference/examples.md` — full query translation examples 

- APL docs: [https://axiom.co/docs/apl/introduction](https://axiom.co/docs/apl/introduction)