Custom Expressions

Overview

Custom Expressions in the MetaRouter platform are powered by Lua. Lua is a general purpose scripting language, designed to be securely embedded in high performance environments. Lua is user-friendly and highly customizable, allowing MetaRouter to introduce new and useful features, primarily in the form of helper functions, for processing your event data efficiently and effectively.

In addition to this guide, Lua offers official documentation, which can be found here.

Limitations

  • We do not support Coroutines, the os library or the io library.
  • You will not have access to the filesystem, network or process environment in which Lua runs.
  • You Lua script must finish running within 10 seconds.

Basic Syntax

Identifiers

Identifiers in Lua can be any string of letters, digits and underscores, not beginning with a digit. An identifier is the name used to identify components of your program: specifically variables and functions.

Single line comments:

-- Start with two dashes (useful for commenting out a single line of code)

Multiline comments:

--[[ 
   Are created with double brackets.
   (useful for commenting out longer runs of code)
--]]

Reserved words

The following words are reserved; meaning we cannot use them as identifiers:

and       break     do        else      elseif
end       false     for       function  if
in        local     nil       not       or
repeat    return    then      true      until
while

Lua is case-sensitive. and is a reserved word, but And and AND are different identifiers.

JSON Values in Lua

JSON and Lua have similar type systems for representing values.

JSON has six data types: boolean, string, number, array, object and null.

Lua provides direct equivalents for each of these types:

TypeJSONLuaNotes
booleanbooleanbooleanUsed by both type systems to represent the logical values true and false.
numbernumbernumberUsed by both type systems to represent real numbers. Neither type system has an integer type.
stringstringstringUsed by both type systems to represent text. Both systems support Unicode characters.
objectobjecttableJSON objects are represented as tables in Lua. Tables are generalized collections capable of representing both dictionaries (aka associative arrays) and lists.
arrayarraytableJSON arrays are represented as tables in Lua as well.
nullnullnullUsed to represent "no value" in both systems

boolean

The boolean type has two values: true and false

bool1 = true  -- declares a global variable named `bool1`, set to true
bool2 = false -- declares a global variable named `bool2`, set to false

number

The number type represents real numbers. Lua, like JSON, does not have an integer type. Both systems use double-precision floating point number systems to represent all numerical values.

-- declare and initialize a global variable named `number1`,
-- set to the real number value 4
number1 = 4

-- declare and initialize a global variable named `number2`,
-- set to the real number value 4.5
number2 = 4.5

string

A string is a sequence of unicode characters, typically used to represent text. Lua and JSON are both capable of representing Unicode characters.

-- declare and initialize a global variable named `string1`
-- to the value enclosed by single quotes:
string1 = 'a single quoted string'

-- declare and initialize a global variable named `string2`
-- to the value enclosed by double quotes:
string2 = "a double quoted string" 

-- declare and initialize a global variable named `string3`
-- to the value enclosed by double brackets:
string3 = [[
  Double brackets start and
  end multiline strings.
]]

-- Lua provides automatic conversions between numbers and strings.
-- declare and initalize a global variable named `number3`
-- to the result of adding a string and a number:
number3 = "10" + 1 -- `number3` will be 11

-- Lua will raise an error when there's not an obvious numeric conversion:
number4 = "hello" + 1 -- will fail `ERROR (cannot convert "hello")` 

tables

The table type is used to represent both arrays and objects in JSON. More generally, tables are associative arrays, where values can be indexed by numbers, like a traditional array or strings, like an object in JSON.

Used to represent an array:

-- declare and initialize a global variable named `t1`
-- referencing a table that contains two strings:
t1 = {"hello", "world"}

-- The JSON equivalent of this table is
-- ["hello", "world"]

Used to represent an object:

-- declare and initialize a global variable named `t2`
-- referencing a table that contains two key, value pairs:
t2 = {}
t2["one"] = 1
t2["two"] = 2

-- The JSON equivalent of table t2 is:
-- {"one": 1, "two": 2}

-- declare and initialize a global variable named `t3`
-- referencing a table that contains two key, value pairs:
t3 = {
  ["one"] = 1,
  ["two"] = 2
}

-- Table t3 is equivalent to t2, using an alternative syntax for
-- declaring and initializing a table.

functions

Functions are first-class values in Lua. They can be stored in variables and passed into other functions as arguments and returned as results, just like other values (nil, boolean, number, string and table).

Conditionals

if then else

An if statement tests a condition and executes its then-part or else-part accordingly. The else-part is is optional.

-- conditional logic that determines an event happened on your home page.
if input.context.page.path == '/' then
  return 'Y'
else
  return 'N'
end

Predefined Variables, Functions and Libraries

global variables

input

null

global functions

Please reach out to the MetaRouter team for assistance with global functions, which we make available to you for ease-of-use. The Global functions that MetaRouter offers include:

  • FLATTEN
  • JOIN
  • LOWER
  • MAP
  • MILLISECONDS_SINCE_EPOCH_FROM_RFC3999
  • MILLISECONDS_SINCE_EPOCH
  • PLUCK
  • REPLACE
  • SECONDS_SINCE_EPOCH_FROM_RFC3999
  • SECONDS_SINCE_EPOCH
  • SPLIT
  • SUBSTRING
  • SUM
  • TO_BOOL
  • TO_FLOAT
  • TO_MD5_HASH
  • TO_SHA256_HASH
  • TO_STRING
  • TO_ROUNDED_INT
  • TO_TRUNCATED_FLOAT
  • TO_TRUNCATED_INT
  • TRIM
  • UPPER
  • LEFT
  • RIGHT

math library

math library functions

string library

The string library provides common utility functions for working with text. String indexes start with 1 in Lau, unlike JavaScript and C. Negative index numbers are interpreted as working your way backwards from the end of a string.

local s = "my string"
local f = s[1]  -- f refers to the string value "m"
local e = s[-1] -- e refers to the string value "g"

string library functions

table library

table library functions

Using Lua

You can access Lua functions within any Integration in the MetaRouter UI by adding an Expression mapping. Lua works with all Config options, including Global, Event and Default Configs. Additionally, you can map multiple parameters within a single Lua expression, as shown with the Facebook e-commerce starter kit:

Expression Types & Examples

Single-Key Expressions

Single Key Expressions are data transforms that create a value of a single output key (eg. a cart_total parameter). This expression has access to the entire incoming event payload, and can calculate a single outgoing key via custom logic from one or more incoming keys.

Example 1 — Adobe Experience Platform products array

A common example for a Single Key Lua Expression is to merge multiple incoming keys into a single outgoing key, with custom concatenation logic. In this case, Adobe Experience Platform and Adobe Analytics would like to have the products from an Order Completion event delivered as a string with a single Key, called products. Below is the logic for how a Lua Expression can calculate that single string.

UI Context

JSON Input

{
  "writeKey": "example",
  "userId": "5d56f1a5b800f125e94c1c62",
  "type": "page",
  "properties": {
    "products": [
      {
        "categoryCode": [
          "27",
          "21"
        ],
        "categoryDescription": [
          "Kitchen",
          "Garden & Patio"
        ],
        "name": "Blue Rhino Stainless Steel Griddle",
        "numberOfUnits": 1,
        "salePrice": 77.58,
        "upc": "0007690308630",
        "ebtEligible": false,
        "deliveryEligible": false,
        "pickupEligible": false,
        "shipEligible": true,
        "productModalitySelected": "ship",
        "marketplaceItem": false,
        "orderRevenue": 77.58,
        "yellowTag": false,
        "shipMethod": "standard shipping"
      }
    ]
  }
}

Lua Expression

function evar89For(inputProduct)
  local fields = {
    TO_STRING(inputProduct.deliveryEligible),
    TO_STRING(inputProduct.pickupEligible),
    TO_STRING(inputProduct.shipEligible)
  }
  return JOIN(fields, '_')
end

function toAdobeProductMap(inputProduct)
  local events = {
    JOIN({'event136', inputProduct.salePrice}, '='),
    JOIN({'event137', inputProduct.numberOfUnits}, '='),
    JOIN({'event210', inputProduct.salePrice}, '='),
  }

  local eVars = {
    JOIN({'evar49', LOWER(inputProduct.name)}, '='),
    JOIN({'evar50', LOWER(JOIN(inputProduct.categoryDescription, '_'))}, '='),
    JOIN({'evar38', LOWER(inputProduct.ebtEligible)}, '='),
    JOIN({'evar88', LOWER(inputProduct.productModalitySelected)}, '='),
    JOIN({'evar89', evar89For(inputProduct)}, '='),
    JOIN({'evar108', TO_BOOL(inputProduct.marketplaceItem or false)}, '='),
    JOIN({'evar129', TO_BOOL(inputProduct.yellowTag or false)}, '='),
  }

  return {
    productName = inputProduct.upc,
    quantity = inputProduct.numberOfUnits,
    price = inputProduct.salePrice,
    events = events,
    eVars = eVars,
  }
end

function toAdobeProductString(adobeProduct)
  local fields = {
    adobeProduct.productName,
    adobeProduct.quantity,
    adobeProduct.price,
    JOIN(adobeProduct.events, '|'),
    JOIN(adobeProduct.eVars, '|'),
  }
  return JOIN(fields, ";")
end

local adobeProducts = MAP(input.properties.products, toAdobeProductMap)
local adobeProductStrings = MAP(adobeProducts, toAdobeProductString)

return ";" .. JOIN(adobeProductStrings, ",")

JSON Output

{
  "products": ";0007690308630;1;77.58;event136=77.58|event137=1|event210=77.58;evar49=blue rhino stainless steel griddle|evar50=kitchen_garden \u0026 patio|evar38=false|evar88=ship|evar89=false_false_true|evar108=false|evar129=false"
}

Multi-Key Expressions

Multi Key Expressions are data transforms that create multiple output key values (eg. product0price, product1price, product2price parameters). This expression has access to the entire incoming event payload, and can calculate an indeterminate number of outgoing key via custom logic from one or more incoming keys.

Example 1 — Flattening a Product Array

In the case below, the user is tracking the products purchased in an Order Completed event as an Array of Objects, according to Analytics.js syntax. The partner, however, would like product information as a flat object of iterated keys:

{
  "0br": "Brand",
  "0id": "Product ID",
  "0nm": "Name",
  "0pr": Price,
  "0qt": Quantity,
  "1br": "Brand",
  "1id": "Product ID",
  "1nm": "Name",
  "1pr": Price,
  "1qt": Quantity,
}

Therefore, the example below outlines a Multi-Key Lua Expression calculating this set of iterated keys.

UI Context

JSON input

{
  "appId": "test",
  "writeKey": "test",
  "anonymousId": "b1ca62d6-22dd-4d0c-acad-a5f225e841a8",
  "userId": "90cf8995-f8f5-4d50-bbb7-5e75954f94e2",
  "context": {
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "page": {
      "path": "/dash",
      "referrer": "http://dev-mr.bluecode.co/dash",
      "search": "",
      "title": "MetaRouter",
      "url": "http://dev-mr.bluecode.co"
    },
    "traits": {
      "name": "MR dev",
      "email": "[email protected]"
    }
  },
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "checkout_id": "fksdjfsdjfisjf9sdfjsd9f",
    "order_id": "50314b8e9bcf000000000000",
    "affiliation": "Google Store",
    "total": 27.5,
    "revenue": 25,
    "shipping": 3,
    "tax": 2,
    "discount": 2.5,
    "coupon": "hasbros",
    "currency": "USD",
    "products": [
      {
        "product_id": "507f1f77bcf86cd799439011",
        "sku": "45790-32",
        "name": "Monopoly: 3rd Edition",
        "price": 19,
        "quantity": 1,
        "category": "Games",
        "brand": "B",
        "url": "https://www.example.com/product/path",
        "image_url": "https:///www.example.com/product/path.jpg"
      },
      {
        "product_id": "505bd76785ebb509fc183733",
        "sku": "46493-32",
        "name": "Uno Card Game",
        "price": 3,
        "quantity": 2,
        "category": "Games"
      }
    ]
  }
}

Lua Expression

local keyMappings = { -- create a lookup table that maps
  product_id = 'id',  -- incoming product property names
  name       = 'nm',  -- to the output property suffix
  brand      = 'br',
  price      = 'pr',
  quantity   = 'qt',
}

local results = {} -- create an empty table to store our results

for inputIndex, product in pairs(input.properties.products) do
  for inputKey, productVal in pairs(product) do
    local outputKey = keyMappings[inputKey]
    if outputKey then
      local outputIndex = inputIndex - 1
      local resultKey = outputIndex .. outputKey
      results[resultKey] = productVal
    end
  end
end

return results

JSON output

{
  "0br": "B",
  "0id": "507f1f77bcf86cd799439011",
  "0nm": "Monopoly: 3rd Edition",
  "0pr": 19,
  "0qt": 1,
  "1id": "505bd76785ebb509fc183733",
  "1nm": "Uno Card Game",
  "1pr": 3,
  "1qt": 2
}

Math Expressions

Math Expressions are data transforms that create output value(s) (eg. a cart_total parameter) based on a calculation from incoming keys. This expression has access to the entire incoming event payload, and can calculate an indeterminate number of outgoing key via custom logic from one or more incoming keys.

Example 1: Calculating a "Cart Total"

A common use-case for Math Expressions using Lua is to calculate a total value of the cart during an Order Completed event. The following example showcases Lua iterating through the product array, and for each product, multiplying the quantity value by the price value, and then summing those values together to specify a cartTotal output parameter.

UI Context

JSON Input

{
  "appId": "test",
  "writeKey": "test",
  "anonymousId": "b1ca62d6-22dd-4d0c-acad-a5f225e841a8",
  "userId": "90cf8995-f8f5-4d50-bbb7-5e75954f94e2",
  "context": {
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "page": {
      "path": "/dash",
      "referrer": "http://dev-mr.bluecode.co/dash",
      "search": "",
      "title": "MetaRouter",
      "url": "http://dev-mr.bluecode.co"
    },
    "traits": {
      "name": "MR dev",
      "email": "[email protected]"
    }
  },
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "checkout_id": "fksdjfsdjfisjf9sdfjsd9f",
    "order_id": "50314b8e9bcf000000000000",
    "affiliation": "Google Store",
    "total": 27.5,
    "revenue": 25,
    "shipping": 3,
    "tax": 2,
    "discount": 2.5,
    "coupon": "hasbros",
    "currency": "USD",
    "products": [
      {
        "product_id": "507f1f77bcf86cd799439011",
        "sku": "45790-32",
        "name": "Monopoly: 3rd Edition",
        "price": 19,
        "quantity": 1,
        "category": "Games",
        "url": "https://www.example.com/product/path",
        "image_url": "https:///www.example.com/product/path.jpg"
      },
      {
        "product_id": "505bd76785ebb509fc183733",
        "sku": "46493-32",
        "name": "Uno Card Game",
        "price": 3,
        "quantity": 2,
        "category": "Games"
      }
    ]
  }
}

Lua Expression

local products = input.properties.products
return SUM(MAP(products, function (p) return p.quantity * p.price end))

JSON Output

{
  "cartTotal": 25
  ...
}

Conditional Logic Expressions

Conditional Logic Expressions are data transforms that create different output key values (eg. homepage vs page parameters) based on the exact nature of the incoming event. This expression has access to the entire incoming event payload, and can leverage a set of rules to determine the output value(s).

Example 1 — Indicating HomePage for Page Events

In this case, the partner would like to know, when it receives a Page event, whether that page was the Home Page, or any other page on the site. This is important for understanding the user's journey. In the below example, a homepage parameter will either have a "Y" or "N" value based on whether or not the input.context.page.path indicates the root of the site.

UI Context

JSON Input

{
  "appId": "test",
  "writeKey": "test",
  "anonymousId": "b1ca62d6-22dd-4d0c-acad-a5f225e841a8",
  "userId": "90cf8995-f8f5-4d50-bbb7-5e75954f94e2",
  "context": {
    "ip": "127.0.0.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "page": {
      "path": "/dash",
      "referrer": "http://dev-mr.bluecode.co/dash",
      "search": "",
      "title": "MetaRouter",
      "url": "http://dev-mr.bluecode.co"
    },
    "traits": {
      "name": "MR dev",
      "email": "[email protected]"
    }
  },
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "checkout_id": "fksdjfsdjfisjf9sdfjsd9f",
    "order_id": "50314b8e9bcf000000000000",
    "affiliation": "Google Store",
    "total": 27.5,
    "revenue": 25,
    "shipping": 3,
    "tax": 2,
    "discount": 2.5,
    "coupon": "hasbros",
    "currency": "USD",
    "products": [
      {
        "product_id": "507f1f77bcf86cd799439011",
        "sku": "45790-32",
        "name": "Monopoly: 3rd Edition",
        "price": 19,
        "quantity": 1,
        "category": "Games",
        "url": "https://www.example.com/product/path",
        "image_url": "https:///www.example.com/product/path.jpg"
      },
      {
        "product_id": "505bd76785ebb509fc183733",
        "sku": "46493-32",
        "name": "Uno Card Game",
        "price": 3,
        "quantity": 2,
        "category": "Games"
      }
    ]
  }
}

Lua Expression

if input.context.page.path == '/' then
    return 'Y'
else
    return 'N'
end

JSON Output

{
  "homepage": "N"
}

Object Manipulation Expressions

Example 1 — Creating a New Object From Scratch

UI Context

JSON Input

{
  "appId": "test",
  "writeKey": "test",
  "anonymousId": "b1ca62d6-22dd-4d0c-acad-a5f225e841a8",
  "userId": "90cf8995-f8f5-4d50-bbb7-5e75954f94e2",
  "context": {
    "ip": "127.0.0.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "page": {
      "path": "/dash",
      "referrer": "http://dev-mr.bluecode.co/dash",
      "search": "",
      "title": "MetaRouter",
      "url": "http://dev-mr.bluecode.co"
    },
    "traits": {
      "name": "MR dev",
      "email": "[email protected]"
    }
  },
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "checkout_id": "fksdjfsdjfisjf9sdfjsd9f",
    "order_id": "50314b8e9bcf000000000000",
    "affiliation": "Google Store",
    "total": 27.5,
    "revenue": 25,
    "shipping": 3,
    "tax": 2,
    "discount": 2.5,
    "coupon": "hasbros",
    "currency": "USD",
    "products": [
      {
        "product_id": "507f1f77bcf86cd799439011",
        "sku": "45790-32",
        "name": "Monopoly: 3rd Edition",
        "price": 19,
        "quantity": 1,
        "category": "Games",
        "url": "https://www.example.com/product/path",
        "image_url": "https:///www.example.com/product/path.jpg"
      },
      {
        "product_id": "505bd76785ebb509fc183733",
        "sku": "46493-32",
        "name": "Uno Card Game",
        "price": 3,
        "quantity": 2,
        "category": "Games"
      }
    ]
  }
}

Lua Expression

return {
  client_ip_address = input.context.ip,
  client_user_agent = input.context.userAgent,
  external_id       = TO_SHA256_HASH(input.anonymousId),
}

JSON Output

{
  "client_ip_address": "127.0.0.1",
  "client_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
  "external_id": "fcda8a2f72aab4ae0504bd60af085ec3b324f4404208d4bb1c9c7672b21261b5"
}

Array Manipulation Expressions

Example 1 — Creating a Simplified Object from an Array

UI Context

JSON Input

{
  "appId": "test",
  "writeKey": "test",
  "anonymousId": "b1ca62d6-22dd-4d0c-acad-a5f225e841a8",
  "userId": "90cf8995-f8f5-4d50-bbb7-5e75954f94e2",
  "context": {
    "ip": "127.0.0.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "page": {
      "path": "/dash",
      "referrer": "http://dev-mr.bluecode.co/dash",
      "search": "",
      "title": "MetaRouter",
      "url": "http://dev-mr.bluecode.co"
    },
    "traits": {
      "name": "MR dev",
      "email": "[email protected]"
    }
  },
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "checkout_id": "fksdjfsdjfisjf9sdfjsd9f",
    "order_id": "50314b8e9bcf000000000000",
    "affiliation": "Google Store",
    "total": 27.5,
    "revenue": 25,
    "shipping": 3,
    "tax": 2,
    "discount": 2.5,
    "coupon": "hasbros",
    "currency": "USD",
    "products": [
      {
        "product_id": "507f1f77bcf86cd799439011",
        "sku": "45790-32",
        "name": "Monopoly: 3rd Edition",
        "price": 19,
        "quantity": 1,
        "category": "Games",
        "url": "https://www.example.com/product/path",
        "image_url": "https:///www.example.com/product/path.jpg"
      },
      {
        "product_id": "505bd76785ebb509fc183733",
        "sku": "46493-32",
        "name": "Uno Card Game",
        "price": 3,
        "quantity": 2,
        "category": "Games"
      }
    ]
  }
}

Lua Expression

return PLUCK(input.properties.products, 'product_id')

JSON Output

{
  "content_ids": [
    "507f1f77bcf86cd799439011",
    "505bd76785ebb509fc183733"
  ]
}