Getting Started
XPR is a safe, sandboxed expression language for evaluating user-defined expressions against structured data. It runs identically in JavaScript, Python, and Go.
What is XPR?
XPR lets you evaluate expressions like:
items.filter(x => x.price > threshold).map(x => x.name)against a context object you provide — safely, with no access to the host environment.
Use cases:
- Dynamic business rules in data pipelines
- User-configurable filters and transforms
- Safe formula evaluation in multi-tenant apps
Install
npm install @xpr-lang/xprbun add @xpr-lang/xprpip install xpr-langgo get github.com/xpr-lang/xpr-goQuick Start
import { Xpr } from '@xpr-lang/xpr';
const engine = new Xpr();
const result = engine.evaluate(
'items.filter(x => x.price > threshold).map(x => x.name)',
{
items: [
{ name: 'Widget', price: 99 },
{ name: 'Gadget', price: 25 },
],
threshold: 50,
}
);
// → ['Widget']from xpr import Xpr
engine = Xpr()
result = engine.evaluate(
'items.filter(x => x.price > threshold).map(x => x.name)',
{
'items': [
{'name': 'Widget', 'price': 99},
{'name': 'Gadget', 'price': 25},
],
'threshold': 50,
}
)
# → ['Widget']import xpr "github.com/xpr-lang/xpr-go"
engine := xpr.New()
result, err := engine.Evaluate(
`items.filter(x => x.price > threshold).map(x => x.name)`,
map[string]any{
"items": []any{
map[string]any{"name": "Widget", "price": 99},
map[string]any{"name": "Gadget", "price": 25},
},
"threshold": 50,
},
)
// → ["Widget"]Custom Functions
Register domain-specific functions that expressions can call:
engine.addFunction('slugify', (s) => s.toLowerCase().replace(/ /g, '-'));
// Now usable: slugify(product.name)engine.add_function('slugify', lambda s: s.lower().replace(' ', '-'))
# Now usable: slugify(product.name)engine.AddFunction("slugify", func(args ...any) (any, error) {
s := args[0].(string)
return strings.ReplaceAll(strings.ToLower(s), " ", "-"), nil
})
// Now usable: slugify(product.name)Security
XPR is designed to be safe by default:
- No side effects — expressions cannot mutate the context
- No host access — no filesystem, network, or environment variables
- Prototype blocking —
__proto__,constructor, etc. are blocked - Depth limit — max AST depth of 50 prevents stack overflow
- Timeout — expressions are killed after 100ms by default
v0.2 Features
Let Bindings
Chain multiple bindings with semicolons — the final expression is the result:
let items = [1,2,3,4,5]; let big = items.filter(x => x > 2); big.map(x => x * 10)→ [30, 40, 50]
let f = (x) => x * 2; f(5)→ 10
Spread Operator
Merge arrays and objects:
[...[1,2], ...[3,4]]→ [1, 2, 3, 4]
{...defaults, ...overrides}→ merged object (overrides wins on key collision)
New Methods
range(5) // → [0,1,2,3,4]
[1,2,3,4,5].chunk(2) // → [[1,2],[3,4],[5]]
[1,2,1,3].unique() // → [1,2,3]
["a","b","c"].zip([1,2,3]) // → [["a",1],["b",2],["c",3]]
items.groupBy(x => x.type) // → {type: [items...]}
{"b":2,"a":1}.entries() // → [["a",1],["b",2]]
{"a":1}.has("a") // → true
"hello".charAt(1) // → "e"
"42".padStart(5, "0") // → "00042"What's New
v0.3 — Date/time functions (now, parseDate, formatDate, date arithmetic), regex functions (matches, match, replacePattern), negative array indexing, spread in function calls.
v0.4 — Destructuring in let bindings and arrow function parameters (object + array, with defaults and rest). First-class regex literals (/pattern/flags).
v0.5 — Math functions (sqrt, log, pow, random, sign, trunc) and constants (PI, E). Type predicates (isNumber, isString, etc.). 13 new array methods (sortBy, take, drop, sum, avg, compact, partition, keyBy, and more). fromEntries(), str.split(/regex/), rest parameters in arrows.
See the Changelog for the full version history.
Next Steps
- Language Specification — full syntax reference
- JavaScript API
- Python API
- Go API