2026.1
Platform & Core
Inventories Resource Feature
Inventories are now a first-class resource at /inventories/<key>. Each inventory has a label and can be extended by apps via slots. Previously, inventory keys were free-form text values with no central definition — now they have a home.
Inventory definitions automatically feed into graph enum dropdowns, so fields like inventoryKey on deliveries and stock positions show labeled options without any manual configuration.
Enhanced Channel Configuration Improvement
Channels can now declare which inventories they support and which countries they serve. This enables:
- Filtered inventory selection — When setting the inventory on a delivery, only inventories assigned to the order's channel are shown
- Filtered country selection — Address forms show only countries configured on the channel
If a channel has no inventories or countries configured, all available options are shown (preserving existing behavior).
Per-country records within a channel are extensible via app slots, allowing apps to attach carrier accounts, tax identifiers, or other country-specific data.
Promotions Feature Breaking
A new Promotions entity on orders replaces the DiscountType.Computed concept. Promotions are a first-class entity with their own graph node, separate from static discounts.
A Promotion runs a Filtrera script against the order state and can produce two types of output:
- Discount effects — Percentage or fixed discounts distributed to order lines and deliveries as calculated discounts
- Promotional messages — Structured messages stored on the order for consuming apps and storefronts
Additional capabilities:
- Combination rules — Promotion groups with allowlist/blocklist rules to control which promotions can be active together
- Dynamic fields — Arbitrary metadata for tracking and integration
- Parameters — Typed inputs to the promotion script
- Localized labels — Human-readable names with per-language translations
Breaking change: DiscountType.Computed is removed. Static discounts (Absolute and Percentage) continue to work as before. The CalculatedDiscount entity now uses ReferenceId and ReferenceType fields instead of DiscountId to identify whether the calculated discount originated from a static discount or a promotion.
Rule API — REST with ETag Concurrency Improvement
The Rule API is now REST-based, enabling proper versioning and concurrency control. Rules can now be updated using If-Match ETag headers to prevent conflicting concurrent modifications — the same pattern used by the IAM API.
moveOrderLine to New Delivery Command Feature
A new moveOrderLine order command allows moving an order line to a different delivery within the same order.
Inventory Dates Improvement Breaking
inventoryDate and expectedAt fields across deliveries, stock positions, reservations, and incoming stock have been changed from timestamps to date-only values (YYYY-MM-DD format in JSON). These are planning dates that represent which day stock is expected or needed — not precise points in time — and the date-only format aligns with ERP/MRP conventions and eliminates timezone ambiguity.
Breaking change: API consumers that currently send or receive "2026-03-17T00:00:00Z" for these fields must update to "2026-03-17".
Global Enum Value Sources Feature
Enum values for graph fields can now be defined once and shared across multiple fields and nodes. Previously, every enum-typed field had to have its values configured independently.
Two types of shared sources are now supported:
- System sources — Inventories, channels, and currencies automatically provide their keys as enum options for any field that references them, without manual configuration
- User-defined sources — Define custom named enum types at
/enums/definitions/<name>/values/and reference them from multiple custom graph fields
Existing per-field enum values at /enums/graph/<node>/<field>/values/ continue to work unchanged.
Ingresses
Server-Sent Events (SSE) Feature
HTTP ingresses now support Server-Sent Events. When a reactor component returns a Filtrera iterator and the client sends Accept: text/event-stream, the server establishes an SSE connection and streams each iterator item to the client as it is produced.
A new events() runtime keyword provides an infinite iterator that waits for and yields matching EventHub events, enabling live data streams:
param orderId: uuid
from
events(
$'actors/orders/{orderId}'
'checkpoint'
)
select event => {
status = event.data.status
timestamp = event.timestamp
}Initial data and live events can be combined using standard Filtrera flatten:
let initMessages = [{ type = 'init', data = order }]
let eventMessages =
events ($'actors/orders/{orderId}', 'checkpoint')
select event => { type = 'update', data = event.data }
from [initMessages, eventMessages] flattenSSE ingresses support the public: true flag, enabling unauthenticated event streams for use cases like order tracking pages. The browser's built-in EventSource API handles reconnection automatically.
Component Runtimes
calculateAvailableStock Macro Feature
A new calculateAvailableStock macro is available in both rule and reactor runtimes. It sends a stock availability request to the SKU actor and returns a { text -> number } map of available quantity per inventory key — replacing the need to write complex graph queries against stock positions, reservations, and allocations manually.
// All inventories, with date cutoff for incoming stock
let stock = calculateAvailableStock (orderLine.skuNumber, delivery.inventoryDate)
from stock->'wh_stockholm' // 50
// Specific inventories only
let stock = calculateAvailableStock (
orderLine.skuNumber,
delivery.inventoryDate,
['wh_stockholm', 'wh_gothenburg']
)
// With allocation keys — includes stock allocated to these keys
let stock = calculateAvailableStock (
orderLine.skuNumber,
delivery.inventoryDate,
['wh_stockholm'],
['vip_allocation']
)The asOf parameter accepts a date (not a timestamp), consistent with the inventory date change above.
triggerHook and Custom Effects Feature
A new triggerHook filter is available in both rule and reactor runtimes, enabling apps to define domain-specific hook points that other rules can subscribe to — enabling a simple form of dependency injection.
The calling rule or reactor triggers a hook by passing a data record and a hook name. All matching rules are evaluated against the hook input, and their effects are returned as an iterator for the caller to inspect, filter, and apply:
// Calling rule — trigger the hook and collect effects
let hookEffects =
{
claimId = claimId,
orderId = orderId,
lines = resolvedLines
}
triggerHook 'OnClaimResolve'
// Filter and batch order commands from hook listeners
let hookOrderCommands =
hookEffects
where e => e.effect == 'orderCommand'Listening rules declare a param input with a literal hook field type — Filtrera's type system ensures the rule is only evaluated for matching hooks:
param input: {
hook: 'OnClaimResolve'
claimId: uuid
orderId: uuid
lines: [{ ... }]
}triggerHook is single-level: a rule triggered via triggerHook cannot itself call triggerHook (it returns an empty iterator), preventing unbounded recursion.
Jobs
Batch Scheduling Feature
Jobs can now be scheduled in batches — creating multiple scheduled job instances in a single operation. This is useful for pre-populating a schedule or queuing a large number of similar jobs without making individual API calls.
Improved Job Scheduling Parameter Assistance Improvement
The job scheduling API now provides assisted parameter input, helping callers supply correctly typed and structured parameters when scheduling jobs. Job definitions expose their parameter schema, making it easier to construct valid scheduling requests.
Portal
Inventories & Channels Management Feature
New Settings views for managing inventories and channels:
- Inventories — Create and configure inventories with labels. App slots allow installed apps to attach additional fields (e.g. warehouse system IDs) to each inventory definition
- Channels — Assign available inventories and countries to each channel. Per-country records support app slots for carrier accounts, tax rules, or other country-specific configuration
Basic App Maintenance Feature
A new Apps section in the portal provides basic management of installed apps: view installed apps, their status, and perform common maintenance operations without needing CLI access.
API & WebSocket
Unified /ws Endpoint Improvement
The Events API has moved to a new unified WebSocket endpoint at /ws. The previous /events endpoint is replaced. Both the Events API and the new Live Queries API share this single connection, authenticated once with a bearer token.
WARNING
If you are connecting to the Events API, update your WebSocket URL from /events to /ws.
Live Queries (Experimental) Feature
INFO
Live Queries are released as experimental in this version. Not all graph query features are supported yet, and the API may evolve before the final release.
Live Queries enable real-time reactive data in portal apps and external integrations. Create a live query over the /ws WebSocket endpoint and the server maintains the query state, pushing updates as the underlying data changes — no polling required.
When a matching entity is created, updated, or removed, the server sends a targeted update message containing only the affected node. Clients maintain a local list by applying add/update/remove messages as they arrive.
const { available, query } = useLiveQueries()
// Reactive list of active orders — updates automatically
const { nodes, loading, totalCount } = query({
edge: 'orders',
filter: "status == 'readyForPicking'",
orderBy: 'createdAt asc'
})The useLiveQueries composable (via @hantera/portal-app) automatically disposes the query when the component is unmounted and re-establishes it on reconnect.
Developer Experience
Dev Mode Evaluation Tracing Feature
h_ app dev now captures real-time evaluation traces for all Filtrera components belonging to your dev app — rules, reactors, discounts — and streams them to the CLI as they run.
Every evaluation prints a summary line in the console:
⚡ priceRule [orders/abc123/onUpdate] 3712 step(s)
🌐 webhook [my-app/webhook.hrc] POST /api/hook → 200 (45ms)For deeper inspection, the optional --trace <path> flag writes complete JSONL trace files containing every evaluated AST node, resolved symbols, and ingress request/response data:
h_ app dev ./my-app --trace ./eval.traceEach evaluation produces a flat, ordered list of nodes with id/parentId references for reconstructing the evaluation tree. Ingress logs capture full request and response bodies (up to 1 MB). Trace files can be opened in any JSON Lines-aware viewer for timeline analysis and debugging.
General
Numerous bug fixes and performance improvements throughout the platform, portal, and design system.