diff --git a/README.md b/README.md index 014dc62..50fddd6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@ The **Universal Conversions Variable for Google Tag Manager Web container** allo - **item count** - etc. +- ### Product ID Formatting + +This template includes advanced options for formatting the product IDs sent to various platforms. + +| Field | Description | +| :--- | :--- | +| **Product ID Format** | A dropdown menu to select which ID to use.
- **Default:** Uses the original `Product ID/SKU` field.
- **Variant ID:** Uses the ID from the `Item Variant ID Key` field.
- **SKU:** Uses the ID from the `Item SKU Key` field.
- **Custom Shopify:** Builds a custom string in the format `shopify_MARKET_prod_var`. | +| **Item Variant ID Key** | **Required for 'Variant ID' & 'Custom Shopify'.** The Data Layer key for your item's variant ID (e.g., `item_variant`). | +| **Item SKU Key** | **Required for 'SKU'.** The Data Layer key for your item's SKU (e.g., `item_sku`). | +| **Market Code** | **Used for 'Custom Shopify'.** The market code (e.g., `GB`, `US`) to be prefixed to the custom ID. | + It currenly supports the following platforms: - Meta Pixel/CAPI - Google Analytics 4 diff --git a/template.js b/template.js index 1cec319..faaa586 100644 --- a/template.js +++ b/template.js @@ -9,6 +9,7 @@ const Object = require('Object'); /*============================================================================== ==============================================================================*/ +// Original Key fields const keyId = data.keyId; const keyPr = data.keyPr; const keyNm = data.keyNm; @@ -18,6 +19,14 @@ const keyImg = data.keyImg; const contentType = data.contentType; const taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent)); const keyDisc = data.keyDiscItemLevel; + +// --- NEW FIELDS --- +const idFormat = data.idFormatType; +const keySku = data.keySku; +const keyVariantId = data.keyVariantId; +const market = data.marketCode || ''; +// --- END NEW FIELDS --- + const customParamMap = data.customParams ? makeTableMap(data.customParams, 'cusKey', 'cusName') : {}; @@ -75,7 +84,9 @@ function getContentName(arr) { } function getContentIds(arr) { - const content_ids = arr.map((item) => item[keyId]); + // --- MODIFIED --- + const content_ids = arr.map((item) => getFormattedId(item)); + // --- END MODIFIED --- return content_ids; } @@ -84,7 +95,9 @@ function getItem(arr) { cat.push(arr[0][keyCat]); let item = { - ProductID: arr[0][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[0]), + // --- END MODIFIED --- ProductName: arr[0][keyNm], Price: arr[0][keyPr], ImageURL: arr[0][keyImg], @@ -112,7 +125,9 @@ function getContents(arr, platform) { if (platform === 'meta') { contents.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, item_price: arr[i][keyPr] }); @@ -120,7 +135,9 @@ function getContents(arr, platform) { if (platform === 'tiktok') { contents.push({ - content_id: makeString(arr[i][keyId]), + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_type: contentType, content_category: arr[i][keyCat], content_name: arr[i][keyNm], @@ -131,7 +148,9 @@ function getContents(arr, platform) { if (platform === 'twitter') { contents.push({ - content_id: arr[i][keyId], + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_name: arr[i][keyNm], content_type: arr[i][keyCat], num_items: qt, @@ -143,6 +162,7 @@ function getContents(arr, platform) { contents.push({ quantity: qt, item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0' + // Pinterest 'contents' array doesn't use ID }); } } @@ -167,7 +187,9 @@ function getItems(arr, platform) { if (platform === 'ga4') { let itemObj = { - item_id: arr[i][keyId], + // --- MODIFIED --- + item_id: getFormattedId(arr[i]), + // --- END MODIFIED --- item_name: arr[i][keyNm], quantity: qt, price: arr[i][keyPr], @@ -189,7 +211,9 @@ function getItems(arr, platform) { cat.push(arr[i][keyCat]); let itemObj = { - ProductID: arr[i][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[i]), + // --- END MODIFIED --- ProductName: arr[i][keyNm], Quantity: qt, ItemPrice: arr[i][keyPr], @@ -208,7 +232,9 @@ function getItems(arr, platform) { if (platform === 'criteo') { let itemObj = { - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, price: arr[i][keyPr] }; @@ -218,7 +244,9 @@ function getItems(arr, platform) { if (platform === 'gAdsOff') { items.push({ - productId: makeString(arr[i][keyId]), + // --- MODIFIED --- + productId: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, unitPrice: makeNumber(arr[i][keyPr]) }); @@ -226,7 +254,9 @@ function getItems(arr, platform) { if (platform === 'pinterest') { items.push({ - product_id: arr[i][keyId], + // --- MODIFIED --- + product_id: getFormattedId(arr[i]), + // --- END MODIFIED --- product_name: arr[i][keyNm], product_quantity: qt, product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 @@ -235,7 +265,9 @@ function getItems(arr, platform) { if (platform === 'reddit') { items.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- category: arr[i][keyCat], name: arr[i][keyNm] }); @@ -263,7 +295,9 @@ function getItems(arr, platform) { } let itemObj = { - sku: arr[i][keyId], + // --- MODIFIED --- + sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID + // --- END MODIFIED --- product_name: arr[i][keyNm], quantity: qt, amount: price > 0 ? price : toFixed2(p * 100 * qt), @@ -302,6 +336,42 @@ function getItems(arr, platform) { Helpers ==============================================================================*/ +// --- NEW HELPER FUNCTION --- +// This function builds the ID based on the user's selection +function getFormattedId(item) { + // 1. Get the default ID (from the 'keyId' field) as a fallback + const defaultId = item[keyId] ? makeString(item[keyId]) : undefined; + + // 2. Check the format dropdown + switch (idFormat) { + case 'sku': + // Use 'keySku' field if available, otherwise fall back to default + return item[keySku] ? makeString(item[keySku]) : defaultId; + + case 'variantId': + // Use 'keyVariantId' field if available, otherwise fall back to default + return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId; + + case 'customShopify': + // Use 'keyId' (as Product) and 'keyVariantId' (as Variant) + const prodId = item[keyId]; + const varId = item[keyVariantId]; + + if (prodId && varId) { + // Build the custom string + return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId); + } + // If data is missing, fall back to default + return defaultId; + + case 'default': + default: + // Return the default ID + return defaultId; + } +} +// --- END NEW HELPER FUNCTION --- + function toFixed2(num) { return math.round(num * 100) / 100; } diff --git a/template.tpl b/template.tpl index 103d314..68b89b5 100644 --- a/template.tpl +++ b/template.tpl @@ -1,4 +1,4 @@ -___TERMS_OF_SERVICE___ +___TERMS_OF_SERVICE___ By creating or modifying this file you agree to Google Tag Manager's Community Template Gallery Developer Terms of Service available at @@ -10,15 +10,19 @@ ___INFO___ { "type": "MACRO", - "id": "cvt_temp_public_id", + "id": "cvt_WF8HR", "version": 1, - "securityGroups": [], "displayName": "Universal Conversions Variable", - "categories": ["UTILITY", "CONVERSIONS", "REMARKETING"], + "categories": [ + "UTILITY", + "CONVERSIONS", + "REMARKETING" + ], "description": "Generates the desired parameter from an array.\nBy stape.io.", "containerContexts": [ "WEB" - ] + ], + "securityGroups": [] } @@ -758,6 +762,49 @@ ___TEMPLATE_PARAMETERS___ "type": "EQUALS" } ] + }, + { + "type": "SELECT", + "name": "idFormatType", + "displayName": "Product ID Format", + "macrosInSelect": true, + "selectItems": [ + { + "value": "default", + "displayValue": "Default (use \u0027Product ID/SKU\u0027 field)" + }, + { + "value": "variantId", + "displayValue": "Variant ID (use \u0027Item Variant ID\u0027 field)" + }, + { + "value": "sku", + "displayValue": "SKU (use \u0027Item SKU\u0027 field)" + }, + { + "value": "customShopify", + "displayValue": "Custom Shopify (shopify_MARKET_prod_var)" + } + ], + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "keyVariantId", + "displayName": "Item Variant ID Key", + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "keySku", + "displayName": "Item SKU Key", + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "marketCode", + "displayName": "ISO Market Code (for Custom Shopify) e.g., GB, US", + "simpleValueType": true } ] @@ -775,6 +822,7 @@ const Object = require('Object'); /*============================================================================== ==============================================================================*/ +// Original Key fields const keyId = data.keyId; const keyPr = data.keyPr; const keyNm = data.keyNm; @@ -784,6 +832,14 @@ const keyImg = data.keyImg; const contentType = data.contentType; const taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent)); const keyDisc = data.keyDiscItemLevel; + +// --- NEW FIELDS --- +const idFormat = data.idFormatType; +const keySku = data.keySku; +const keyVariantId = data.keyVariantId; +const market = data.marketCode || ''; +// --- END NEW FIELDS --- + const customParamMap = data.customParams ? makeTableMap(data.customParams, 'cusKey', 'cusName') : {}; @@ -841,7 +897,9 @@ function getContentName(arr) { } function getContentIds(arr) { - const content_ids = arr.map((item) => item[keyId]); + // --- MODIFIED --- + const content_ids = arr.map((item) => getFormattedId(item)); + // --- END MODIFIED --- return content_ids; } @@ -850,7 +908,9 @@ function getItem(arr) { cat.push(arr[0][keyCat]); let item = { - ProductID: arr[0][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[0]), + // --- END MODIFIED --- ProductName: arr[0][keyNm], Price: arr[0][keyPr], ImageURL: arr[0][keyImg], @@ -878,7 +938,9 @@ function getContents(arr, platform) { if (platform === 'meta') { contents.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, item_price: arr[i][keyPr] }); @@ -886,7 +948,9 @@ function getContents(arr, platform) { if (platform === 'tiktok') { contents.push({ - content_id: makeString(arr[i][keyId]), + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_type: contentType, content_category: arr[i][keyCat], content_name: arr[i][keyNm], @@ -897,7 +961,9 @@ function getContents(arr, platform) { if (platform === 'twitter') { contents.push({ - content_id: arr[i][keyId], + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_name: arr[i][keyNm], content_type: arr[i][keyCat], num_items: qt, @@ -909,6 +975,7 @@ function getContents(arr, platform) { contents.push({ quantity: qt, item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0' + // Pinterest 'contents' array doesn't use ID }); } } @@ -933,7 +1000,9 @@ function getItems(arr, platform) { if (platform === 'ga4') { let itemObj = { - item_id: arr[i][keyId], + // --- MODIFIED --- + item_id: getFormattedId(arr[i]), + // --- END MODIFIED --- item_name: arr[i][keyNm], quantity: qt, price: arr[i][keyPr], @@ -955,7 +1024,9 @@ function getItems(arr, platform) { cat.push(arr[i][keyCat]); let itemObj = { - ProductID: arr[i][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[i]), + // --- END MODIFIED --- ProductName: arr[i][keyNm], Quantity: qt, ItemPrice: arr[i][keyPr], @@ -974,7 +1045,9 @@ function getItems(arr, platform) { if (platform === 'criteo') { let itemObj = { - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, price: arr[i][keyPr] }; @@ -984,7 +1057,9 @@ function getItems(arr, platform) { if (platform === 'gAdsOff') { items.push({ - productId: makeString(arr[i][keyId]), + // --- MODIFIED --- + productId: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, unitPrice: makeNumber(arr[i][keyPr]) }); @@ -992,7 +1067,9 @@ function getItems(arr, platform) { if (platform === 'pinterest') { items.push({ - product_id: arr[i][keyId], + // --- MODIFIED --- + product_id: getFormattedId(arr[i]), + // --- END MODIFIED --- product_name: arr[i][keyNm], product_quantity: qt, product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 @@ -1001,7 +1078,9 @@ function getItems(arr, platform) { if (platform === 'reddit') { items.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- category: arr[i][keyCat], name: arr[i][keyNm] }); @@ -1029,7 +1108,9 @@ function getItems(arr, platform) { } let itemObj = { - sku: arr[i][keyId], + // --- MODIFIED --- + sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID + // --- END MODIFIED --- product_name: arr[i][keyNm], quantity: qt, amount: price > 0 ? price : toFixed2(p * 100 * qt), @@ -1068,6 +1149,42 @@ function getItems(arr, platform) { Helpers ==============================================================================*/ +// --- NEW HELPER FUNCTION --- +// This function builds the ID based on the user's selection +function getFormattedId(item) { + // 1. Get the default ID (from the 'keyId' field) as a fallback + const defaultId = item[keyId] ? makeString(item[keyId]) : undefined; + + // 2. Check the format dropdown + switch (idFormat) { + case 'sku': + // Use 'keySku' field if available, otherwise fall back to default + return item[keySku] ? makeString(item[keySku]) : defaultId; + + case 'variantId': + // Use 'keyVariantId' field if available, otherwise fall back to default + return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId; + + case 'customShopify': + // Use 'keyId' (as Product) and 'keyVariantId' (as Variant) + const prodId = item[keyId]; + const varId = item[keyVariantId]; + + if (prodId && varId) { + // Build the custom string + return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId); + } + // If data is missing, fall back to default + return defaultId; + + case 'default': + default: + // Return the default ID + return defaultId; + } +} +// --- END NEW HELPER FUNCTION --- + function toFixed2(num) { return math.round(num * 100) / 100; }