Skip to content

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 ​

bash
npm install @xpr-lang/xpr
bash
bun add @xpr-lang/xpr
bash
pip install xpr-lang
bash
go get github.com/xpr-lang/xpr-go

Quick Start ​

typescript
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']
python
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']
go
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:

typescript
engine.addFunction('slugify', (s) => s.toLowerCase().replace(/ /g, '-'));
// Now usable: slugify(product.name)
python
engine.add_function('slugify', lambda s: s.lower().replace(' ', '-'))
# Now usable: slugify(product.name)
go
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 ​