Logic Variables
Logic Variables
GTM trigger conditions are AND-only. There’s no OR, no XOR, no nested logic. The if/else ladders that should be one configuration become tangled Custom JavaScript variables nobody wants to touch. Type checks and value validation get reinvented in every container. Logic is the decision-modeling layer — boolean operators that fix what GTM lacks, IF ELSE IF builders that replace JavaScript ladders, and a predicate library of 38 type and value checks that compose into them. No Custom JavaScript.
Why this category exists
Section titled “Why this category exists”Three structural problems show up in every GTM container:
GTM has no native OR. Trigger conditions in a single trigger use AND logic — all conditions must be true for the trigger to fire. To express OR — fire when any of several conditions is true — most analysts reach for workarounds: multiple triggers on the same tag, a RegEx Table variable, or a Custom JavaScript variable.
”As GTM’s interface instructs, ALL conditions in the same trigger must be met, therefore, most likely, such a trigger will never be activated because it is not possible for the URL of a website to contain all 3 options.”
Decision logic always becomes Custom JavaScript. Anything more sophisticated than equals/contains comparisons turns into a function() { if (…) return X; else if (…) return Y; } ladder. The rules live only in the code.
Type and value validation gets reinvented constantly. “Is this value a number?” “Does this dataLayer key exist?” “Is the input a valid Unix timestamp?” Each check is a small Custom JavaScript variable that nobody centralizes. The Logic category provides 38 named type and value predicates that you import once and reuse everywhere.
Two jobs: predicates and routing
Section titled “Two jobs: predicates and routing”The category has two complementary halves. Understanding them separately is the fastest path to using them well together.
Decide what to do based on conditions
IF ELSE IF builders, AND/OR/XOR/NOT operators, value fallbacks. These take conditions or values as input and return whichever output matches.
Test whether values satisfy a property
38 named tests for type (isString, isNumber, isArray), existence (isDefined, isNull, isEmpty), comparison (isGreaterThan, isInRange), and semantics (isURL, isJSON, isUnixTimestamp).
Pick the first value that exists
firstDefined and firstTruthy take multiple inputs and return the first one that satisfies the test. The clean version of the JavaScript || chain.
The predicates compose into the routing variables. isURL can feed an IF (else), an OR, an IF ELSE IF (Advanced) row, or a 🧩 WEIGHTED condition (from Number). The same boolean primitive works in every consumer.
The variables — at a glance
Section titled “The variables — at a glance”Forty-nine variables, organized by job. The hero group is small but high-impact: it’s where the routing and combination work happens.
“42”, “3.14”.isArray / isNotArrayIs the value an array?isObject / isNotObjectIs the value a plain object (not array, not null)?isFunction / isNotFunctionIs the value a function?isPrimitiveString, number, boolean, null, undefined, or symbol."".isNaN / isNotNaNIs the value JavaScript’s NaN?isInfinityIs the value Infinity or -Infinity?isTruthy / isFalsyCoerces to true or false in a boolean context.event_time shape).isUnixTimestampMilliseconds13-digit Unix timestamp.isBusinessHoursWithin configured business hours (Date category).Build OR conditions in GTM triggers
Section titled “Build OR conditions in GTM triggers”Native GTM triggers fire only when all conditions in a single trigger evaluate true. To express OR — fire when any of several conditions is true — the Logic category offers a clean approach.
| Field | Value |
|---|---|
| Input 1 | {{Page Path}} contains /pricing |
| Input 2 | {{Page Path}} contains /signup |
| Input 3 | {{Page Path}} contains /demo |
OR equals true — fires on any of the three pathsWhy not multiple triggers? Multiple triggers on the same tag also act as OR — but they multiply your trigger inventory and obscure intent. 🧩 OR in one trigger keeps the logic visible in one place.
Combine AND, OR, XOR, NOT for complex conditions
Section titled “Combine AND, OR, XOR, NOT for complex conditions”The four boolean operators compose into arbitrary expressions. Need “(page is pricing OR demo) AND (user is logged in)”? Nest 🧩 OR inside 🧩 AND. Need “exactly one of two flags is set”? Use 🧩 XOR.
| Inputs | AND | OR | XOR |
|---|---|---|---|
| true, true | true | true | false |
| true, false | false | true | true |
| false, false | false | false | false |
Replace if/else Custom JavaScript with IF ELSE IF
Section titled “Replace if/else Custom JavaScript with IF ELSE IF”Anywhere a Custom JavaScript variable contains an if/else if/else ladder, an IF ELSE IF builder replaces it as configuration. Two variants:
🧩 IF ELSE IF (Predefined)— every row tests the same input against different values. Like a switch statement.🧩 IF ELSE IF (Advanced)— each row tests its own arbitrary condition. Conditions can use any predicate.
| Input value | → Output |
|---|---|
”mobile" | "m" |
"tablet" | "t" |
"desktop" | "d” |
| Default | ”unknown” |
{{DL - device_category}}; output flows to the destination tag| Condition | → Output |
|---|---|
{{DL - lifetime_value}} > 1000 | ”vip” |
{{DL - is_subscriber}} equals true | ”subscriber” |
{{DL - first_purchase_date}} isDefined | ”customer” |
| Default | ”visitor” |
Return the first defined value (coalesce)
Section titled “Return the first defined value (coalesce)”“User ID, fall back to guest ID, fall back to session ID, fall back to anonymous.” This pattern shows up in every container that handles authentication state.
| Input | Value |
|---|---|
| Input 1 | {{DL - user_id}} |
| Input 2 | {{DL - guest_id}} |
| Input 3 | {{DL - session_id}} |
| Input 4 (final fallback) | “anonymous” |
undefinedFor “first non-empty value” semantics — where you want to skip 0, "", null, false, and undefined alike — use 🧩 firstTruthy instead.
| Input chain | firstDefined | firstTruthy |
|---|---|---|
0, “id”, “fb” | 0 | ”id" |
"", “id”, “fb" | "" | "id” |
undefined, “id”, “fb" | "id" | "id” |
null, “id”, “fb” | null | ”id” |
Validate dataLayer values before tagging
Section titled “Validate dataLayer values before tagging”DataLayer values arrive as whatever the backend pushed — and backends ship bugs. A revenue field that’s supposed to be a number arrives as a string. A user ID that should always be present is sometimes undefined. Type and value validation in the container catches these before they reach a tag.
| Field | Value |
|---|---|
| Input | {{DL - ecommerce.value}} |
true for a real number; false for NaN, Infinity, strings, undefinedDetect Unix timestamps, JSON, URLs, SHA-256 hashes
Section titled “Detect Unix timestamps, JSON, URLs, SHA-256 hashes”Beyond raw type checks, the category includes semantic predicates that test whether a value looks like a known format:
isURL— value parses as a syntactically valid URL. Useful before sending topage_referrerorlink_url.isJSON— value is a parseable JSON string. Pair withparseJSONto safely process stringified objects.isSHA256— value is a 64-character hex string. Useful to confirm PII has been hashed before reaching a CAPI tag.- Unix timestamp variants —
isUnixTimestamp,isUnixTimestampSeconds,isUnixTimestampMilliseconds. Distinguish 10-digit (seconds) from 13-digit (ms) timestamps.
| Condition | → Output |
|---|---|
{{DL - timestamp}} isUnixTimestampSeconds | {{DL - timestamp}} (already correct) |
{{DL - timestamp}} isUnixTimestampMilliseconds | {{Apply - millisecondsToSeconds}} |
| Default | {{Apply - currentTimestamp → millisecondsToSeconds}} |
Use predicates in WEIGHTED for scoring
Section titled “Use predicates in WEIGHTED for scoring”Predicates aren’t just for routing decisions — they’re also the building blocks of scoring pipelines. 🧩 WEIGHTED from the Number category takes condition-weight pairs and returns the sum of weights for conditions that evaluate true.
| Condition | Weight |
|---|---|
{{DL - email}} isURL equals false | 5 (real email, not a URL misuse) |
{{DL - phone}} isStringifiedNumber | 3 |
{{DL - company}} isNotEmptyString | 10 (B2B signal) |
{{DL - lifetime_value}} isInRange [100, 10000] | 15 |
Default values for missing dataLayer keys
Section titled “Default values for missing dataLayer keys”When a dataLayer key is sometimes missing, three different patterns serve three different needs:
Pattern 1: GTM’s native default-value setting
The dataLayer variable type in GTM has a “Default Value” field. Built into GTM, no Logic category required. Limitation: only triggers on undefined.
Pattern 2: 🧩 firstDefined
Multiple dataLayer variables coalesced to the first non-undefined one. Use when you have a hierarchy of fallback sources.
Pattern 3: 🧩 IF (else) with predicates
Explicit control over what counts as “missing.” Use isEmpty, isFalsy, or isNull.
| Field | Value |
|---|---|
| Condition | {{DL - user_segment}} isEmpty |
| Then return | ”unknown” |
| Else return | {{DL - user_segment}} |
Composed patterns — chaining variables
Section titled “Composed patterns — chaining variables”Validated user identifier
First defined ID across multiple sources, falling back to “anonymous.”
Conditional event firing
Tag fires only when input is a valid number AND in a sane range.
Schema-routed timestamp
Detect timestamp format and route through the right unit conversion.
Cross-category scoring
Logic predicates feed Number’s WEIGHTED, which feeds clamp and scaleToRange.
Why you don’t need Custom JavaScript
Section titled “Why you don’t need Custom JavaScript”The dominant Custom JavaScript pattern for decision logic in GTM:
function() {
var ltv = Number({{DLV - lifetime_value}});
var sub = {{DLV - is_subscriber}};
var first = {{DLV - first_purchase_date}};
if (ltv > 1000) return ‘vip’;
else if (sub === true) return ‘subscriber’;
else if (first !== undefined) return ‘customer’;
else return ‘visitor’;
}Named templates encode the rules as configuration:
- The rules are visible. A reviewer can read an IF ELSE IF (Advanced) variable and know the policy without reading code.
- Type coercion is explicit. Predicates validate before routing.
- Server-side portable. sGTM’s stricter sandbox accepts named templates.
- Composable. Reusing the “is VIP” check elsewhere means referencing one variable, not duplicating code.
Cheatsheet
Section titled “Cheatsheet”| Goal | Variable | Mode |
|---|---|---|
| OR condition in a trigger | 🧩 OR | Direct |
| AND condition with multiple inputs | 🧩 AND | Direct |
| Exactly one of N inputs is true | 🧩 XOR | Direct |
| Invert a boolean | 🧩 NOT | Direct / Apply |
| One condition, two outcomes | 🧩 IF (else) | Direct |
| Switch on one input value | 🧩 IF ELSE IF (Predefined) | Direct |
| Ladder with arbitrary conditions | 🧩 IF ELSE IF (Advanced) | Direct |
| First non-undefined value (coalesce) | 🧩 firstDefined | Direct |
| First truthy value | 🧩 firstTruthy | Direct |
| Type checks (string, number, array, etc.) | isString / isNumber / isArray / etc. | Direct |
| Existence (defined, null, empty) | isDefined / isNull / isEmpty / isEmptyString | Direct |
| Comparison | isEqualTo / isGreaterThan / isLessThan / isInRange | Direct |
| Format detection (URL, JSON, SHA-256) | isURL / isJSON / isSHA256 | Direct |
| Unix timestamp shape | isUnixTimestamp / isUnixTimestampSeconds / isUnixTimestampMilliseconds | Direct |
How do I create an OR condition in a GTM trigger?
GTM trigger conditions use AND logic — all conditions must be true. To express OR, use the 🧩 OR variable as a single trigger condition. Pass the individual conditions as inputs. The variable returns true when any input is true.
How do I replace an if/else if Custom JavaScript variable in GTM?
Use 🧩 IF ELSE IF (Advanced) for ladders with arbitrary conditions per row, or 🧩 IF ELSE IF (Predefined) when all rows compare the same input to different values. The variable returns the output of the first row whose condition matches.
How do I return the first defined value from multiple dataLayer variables?
Use 🧩 firstDefined to take the first input that is not undefined. Use 🧩 firstTruthy when you want to skip empty strings, zero, false, and null in addition to undefined.
How do I validate that a dataLayer value is a number?
Use isNumber or isValidNumber as a trigger condition. isValidNumber additionally rejects NaN and Infinity. For numeric strings, use isStringifiedNumber and convert with toNumber from Number.
What’s the difference between firstDefined and firstTruthy?
firstDefined returns the first input that is not undefined — so 0, "", null, false all pass through. firstTruthy returns the first input that coerces to true — skipping all falsy values. Use firstDefined for ID fallbacks where 0 is valid; use firstTruthy for “give me any non-empty value.”
Are these variables sandbox-safe in server-side GTM?
Yes. All templates run inside GTM’s sandboxed JavaScript environment using only permissioned APIs. They work in both web and server containers.