Skip to main content
Layers extends JSONLogic with custom operators designed for e-commerce use cases.

lower

Converts a string value to lowercase. This is useful for case-insensitive comparisons when combined with other operators like ==, startsWith, endsWith, or in. Syntax:
{
  "lower": value
}
Example: Convert an attribute value to lowercase before comparison:
{
  "==": [
    { "lower": { "var": "_attribute:vendor" } },
    "nike"
  ]
}
Edge cases:
  • Returns null if the input is null
  • Returns an empty string if the input is an empty string
  • Returns the original value unchanged if the input is not a string (e.g., a number)
Use cases:
  • Case-insensitive string matching in calculated attribute formulas
  • Normalizing attribute values before comparison
  • Building case-insensitive mapping rules with if/in chains

startsWith

Tests whether a string starts with a given prefix. Both arguments must be strings. Syntax:
{
  "startsWith": [string, prefix]
}
Example: Check if a product’s SKU starts with a specific prefix:
{
  "startsWith": [
    { "var": "_attribute:sku" },
    "PRE-"
  ]
}
Case-insensitive prefix matching using lower:
{
  "startsWith": [
    { "lower": { "var": "_attribute:tags.0" } },
    "sale"
  ]
}
Edge cases:
  • Returns false if either argument is not a string
  • The comparison is case-sensitive by default — use lower for case-insensitive matching
Use cases:
  • Categorizing products by SKU prefix
  • Matching tag patterns for calculated attribute derivation
  • Filtering products by attribute value prefixes

endsWith

Tests whether a string ends with a given suffix. Both arguments must be strings. Syntax:
{
  "endsWith": [string, suffix]
}
Example: Check if a product type ends with a specific suffix:
{
  "endsWith": [
    { "var": "_attribute:product_type" },
    "-bundle"
  ]
}
Case-insensitive suffix matching using lower:
{
  "endsWith": [
    { "lower": { "var": "_attribute:title" } },
    "gift set"
  ]
}
Edge cases:
  • Returns false if either argument is not a string
  • The comparison is case-sensitive by default — use lower for case-insensitive matching
Use cases:
  • Identifying bundled or grouped products by naming convention
  • Matching attribute values that follow a suffix pattern
  • Building calculated attributes based on string endings

count

Returns the count of elements in an array. Syntax:
{
  "count": array
}
Example:
{
  "count": { "var": "_raw:raw.variants" }
}
Edge cases:
  • Returns null if the input is null or empty string
  • Returns the length of the array if valid
Use cases:
  • Counting the number of variants in a product
  • Calculating the number of images
  • Determining the number of options available

parseDate

Parses a date string or timestamp and returns a Unix timestamp (seconds since epoch). Syntax:
{
  "parseDate": dateValue
}
Input types:
  • String: ISO 8601 date strings, dot-separated dates (e.g., “7.26.2024”), or any format parsable by PHP’s Carbon library
  • Numeric: Unix timestamps in seconds or milliseconds (automatically normalized)
Returns: Unix timestamp in seconds, or null if the input is invalid or unparseable Examples: Parse an ISO date string:
{
  "parseDate": "2024-01-15T10:30:00Z"
}
Parse a dot-separated date (M.D.Y format):
{
  "parseDate": "7.26.2024"
}
Parse a dot-separated date (D.M.Y format):
{
  "parseDate": "26.7.2024"
}
Parse from a product attribute:
{
  "parseDate": { "var": "_attribute:published_at" }
}
Parse with fallback:
{
  "parseDate": {
    "or": [
      { "var": ["_attribute:published_at", null] },
      { "var": ["_attribute:created_at", null] }
    ]
  }
}
Dot-separated date format support: The parseDate operator supports dot-separated date formats (e.g., “7.26.2024”) with automatic disambiguation:
  • Unambiguous D.M.Y: If the first number is greater than 12, it’s interpreted as day (e.g., “26.7.2024” → July 26, 2024)
  • Unambiguous M.D.Y: If the second number is greater than 12, it’s interpreted as day (e.g., “7.26.2024” → July 26, 2024)
  • Ambiguous dates: When both numbers are ≤ 12 (e.g., “7.6.2024”), the format defaults to M.D.Y (US convention) → July 6, 2024
Edge cases:
  • Returns null if input is null or empty string
  • Returns null for unparseable date strings (e.g., “not-a-date”) instead of throwing an exception
  • Automatically converts millisecond timestamps (> 10 digits) to seconds
  • Handles both numeric timestamps and string dates
  • Supports zero-padded dot-separated formats (e.g., “07.26.2024”)

daysSince

Calculates the number of days between a given date and the current date. Syntax:
{
  "daysSince": dateValue
}
Input types:
  • String: Date strings (ISO 8601, dot-separated formats like “7.26.2024”, or any format parsable by PHP’s Carbon)
  • Numeric: Unix timestamps in seconds or milliseconds
  • JSONLogic expression: Output from parseDate or other date operations
Returns: Integer number of days (always positive), or null if the input is invalid or unparseable Examples: Calculate days since publication:
{
  "daysSince": { "var": "_attribute:published_at" }
}
Calculate days since a dot-separated date:
{
  "daysSince": "7.26.2024"
}
Calculate days since creation with parseDate:
{
  "daysSince": {
    "parseDate": { "var": "_attribute:created_at" }
  }
}
Calculate days since the newest variant was created:
{
  "daysSince": {
    "reduce": [
      {
        "map": [
          { "var": "_raw:raw.variants" },
          { "parseDate": { "var": "created_at" } }
        ]
      },
      {
        "if": [
          {
            "or": [
              { "==": [{ "var": "accumulator" }, null] },
              { ">": [{ "var": "current" }, { "var": "accumulator" }] }
            ]
          },
          { "var": "current" },
          { "var": "accumulator" }
        ]
      },
      null
    ]
  }
}
Dot-separated date format support: Like parseDate, the daysSince operator supports dot-separated date formats with automatic disambiguation:
  • Unambiguous D.M.Y: “26.7.2024” → July 26, 2024
  • Unambiguous M.D.Y: “7.26.2024” → July 26, 2024
  • Ambiguous dates: “7.6.2024” defaults to M.D.Y (US convention) → July 6, 2024
Edge cases:
  • Returns null if input is null or empty string
  • Returns null for unparseable date strings (e.g., “not-a-date”) instead of throwing an exception
  • Automatically converts millisecond timestamps to seconds
  • Returns absolute value (always positive number of days)
Use cases:
  • Product age calculations
  • Time-based merchandising rules
  • Freshness indicators for new products
  • Age-based product filtering

now

Returns the current Unix timestamp in seconds. Syntax:
{
  "now": []
}
Returns: Current Unix timestamp (seconds since epoch) Example: Calculate if a product is new (published within last 30 days):
{
  "<": [
    {
      "-": [
        { "now": [] },
        { "parseDate": { "var": "_attribute:published_at" } }
      ]
    },
    2592000
  ]
}
Use cases:
  • Real-time date comparisons
  • Calculating time differences
  • Dynamic time-based rules

toTimestamp

Converts a flexible date value (ISO 8601 string, dot-separated date, or numeric timestamp) to a Unix timestamp in seconds. Behaves like parseDate but uses the extended flexible-date parser shared by the other date operators. Syntax:
{
  "toTimestamp": dateValue
}
Returns: Unix timestamp in seconds, or null if the value cannot be parsed. Example: Normalize a launch date attribute to a timestamp:
{
  "toTimestamp": { "var": "_attribute:launch_date" }
}
Edge cases:
  • Returns null for null, empty strings, or unparseable values.
  • Millisecond timestamps are automatically normalized to seconds.
Use cases:
  • Storing a consistent numeric representation of a date for downstream arithmetic.
  • Comparing dates that may arrive in multiple formats.

dateDiff

Calculates the signed difference between two dates using a configurable unit. Syntax:
{
  "dateDiff": [fromDate, toDate, unit?]
}
Arguments:
  • fromDate — Any date value accepted by parseDate (ISO string, dot-separated, numeric timestamp, or a JSONLogic expression).
  • toDate — Any date value accepted by parseDate.
  • unit — Optional. One of seconds, minutes, hours, days (default), weeks, months, or years. Unit names are case-insensitive and accept singular or plural forms.
Returns: Integer difference (toDate - fromDate), or null if either date is invalid or the unit is not a string. The result is signed — negative when toDate is earlier than fromDate. Example: Days between publication and the last update:
{
  "dateDiff": [
    { "var": "_attribute:published_at" },
    { "var": "_attribute:updated_at" },
    "days"
  ]
}
Hours since a product was first seen:
{
  "dateDiff": [
    { "var": "_attribute:first_seen_at" },
    { "now": [] },
    "hours"
  ]
}
Edge cases:
  • Returns null when either date is unparseable.
  • Unknown unit strings fall back to days.
Use cases:
  • Measuring product age in a specific unit without manual second-to-unit conversion.
  • Building freshness or staleness thresholds.
  • Calculating promotion windows in hours or weeks.

dateAdd

Adds an amount of time to a date and returns the resulting Unix timestamp in seconds. Syntax:
{
  "dateAdd": [date, amount, unit?]
}
Arguments:
  • date — Any date value accepted by parseDate.
  • amount — Integer amount to add. Use a negative value to subtract.
  • unit — Optional. One of seconds, minutes, hours, days (default), weeks, months, or years.
Returns: Unix timestamp in seconds after adding amount units, or null if the date is invalid or arguments are malformed. Example: Timestamp 14 days after publication:
{
  "dateAdd": [
    { "var": "_attribute:published_at" },
    14,
    "days"
  ]
}
Compute an expiration timestamp 6 months in the future:
{
  "dateAdd": [
    { "now": [] },
    6,
    "months"
  ]
}
Use cases:
  • Computing expiration or embargo timestamps for rule gating.
  • Producing future dates relative to a catalog attribute.

upper

Converts a string to uppercase. Mirrors lower but uppercases the result. Syntax:
{
  "upper": value
}
Example:
{
  "upper": { "var": "_attribute:vendor" }
}
Edge cases:
  • Returns null if the input is null.
  • Returns an empty string if the input is an empty string.
  • Non-string values are returned unchanged.
Use cases:
  • Normalizing attribute values to a uniform case.
  • Producing display-ready labels.

trim

Removes leading and trailing whitespace from a string. Syntax:
{
  "trim": value
}
Example:
{
  "trim": { "var": "_attribute:title" }
}
Edge cases:
  • Returns null or the empty string unchanged.
  • Non-string values are returned unchanged.
Use cases:
  • Cleaning up values imported from spreadsheets or external sources before comparison.

normalizeWhitespace

Trims a string and collapses any run of internal whitespace into a single space. Syntax:
{
  "normalizeWhitespace": value
}
Example: Normalize an attribute that may contain tabs or double spaces:
{
  "normalizeWhitespace": { "var": "_attribute:description" }
}
Returns "Premium cotton shirt" when given " Premium\tcotton shirt ". Edge cases:
  • Returns null or empty string unchanged.
  • Non-string values are returned unchanged.
Use cases:
  • Normalizing text used as a lookup key.
  • Producing stable values for hashing or grouping.

replace

Replaces all occurrences of a substring with another substring. All three arguments must be strings. Syntax:
{
  "replace": [value, search, replace]
}
Example: Strip a SKU prefix:
{
  "replace": [
    { "var": "_attribute:sku" },
    "PRE-",
    ""
  ]
}
Edge cases:
  • Returns null if any argument is not a string.
Use cases:
  • Removing or swapping known tokens inside attribute values.
  • Producing canonical forms of identifiers.

replaceRegex

Replaces matches of a regular expression pattern with a replacement string. All three arguments must be strings. Syntax:
{
  "replaceRegex": [value, pattern, replacement]
}
Arguments:
  • value — Source string.
  • pattern — A regular expression. You can provide either a bare pattern (for example, \d+) or one already wrapped in delimiters. The operator chooses a safe delimiter automatically when the pattern is bare.
  • replacement — Replacement string. Backreferences such as $1 are supported.
Example: Strip trailing numbers from a title:
{
  "replaceRegex": [
    { "var": "_attribute:title" },
    "\\s+\\d+$",
    ""
  ]
}
Mask digits in a description:
{
  "replaceRegex": [
    { "var": "_attribute:description" },
    "\\d",
    "#"
  ]
}
Edge cases:
  • Returns null if any argument is not a string.
  • Invalid regular expressions return null.
Use cases:
  • Cleaning noisy text attributes.
  • Extracting or reformatting structured data embedded in strings.

contains

Tests whether a string contains a substring, or whether a value appears in an array. Array matching is case-insensitive; string matching is case-sensitive. Syntax:
{
  "contains": [haystack, needle]
}
Example: Check if a description mentions a material:
{
  "contains": [
    { "var": "_attribute:description" },
    "cotton"
  ]
}
Check if a tag is present (case-insensitive):
{
  "contains": [
    { "var": "_attribute:tags" },
    "sale"
  ]
}
Edge cases:
  • Returns false when the haystack is neither a string nor an array.
  • Returns false when searching for a non-string needle inside a string haystack.
Use cases:
  • Tag checks without having to remember the argument order of in.
  • Substring searches in product descriptions or titles.

includes

Alias-style operator that behaves like contains: checks if an array includes a value (case-insensitive) or if a string contains a substring (case-sensitive). Syntax:
{
  "includes": [haystack, needle]
}
Example: Check if the tags array includes "new-arrival":
{
  "includes": [
    { "var": "_attribute:tags" },
    "new-arrival"
  ]
}
Edge cases:
  • Returns false when the haystack is neither a string nor an array.
Use cases:
  • Reading more naturally in formulas authored by users familiar with JavaScript’s Array.includes.

intersects

Returns true if two arrays share at least one value. Uses case-insensitive comparison for string values. Syntax:
{
  "intersects": [arrayA, arrayB]
}
Example: Check if a product’s tags overlap with a list of promoted tags:
{
  "intersects": [
    { "var": "_attribute:tags" },
    ["sale", "clearance", "promo"]
  ]
}
Edge cases:
  • Returns false if either argument is not an array.
Use cases:
  • Matching a product against any of several tag allowlists.
  • Detecting shared category memberships between two sets.

get

Reads a value from a nested array or object by dot-notation path, returning a default when the path does not exist. Syntax:
{
  "get": [target, path, default?]
}
Arguments:
  • target — The array or object to read from.
  • path — A dot-notation string such as "metafields.custom.material".
  • default — Optional value returned when the path is missing. Defaults to null.
Example: Read a deeply nested metafield with a fallback:
{
  "get": [
    { "var": "_raw:raw" },
    "metafields.custom.material",
    "unspecified"
  ]
}
Edge cases:
  • Returns the default when target is not an array or object or when path is not a string.
Use cases:
  • Safely reading nested raw Shopify data without needing chained var calls.
  • Supplying fallbacks inline.

exists

Tests whether a nested path resolves to any value (including null) inside an array or object. Syntax:
{
  "exists": [target, path]
}
Example:
{
  "exists": [
    { "var": "_raw:raw" },
    "metafields.custom.material"
  ]
}
Edge cases:
  • Returns false if target is not an array or object, or if path is not a string.
  • Returns true even when the value at the path is explicitly null, as long as the path itself exists.
Use cases:
  • Feature-flag-style gates based on the presence of a metafield.
  • Differentiating between “missing” and “explicitly set to null” data.

coalesce

Returns the first non-null argument. Accepts one or more arguments. Syntax:
{
  "coalesce": [valueA, valueB, ...]
}
Example: Use the product subtitle if present, otherwise fall back to the title:
{
  "coalesce": [
    { "var": "_attribute:subtitle" },
    { "var": "_attribute:title" }
  ]
}
Edge cases:
  • Returns null only if every argument is null.
  • Empty strings and 0 are considered valid values and short-circuit the chain.
Use cases:
  • Supplying cascading fallbacks across multiple attributes.
  • Simplifying long or/var fallback chains.

pick

Returns a new object containing only the selected keys from a source object. Missing keys are omitted from the result. Syntax:
{
  "pick": [target, keys]
}
Arguments:
  • target — An array or object to read from.
  • keys — Either a single dot-notation key string or an array of key strings.
Example: Extract a subset of pricing fields:
{
  "pick": [
    { "var": "_raw:raw" },
    ["price", "compare_at_price", "currency"]
  ]
}
Edge cases:
  • Returns null if the target is not an array or object.
  • Keys that do not exist on the target are silently skipped.
Use cases:
  • Producing a compact summary object for use in downstream calculated attributes.
  • Reducing the payload size when composing objects in calculated attributes.

split

Splits a string into an array using a delimiter. Passing an empty delimiter produces an array of individual characters (multibyte-safe). Syntax:
{
  "split": [value, delimiter]
}
Example: Split a comma-separated list into an array:
{
  "split": [
    { "var": "_attribute:tags_csv" },
    ","
  ]
}
Edge cases:
  • Returns null if either argument is not a string.
Use cases:
  • Normalizing CSV-style attribute values into arrays before applying map, filter, or in.
  • Extracting prefixes from titles using a known separator together with first.

first

Returns the first element of an array, or null when the value is not an array or is empty. Syntax:
{
  "first": array
}
Example: Grab the part of a title before " in ":
{
  "if": [
    {
      "in": [" in ", { "var": "_attribute:title" }]
    },
    {
      "first": [
        {
          "split": [{ "var": "_attribute:title" }, " in "]
        }
      ]
    },
    { "var": "_attribute:title" }
  ]
}
Use cases:
  • Taking the leading segment from a split string.
  • Picking the primary item from an ordered list of variants or images.

last

Returns the last element of an array, or null when the value is not an array or is empty. Syntax:
{
  "last": array
}
Example:
{
  "last": { "var": "_raw:raw.variants" }
}
Use cases:
  • Accessing the most recently appended entry in a list.

at

Returns the element at a specific index in an array. Supports negative indices, which count from the end. Syntax:
{
  "at": [array, index]
}
Example: Read the second image URL from a raw Shopify product:
{
  "at": [
    {
      "map": [
        { "var": "_raw:raw.images" },
        { "var": "src" }
      ]
    },
    1
  ]
}
Read the last variant price using a negative index:
{
  "at": [
    {
      "map": [
        { "var": "_raw:raw.variants" },
        { "var": "price" }
      ]
    },
    -1
  ]
}
Edge cases:
  • Returns null for out-of-range indices, non-array inputs, or non-numeric indices.
Use cases:
  • Selecting a specific position in ordered raw data.
  • Reading the last or second-to-last item without first computing the length.

join

Joins an array of values into a single string using the provided delimiter (empty string by default). Syntax:
{
  "join": [array, delimiter?]
}
Example: Build a comma-separated tag label:
{
  "join": [
    { "var": "_attribute:tags" },
    ", "
  ]
}
Edge cases:
  • Returns null if the first argument is not an array or the delimiter is not a string.
Use cases:
  • Producing display-ready strings from arrays.
  • Composing keys for grouping or deduplication.

unique

Returns a new array with duplicate values removed, preserving original order. Syntax:
{
  "unique": array
}
Example: Deduplicate a list of variant options:
{
  "unique": {
    "map": [
      { "var": "_raw:raw.variants" },
      { "var": "option1" }
    ]
  }
}
Edge cases:
  • Returns null when the input is not an array.
  • Uses strict comparison rules, so "1" and 1 are treated as distinct.
Use cases:
  • Cleaning up derived arrays before using count, join, or intersects.
  • Removing duplicate tag values.

length

Returns the length of a string (in bytes) or an array. Returns null for other types. Syntax:
{
  "length": value
}
Example:
{
  "length": { "var": "_attribute:title" }
}
Edge cases:
  • Returns null for null, numbers, booleans, and objects.
  • String length is measured in bytes, so multibyte characters may contribute more than one.
Use cases:
  • Guarding against very short or very long attribute values in calculated attributes.
  • Counting items in derived arrays without needing count.

substring

Extracts a substring given a start position and optional length. Accepts negative indices (counted from the end) the same way as substr. Syntax:
{
  "substring": [value, start, length?]
}
Example: First three characters of an SKU:
{
  "substring": [
    { "var": "_attribute:sku" },
    0,
    3
  ]
}
Last five characters of a title:
{
  "substring": [
    { "var": "_attribute:title" },
    -5
  ]
}
Edge cases:
  • Returns null if the value is not a string, or the start or length arguments are not numeric.
Use cases:
  • Extracting a prefix such as a category code from a SKU.
  • Producing short preview snippets for display.

clamp

Restricts a numeric value to a [minimum, maximum] range. Syntax:
{
  "clamp": [value, minimum, maximum]
}
Example: Clamp a boost score to the range [0, 100]:
{
  "clamp": [
    { "var": "_attribute:boost_score" },
    0,
    100
  ]
}
Edge cases:
  • Returns null if any of the three arguments are not numeric.
Use cases:
  • Normalizing relevancy or boost scores to a fixed scale.
  • Enforcing guardrails on computed values.

round

Rounds a numeric value to the specified precision. Precision defaults to 0. Syntax:
{
  "round": [value, precision?]
}
Example: Round a price to two decimals:
{
  "round": [
    { "var": "_attribute:price" },
    2
  ]
}
Round to the nearest integer:
{
  "round": { "var": "_attribute:price" }
}
Edge cases:
  • Returns null if either argument is not numeric.
Use cases:
  • Producing display-friendly prices or ratings.
  • Normalizing floating-point values before bucketing.

floor

Rounds a numeric value down to the nearest whole number. Syntax:
{
  "floor": value
}
Example:
{
  "floor": { "var": "_attribute:rating" }
}
Edge cases:
  • Returns null if the argument is not numeric.
Use cases:
  • Creating discrete buckets from continuous numeric attributes.
  • Paging-style calculations.

ceil

Rounds a numeric value up to the nearest whole number. Syntax:
{
  "ceil": value
}
Example:
{
  "ceil": { "var": "_attribute:rating" }
}
Edge cases:
  • Returns null if the argument is not numeric.
Use cases:
  • Computing minimum counts (for example, required pages) from ratios.
  • Rounding up scoring outputs.

abs

Returns the absolute value of a numeric input. Syntax:
{
  "abs": value
}
Example: Compute the absolute difference between current and original prices:
{
  "abs": {
    "-": [
      { "var": "_attribute:price" },
      { "var": "_attribute:original_price" }
    ]
  }
}
Edge cases:
  • Returns null if the argument is not numeric.
Use cases:
  • Measuring magnitude of differences regardless of direction.

log1p

Returns ln(1 + value), the natural logarithm of 1 + value. This is safer than log(value) when value can be zero. Syntax:
{
  "log1p": value
}
Example: Dampen a large count for use in a ranking signal:
{
  "log1p": { "var": "_attribute:order_count" }
}
Edge cases:
  • Returns null if the argument is not numeric.
Use cases:
  • Producing diminishing-returns-style signals in relevancy formulas.
  • Stabilizing long-tail numeric distributions before boosting.

pow

Raises a base to an exponent. Syntax:
{
  "pow": [base, exponent]
}
Example: Square a normalized score:
{
  "pow": [
    { "var": "_attribute:normalized_score" },
    2
  ]
}
Take a square root using a fractional exponent:
{
  "pow": [
    { "var": "_attribute:price" },
    0.5
  ]
}
Edge cases:
  • Returns null if either argument is not numeric.
Use cases:
  • Custom scoring curves in calculated attributes.
  • Compounding or damping signal values.