Introduction
Expression Engine evaluates a short string against a bag of structured data and gives you back a real, typed value:
$engine->evaluate('trigger.user.age >= 18', [
'trigger' => ['user' => ['age' => 21]],
]);
// → true (a real bool)
It also fills {{ }} placeholders in longer strings:
$engine->resolve('Hi {{ trigger.user.name }}, you have {{ trigger.cart.items.count() }} items', [
'trigger' => [
'user' => ['name' => 'Ada'],
'cart' => ['items' => [/* ... */]],
],
]);
// → "Hi Ada, you have 3 items"
When to reach for it
The engine is built for low-code surfaces where a non-developer, or a piece of stored configuration, needs to express a dynamic value:
- Workflow automations — "run this step only if
trigger.order.total > 100". - Dynamic forms — computed fields, conditional visibility rules.
- Templated config and notifications — subject lines, webhook payloads, message bodies with data interpolated in.
It is deliberately not a general-purpose scripting language. There are
no user-defined variables (beyond set()/get() within a run), no
loops, no assignment, and no access to arbitrary PHP. Expressions read
data, transform it through a fixed, safe set of methods and functions,
and produce a value.
Installation
composer require qanna-rsa/expression-engine
Requires PHP 8.1+ and Laravel 10, 11, 12, or 13.
Laravel auto-discovers the service provider and the ExpressionEngine
facade. Publish the config file if you want to change defaults:
php artisan vendor:publish --tag=expression-engine-config
This writes config/expression-engine.php.
Quickstart
Resolve the engine from the container (it is registered as a singleton,
configured from config/expression-engine.php):
use Qanna\ExpressionEngine\Engine;
$engine = app(Engine::class);
Or use the facade:
use Qanna\ExpressionEngine\Facades\ExpressionEngine;
ExpressionEngine::evaluate('trigger.total * 1.15', ['trigger' => ['total' => 200]]);
// → 230.0
Evaluate an expression
$engine->evaluate('trigger.items.filter(current.active).map(current.name)', [
'trigger' => ['items' => [
['name' => 'Widget', 'active' => true],
['name' => 'Gadget', 'active' => false],
]],
]);
// → ["Widget"]
Fill a template
$engine->resolve('Order {{ trigger.order.ref }} — {{ trigger.order.total.format(2) }}', [
'trigger' => ['order' => ['ref' => 'A-1001', 'total' => 49.5]],
]);
// → "Order A-1001 — 49.50"
Resolve every string in a structure
resolveArray() walks an array recursively and resolves {{ }} in every
string value — handy for templated notification or webhook payloads:
$engine->resolveArray([
'to' => '{{ trigger.user.email }}',
'subject' => 'Welcome, {{ trigger.user.name }}',
'body' => [
'greeting' => 'Hi {{ trigger.user.name.title() }}',
],
], $context);
Where to go next
- New to the mental model? Read Expressions and templates and Context and paths.
- Want the full list of what you can call? See Chainable methods and Functions.
- Building an editor UI on top of the language? See Building autocomplete.
- Try expressions interactively with
php artisan expression:playground— see the CLI reference.