FQL, which stands for Functional Query Language, is a small language designed to give you the ability to manipulate and display dynamic data.
Nearly every field or configuration option in Shipcode supports FQL and dynamic data. For example, you could use FQL to set the title of a Product Display Page, or to grab a color value from an API response and use it to set the color of text within a component.
Expressions
FQL expressions are wrapped in double braces, as shown here: {{ expression }}
The simplest form of an FQL expression would be to to render a static string: {{ “example” }}
However, this isn’t very useful, and is unnecessary as you could use the static text editor for this value. Assuming that you have a Data Zone named “Product”, here is how you would use FQL to dynamically display the title of the product in a Text component: {{ $(Product).title }}
Once you have provided data, you can then use operators called Pipes to manipulate the data further. For example, you could use the titleCase Pipe to capitalize the first letters of the product name: {{ $(Product).title | titleCase }}
Terminology
The terminology for the parts of FQL expressions are described below:

Root
A Root is the start of all FQL expressions, and exists to either select data to pull from FQL context (most common), or otherwise provide some static data to manipulate.
The above diagram shows the most common way to write a Root. It consists of a Scope (which can be omitted - if omitted, it's assumed to be $this), and some number of Path elements. See below for more details on how to use Scopes & Paths.
Context & Layers
Any part of the application that can provide data to FQL, such a Data Zone in Design Mode or a GraphQL Node in Logic Mode, is referred to as a Context within the realm of FQL. Like layers of an onion, Contexts can have multiple layers; for example, you may be constructing an expression within multiple embedded Data Zones. Shipcode also provides “out-of-the-box” Contexts such as the user’s device information and the route parameters used to access the current Layout.
Scope
A scope is a keyword or syntax that tells FQL which layer of context to pull from. There are several different ways to access the desired context:
$root
$root accesses the "top" FQL context layer currently available.
$this
Selects the innermost context layer. It is the default implied scope, so if you ever see a FQL expression that looks like {{ foo.bar }}, it is equivalent to {{ $this.foo.bar }}.
$parent[<n>]
Replace <n> with an integer. Goes up the tree to access context layers above $this. The index begins at 0 and starts at the nearest context, meaning that $parent[0] is equivalent to $this, $parent[1] would access the scope one layer above, and so on.
$(TagName)
Contexts can be “tagged” to make their purpose more obvious within FQL expressions. Most sources of data will automatically provide a default tag; for example, a new Data Zone component will have the “DataZone” tag. You can provide your own tag as well by selecting the appropriate component and opening the Settings Cog. Contexts can then be referenced by their tag using the $(<TagName>) syntax, such as $(DataZone) or $(ProductData). If you have two contexts of the same tag, you can use index notation to specify which context to use. $(DataZone[0]) selects the innermost layer with the DataZone tag, $(DataZone[1]) selects the next-closest matching layer, and so on. You can also use negative indices to count in reverse: [-1] selects the outermost layer with the tag while [-2] selects the second-outermost.
$inspect
This is a scope intended for debugging purposes. It gives you the underlying data structure that the FQL system uses to see what layers and tags are available in the current context. It is great for seeing what context layers there are, and how each layer is tagged. Though it is possible to get meaningful "production" data from it (like a product title or image URL, for example), it is strongly recommended that you only use this while developing in FQL, and not in a live production app, primarily for performance reasons - FQL has some complex optimizations to determine when the underlying context changes enough for a re-render, and $inspect overrides some of that.
Path
The path is an optional part of the root that selects data from within a scope, such as when the scope is an object or array. This aspect will be extremely familiar to a JavaScript developer, as the logic is the same. Consider the following JavaScript object:
JavaScript
{
name: {
first: "John",
last: "Smith"
},
age: 35,
children: [
{
name: {
first: "Joey",
last: "Smith"
},
},
{
name: {
first: "Sally",
last: "Smith"
}
}
]
}
Dot Notation
You can use dot notation to get different parts of the above data structure:{{ $this.name.first }} or just {{ name.first }} would give the string John.
{{ $this.age }} or {{ age }} would give the number 35.
{{ $this.children }} or {{ children }} would give the list (i.e., array) of children, each of which is an object.
You could then use subsequent FQL expressions to manipulate the child objects.
Index Notation
You can also use index notation to get data as well, such as {{ $this["firstName"] }} to get John. For keys like firstName, lastName, or age, it's rare to do this because dot notation is easier to read and type. However, keys that contain spaces or special characters need to use index notation and single or double quotation marks, such as {{ $this['my property-name'] }}.
Index notation can also be used to reference specific elements within arrays. Arrays are 0-based, which means the first element of an array is selected by using [0] index.
{{ children[0] }} will get {name: {first: "Joey", last: "Smith"}}
{{ children[1] }} will get the second element from the array.
Combining Notations
You can combine notations if needed:{{ children[0].name }} will return the object {first: "Joey", last: "Smith"}
{{ $this.children[0].name.last }} will return Smith
{{ $this.children[1].name.first }} will return Sally
Pipes
See Pipes Reference for a list of all pipes.
Pipes are FQL's answer to functions. They give you the ability to manipulate the data provided by evaluating the query root or other Pipes.
FQL expressions are evaluated left to right, and the resulting value from a query root or Pipe is then passed into any subsequent Pipe or selector. Consider the following example:
{{ $(Product).title | titleCase }}
Once the root is evaluated, the product’s title is “piped” to the titleCase Pipe, which will capitalize the first letter of every word.
If $(Product).title evaluates to “red jacket”, the titleCase Pipe will change that to “Red Jacket”, and thus the entire expression will evaluate to Red Jacket.
📍📍📍 Note 📍📍📍
Pipe names are case-sensitive and always start with a lowercase letter.
Arguments
Many Pipes accept arguments as well. When using arguments, add a colon (:) after the Pipe name as well as between each argument. The replace Pipe, for example, requires you to specify a string to search for and a string to replace it with: {{ $(Product).title | replace: “red”: “green” }} will evaluate to “green jacket” in this example.
Multiple Pipes
FQL expressions can combine multiple Pipes. Consider this example: {{ $(Product).title | replace: “red”: “green” | titleCase }}
The replace Pipe will be evaluated first and the uppercase Pipe will be evaluated second. The entire expression will evaluate to “Green Jacket”.
Nested Pipes
Much like code or mathematical formulas, FQL supports nesting syntax with parenthesis to specify the order of operation and make compound expressions.
Consider the following expression: {{ age | isGreaterThanOrEqualTo: 18 | ifElse: "Adult": "Minor" }}
This would evaluate to "Adult" if age is 18 or greater or "Minor" if the age is lower. Imagine that you want to be a bit more specific and return "Senior" if the person is 55 or older. You can use nesting to combine multiple ifElse Pipes: {{ age | isGreaterThanOrEqualTo: 18 | ifElse: ( age | isGreaterThanOrEqualTo: 55 | ifElse: "Senior": "Adult" ): "Minor"}}
Order-Insensitive Array Comparison
Two specialized Pipes compare array contents while ignoring order but still respecting duplicate counts (multiset semantics):
hasSameMembersAs- Returns true/false if two arrays contain the same members (including duplicate counts) regardless of order.filterHasSameMembersAs- Filters an array of objects whose nested target array matches the provided comparison array as a multiset.
Key characteristics:
- Order is ignored:
[1,2,3]and[3,2,1]match. - Duplicate counts must match:
[1,1,2]does not match[1,2,2]. - Deep equality for elements (same as other equality Pipes).
- Primitive-only arrays use an O(n) frequency map; mixed/object arrays fall back to an O(n^2) search.
Examples
Primitive multiset equality:
{{ ["apple","banana","apple"] | hasSameMembersAs :["banana","apple","apple"] }} → true
{{ ["apple","banana","apple"] | hasSameMembersAs :["apple","banana","APPLE"] }} → false
Object array (e.g. product variant options):
const target = [
{ name: 'Color', value: 'Black' },
{ name: 'Size', value: 'XL' },
];
const variants = [
{
selectedOptions: [
{ name: 'Size', value: 'XL' },
{ name: 'Color', value: 'Black' },
],
availableForSale: true,
},
{
selectedOptions: [
{ name: 'Color', value: 'Black' },
{ name: 'Size', value: 'XL' },
],
availableForSale: false,
},
{
selectedOptions: [
{ name: 'Size', value: 'L' },
{ name: 'Color', value: 'Black' },
],
availableForSale: true,
},
];
Usage:
{{ variants | filterHasSameMembersAs :target :"selectedOptions" | mapPath:"availableForSale" }}
→ [true,false]
Duplicate count sensitivity:
const variantTags = [
{
selectedOptions: [
{ name: 'Tag', value: 'A' },
{ name: 'Tag', value: 'B' },
{ name: 'Tag', value: 'A' },
],
},
];
const target = [
{ name: 'Tag', value: 'A' },
{ name: 'Tag', value: 'A' },
{ name: 'Tag', value: 'B' },
];
{{ variantTags | filterHasSameMembersAs :target :"selectedOptions" }} → keeps items with
two 'A' and one 'B'When to Use
- Comparing selected options / facets where order is not guaranteed.
- Ensuring two user-selected sets match regardless of order.
- Filtering variant lists to a target configuration.
When NOT to Use
- When order matters. You can use
isEqualToandfilterIsEqualToon arrays and even object arrays when order matters. - For very large arrays (hundreds+) where a sorted comparison may be cheaper overall.
💡💡💡 Tip 💡💡💡
- Normalize then compare:
{{ array | sort:"asc" | hasSameMembersAs :( otherArray | sort:"asc" ) }}- Ignore duplicates: apply
uniqueto both arrays before comparing.
Data Types and Literals
FQL has a few "native" types and a few "semi-native" types. All of these types, and their literals, are valid anywhere you can put a root or a parameter.
The Data Types and Literals are:
- String
- Type: Native
- Examples:
"foo",'foo' - Description: Single or double-quoted literal text.
- Number
- Type: Native
- Examples:
0,-1.234 - Description: Simple JavaScript number.
- Boolean
- Type: Native
- Examples:
true,false - Description: Simple true or false.
- Null
- Type: Native
- Examples:
null - Description: The JavaScript null value.
- Undefined
- Type: Native
- Examples:
undefined - Description: The JavaScript undefined value.
- RegExp
- Type: Native
- Examples:
/[bc]op/g - Description: A regular expression literal. Often used as Pipe parameters.
- MergeField
- Type: Native
- Examples:
foo.bar,$this[1].uri - Description: Pulls a value from a context.
- Array
- Type: Semi-Native
- Examples: no literal representation
- Description: See Pipes section for working with Arrays.
- Object
- Type: Semi-Native
- Examples: no literal representation
- Description: See Pipes section for working with Objects.
Escaping Strings
String literals must always be 'single-quoted' or "double-quoted". They mean the same thing either way, the only difference being escaping quotes inside the string. For instance, to put the text Branding Brand's Best into a single-quoted string, you would need to escape the quote: 'Branding Brand\'s Best'. A double-quoted string can handle the phrase literally, however: "Branding Brand's Best". A double-quoted string needs to escape double quotes, and a single-quoted string needs to escape single quotes.
