Order Routing Rules
Order routing rules are declarative manifests that match cart lines to fulfillment locations. Unlikefulfillment_constraints
functions (which run WASM and can block order placement), routing rules
are pure JSON matchers evaluated by the platform at order placement.
Use routing rules when the assignment is data-driven (SKU prefix,
tag, country, line attribute) and fulfillment_constraints when you
need code (call your inventory service, look up real-time stock,
hit a 3PL API).
When to use rules vs constraints
| Use a routing rule when… | Use a fulfillment_constraints function when… |
|---|---|
Assignment is a fixed lookup (country → locationId). | You need to call an external service to decide. |
Logic is JSON-expressible (startsWith, in, gt). | Logic needs imperative code (loops over inventory, joins, computed scores). |
| You want a non-developer to author the rule. | You’re shipping WASM anyway. |
| You’re OK with order-time evaluation only. | You also need to block orders. |
fulfillment_constraintsnarrows the set of locations a line can ship from (block if zero, otherwise narrow to the eligible set).- Routing rules pick the preferred location from that narrowed set (West Coast → Oakland; everything else → New Jersey).
Where rules live
Each rule is one entry inextensions.orderRoutingRules on your
app.json. After install, each entry is persisted as a single schema
file at:
type: 'fulfillment_location_rule', so the dispatcher can
consume a single union of all routing extensions across all installed
apps. Per-shop cap: 25 active rules across all apps.
Manifest
Fields
| Field | Required | Description |
|---|---|---|
handle | yes | URL-safe rule id, unique per app. Used as the schema filename. |
title | yes | Human label shown in the merchant’s fulfilment dashboard. |
type | no | Always "fulfillment_location_rule". Defaults to this if omitted. |
rule.match | yes | Matcher object. See Match operators. |
rule.assign | yes | Assignment block. See Assignment. |
rule.fallback | no | Behaviour when no rule in the rule set matches. See Fallback. |
Match operators
Paths use dotted notation against the cart input. A path can be a literal field name or include[] to project across an array.
Full operator list
| Form | Meaning | Example |
|---|---|---|
"path": "value" | Exact equality. | "shippingAddress.country": "US" |
"path": ["v1", "v2"] | Any-of (the value is one of these). | "shippingAddress.province": ["CA", "WA"] |
"path": { "equals": v } | Same as "path": v, explicit form. | { "equals": "FRAGILE" } |
"path": { "in": [v1, v2] } | Same as "path": [v1, v2], explicit form. | { "in": ["CA", "NV"] } |
"path": { "gt": n } | Numeric: strictly greater than. | "cart.totalPrice": { "gt": 100 } |
"path": { "gte": n } | Numeric: greater than or equal. | { "gte": 50 } |
"path": { "lt": n } | Numeric: strictly less than. | { "lt": 250 } |
"path": { "lte": n } | Numeric: less than or equal. | { "lte": 5 } |
"path": { "startsWith": s } | String prefix match. | { "startsWith": "ABC-" } |
"path": { "endsWith": s } | String suffix match. | { "endsWith": "-XL" } |
"path": { "contains": s } | Substring (strings) or includes (arrays). | { "contains": "fragile" } |
"path": { "not": <op> } | Negate any of the above. | { "not": { "in": ["CA"] } } |
Boolean composition
Multiple keys in the samematch block are ANDed. For explicit
boolean logic, use any (OR) or all (AND) lists at the top level:
(country == US OR country == CA) AND totalPrice >= 50.
Common paths
| Path | Type | Notes |
|---|---|---|
shippingAddress.country | string (ISO-3166-1 alpha-2) | "US", "CA", "GB" |
shippingAddress.province | string (e.g. "CA") | State / region code. |
shippingAddress.city | string | |
shippingAddress.zip | string | Useful with startsWith for ZIP prefixes. |
cart.totalPrice | number | Subtotal in display currency. |
cart.itemCount | number | Sum of quantities. |
cart.currency | string | "USD", "INR", etc. |
cart.lines[].merchandise.sku | string (per line) | Project over every line. |
cart.lines[].merchandise.productId | string (per line) | |
cart.lines[].merchandise.attributes.<key> | string (per line) | Free-form per-line attributes (hazmat, oversized, …). |
cart.lines[].quantity | number (per line) | |
customer.tags | string[] | Use contains for tag membership. |
customer.id | string |
[]) match if any line satisfies the inner
predicate. To require all lines to match, wrap in all:
Assignment
| Field | Required | Description |
|---|---|---|
locationId | yes | Target fulfilment location. Format: gid://launchmystore/Location/<uuid>. |
priority | no | Tie-breaker (default 0). The highest-priority match in the rule set wins. |
fallback | no | If true, only applies when no other rule matched. Useful for catch-all rules. |
priority. Ties are broken by declaration order across apps — there
is no defined cross-app ordering today, so prefer unambiguous priorities
when two apps might match the same line.
Fallback
A catch-all rule:match: {} matches anything. The fallback: true flag tells
the evaluator to only consider this rule when nothing else matched. With
fallbacks, you don’t need to enumerate every possible region — write the
specifics, then catch the rest.
Worked examples
West Coast US → Oakland; everything else → Newark
us-west has
priority: 10 and wins. An Idaho order only matches us-default and
ships from Newark.
Hazmat → licensed hub regardless of region
priority: 100 outranks the regional rules above. Any cart containing a
hazmat line is routed to the hazmat hub, ignoring shipping address.
International → 3PL
Backorder → drop-shipper
inventory_state attribute is "backorder" (typically
populated by your inventory app via a cart_transform function) is sent
straight to the drop-shipper, bypassing all other routing.
High-value orders → expedited centre
Inspection
Once the runtime evaluator is wired, the result of routing will be persisted on the order’sadditionalFields.orderRouting:
us-west matched at priority 10”.
A line that has no matching rule is omitted from the array — the
merchant’s manual fulfilment workflow handles it.
Interaction with fulfillment_constraints
When both routing rules and a fulfillment_constraints function are
installed for the same store, the platform runs them in this order:
The rule evaluator respects the narrowed set: a rule that assigns
oakland-dc to a line whose constraint allows only [newark-dc] is
ignored for that line. Use fulfillment_constraints to enforce
hard physical limits (a location is the only one with stock) and use
routing rules to express preferences within the feasible set.
Per-shop cap
A store can have up to 25 activefulfillment_location_rule
extensions across all installed apps. The cap matches the
FUNCTION_ACTIVE_LIMITS[DISCOUNT] entry — it’s the loosest cap in the
function-types table because routing rules are non-blocking and pure
JSON. Install fails with HTTP 400 if the cap would be exceeded.
See also
- Fulfillment Constraints — the WASM-backed counterpart that can block orders.
- Cart Transform — populate
cart.lines[].merchandise.attributesso routing rules have something to match on. - App Manifest — full top-level manifest schema.