ScriptLite — a sandboxed ECMAScript subset interpreter for PHP (with optional C extension)

Published: (March 6, 2026 at 10:50 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

What it does

It runs JavaScript (ES5/ES6 subset) inside PHP. No filesystem access, no network, no eval, no require — scripts can only touch the data you explicitly pass in. Think of it as a sandbox where users write logic and you control exactly what they can see and do.

$engine = new ScriptLite\Engine();

// User‑defined pricing rule stored in your database
$rule = '
    let total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
    if (total > 100) total *= (1 - discount);
    Math.round(total * 100) / 100;
';

$result = $engine->eval($rule, [
    "items" => [
        ["price" => 29.99, "qty" => 2],
        ["price" => 49.99, "qty" => 1],
    ],
    "discount" => 0.1,
]);
// $result === 98.97

It supports the stuff people actually use day to day: arrow functions, destructuring, template literals, spread/rest, array methods (map, filter, reduce, …), object methods, regex, try/catch, Math, JSON, Date, and more.

PHP interop

You can pass PHP objects directly. Scripts can read properties, call methods, and mutations flow back to your PHP side:

$order = new Order(id: 42, status: 'pending');

$engine->eval('
    if (order.total() > 500) {
        order.applyDiscount(10);
        order.setStatus("vip");
    }
', ["order" => $order]);

// $order->status is now "vip"

You can also pass PHP closures as callable functions, so you control exactly what capabilities the script has:

$engine->eval('
    let users = fetchUsers();
    let active = users.filter(u => u.lastLogin > cutoff);
    active.map(u => u.email);
', [
    "fetchUsers" => fn() => $userRepository->findAll(),
    "cutoff" => strtotime("-30 days"),
]);

Three execution backends

Bytecode VM

Compiles to bytecode, runs on a stack‑based VM in pure PHP. Works everywhere, no dependencies.

PHP transpiler

Translates the JavaScript to PHP source code that OPcache/JIT can optimize. About 40× faster than the VM. Good for hot paths.

C extension

A native bytecode VM with computed‑goto dispatch. About 180× faster than the PHP VM. Built out of curiosity about the ultimate speed.

The API is the same regardless of backend. The engine picks the fastest available one automatically:

$engine = new Engine();        // uses C ext if loaded, else PHP VM
$engine = new Engine(false);   // force pure PHP

// Same code, same results, different speed
$result = $engine->eval('items.filter(x => x > 3)', ["items" => [1, 2, 3, 4, 5]]);

The transpiler path is interesting if you want near‑native speed without a C extension:

// Transpile once, run many times with different data
$callback = $engine->getTranspiledCallback($script, ["data", "config"]);
$result1 = $callback(["data" => $batch1, "config" => $cfg]);
$result2 = $callback(["data" => $batch2, "config" => $cfg]);

Possible use cases

  • User‑defined formulas — let users write price * quantity * (1 - discount) in a CMS, form builder, or spreadsheet‑like app.
  • Validation rules — store rules like value.length > 0 && value.length.

909 tests across all backends. MIT licensed. PHP 8.3+.

Would love to hear what you think, especially if you’ve run into similar “I need users to write logic but not PHP” situations. What did you end up doing?

0 views
Back to Blog

Related posts

Read more »