Common Expression Language
Common Expression Language, abbreviated CEL, is a small expression language used in ShipStream when you need advanced matching logic without writing a full script. CEL expressions always answer one question: does this record match?
ShipStream currently uses CEL in Product Profiles, Handling Classes, and Webhook Events Filters.
Where CEL Is Used
| Feature | What the expression checks | Result when true | Result when false |
|---|---|---|---|
| Product Profiles | Product fields | The Product receives the Product Profile | The Product does not receive the Product Profile |
| Handling Classes | Product fields | The Product receives the Handling Class if no earlier class matched | ShipStream checks the next Handling Class |
| Webhook Events Filters | Webhook payload fields | The webhook request is sent | The event is skipped and no HTTP request is made |
All CEL fields are referenced by their field code. Field codes are shown in the expression builder or come from the webhook payload field names.
Basic Syntax
Use comparison operators to compare a field with a value:
status == "complete"
Use dot notation for nested webhook payload fields:
shipping_address.country_id == "US"
Use parentheses to group logic:
(source == "shopify" || source == "manual") && status == "complete"
CEL expressions must evaluate to true or false. An expression like qty_ordered + 1 is not valid by itself because it returns a number instead of a yes-or-no result.
Values
| Value type | Example | Notes |
|---|---|---|
| Boolean | true, false | Use lowercase true and false. |
| String | "shopify" | Use double quotes around text. |
| Number | 10, 10.5 | Product measurement fields can be compared with whole numbers or decimals. |
| List | ["picked", "packed"] | Useful with the in operator. |
| Empty field | present(max_per_package) | Product expressions can test whether a Product field has a saved value. |
Operators
| Operator | Meaning | Example |
|---|---|---|
== | Equals | status == "complete" |
!= | Does not equal | source != "manual" |
&& | Logical AND | status == "complete" && source == "shopify" |
|| | Logical OR | status == "picked" || status == "packed" |
! | Logical NOT | !is_global |
>, <, >=, <= | Number comparison | qty_ordered >= 10 |
in | Value is in a list | status in ["picked", "packed"] |
Use && and || for AND and OR. The words and and or are not valid CEL operators.
String Functions
String functions are useful when exact matching is too strict.
| Function | Meaning | Example |
|---|---|---|
startsWith(prefix) | String begins with a prefix | order_ref.startsWith("WEB-") |
endsWith(suffix) | String ends with a suffix | order_ref.endsWith("-INT") |
contains(substring) | String contains a substring | shipping_method.contains("express") |
toLower() | Convert to lowercase | carrier_code.toLower() == "fedex" |
toUpper() | Convert to uppercase | source.toUpper() == "SHOPIFY" |
Call string functions on the field value, such as field.startsWith("X"), not startsWith(field, "X").
Conversion and Size Functions
| Function | Meaning | Example |
|---|---|---|
int(x) | Convert a value to an integer | int(qty_ordered) >= 10 |
string(x) | Convert a value to text | string(warehouse_id) == "3" |
size(x) | Length of a string or list | size(items) > 1 |
Use conversions when a field may arrive as text but you need a numeric comparison.
Empty Product Fields
Product Profile and Handling Class expressions can use present(field) to test whether a Product field has a saved value.
present(max_per_package)
Use !present(field) to match an empty field:
!present(max_per_package)
When comparing a field that might be empty, include present() if the empty case matters:
!present(max_per_package) || max_per_package <= 1.0
This example matches Products where Max Per Package is empty or no greater than 1.
present(), not has(), in ShipStream CEL. Some general CEL examples use has(), but it is not enabled in ShipStream.Regular Expressions
Product Profile and Handling Class expressions support matches() for regular-expression matching:
matches(sku, "^29-")
You can also call it on a field value:
sku.matches("BOX-[0-9]+$")
Patterns are partial matches unless you anchor them with ^ and $. For example, matches(sku, "329") matches 29-329-5002.
Webhook Events Filters do not support matches(). Use startsWith(), endsWith(), or contains() for webhook filters.
Not Supported
ShipStream does not enable CEL macros because they can iterate over large lists and make expressions expensive to evaluate.
These macros are not available:
mapfilterallexistshas
Use direct comparisons, in, string functions, or explicit field checks instead.
Behavior on Errors
Product Profile and Handling Class expressions are validated when you save. Expressions fail validation if they have invalid syntax, reference a field that is not available in that screen, or return something other than true or false.
Webhook Events Filters are also validated when you save, but the available payload fields depend on the event topic. If a saved webhook filter cannot be evaluated for a particular event, ShipStream sends the event as if no filter were set. This prevents a broken filter from silently dropping webhook events.
Examples
| Goal | Expression |
|---|---|
| Only Shopify events | source == "shopify" |
| Only events from a channel family, case-insensitive | source.toLower().startsWith("shopify") |
| Only freight shipments | shipment_type == "freight" |
| Shipments to Canada | shipping_address.country_id == "CA" |
| One of several carriers | carrier_code in ["fedex", "ups", "usps"] |
| High-volume orders from one channel | source == "shopify" && int(qty_ordered) >= 10 |
| Product field has a value | present(max_per_package) |
| Product field is empty | !present(max_per_package) |
| Empty or no more than 1 | !present(max_per_package) || max_per_package <= 1.0 |
| SKU starts with a numeric family | sku.matches("^29-") |