Layers uses JSONLogic for creating calculated attributes with dynamic values. A JSONLogic rule is structured as follows: { "operator" : ["values" ... ] }. For example, { "cat" : ["I love", " ", "pie"] } results in “I love pie”.

Overview

JSONLogic provides a powerful way to transform and validate product data within calculated attributes. The platform supports both standard JSONLogic operators and custom extensions designed specifically for e-commerce use cases.

Data Access Patterns

When writing JSONLogic formulas in Layers, you have access to two types of data:

Catalog Attributes

Reference existing catalog attributes using the _attribute: prefix followed by the attribute code:
{
  "var": "_attribute:price_range.from"
}
Use dot notation to access nested properties:
{
  "var": "_attribute:metafields.custom.material"
}

Raw Shopify Data

Access raw underlying Shopify data before transformation using the _raw:raw. prefix:
{
  "var": "_raw:raw.variants"
}
Common raw data paths include:
  • _raw:raw.variants - Array of product variants
  • _raw:raw.featured_media - Featured image data
  • _raw:raw.images - Array of product images
  • _raw:raw.options - Product options
Use dot notation to access nested properties in raw data:
{
  "var": "_raw:raw.variants.0.created_at"
}

Standard JSONLogic Operators

Layers supports all standard JSONLogic operators. For complete documentation of standard operators, see jsonlogic.com/operations.html.

Accessing Data

var

Retrieves a value from the data object. This is the most fundamental operator for accessing product attributes.
{
  "var": "_attribute:title"
}
With default value if the field doesn’t exist:
{
  "var": ["_attribute:published_at", null]
}
Access the current context (useful in map operations):
{
  "var": ""
}

Logic and Boolean Operations

if

Conditional logic with if-then-else structure.
{
  "if": [
    { ">" : [{ "var": "_attribute:price" }, 100] },
    "expensive",
    "affordable"
  ]
}
Multiple conditions (if-elseif-else):
{
  "if": [
    { "<" : [{ "var": "_attribute:price" }, 50] }, "cheap",
    { "<" : [{ "var": "_attribute:price" }, 100] }, "moderate",
    "expensive"
  ]
}

and

Logical AND operation. Returns true if all conditions are true.
{
  "and": [
    { ">" : [{ "var": "_attribute:price" }, 50] },
    { "<" : [{ "var": "_attribute:price" }, 200] }
  ]
}

or

Logical OR operation. Returns true if any condition is true.
{
  "or": [
    { "var": ["_attribute:published_at", null] },
    { "var": ["_attribute:created_at", null] }
  ]
}

! (not)

Logical NOT operation. Negates a boolean value.
{
  "!": { "var": "_attribute:is_available" }
}

!! (double not)

Converts a value to boolean. Returns true if the value exists and is truthy.
{
  "!!": { "var": "_raw:raw.featured_media" }
}

Comparison Operations

== (equals)

Loose equality comparison.
{
  "==": [
    { "var": "_attribute:status" },
    "active"
  ]
}

=== (strict equals)

Strict equality comparison (type and value must match).
{
  "===": [
    { "var": "_attribute:variant_count" },
    1
  ]
}

!= (not equals)

Loose inequality comparison.
{
  "!=": [
    { "var": "_attribute:vendor" },
    ""
  ]
}

!== (strict not equals)

Strict inequality comparison.
{
  "!==": [
    { "var": "_attribute:price_range.from" },
    { "var": "_attribute:price_range.to" }
  ]
}

> (greater than)

{
  ">": [
    { "var": "_attribute:price" },
    100
  ]
}

>= (greater than or equal)

{
  ">=": [
    { "var": "_attribute:inventory_quantity" },
    10
  ]
}

< (less than)

{
  "<": [
    { "var": "_attribute:price" },
    50
  ]
}
Between comparison (compound):
{
  "<": [50, { "var": "_attribute:price" }, 100]
}

<= (less than or equal)

{
  "<=": [
    { "var": "_attribute:discount_percentage" },
    25
  ]
}

Arithmetic Operations

+ (addition)

{
  "+": [
    { "var": "_attribute:base_price" },
    { "var": "_attribute:tax_amount" }
  ]
}

- (subtraction)

{
  "-": [
    { "var": "_attribute:price" },
    { "var": "_attribute:discount" }
  ]
}

* (multiplication)

{
  "*": [
    { "var": "_attribute:price" },
    1.1
  ]
}

/ (division)

{
  "/": [
    { "var": "_attribute:total_sales" },
    { "var": "_attribute:order_count" }
  ]
}

% (modulo)

{
  "%": [
    { "var": "_attribute:inventory_quantity" },
    2
  ]
}

Array Operations

map

Transforms each element in an array.
{
  "map": [
    { "var": "_raw:raw.variants" },
    { "var": "price" }
  ]
}
Map with complex transformation:
{
  "map": [
    { "var": "_raw:raw.variants" },
    {
      "if": [
        { "var": "available" },
        1,
        0
      ]
    }
  ]
}

filter

Filters an array based on a condition.
{
  "filter": [
    { "var": "_raw:raw.variants" },
    { "var": "available" }
  ]
}
Filter with complex condition:
{
  "filter": [
    { "var": "_raw:raw.variants" },
    {
      ">": [
        { "var": "price" },
        50
      ]
    }
  ]
}

reduce

Reduces an array to a single value using an accumulator.
{
  "reduce": [
    { "var": "_raw:raw.variants" },
    {
      "+": [
        { "var": "accumulator" },
        { "var": "current" }
      ]
    },
    0
  ]
}
The reduce operation has access to:
  • accumulator - The accumulated value
  • current - The current array element
  • The third argument is the initial value for the accumulator

all

Returns true if all elements in an array match the condition.
{
  "all": [
    { "var": "_raw:raw.variants" },
    { "var": "available" }
  ]
}

some

Returns true if any element in an array matches the condition.
{
  "some": [
    { "var": "_raw:raw.variants" },
    {
      ">": [
        { "var": "price" },
        100
      ]
    }
  ]
}

none

Returns true if no elements in an array match the condition.
{
  "none": [
    { "var": "_raw:raw.variants" },
    {
      "<": [
        { "var": "inventory_quantity" },
        0
      ]
    }
  ]
}

in

Checks if a value is in an array.
{
  "in": [
    "Blue",
    { "var": "_attribute:color_options" }
  ]
}
Check if a substring is in a string:
{
  "in": [
    "cotton",
    { "var": "_attribute:description" }
  ]
}

merge

Merges multiple arrays into a single array.
{
  "merge": [
    [1, 2, 3],
    [4, 5, 6]
  ]
}

String Operations

cat

Concatenates strings.
{
  "cat": [
    { "var": "_attribute:vendor" },
    " - ",
    { "var": "_attribute:product_type" }
  ]
}

substr

Extracts a substring. Takes the string, start position, and optional length.
{
  "substr": [
    { "var": "_attribute:sku" },
    0,
    3
  ]
}
Negative indices count from the end:
{
  "substr": [
    { "var": "_attribute:title" },
    -5
  ]
}

Miscellaneous Operations

missing

Returns an array of keys that are missing or have falsy values.
{
  "missing": [
    "_attribute:title",
    "_attribute:price",
    "_attribute:vendor"
  ]
}

missing_some

Returns empty array if at least the specified number of keys exist.
{
  "missing_some": [
    2,
    [
      "_attribute:published_at",
      "_attribute:created_at",
      "_attribute:updated_at"
    ]
  ]
}

Custom Layers Operators

Layers extends JSONLogic with custom operators designed for e-commerce use cases.

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, 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 Examples: Parse an ISO date string:
{
  "parseDate": "2024-01-15T10:30:00Z"
}
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] }
    ]
  }
}
Edge Cases:
  • Returns null if input is null or empty string
  • Automatically converts millisecond timestamps (> 10 digits) to seconds
  • Handles both numeric timestamps and string dates

daysSince

Calculates the number of days between a given date and the current date. Syntax:
{
  "daysSince": dateValue
}
Input Types:
  • String: Date strings (ISO 8601 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 Examples: Calculate days since publication:
{
  "daysSince": { "var": "_attribute:published_at" }
}
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
    ]
  }
}
Edge Cases:
  • Returns null if input is null or empty string
  • 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

Complex Examples

Example 1: SKU Coverage Ratio

Calculate the ratio of available variants to total variants:
{
  "/": [
    {
      "reduce": [
        {
          "map": [
            { "var": "_raw:raw.variants" },
            { "if": [ { "var": "available" }, 1, 0 ] }
          ]
        },
        { "+": [ { "var": "accumulator" }, { "var": "current" } ] },
        0
      ]
    },
    { "count": { "var": "_raw:raw.variants" } }
  ]
}
Explanation:
  1. Maps over variants, converting availability to 1 or 0
  2. Reduces the array by summing all values
  3. Divides by the total count of variants
  4. Result is a ratio between 0 and 1

Example 2: Product Freshness Score

Calculate a freshness score based on when the product was published:
{
  "if": [
    {
      "<": [
        {
          "daysSince": {
            "parseDate": { "var": "_attribute:published_at" }
          }
        },
        7
      ]
    },
    "new",
    {
      "if": [
        {
          "<": [
            {
              "daysSince": {
                "parseDate": { "var": "_attribute:published_at" }
              }
            },
            30
          ]
        },
        "recent",
        "established"
      ]
    }
  ]
}

Example 3: Has Discounted Variants

Check if any variant has a compare_at_price greater than its price:
{
  "some": [
    { "var": "_raw:raw.variants" },
    {
      "and": [
        { "!!": { "var": "compare_at_price" } },
        {
          ">": [
            { "var": "compare_at_price" },
            { "var": "price" }
          ]
        }
      ]
    }
  ]
}

Example 4: Variant Count by Availability

Count available vs unavailable variants:
{
  "reduce": [
    {
      "map": [
        { "var": "_raw:raw.variants" },
        {
          "if": [
            { "var": "available" },
            { "eachKey": { "available": 1, "unavailable": 0 } },
            { "eachKey": { "available": 0, "unavailable": 1 } }
          ]
        }
      ]
    },
    {
      "eachKey": {
        "available": {
          "+": [
            { "var": ["accumulator.available", 0] },
            { "var": "current.available" }
          ]
        },
        "unavailable": {
          "+": [
            { "var": ["accumulator.unavailable", 0] },
            { "var": "current.unavailable" }
          ]
        }
      }
    },
    { "eachKey": { "available": 0, "unavailable": 0 } }
  ]
}

Best Practices

Use Descriptive Variable Access

Always use the appropriate prefix for clarity:
// Good
{ "var": "_attribute:price" }
{ "var": "_raw:raw.variants" }

// Avoid ambiguity
{ "var": "price" }

Handle Null Values

Always provide fallback values when data might be missing:
{
  "or": [
    { "var": ["_attribute:published_at", null] },
    { "var": ["_attribute:created_at", null] }
  ]
}

Combine Operations for Complex Logic

Build complex calculations by combining multiple operators:
{
  "daysSince": {
    "parseDate": {
      "or": [
        { "var": ["_attribute:published_at", null] },
        { "var": ["_attribute:created_at", null] }
      ]
    }
  }
}

Test with Real Data

Use the calculated attribute testing interface in the Layers dashboard to validate your JSONLogic formulas with actual product data before deploying them to production.

Keep Formulas Maintainable

For very complex calculations, consider breaking them into multiple calculated attributes that reference each other rather than creating one massive formula.

See Also