# Welcome to Kameleoon Source: https://docs.kameleoon.com/home/welcome Kameleoon is a powerful, unified platform designed to help teams deliver better digital experiences through experimentation, feature management, and personalization. ## Choose your path Select the portal that best fits your role and what you are trying to build today. For marketers, product managers, and QA teams. Learn how to create, configure, and analyze experiments, manage personalizations, and use the Widget Studio. For engineers and technical teams. Find implementation guides, SDK references, API documentation, and edge-compute starter kits. *** ## Don't know where to start? Take this quick 3-question quiz to find the exact documentation you need for your tech stack and use case. *** ## Use docs with your AI assistant Ask questions, get step-by-step guidance, and explore the full Kameleoon knowledge base—directly from your AI tool. Open any article in Claude or ChatGPT with one click, or configure a persistent MCP server for Claude Desktop, Cursor, VS Code, and more. *** ## Core capabilities Blend experimentation, feature management, and personalization into one platform across web, mobile, and backend environments. Use natural language prompts to instantly generate variations, or use the visual editor to test copy, design, and layouts on any website or single-page app. Decouple deployment from release. Roll out new features progressively, run server-side experiments, and reduce risk with targeted feature flags. Deliver experiences tailored to specific users in real-time by leveraging behavioral data, CRM integrations, or your data warehouse. Access reliable, real-time results powered by Kameleoon's multi-statistical engine (CUPED, Bayesian, or Sequential testing). *** ## Explore Kameleoon's tech stack Deploy Kameleoon anywhere. We provide more than a dozen SDKs, REST APIs, and integrations to fit perfectly into your existing ecosystem. Implement experiments quickly using JavaScript, TypeScript, React, Vue, PHP, Python, Ruby, Go, or Java. [Explore Web SDKs →](../developer-docs/sdks/web-sdks/js-sdk) Bring feature flagging and experimentation to your native apps with iOS, Android, Flutter, and React Native SDKs. [Explore Mobile SDKs →](../developer-docs/sdks/mobile-sdks/ios-sdk) Programmatically control experiments, automate workflows, and inject custom data using Kameleoon's REST and JS APIs. [Explore APIs →](../developer-docs/apis/overview) # Create a key page Source: https://docs.kameleoon.com/user-manual/assets/advanced-targeting-tools/create-a-key-page Create a key page to target visitors who access specific URLs on your website and use it as a targeting condition in segments. Use a **key page** to target users who visit specific pages on your website. ## Create a new key page To create a new key page: 1. Click **Settings** > **Advanced tools**. 2. Click **New tool** at the top right of the page. 3. Click **Key page**. 4. Select one or more projects, name your key page, and enter the URL of the page you want to target. 5. Click **Create**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/advanced-targeting-tools/create-a-key-page/Capture-décran-2023-09-20-à-17.39.29.png) ## Define a key page as targeting condition To add a key page to a new segment, use the **Key pages** condition, available only in the Trigger Builder. 1. Navigate to the **Conditions** tab in the Trigger Builder. 2. Select the **Key pages** condition in the **Pages viewed** tab. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/advanced-targeting-tools/create-a-key-page/key-page-trigger-builder.png) 3. Select the key page in the dropdown menu and include or exclude visitors meeting the key page's criteria. # Create an acquisition channel Source: https://docs.kameleoon.com/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel Create custom acquisition channels to target visitors based on their traffic source, then use them as segment conditions or result filters. You can target visitors using acquisition channels. Kameleoon automatically creates five default channels: * Bing * Baidu * Google * Yahoo * Google Ads ## Create a new acquisition channel To create a new acquisition channel: 1. Click **Settings** > **Advanced targeting tools**. 2. Click **New tool** in the top right of the page. 3. Click **Acquisition channel**. 4. Select one or more projects, name your acquisition channel, and select an acquisition method in the dropdown menu: * Referrer URL * URL of current page * JavaScript ![Acquisition channel setup showing project selection, name, and acquisition method dropdown](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/advanced-targeting-tools/create-an-acquisition-channel/Capture-décran-2023-09-20-à-17.13.03-1.png) ### Referrer URL (regular expression) Indicate the URL the visitor must come from for Kameleoon to target them. ### URL of the current page (regular expression) Indicate the URL the visitor must visit for Kameleoon to target them. See [Regular expressions](./regular-expressions) for the syntax supported by the Referrer URL and URL of the current page fields. ### JavaScript Indicate the JavaScript assessing the visitor’s acquisition channel. Example: ```javascript theme={null} (function(){ if (/user-manual/param=123/.test(window.location.search) ) { return true } })(); ``` ### Additional information about acquisition channels Kameleoon checks the acquisition channel after each page view and stores it for the visit's duration. Each new visit has its own acquisition channel. If a visitor uses multiple acquisition channels across different visits (for example, visit 1 = SEA, visit 2 = Direct, and visit 3 = SEA), the visitor accumulates two visits for SEA and one visit for Direct in the Results page when displaying by visit. The Results page displays one visitor for SEA and one visitor for Direct in the visitor view. A visit or visitor can have several acquisition channels assigned at the same time. Kameleoon records matches in the page event. ## Add an acquisition method You can add acquisition methods to define your acquisition channel more precisely. Select the method and fill in the corresponding field. ![Acquisition method dropdown and configuration field for an acquisition channel](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/advanced-targeting-tools/create-an-acquisition-channel/Capture-décran-2023-09-20-à-17.15.10.png) Kameleoon links multiple acquisition methods with an **and** or **or** boolean. **And** limits the scope (visitor must fulfill all conditions), while **or** broadens the scope (visitors must fulfill one condition). Click the **X** to delete an acquisition method from your acquisition channel. ## Define an acquisition channel as targeting condition To add an acquisition channel to a new segment: 1. Select the **Acquisition channel** condition in the **Acquisition** tab. 2. In the dropdown menu, select the created channels and include or exclude the visitors meeting the acquisition channel criteria. ## Use an acquisition channel as a filter or breakdown on the Results page You can use acquisition channels to filter or break down the data on the Results page. Both options are in the **Audience** tab of the Results page settings sidebar. See [Results page settings](../../experiment-analytics/analyze-results/results-page/results-page-settings) for more on the settings sidebar. ### Filter by acquisition channel To filter Results page data by acquisition channel: 1. Open the settings sidebar and click the **Audience** tab. 2. Click **Add filter** > **Add filter**. 3. Select the **Acquisition channel** identifier. 4. Choose an operator, select one or more channels, and include or exclude the visitors who meet the criteria. 5. Click **Apply filter**. ### Break down by acquisition channel To break down Results page data by acquisition channel: 1. Open the settings sidebar and click the **Audience** tab. 2. Click the chevron to expand the **Breakdown audience** menu, then click **Add breakdown**. 3. Select **Acquisition channel** and specify its criteria. 4. Click **Apply**. You can apply this breakdown to all goals by checking the corresponding box below the dropdown list. When you break down results by acquisition channel, two system-generated entries may appear: * **RESTART**: Kameleoon assigns this label to sessions with no referrer. Sessions without a referrer typically occur when a visitor's session becomes inactive while the browser tab remains open and the visitor then returns to the page. * **DELETED**: Kameleoon assigns this label to visits linked to a deleted acquisition channel. Because the channel no longer exists, Kameleoon can't match the visit to a named channel. # Manage advanced targeting tools Source: https://docs.kameleoon.com/user-manual/assets/advanced-targeting-tools/manage-advanced-targeting-tools Access, create, and edit advanced targeting tools (acquisition channels and key pages) to refine visitor segmentation in your experiments. Advanced tools improve your visitor segmentation by adding conditions to new segments. Use criteria, like acquisition channels and key pages, to fine-tune your visitor segmentation. Advanced tools are only available in Premium accounts. If you want to upgrade your package, [contact Kameleoon](https://www.kameleoon.com/en/contact). ## Access advanced tools To access advanced tools: * Click **Settings** > **Advanced tools**. You can create two types of advanced tools: * Acquisition channels * Key pages ## Create a new advanced tool To create a new advanced tool: 1. Click **New tool** in the upper-right corner of the page. A pop-in appears. 2. Choose the type of advanced tool you want to create. The pop-in now shows your tool's setup options. ## Edit an advanced tool To edit an advanced tool: 1. Open the **Advanced tools dashboard**. 2. Click **Edit** at the end of a tool's card. 3. Click the **three-dots menu** to: * edit a tool's parameters * duplicate the tool * delete it ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/advanced-targeting-tools/manage-advanced-targeting-tools/Capture-décran-2023-09-20-à-15.37.51.png) You can't delete a tool used in a live experiment. An icon appears in the bottom-left corner of the confirmation pop-in, indicating which experiments use which tools. [How to create an acquisition channel](./create-an-acquisition-channel) # Regular expressions Source: https://docs.kameleoon.com/user-manual/assets/advanced-targeting-tools/regular-expressions Reference for the regular expression syntax Kameleoon supports in the matches the regular expression operator across segments, goals, and advanced targeting tools conditions. A regular expression is a pattern that matches text against a set of rules instead of a single exact string. Use a regular expression when a condition needs to match multiple variations of a URL, such as different query parameters or page numbers, rather than one exact address. Kameleoon conditions that offer a **matches the regular expression** operator use the syntax on this page, including segment conditions like Landing page URL and Referring website URL, goal URL conditions, and advanced targeting tools like acquisition channels and key pages. ## Regular expression syntax * `.` matches any single character. For example, `Kamele.on` matches `Kameleoon`, `Kamele0n`, and `Kamele8on`. * `*` matches the preceding character zero or more times. For example, `Kameleoo*n` matches `Kameleon`, `Kameleoon`, and `Kameleooon`. * `?` matches the preceding character zero or one time. For example, `Kameleoo?n` matches `Kameleon` and `Kameleoon`. * `+` matches the preceding character one or more times. For example, `Kameleo+n` matches `Kameleoon` and `Kameleoooon`. * `|` acts as an OR operator between two patterns. For example, `Kameleoon|Chameleoon` matches `Kameleoon` or `Chameleoon`. * `^` matches the start of the string. For example, `^Kameleoon` matches URLs starting with `Kameleoon`. * `$` matches the end of the string. For example, `Kameleoon$` matches URLs ending with `Kameleoon`. * `()` groups several elements, usually combined with `|`. For example, `Kameleoon (AB Testing|Conversion) Tool` matches `Kameleoon AB Testing Tool` or `Kameleoon Conversion Tool`. * `[]` matches any single character inside the brackets. For example, `/Kameleoon/[234]` matches `Kameleoon 2`, `Kameleoon 3`, and `Kameleoon 4`. * `-` defines a range inside `[]`. For example, `/kameleoon/[2-9]` matches every page from `Kameleoon 2` to `Kameleoon 9`. * `{}` sets a minimum and maximum number of repetitions of the preceding character. For example, `Kameleo{2,4}n` matches `Kameleoon`, `Kameleooon`, and `Kameleoooon`. Escape special characters (for example, `,`, `.`, `*`) with a backslash: `\,`, `\.`, `\*`. Regular expressions are case-sensitive. ## Examples * `^.{10}$` matches URLs containing exactly 10 characters. * `general\.(html|php)$` matches URLs ending with either `general.php` or `general.html`. * `/fr/` matches URLs containing `/fr/`. * `/annexes/.*\d{3}` matches URLs containing the fragment `/annexes/` and a three-digit number. * `/annexes/.*\d{2}.*test.html$` matches URLs containing the fragment `/annexes/`, a two-digit number, and ending with `test.html`. # Create custom data Source: https://docs.kameleoon.com/user-manual/assets/custom-data/create-custom-data Create custom data to target and segment visitors using your own data sources, such as CRM attributes, tag manager variables, or JavaScript. Creating custom data enables you to target visitors according to data already available, like user account information. All of your custom data is available under **Settings** > **Custom data**, where you can create and [manage custom data](./manage-custom-data). ## Create new custom data To create new custom data: 1. Click **Settings** > **Custom data**. 2. Click **New custom data** in the top right of the page. You must fill in several fields to set up custom data. ### Structure of the creation pop-in ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/custom-data/create-custom-data/custom-data-new.png) 1. Name the new custom data. 2. Select a project. 3. Tell Kameleoon how to retrieve custom data. ### Custom data name Give your custom data an easily identifiable name. If you select several websites, make sure you pull custom data from all of them. You can have a maximum of 255 active custom data points. ### Value retrieval #### Retrieval method Choose the custom data's value retrieval method from the dropdown menu. The main tag management tools are: * GTM data layer (`dataLayer`) * Commanders Act data layer (`tc_vars`) * Tealium (`utag.data` & `utag_data`) For these tag managers, you must indicate the name of the variable you want to retrieve. **If you choose to use GTM `dataLayer`**, proceed as follows to retrieve values: * If the key is in level 1 of the `dataLayer`, enter `name` in the **Variable Name** field. * If the key is in level 2 of the `dataLayer`, enter `properties.countryCode` in the **Variable Name** field. **If you choose Kameleoon Activation API**, set the value yourself from your website's JavaScript, typically your global script, using the Activation API's [`setCustomData()`](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#setcustomdata) method. **If you choose Kameleoon SDK method**, set the value using a [Kameleoon SDK](../../../developer-docs/sdks/web-sdks/js-sdk)'s `addData()` method. Use this retrieval method when you target visitors with Kameleoon Feature Experimentation. **If you choose Custom JavaScript code**, write the code directly into this field. Your code must return an object with two keys: `value`, with the value you want to provide for this custom data, and (optionally) `override`, with a boolean value (false by default). Kameleoon runs the code again every 100 ms for the first 3 seconds after the first invocation, then every 3 seconds after that. Here is an example: ```javascript theme={null} if (! window.myObject) return null; // custom data is not set, but code runs again later if (window.myObject) return {"value": window.myObject.x, "overwrite": true}; // returning a value and setting the custom data if (window.someObject.value == 3) return {"value": null} // stopping the periodic execution of the code, without setting the custom data ``` Avoid this retrieval method if possible. The code in this field must run synchronously and return its value immediately, so it can't call an asynchronous method such as `retrieveDataFromRemoteSource()`. Write your JavaScript in a different location instead, such as Tag Manager, an external script file, or inline script code in the HTML, and use the Kameleoon Activation API retrieval method to set the custom data. #### Type Select the structure of the data you want to retrieve from the dropdown menu. The data type can be: * single (example: `127`) * list (example: `126, 127, 128`; `shoes, dress, coat`) * count list of (example: `shoes: 3, dress: 1, coat: 1`) #### Format Select the format of the data you want to retrieve from the dropdown menu. The data format can be: * string (example: `woman dress`) * boolean (example: `female = "true/false"`) * number (example: `cart amount = "127"`) #### Scope Select your custom data's scope in the dropdown menu: * **Page:** For each new page, Kameleoon resets the data's value. For example, cart amount. * **Visit:** Kameleoon resets the data's value at each new visit. For example, geolocation. * **Visitor:** The unit of data relates to the visitor and not to the visit. For example, female = true. ### Options Click **Next** to access available options for your custom data. #### Use this data only locally for targeting purposes If you use your custom data solely for targeting, you don't need to store it on the server. In this scenario, use the `LocalData` API, which helps conserve server space because it doesn't count towards the limit of 255 active custom data points. However, you can't use custom data managed through the `LocalData` API for reporting on the Results page. If you choose to store custom data on the server, be cautious not to save sensitive information. Also, the value of any custom data you send to the server can't exceed 1,000 characters. #### Use this custom data as input for AI Predictive Targeting If your project has [AI Predictive Targeting](../../ai-predictive-targeting/overview) enabled, you can choose whether to include this custom data as an input for the predictive model. You can't use custom data that has **Use this data only locally for targeting purposes** enabled as model input, because Kameleoon doesn't send local data to the server. Don't activate this option if the custom data is highly targeted (for example, specific to individual visitors or very small segments). The model only accepts **Number** and **Boolean** custom data types. Avoid adding sensitive data such as dates, timestamps, or unique visitor identifiers (like email addresses or customer account numbers), as these elements are unsuitable for predictive models. #### Use this custom data as a unique identifier for cross-device history reconciliation When you activate this option, Kameleoon treats this custom data as a unique identifier for your visitors and uses it to map several Kameleoon visits to a unique user. For more information, see [cross-device experimentation](../../../developer-docs/cross-device-experimentation). #### Save the values for the targeting condition associated with the custom data point This option allows you to define a specific list of values that are then available for this custom data in the segment builder. In the **New custom data** pop-in (first page of the creation flow), click **Paste a sample code** to get a code example. Your code must synchronously return a JavaScript array of objects. Each object in this array should represent a possible value for the custom data and must include: * A `value` key, containing the actual data (its type must match the custom data's defined type). * A `label` key, providing a string description for that value. Example: ```javascript theme={null} return [{value:'q2f6hsbzpf', label:'First segment'}, {value:'8ney4225y65a', label:'Other segment'}, {value:'3h6vjtz26b', label:'Loyal users'}]; ``` If the format is incorrect, an error message appears when you click **Next**. Once you've entered your JavaScript, click **Next** > **Create**. To learn how to use custom data values in the segment builder, see the [create a segment](../segments/create-a-segment) article. ## Define custom data as a targeting condition To add a custom data to a new segment: 1. Select your desired data in the **Custom data** tab of the Conditions column. 2. In the dropdown menu, set the custom data value required to target a user. 3. Include or exclude visitors meeting the custom data condition. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/custom-data/create-custom-data/custom-data-segment.png) Depending on the type of custom data, the targeting condition's configuration changes ("is true/is false," "contains," "is equal to"). You can also select custom data as a targeting condition directly in a personalization's **Specific visitors** targeting rule, without creating a segment. For more information, see [set up and launch a personalization](../../web-personalization/set-up-and-launch-a-personalization). ## Filter or break down by custom data on the Results page On an experiment's Results page, you can filter or break down the results according to custom data associated with the experiment. [More about custom data in the Kameleoon developer documentation](../../../developer-docs/custom-data) # Manage custom data Source: https://docs.kameleoon.com/user-manual/assets/custom-data/manage-custom-data Find, edit, duplicate, and delete custom data from the Kameleoon Custom data dashboard using search, filter, and sort tools. ## Access the Custom data page To access the **Custom data** page in Kameleoon, click **Settings** > **Custom data**. ## Manage your custom data Each custom data has a card that displays its information. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/custom-data/manage-custom-data/custom-data-page.png) ### Find a custom data's index Kameleoon assigns each custom data a numeric **index** when you create it. The index uniquely identifies a custom data, and you need it when you reference the custom data from code, including: * The `CustomDataNameOrIndex` argument of `setCustomData()` in the [Activation API](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#setcustomdata). * The keys of the `metadata` object passed to `processConversion()` and `triggerGoal()` (for example, `{ 5: "Amex Credit Card" }` references the custom data with index `5`). * SDK methods that accept a custom data index. To view the index of a custom data: 1. Click **Settings** > **Custom data**. 2. Locate the custom data in the list. The numeric value next to its name (in the **INDEX** column) is the index used in code. You can also sort custom data by index; see [Sort custom data](#sort-custom-data). ### Find a custom data A search bar, a filter tool, and a sorting tool help you find a particular custom data. #### Filter custom data You can filter the displayed custom data based on several criteria: * Project * Key date * Creator ##### Project Filter the displayed custom data by project. Select the projects and click **Apply**. ##### Key date Narrow the list by key date: last modification or creation date. Select a date and click **Apply**. ##### Creator Narrow the list by the author who created each custom data. Check the authors and click **Apply**. #### Sort custom data Sort your custom data using any of the following criteria: * Name * Index * Project * Edition date * Creation date * Creator ##### Name Hover over **Name** and click the chevron to sort custom data in alphabetical (or reverse alphabetical) order. ##### Index Hover over **linked to** and click the chevron to rank the custom data by index. ##### Project Hover over **Project** and click the chevron to sort custom data in alphabetical (or reverse alphabetical) order by project. ##### Edition date Hover over **Edited** and click the chevron to sort custom data from newest edition to oldest or vice versa. ##### Creation date Hover over **Created** and click the chevron to sort custom data from newest creation to oldest or vice versa. ##### Creator Hover over **Creator** and click the chevron to sort custom data in alphabetical (or reverse alphabetical) order by creator. ### Edit a custom data To edit an existing custom data: 1. Locate the custom data's card. 2. Click **Edit** on the custom data card. A pop-in window opens. 3. Enter your changes in the pop-in. Click **Next** > **Save** to save your changes when they're complete. ### Duplicate a custom data To duplicate custom data: 1. Select a custom data. 2. Click the **three-dots menu**. 3. Click **Duplicate**. The Duplicate sidebar opens. 4. Click **Save** to validate the duplication. ### Delete a custom data To delete custom data: 1. Select a custom data. 2. Click the **three-dots** menu > **Delete**. ### Multiple selection To select multiple custom data and apply a common action to them: 1. Select a custom data. 2. Click the **three-dots menu**. 3. Click **Select**. The custom data becomes selectable and the **Grouped actions** sidebar opens. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/custom-data/manage-custom-data/grouped-actions.png) Two actions are possible: * **Select all** * **Deselect all** To select all custom data, check the **box** next to **Select all**. To deselect all, click the **box** again. # Create a goal Source: https://docs.kameleoon.com/user-manual/assets/goals/create-a-goal Create and configure goals to track conversions in your experiments, choosing from click tracking, scroll tracking, page access, time spent, and more. *Watch the [video tutorial](https://academy.kameleoon.com/how-to-plan-your-first-experiment/162248) in the Kameleoon Academy to learn how to set up goals.* An experiment alone can only tell you which variation each visitor saw, not whether that variation improved your site's performance. Goals close that gap: each one defines what counts as a conversion, so Kameleoon can measure whether your campaign achieves the results you're testing for. Link one or more goals to your experiment to start measuring. All goals you create in Kameleoon are available on the **Goals** page in the **Settings** menu. ## Access the Goals page To access your goals, click **Settings** > **Goals**. By default, Kameleoon includes the **Retention rate** goal for all sites associated with your account. This goal records the number of visitors who click at least once after landing on the first page of their visit. ## Create a new goal If you have protected your website using an iframe, the creation of a goal in the app may not be possible. ### Name and describe your goal ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-name-describe.png) To name and describe your goal: 1. Click **New goal** on the Goals page. The **Add a new goal** modal opens. 2. Fill in the details Kameleoon needs to identify your goal: * **Name**: A name for your goal. * **Project**: The site the goal applies to, chosen from a dropdown of your site URLs. * **Description** (optional): Up to 120 characters describing the purpose of your goal. This information helps you analyze results later. * **+ Add tags**: Attach one or more tags to the goal for later filtering. 3. Click **Next** to continue, or **Cancel** to close the modal without creating the goal. ### Choose a goal type ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-choose-type.png) Clicking **Next** opens the **Add a new goal** modal, showing a categorized grid of goal type cards: **Standard goals** * **Click tracking**: Triggered when a user clicks on a specific element. * **Time spent**: Triggered when a user spends a defined time on your site. * **Access to a page**: Triggered when a user visits a specific page. * **Retention rate**: Triggered when a user clicks after landing on the first page of their visit. * **Number of pages viewed**: Triggered when a user views a defined number of pages. * **Scroll tracking**: Triggered when a user reaches a certain percentage of the page height. **Advanced** * **Custom Goal**: Target highly specific visitor behaviors by defining your own events. * **Data warehouse tracking**: Sync events from Snowflake, BigQuery, or other external data sources. **Formula metrics** * **Ratio metric**: Measure relative performance by dividing one metric goal by another. To choose a type: 1. Select a card. 2. Click **Create** to continue, or **Back** to return to the previous modal without selecting a type. Clicking **Create** opens the **Define your goal** page, which uses a two-step progress stepper with **Step 1, Define triggering conditions** and **Step 2, Advanced settings**. Step 1 shows a green checkmark once it's complete, and Step 2 becomes available only after that. The fields on Step 1 depend on the goal type you selected. ### Click tracking ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-click-tracking.png) Choose how you want to define the element that triggers a conversion: * **With the Visual Editor**: Select the interactive element you want to track with the graphical editor. * **Using CSS selector**: Specify the CSS selector of the element you want to track. * **From an existing experiment**: Track an element that was previously added in one of your variations. * **With the Prompt-based Editor**: Describe the element you want to track, and Kameleoon generates the goal. Kameleoon greys out the Prompt-based Editor option and marks it as not yet available, matching the scroll tracking visual picker described below. #### Using CSS selector 1. In the **Click targets** field, enter one or more selectors, separated by commas. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-click-targets-css-selector.png) 2. Clicking **+ Add selector** opens a dropdown with the same four methods: with the Visual Editor, using a CSS selector, from an existing experiment, or with the Prompt-based Editor, which stays off. Choose one to add another target using a different method. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-add-selector-dropdown.png) #### Manually with the Visual Editor 1. An **Open visual editor** dialog asks "On which page (URL) do you want to track clicks?" and prefills the field with your site's URL. Click **Open Editor** to continue, or **Cancel** to close the dialog. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-open-visual-editor-modal.png) 2. The editor opens a split view: your live site renders on the left, and a **Define click tracking** panel opens on the right. Click each element you want to track in the preview area. Clicking an element: * Adds a highlighted outline box around the element in the preview, confirming your selection. * Auto-fills the panel's **Selector** field with the element's CSS selector. For example, clicking a button auto-fills a selector like `#pbx-btn-send`. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-visual-editor-split-view.png) 3. Use the field's clear (X) control or its three-dot options menu to adjust a selection, or click **+ Add selector** to pick more elements. Click **Next** to save your selections, or **Cancel** to close the editor without saving. #### Choose where to track this goal Regardless of which method you use to define the target element, choose where the click counts as a conversion: * **Anywhere on the site** (default): Records a conversion whenever a visitor clicks the element. If several pages share the same CSS selector, every matching click counts. * **Only on specific pages**: Records a conversion only when the click happens on a page that matches URL conditions you define. Selecting this option reveals a URL-condition builder: choose a match type (for example, **URL contains**) and enter a URL in the text field, such as `www.kameleoon.com/products/`. Click **+ Add target URL** to add further conditions. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-only-specific-pages.png) If several pages share the same CSS selector, use **Only on specific pages** to scope the goal to a single page instead of relying on a page-unique selector. If your server settings use the X-Frame-Options header to block your website from displaying in an iframe, the visual editor doesn't display your URL, and you can't add click tracking this way. You must change your server settings and add an exception for the `kameleoon.com` domain and subdomains. ### Scroll tracking ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-scroll-tracking.png) Step 1 for a Scroll tracking goal has two tabs, **Manual** (selected by default) and **Visual picker**. Kameleoon greys out Visual picker and marks it as not yet available, so the selected-element mode described below stays manual-only for now. Under **Manual**, configure: 1. **URL of the page**: The URL of the page on which you want to create the scroll tracker. 2. **visitor scrolls down to**: How Kameleoon measures scroll depth, chosen from a dropdown: * **the selected element**: Visualize the elements of the page by hovering over them, then click the one you want to use as the scroll target. The level appears directly below the selected element. * **a precise percentage** (default): Enter a percentage of the page height using the numeric stepper, for example 60%. The percentage measures from the top of the page: 0% places the level at the very top, 50% in the middle, and 100% at the very bottom. * **a height in pixels**: Enter a fixed height in pixels using the numeric stepper. 0 pixels corresponds to the very top of the page, and the maximum height correlates with the page size. If your server settings use the X-Frame-Options header to block your website from displaying in an iframe, the page preview doesn't display your URL, and you can't add scroll tracking this way. You must change your server settings and add an exception for the `kameleoon.com` domain and subdomains. ### Access to a page Enter the page a visitor must reach to convert. Choose one of three match types: * **contains**: Targets all pages sharing the same URL fragment. * **matches exactly**: Targets only the page with the exact URL you entered. * **matches the regular expression**: Targets a URL and its variations, according to the regular expression you enter. For help creating regular expressions, see [Regular expressions](../advanced-targeting-tools/regular-expressions). URL fragments and regular expressions are case-sensitive. **Example:** Your product page URLs are typically structured like this: `https://www.mysite.com/products/product-1` Here's how to apply an **Access to a page** goal for different scenarios: * **To a specific product page:** * Select **matches exactly** and enter the full, precise URL. For example: `https://www.mysite.com/products/product-1` * **To all product pages:** * Select **contains** and type in `/products/`. Your goal then applies to all pages that include the `/products/` fragment in their URL. * **For URLs with common variations (for example, "product" vs. "products"):** * If your product page URLs sometimes contain "product" instead of "products," use a regular expression. For instance, `https://www.mysite.com/products*` matches URLs containing either "products" or "product" at that point. ### Time spent Enter the time a visitor must spend on your website to convert. Choose a limit: * **more than**: The visitor must spend at least a given time on the page. * **exactly**: The time spent must match exactly. * **less than**: The time spent must not exceed a given duration. Enter a value and select a unit from the dropdown menu: seconds, minutes, or hours. ### Number of pages viewed Enter the number of pages a visitor must view to convert. You can choose an exact number, a minimum, or a maximum. ### Retention rate Kameleoon includes this goal by default for every new site in your account. The retention rate records the number of visitors who clicked at least once after landing on their entry page, so its Step 1 page needs no extra fields once you name and describe it. ### Custom goal ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-custom-goal.png) The Custom Goal setup page displays three collapsible cards in a two-column layout showing three ways to trigger a custom goal. An up-chevron marks a card as expanded. #### Option 1: Using a front-end event triggered by the JS Activation API Choose this option if the conversion event happens **in the user's browser (client-side)**. * **How it works:** Kameleoon provides a specific function (`Kameleoon.API.Goals.processConversion(ID)`, where `ID` is your goal's unique identifier) that you insert into your JavaScript code. When the user performs the action you are tracking, your code executes this function to send the conversion signal directly to the Kameleoon platform. Kameleoon generates the goal's ID once you create the goal. Click the **Copy** icon to copy the event code and implement it in your JavaScript (or pass it along to your development team for implementation). For more information on the `processConversion()` method, see [the Activation API reference](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#processconversion). #### Option 2: Using a back-end event via SDK (for example, Node JS) Choose this option if the conversion event happens **on your server (backend)**. This method is necessary for tracking actions that don't rely on the user's browser session (for example, successful payment processing). **How it works:** the Kameleoon server-side SDK uses a specific method (`trackConversion()`) to communicate the conversion event directly from your server to the Kameleoon platform. Click the **Copy** icon to copy the SDK event code and pass it to your backend development team for implementation. **Code example:** ```typescript theme={null} trackConversion({visitorCode, goalId}) ``` * **`visitorCode`** is the visitor's unique identifier you want to target. * **`goalId`** is the goal's unique identifier. Kameleoon generates the goal's ID once you create the goal. For more information on the `trackConversion()` method, see the [Node.js SDK reference](../../../developer-docs/sdks/web-sdks/nodejs-sdk#trackconversion). #### Option 3: Using custom JavaScript code Choose this option if you need to run specific JavaScript logic to determine when to trigger the goal. Custom JavaScript is the most flexible option and is useful if you need to dynamically calculate revenue or capture additional metadata before sending the conversion signal. * **How it works:** The code you enter in the wider code-editor panel runs automatically once your site is ready. Within that code, use the `triggerGoal()` method to fire a conversion. You can also optionally pass revenue and metadata parameters. Enter your custom JavaScript code directly into the panel, which prefills a comment scaffold showing usage examples. Click the fullscreen-expand icon to edit in a larger view. | Function | Description | Example code | | -------------------------------- | -------------------------------------------------- | ---------------------------------- | | `triggerGoal()` | Triggers a conversion with no parameters. | `triggerGoal();` | | `triggerGoal(revenue)` | Triggers a conversion with a revenue value. | `triggerGoal(49.99);` | | `triggerGoal(revenue, metadata)` | Triggers a conversion with revenue and extra data. | `triggerGoal(49.99, {5: "Gold"});` | For more information on using custom JavaScript code for custom goals, see Kameleoon's [developer documentation](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#triggergoal-custom-code-for-custom-goal). *** Alternatively, if you are initiating a conversion from a tag management system (for example, Google Tag Manager), use the [Kameleoon Command Queue](../../../developer-docs/apis/activation-api-js/api-reference/command-queue), which delays the command's execution until the Kameleoon engine finishes loading. To set this up in Google Tag Manager: 1. Go to **GTM** > **Tags** > **New** > **Tag Configuration** > **Custom HTML** and insert the following code: ```html theme={null} ``` 2. Select the trigger that sends the goal conversion call to Kameleoon. For more information about `Kameleoon.API.Goals`, see the [API reference](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#kameleoonapigoals). ### Data warehouse tracking ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-data-warehouse-bigquery.png) Select **Data warehouse tracking** from the **Advanced** group in the type grid to pull conversion data from an external warehouse instead of tracking events directly on your site. Step 1 shows a **Datawarehouse goal** header with a **Datawarehouse integration** dropdown—choose **BigQuery**, **Snowflake**, **Redshift**, or **Databricks**—plus a **Frequency** dropdown and a **Query** textarea. The remaining fields change based on the integration you choose, since each warehouse identifies and connects to your data differently: * BigQuery asks for a **BigQuery project ID** and a **Region**. * Snowflake asks for a **Warehouse**, a **Database**, and a **Schema**. * Databricks asks for a **Catalog** and a **Schema**. * Redshift asks whether your cluster runs in **Provisioned** or **Serverless** mode, a **Redshift cluster ID**, a **Region**, and a **Database**. For the exact fields and query format for your integration, see: * [Use BigQuery as a source](../../integrations/data-warehouses/bigquery/use-bigquery-as-a-source-pulling-events-for-reporting) * [Using Snowflake as a source](../../integrations/data-warehouses/snowflake/use-snowflake-as-a-source-pulling-events-for-reporting) * [Use Databricks as a source](../../integrations/data-warehouses/databricks/use-databricks-as-a-source-pulling-events-for-reporting) * [Use Amazon Redshift as a source](../../integrations/data-warehouses/amazon-redshift/use-amazon-redshift-as-a-source-pulling-events-for-reporting) ### Ratio metric ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-ratio-metric.png) Select **Ratio metric** from the **Formula metrics** group in the type grid to create a goal that divides one existing metric by another. Step 1 shows an A/B builder where you pick both metrics and preview the resulting formula live. For the full configuration walkthrough, see [Ratio Metrics](../../experiment-analytics/analyze-results/data-and-metrics/ratio-metrics#creating-a-ratio-metric). ## Metadata ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-metadata.png) Most goal types' setup flow ends with an **Attach additional data to conversions (Optional)** card. Before you add any, it shows "No metadata added yet" with the helper text "Enrich your conversions with custom properties to get deeper insights," alongside a **+ Add metadata** button. Ratio metric goals are the exception: since they combine two existing goals' data rather than capturing their own conversions, their setup flow ends directly with the A/B formula builder and has no metadata card. ### What's metadata? Metadata are additional properties attached to a goal conversion. They let you filter or break down your results by specific values (for example, payment method, delivery type). For example, if you want to track how many "Purchase" conversions happened with each payment type, you can use metadata to capture that value. ### Using metadata To use metadata: 1. Define a custom data. For more information, see [Create custom data](../custom-data/create-custom-data). 2. Link the custom data as metadata when creating or editing a goal by selecting it from the dropdown. Kameleoon won't consider custom data marked **Use this data only locally for targeting purposes**, because it doesn't send that data to its servers. ## Advanced settings Step 2 of the **Define your goal** page, **Advanced settings**, becomes available once Step 1 shows a green checkmark. It has three sections, **Properties** (which includes outlier handling for Custom goals), **Usage**, and **Data sharing**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-advanced-settings.png) ### Handle outliers *This option is only available for Custom goals.* [More about Winsorization at Kameleoon](../../experiment-analytics/statistical-methods/winsorization) ### Usage Three toggles control who can use the goal, all off by default: * **Make this goal only available for specific experiments**: By default, this goal is available for all experiments. Enable this toggle to select which ones can use it. * **Make this goal only available for specific personalizations**: By default, this goal is available for all personalizations. Enable this toggle to limit which ones can use it. * **AI Predictive Targeting**: Trains a model on this goal at the key moments you define, to target visitors by their likelihood to convert. Enabling this toggle reveals an **Add prediction trigger** button. Click it, then select the triggers you want to enable. [More about triggers](../triggers/create-a-trigger) ### Data sharing Select which integrations should receive data when this goal converts. A live "N integrations selected" counter tracks your selections, and a "Can't find it? Browse integrations" link opens the integrations catalog if none of the listed options match. **Google Analytics 4** After you set up the integration in Kameleoon, Kameleoon automatically sends goal conversions to GA4. GA4 doesn't need additional configuration to receive them. Each goal conversion appears in GA4 as an event with the same name as your Kameleoon goal. For example, if you name your goal `Purchase complete`, it appears in GA4 as a `Purchase complete` event. Each event includes the following parameters: * `type`: the goal type (for example, `CLICK` or `CUSTOM`) * `id`: the Kameleoon goal ID * `source`: always `Kameleoon` * `revenue`: only included when you pass a revenue value with the conversion You don't need to manually create these as Custom Events in GA4 for Kameleoon to send them. However, if you want to mark them as GA4 conversions or create audiences from them, you must configure those manually in GA4 after the data starts arriving. **Google Tag Manager:** if your site uses GTM, Kameleoon pushes goal events into the `dataLayer` rather than sending them directly to GA4. You must configure a GTM tag and trigger to forward those events to GA4. The `dataLayer` is also a useful place to verify that goal events are firing correctly before checking GA4 reports. For setup instructions, see [Google Tag Manager](../../integrations/as-a-destination/analytics/google-analytics-4/google-tag-manager). The code below defines the available goal types: ```typescript theme={null} const enum GoalType { CLICK = "CLICK", CUSTOM = "CUSTOM", SCROLL = "SCROLL", PAGE_VIEWS = "PAGE_VIEWS", URL = "URL", TIME_SPENT = "TIME_SPENT", RETENTION_RATE = "RETENTION_RATE", } ``` When you finish configuring Step 1 and Step 2, save the goal to add it to your account. ## The goal summary page Saving a goal takes you to its Summary page, where you can review the configuration you just set and continue managing the goal without returning to the Goals dashboard. ![Goal summary page showing the Definition and Usage cards, and a sidebar with Data sharing plus project and creation details](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/create-a-goal/goal-creation-summary-page-step-1.png) The page header shows the goal's name, status (for example, **Active**), and **Goal ID**. Below the header, two tabs organize the page: * **Insights**: the goal's performance data, including key metrics, a daily events timeline, and impacted campaigns. For details, see [Goal reporting](goal-reporting). * **Summary**: the goal's configuration, selected by default right after you create the goal. The Summary tab groups the goal's configuration into cards: * **Definition**: the triggering condition you set in Step 1, plus any metadata attached to conversions. Click **Edit** to change these settings. * **Usage**: the toggles you set in Step 2 that control who can use the goal, plus a **Set this goal as your project primary goal** toggle that defines this goal as your project's main success metric. Only one goal can be primary per project. Click **Edit** to change these settings. * **Data sharing**: the integrations you selected in Step 2 to receive data when the goal converts, or **No data sharing tools** if you didn't select any. Click **Edit** to change this setting. A sidebar next to the Definition and Usage cards lists the goal's **Project**, what it's **Linked to** (for example, an experiment, or **Not set** if it isn't linked to any campaign), its **Last modification** and **Created** dates, and its **Creator**. # Create a goal conversion from GTM Source: https://docs.kameleoon.com/user-manual/assets/goals/create-a-goal-conversion-tag-from-gtm Create Kameleoon goal conversion tags in Google Tag Manager to track events, revenue, and metadata without modifying your site's code directly. If you manage your site's tracking through Google Tag Manager, you can fire Kameleoon goal conversions directly from GTM tags, without modifying your site's code. This is useful when your development workflow runs through GTM, or when you want to keep all conversion tracking centralized in one place. This article covers how to create a basic conversion tag, a tag that records revenue, and a tag that passes metadata alongside the conversion. ## Create a tag 1. Click **New**. 2. Name your tag (for example, "Kameleoon - add to cart", "Kameleoon - transaction"). 3. Select **Custom HTML** in **Tag Configuration**, and insert this code: ```javascript theme={null} ; ``` * You must replace `123456` with the Kameleoon goal ID. 4. Select an existing trigger or create a new one (depending on the `dataLayer` format). ## Create a tag with revenue To create a tag with revenue: 1. Click **New**. 2. Name your tag. 3. Select **Custom HTML** in **Tag Configuration**, and insert this code: ```javascript theme={null} ``` `123456` is the Kameleoon goal ID, and `{{TransactionRevenue}}` is a variable with revenue attached to the conversion. 4. Select an existing trigger or create a new one (depending on the `dataLayer` format). ## Create a tag with metadata The example below adds a payment method and order ID as metadata to the goal conversion. To create a tag with metadata: 1. Click **New**. 2. Name your tag. 3. Select **Custom HTML** in **Tag Configuration**, and insert this code: ```javascript theme={null} ``` * **`123456`** is the Kameleoon goal ID. * **`{{ TransactionRevenue }}`** is a variable with revenue attached to the conversion. * **`{{ paymentMethod}}`** is a variable with the payment method attached to the conversion, and **`0`** is the index of the custom data where the variable will be added. * **`{{ orderID }}`** is a variable with the order ID attached to the conversion, and **`1`** is the index of the custom data where the variable will be added. 4. Select an existing trigger or create a new one (depending on the `dataLayer` format). # Goal reporting Source: https://docs.kameleoon.com/user-manual/assets/goals/goal-reporting Review a goal's health, recent activity, and the experiments impacting it from a single, consolidated Goal Reporting page. The Goal Reporting page gives you a single, consolidated view of a goal. Instead of piecing together information from the Goals dashboard tooltip, Live Events, and individual Results pages, you can confirm that a goal is collecting data and behaving normally, review its recent activity, and see how your experiments have impacted it over time. ## Access Goal Reporting In the Goals dashboard, click a goal's name to open its Goal Reporting page. ## Page header The header identifies the goal and sets the time range for the rest of the page: * **Goal name** and its current status (for example, **Active**). * A **health badge** (for example, **Needs attention**) that flags when the goal's recent activity warrants review, such as when anomalies are detected. * **Goal ID**, with a copy button. * A **time range selector** with preset ranges: **7D**, **14D**, **30D**, **3M**, and **6M**. The selected range applies to the KPI cards and the Daily events timeline. It does not affect the Impacted campaigns table (see below). ## Secondary actions Click the **⋮** button at the top of the page to access the following options: * **Copy link**: copies a direct link to the Goal Reporting page. * **Refresh**: reloads the page data. * **Edit settings**: opens the goal's settings so you can update its configuration. ![Secondary actions menu showing Copy link, Refresh, and Edit settings options](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/goal-reporting/goal-reporting-secondary-actions.png) ## Key metrics Three KPI cards summarize the goal's performance over the selected time range. Each card shows the metric value and a badge with the change against the previous equivalent period, in green for an increase or red for a decrease. * **Events**: the total number of times the goal was triggered. * **Conversion rate**: the percentage of visitors who triggered the goal over the period. * **Revenue**: the total revenue generated from goal conversions. Shows `0 €` when no revenue is tracked for the goal. ![Goal reporting KPI cards showing Events, Conversion rate, and Revenue metrics with period-over-period change badges](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/goal-reporting/goal-reporting-info.png) ## Daily events timeline The timeline charts the goal's daily events across the selected time range, so you can spot trends, seasonality, and gaps in collection at a glance. * **Expected range (± 2σ)**: the shaded band shows the statistically expected range for daily activity. Values inside the band are considered normal. * **Anomaly detection**: days that fall outside the expected range are highlighted on the chart, and an **anomalies detected** indicator shows how many were found (for example, *2 anomalies detected*). * Use the controls in the top right of the card to zoom in and out and to refresh the chart, and the chevron next to the section title to collapse or expand it. ![Daily events timeline chart showing goal activity with the expected range band and an anomaly highlighted](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/goal-reporting/goal-reporting-graph.png) ## Impacted campaigns The Impacted campaigns table consolidates experiment-level results into one view, so you can see how your experimentation program has affected the goal without opening each Results page individually. The table groups experiments by the direction of their impact, under **Positive impact experiments** and **Negative impact experiments** headings, with the highest-impact experiments listed first within each group. The table shows the following columns: * **Experiment**: the name of the experiment. * **Variation**: the variation measured against the goal. * **Status**: the experiment's current status. * **Improvement rate**: the variation's measured effect on the goal. * **Reliability**: the statistical reliability of the result. * **Confidence interval**: the confidence interval around the improvement rate. The statistical method behind the Reliability and Confidence interval columns depends on the experiment's status: **Online** experiments use sequential testing, and **Paused** or **Stopped** experiments use the classical frequentist method. Each row also includes quick actions to open the experiment and view its results. ### Which experiments appear An experiment appears in the Impacted campaigns table only when it meets the following criteria: * It is a web experiment that uses the goal and is currently **Online**, **Paused**, or **Stopped**. The table excludes draft and planned experiments, personalizations, and feature flags. * It has a measurable effect on the goal. The table excludes experiments with a 0% improvement rate. The table shows up to 10 experiments. Because of these filters, the number of experiments listed can be lower than the "Linked to" count shown on the Goals dashboard. ### Data freshness The Impacted campaigns table reflects pre-aggregated historical data. Kameleoon refreshes it once a day, and it is current through the previous day (for example, *Last updated: Yesterday at 02:00 UTC*). Unlike the KPI cards and timeline, the table is independent of the time range selector: it always shows the consolidated data rather than the selected period. ### Change an experiment's status You can change an experiment's status directly from the table using the **Status** dropdown. Only the transition from **Online** to **Paused** is available; Paused and Stopped experiments show a read-only badge. Because changing the status affects the experiment everywhere in the platform (not only on this page), a confirmation dialog appears before the change takes effect. # Manage goals Source: https://docs.kameleoon.com/user-manual/assets/goals/manage-goals Find, edit, duplicate, delete, and bulk-manage goals from the Kameleoon Goals dashboard using filters, sort options, and grouped actions. As your experimentation program grows, goals accumulate across experiments and sites. Without a central place to manage them, updating or finding a goal requires opening each experiment individually. The Goals page centralizes that work in one dashboard, from quick edits to bulk cleanup. ## Access the Goals page Click **Settings** > **Goals** to open the Goals page. ## Manage your goals Each goal appears as a row on the Goals page. ![goals-page](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/manage-goals/goals.png) Kameleoon includes the **Retention rate** goal by default for all sites associated with your account. It records the number of visitors who click at least once after landing on the first page of their visit. ### Find a goal Use the **search bar**, **filter tool**, and **sorting tool** to locate a specific goal. #### Filter goals To filter goals, select one or more criteria, then click **Apply**: * **Project**: show goals associated with specific websites. * **Type**: show goals of a specific type (for example, custom goals only). * **Status**: show goals by status, either Active or Inactive. * **Tag**: show goals with specific tags. * **Key date**: show goals by last modification or creation date. * **Creator**: show goals created by specific users. * **Tracking AI**: show goals that use tracking AI. #### Sort goals To sort goals, hover over a column header and click the chevron to toggle ascending or descending order. You can sort by: * **Name**: alphabetical order. * **Linked to**: campaign type. * **Edited**: edition date. * **ID**: goal ID. * **Created**: creation date. * **Website**: associated website, alphabetically. ### Edit a goal Clicking into an existing goal, whether from its row's name or its **Edit** action, opens the same **Define your goal** page used to create it, pre-filled with the goal's current configuration. The page uses the same two-step stepper described in [Create a goal](../../assets/goals/create-a-goal), with **Step 1, Define triggering conditions** and **Step 2, Advanced settings**. To edit an existing goal: 1. Find the goal's row and click its name, or click **Edit**, to open the **Define your goal** page. 2. Update Step 1 or Step 2 as needed, then save your changes. Editing a goal used by a running, paused, or planned experiment changes how Kameleoon measures your conversions, making results analysis more complex. Stop and duplicate the experiment before editing any of its goals. ### Duplicate a goal To duplicate a goal: 1. Find the goal's row and click the **three-dots menu** on the right. 2. Click **Duplicate**. The Duplicate sidebar opens. 3. Click **Continue** to confirm the duplication. ### Delete a goal Deleting a goal is permanent and can't be undone. If the goal is still linked to an experiment, feature flag, or personalization, remove it from that campaign first. Deleting a goal that's still in use breaks the campaign's tracking and can invalidate its historical results. To delete a goal: 1. Find the goal's row and click the **three-dots menu** on the right. 2. Click **Delete**. ### Select multiple goals You can select multiple goals and apply a bulk action to all of them at once. To enter multi-select mode: 1. Click the **three-dots menu** on any goal's row. 2. Click **Select**. The goals become selectable and the **Grouped actions** sidebar opens. To select or deselect all goals on the page, click **Options**, then **Select all** or **Deselect all**. The following bulk actions are available in the **Grouped actions** sidebar: * **Activate**: activate all selected goals. * **Disable**: turn off all selected goals. * **Manage tags**: apply or remove tags across selected goals. * **Export**: export selected goals as **PDF** or **Excel**. Set export options if needed, then click **Export Dashboard**. # Retention, bounce, and exit rates Source: https://docs.kameleoon.com/user-manual/assets/goals/retention-bounce-and-exit-rates Understand how Kameleoon measures retention and bounce rates and how these metrics compare to engagement and bounce rates in Google Analytics 4. If you use Kameleoon alongside Google Analytics 4, you may notice that their retention and bounce rate numbers don't match. This happens because the two platforms define these metrics differently: Kameleoon measures whether a visitor clicked on their landing page, while GA4 measures session engagement across a broader set of criteria. Understanding this difference is important before you interpret experiment results or compare data across tools. This article explains how each platform calculates retention and bounce rates, what those numbers mean in practice, and how to track bounce rate in Kameleoon. It also covers the exit rate, which exists in GA4 but has no equivalent in Kameleoon. ## Retention rate In Kameleoon, the retention rate measures **how many visitors and visits make a click on the first page they land on**. It is the **opposite of the bounce rate**. Kameleoon provides this as a pre-created goal, so you only need to attach it to an experiment to start tracking conversions. Use this goal only for experiments running on landing pages. Otherwise, the goal may trigger before the visitor is targeted, which prevents the test from recording the conversion. Tests only record post-targeting conversions. **Kameleoon example:** ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/retention-bounce-and-exit-rates/image-20-1-1920x774.png) The page's retention rate above is higher for the variations compared to the original version. In the screenshot above, approximately 55% of visits included at least one click on their landing page, which is also the targeted page (the click occurred after the targeting). GA4 defines the engagement rate as the **percentage of sessions that lasted more than 10 seconds, had conversion events, or included more than one page view**. It measures engagement **at the session level**, meaning that if a user interacts with a second page or triggers an event, GA4 considers the session engaged. The engagement rate is useful for analytics, providing insights into engagement across any page of the entire site. **GA4 example:** ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/retention-bounce-and-exit-rates/image-21-1.png) The site's engagement rate above is the percentage of engaged sessions compared to the total sessions. In the above screenshot, email led to 1,356 engaged sessions out of 1,630 total, equating to an engagement rate of 83.19%. See the [GA4 documentation](https://support.google.com/analytics/answer/12195621?hl=en) for more information on how to improve the retention rate. ## Bounce rate The bounce rate measures **how many visitors and visits leave the site without making any click on the page**. This metric is the **opposite of the retention rate**. You can add the code below to the **Kameleoon Global Script** to track conversions when visitors bounce (when they leave the page without clicking). This script runs on **all pages**, meaning the script records a bounce on the **last page the visitor viewed**. To track the **bounce rate for a specific page**, simply wrap the code in an `if` statement that checks the page URL. ```javascript theme={null} // set the right GOAL_ID below if(document.location.href.includes('cart')) { Kameleoon.API.Goals.processConversion(GOAL_ID) const handleBounceConversion = () => { Kameleoon.API.Goals.cancelConversion(GOAL_ID) document.removeEventListener('click', handleBounceConversion) } Kameleoon.API.Utils.addEventListener(document, 'click', handleBounceConversion) } ``` **Kameleoon example:** ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/retention-bounce-and-exit-rates/image-22-2-1920x777.png) The page's bounce rate above is lower for the variations compared to the original version (which is set as the winning variation since it has a higher conversion rate). In the screenshot, about 45% of visits did not make any clicks on their landing page. Note that, in this example, the code only tracks the bounce goal on the landing page. GA4 defines the bounce rate as the **percentage of sessions that lasted less than 10 seconds, had no conversion events, or included only one page view**. It measures bounce **at the session level**, meaning that if a user interacts with a second page or triggers an event, GA4 does not consider the session a bounce. The bounce rate is useful for analytics, providing insights into engagement across any page of the entire site. **GA4 example:** ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/retention-bounce-and-exit-rates/image-23-2.png) GA4 doesn't have a standard bounce rate report. You'll need to customize individual reports in order to see it. Bounce rate is the percentage of sessions that aren't engaged. In the case above, that would be 16.81% (because you can subtract the 83.19% of engaged sessions from 100%). See the [GA4 documentation](https://support.google.com/analytics/answer/12195621?hl=en) for more information on how to improve the bounce rate. ## Exit rate In GA4, for a given page, the exit rate is the ratio of sessions where that page was the last page viewed to the total number of sessions that included that page. **GA4 example:** * Session 1: page A → page B → page C * Session 2: page A * Session 3: page A → page C * Session 4: page B → page C → page A → page B The exit rate for page C is **66%**, as it was the last page viewed in 2 sessions (sessions 1 and 3) out of 3 sessions that included that page (sessions 1, 3, and 4). See more info in [GA4 documentation](https://support.google.com/analytics/answer/11080047?hl=en). Kameleoon cannot record the exit rate, because its data structure is based on visits and visitors rather than page views. # Revenue goal Source: https://docs.kameleoon.com/user-manual/assets/goals/revenue-goal Set up accurate revenue goal tracking in Kameleoon using tag managers, global scripts, or SDKs, with guidance on currency, format, and deduplication. Setting up a revenue goal in Kameleoon is straightforward, but it requires careful configuration to ensure everything works correctly. Every website has its own structure, purchase flow, tracking setup, and edge cases, so there's no one-size-fits-all solution. Without proper configuration, you risk missing key data points, double-counting transactions, or misinterpreting performance. The recommendations below cover the most common challenges Kameleoon users encounter and offer flexible solutions you can adapt to your setup. ## Implementation method Kameleoon supports multiple integration methods to track revenue goals effectively, depending on your experimentation type and technical setup. The sections below cover the recommended approaches for both Web Experimentation (WE) and Feature Experimentation (FE), including support for offline conversions via the Kameleoon Data API. ### Web Experimentation * **Using a tag manager:** Implement the [`kameleoonQueue`](../../../developer-docs/apis/activation-api-js/api-reference/command-queue#syntax), which ensures that conversion events trigger in sync with your analytics platform, maintaining data consistency across tools and improving your site's performance. For a detailed implementation guide in Google Tag Manager, refer to [this comprehensive guide](../../integrations/as-a-destination/analytics/google-analytics-4/google-tag-manager) and the configuration steps below to set up the revenue goal. * **[Using the global custom script:](../../project-management/manage-your-projects#global-script)** If `kameleoonQueue` isn't compatible with your setup, you can manually implement the revenue goal using the Activation API within the global custom script in the Kameleoon app. ### Feature Experimentation If you're using one of [Kameleoon's SDKs](../../../developer-docs/feature-experimentation/get-started/overview), you can track revenue goals using the [`trackConversion()`](../../../developer-docs/sdks/web-sdks/nodejs-sdk#trackconversion) method. Refer to the steps below to properly configure the revenue goal based on your SDK environment. ### Data API *(Applicable for both WE and FE)* For offline conversions, such as purchases completed over the phone or in-store, you can use the [Kameleoon Data API](../../../developer-docs/apis/data-api-rest/tutorials/processing-offline-goal-conversions-in-experiments#sending-offline-goal-conversion) to process offline goal conversions. This method is best when the conversion event occurs outside the user's online journey but still must be tracked and attributed within your Kameleoon experiments. ## Configuration ### Scope Before setting up your revenue goal, check whether the transaction confirmation always occurs on a single, consistent page. In some setups—especially those involving mobile apps, embedded checkouts, or third-party payment providers—the confirmation might appear in a web view or on a separate domain. Install Kameleoon on **all** relevant pages or views where confirmation may appear. This ensures Kameleoon reliably tracks conversions and captures all revenue data. If a user can complete a transaction across multiple URLs (due to variations in query parameters or different payment flows, for example), add **all relevant URLs** in Kameleoon's settings so that no conversions are missed. ### Transaction goal (access the confirmation page) Set a goal for accessing the confirmation page without a revenue amount. This goal helps you identify missing conversions for the revenue goal—delays in loading the revenue amount on the page (in the DOM, `dataLayer`, or a similar object) are a common cause of missed conversions. To set up a transaction goal in WE, choose between two goal types: * **Access to a page:** Set the URL in the goal's configuration. Depending on the URL's structure, use either **URL contains** or **Matches the regular expression** to account for variations in the URL (different paths or query parameters, for example). Avoid **Corresponds exactly to**, as the URL will likely include parameters that vary. When using the **Access to a page** goal type, if visitors can reload the confirmation page without being redirected (to the homepage, for example), the goal may trigger again. In this case, use the **converted visits** metric rather than **all conversions** on the Results page to avoid overcounting. * **Custom goal:** This goal type prevents the goal from converting again if the confirmation page reloads. Implement a custom goal in the global custom script and use a `sessionStorage` check to prevent duplicate conversions (see the example code below). This approach is recommended because you can reuse the same code to trigger the revenue goal, which improves performance by eliminating the need for a native **Access to a page** goal. ```javascript theme={null} if ( document.location.href.includes("/confirmation/") && !window.sessionStorage.getItem("kameleoonGoalConverted") ) { // Set an item in sessionStorage to prevent a second conversion if the page is reloaded window.sessionStorage.setItem("kameleoonGoalConverted", "true"); // Convert the transaction goal, replace the ID below Kameleoon.API.Goals.processConversion(355733); } else if (!document.location.href.includes("confirmation")) { // Remove the item from sessionStorage to allow conversion for a new transaction window.sessionStorage.removeItem("kameleoonGoalConverted"); } ``` ### Revenue format Ensure the revenue amount is properly formatted and validated before processing: * Replace commas (`,`) with periods (`.`) for decimal consistency. * Remove any spaces (can be present in amounts over 1,000). * Strip out currency symbols (such as `$`). * Convert the cleaned string into a numeric data type for accurate calculations. For example: ```javascript theme={null} let revenue = parseFloat(revenue.replace(",", ".").replace(/[^0-9.]/g, "").replace(/\s+/g, "")); ``` ### Currencies A website may support multiple currencies. Compare conversion values expressed in the same currency to avoid misleading results or incorrect analyses. Verify that the currency used in your tracking and reporting is consistent across all conversions. You can trigger a goal for each currency and/or use Kameleoon's conversion web service. The endpoint is provided below—ensure all arguments passed are valid. ``` ; ``` Never convert the same goal using amounts in different currencies. You must either create a separate goal for each currency, or unify them by setting up a global goal using the web service endpoint above. This service converts all amounts into a single currency of your choice. See the complete code example at the end of this article for a detailed implementation of the currency conversion web service. ### Order ID Add a [custom data](../custom-data/create-custom-data) to store the order ID and use it as [metadata](./create-a-goal#metadata) for the revenue goal. This links each conversion to the order ID, making it easier to match transactions with your analytics tool and investigate data discrepancies. Configure the custom data using the screenshot below as a reference. Adjust the format depending on whether the order ID on your site is a number or a string. New custom data configuration for Order ID To associate the **OrderID** custom data with your revenue goal: 1. In the goal creation flow, enter your goal's information and select **Custom goal** as the goal type. 2. Click **Next** > **Advanced settings**. 3. Toggle **Collect metadata, associating goals to custom data**. 4. Select **OrderID** from the dropdown. Associating OrderID custom data with the revenue goal See the complete code sample below for how to set the custom data value. ## Complete example *This example is for WE and can be used as a reference for implementation in FE.* Now that you've reviewed each key element—scope, transaction logic, revenue format, currency handling, and order ID tracking—you're ready to bring everything together. Below is a complete example of how to implement the revenue goal using the global custom script in Kameleoon. ```javascript theme={null} // Amount conversion using Kameleoon's currency conversion web service const convertCurrency = async (revenue, inputCurr, outputCurr) => { if (revenue == 0) return 0; const response = await fetch( `https://customers.kameleoon.com/kameleoon/currencies/convert?inputCurr=${inputCurr}&outputCurr=${outputCurr}&amount=${revenue}`, { method: 'GET', headers: { 'Content-Type': 'text/plain', }, } ); return response.json(); }; // Logic to: // - Convert the transaction goal // - Convert a goal for each currency // - Convert the global revenue goal // - Set the orderID custom data // repalce all goal IDs in the code if (document.location.href.includes("/confirmation/") && !window.sessionStorage.getItem("kameleoonGoalConverted")) { Kameleoon.API.Goals.processConversion(355733); // Transaction goal sessionStorage.setItem("kameleoonGoalConverted", "true"); let revenueLayer; Kameleoon.API.Core.runWhenConditionTrue(() => { revenueLayer = window.dataLayer?.find(layer => layer.ecommerce?.purchase?.actionField?.revenue); return revenueLayer; }, () => { let revenue = parseFloat(revenueLayer.ecommerce.purchase.actionField.revenue); const inputCurr = revenueLayer.ecommerce.purchase.actionField.inputCurrency; const orderID = revenueLayer.ecommerce.purchase.actionField.orderID; if (!isNaN(revenue)) { revenue = revenue.toString().replace(",", ".").replace(/[^0-9.]/g, "").replace(/\s+/g, ""); // Currency-specific revenue goals switch (inputCurr) { case 'GBP': Kameleoon.API.Goals.processConversion(355641, revenue); break; case 'USD': Kameleoon.API.Goals.processConversion(355643, revenue); break; case 'EUR': Kameleoon.API.Goals.processConversion(355642, revenue); break; default: Kameleoon.API.Goals.processConversion(355649); // Other currencies (no revenue passed) } // Global revenue goal in output currency (you can use ISO 4217 codes for each country) const outputCurr = "USD"; convertCurrency(revenue, inputCurr, outputCurr).then((convertedRevenue) => { // The custom data must be set before the goal is converted Kameleoon.API.Data.setCustomData("orderID", orderID); Kameleoon.API.Goals.processConversion(353518, convertedRevenue); }).catch((error) => { // Create a Custom Data to store potential errors for debugging Kameleoon.API.Data.setCustomData("[KAM] - currency webservice error", `error: ${error.toString()}; revenue: ${revenue}; inputCurr ${inputCurr}; outputCurr ${outputCurr}`); console.error("Error in currency conversion:", error); }); } }); } else if (!document.location.href.includes("/confirmation/")) { sessionStorage.removeItem("kameleoonGoalConverted"); } ``` Below is a screenshot of the `dataLayer` structure on the confirmation page for reference. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/revenue-goal/image-43-1-1536x631.png) Setting up a reliable revenue goal in Kameleoon requires careful alignment with your site's structure, `dataLayer` setup, and currency formats. By following the guidelines above, whether through a tag manager or global custom script, you can ensure accurate tracking, reduce discrepancies with your analytics tools, and gain more meaningful insights from your experiments. Test thoroughly and validate your implementation to maintain data integrity. # Set up a goal campaign Source: https://docs.kameleoon.com/user-manual/assets/goals/set-up-a-goal-campaign Apply a goal to an experiment or personalization to start tracking conversions and measuring campaign performance. Creating a goal makes it available in Kameleoon, but goals don't track conversions on their own. You must link each goal to the specific experiments or personalizations where you want to measure performance. Until you do, those campaigns have no way to record whether a visitor converted. ## Create a new goal [Read this article to learn how to create a new goal](./create-a-goal). ## Set up a goal in an experiment To apply a goal to an experiment: 1. Go to the **Goals** section on the editor's finalization page. The list of existing goals appears. 2. Select the goal you created. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/set-up-a-goal-campaign/Capture-décran-2023-11-22-à-11.48.12-1-1920x886.png) 3. Click **Next**. ## Set up a goal in a personalization To add a goal to a personalization: 1. [Open your desired personalization or create a new one](../../web-personalization/set-up-and-launch-a-personalization). 2. Open the **Goals** section in the finalization flow. The list of existing goals appears. 3. Select an existing goal. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/goals/set-up-a-goal-campaign/personalization-goals.png) # Edit an image Source: https://docs.kameleoon.com/user-manual/assets/images/edit-an-image Resize, crop, draw, add shapes, and apply filters to images directly in the Kameleoon Media library Image editor. The Kameleoon Media library is the repository where all visual assets you upload to Kameleoon live. It includes a robust Image editor that lets you modify images directly within the platform. This article helps you access and use the editor. ## Access the Image editor * **During upload:** After uploading a new image, the editor opens automatically, letting you make adjustments before saving the image to your library. * **From the Media library:** To edit an existing image in the library, hover over an image and click the three-dots menu. Select **Edit Image** from the dropdown menu. The Image editor opens. Accessing the Image editor from the Media library ## Structure of the Image editor ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/images/edit-an-image/Capture-decran-2024-11-05-a-15.01.04-1920x1004.png) * **The top toolbar** includes options to: * Resize * Crop * Flip * **The left toolbar** provides more in-depth editing tools, such as: * Frame * Draw * Shapes * Filters * **In the top right** of the Image editor, you can expand the pop-up, undo your changes, reapply them, or swap the current image for another without leaving the editor. ## Edit image content Use the left toolbar to make changes to your image. You can resize, crop, add shapes, or apply filters. ### Frame The Frame tool lets you resize, crop, or flip your image. #### Resize To resize your image, click **Resize**. Enter the desired dimensions or use the slider to adjust the image's size. #### Crop Select **Crop** if you need to trim your image's edges. You can choose a preset dimension or freely select the crop area. #### Flip Use the **Flip** tool to flip your image horizontally or vertically. ### Draw Use **Draw** to draw on your images. Select the brush size and color to personalize your drawing. ### Shapes Insert shapes using the **Shapes** tool. You can adjust the size, color, and position of the shapes as needed. ### Filters Apply filters to your image using **Filters**. You can also adjust your image's brightness, noise, or threshold by clicking **Adjustments**. ## Edit image details You can edit metadata such as the image name, tags, or description by clicking **Edit Image Details** at the bottom of the editor. ## Save or discard changes * **Save image**: Click **Save image** to save your changes and return to the Media library. * **Discard changes**: Close the editor without saving to discard your changes. # Manage your images Source: https://docs.kameleoon.com/user-manual/assets/images/manage-your-images Search, filter, sort, and perform actions on images in the Kameleoon Media library, including editing, tagging, duplicating, and deleting. The Kameleoon Media library is the repository where all visual assets you upload to Kameleoon live. It offers a variety of tools to help you manage your images. This article covers using the search and filter options, accessing image-specific actions, and customizing your library view. ## Searching and filtering images * **Search bar:** Located at the top of the Media library, the search bar lets you find images by entering a keyword or file name. * **Filters:** On the Media library's right side, you can filter images by file type (for example, JPEG, PNG, GIF, WEBP, SVG). Select a file type, then click **Apply** to update the displayed images. ## Image display options The Media library provides two display options: * **Grid view (default view):** Displays images in a thumbnail format. * **List view:** Displays images in a list format with additional details, such as modification date, creator, and tags. Switch between views by clicking their icons in the upper-right corner. Grid and list view options in the Media library ## Sorting images Click the **Sort** icon to change the images' display order. You can sort images by: * **Modification date** (newest or oldest first) * **Upload date** (newest or oldest first) * **Alphabetical order** (A to Z or Z to A) Sort options for images in the Media library ## Managing individual images Click an image's **three-dots** menu to access available secondary actions: * **Edit image:** Opens the selected image in the Image editor. * **See image details:** Displays the image’s metadata, including size, format, dimensions, creator, creation date, and modification date. You can also copy the image link or edit the image's name, alt text, and description. * **Manage tags:** Tags help organize images, making them easier to find when searching. Click **Add a tag** in the **Image details** panel to assign tags to the image. * **Download image:** Allows you to download a copy of the image to your device. * **Duplicate image:** Creates a copy of the image within the library. * **Select multiple images:** Enables grouped actions by selecting multiple images. * **Delete:** Permanently removes the image from the library. # Upload an image Source: https://docs.kameleoon.com/user-manual/assets/images/upload-an-image Access the Kameleoon Media library and upload images from your computer or via URL to use in your campaigns. The Kameleoon Media library is the repository where all visual assets you upload to Kameleoon live, letting you manage images used in your campaigns. This article helps you access the Media library, understand its structure, and upload new images. ## Accessing the Media library To access the Media library, click **Studio** > **Media library**. ## Media library structure ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/images/upload-an-image/media-library.png) In the Media library, you'll find: * **Search bar:** At the top of the page, you can search for images by name. * **Filter options:** Filters on the right let you refine images by file type. * **Image display:** The Media library displays images in a grid view by default. You can toggle between grid and list view for easier navigation. * **Add an image button:** Upload new images using this button. * **Image details and options:** Hover over any image to access details and options. ## Uploading a new image Follow these steps to upload a new image to the Media library: 1. Click **Add an image**. 2. Choose a project to associate the image with in the dropdown > click **Continue**. 3. Choose an upload method: * **Upload from computer:** Select an image file from your computer. * **Upload via URL:** Provide the URL of the image you want to use. The Media library accepts the following file formats: PNG, JPG, GIF, SVG, and WebP, with a maximum file size of 3 MB. 4. After you select an image, the **Image editor** opens, letting you edit your image (for example, resizing, cropping, or adding shapes). 5. Click **Save Image** to add it to the Media library. Kameleoon automatically removes images from your library after 12 months if they're not associated with an active campaign or widget. # Create a segment Source: https://docs.kameleoon.com/user-manual/assets/segments/create-a-segment Learn how to use the Kameleoon Segment Builder to define and target visitor groups based on their characteristics and behavior. *For a video tutorial on targeting segments, visit [the Kameleoon Academy](https://academy.kameleoon.com/how-to-plan-your-first-experiment/162036?utm_source=user_manual\&utm_medium=online).* Use segments to target your visitors. You can create, modify, and duplicate segments with the Kameleoon Segment Builder. ## Access the Segment Builder ### From the app To access the Segment Builder in the Kameleoon app, click **Settings** > **Segments**. Click **New segment**. Kameleoon opens the segment creation pop-in. ### From the A/B finalization flow You can create a new segment directly from the A/B test finalization flow, which opens the segment creation pop-in. ## Create a new segment ### Types of segmentation Kameleoon manages two types of segmentation: * ***A posteriori* segmentation** occurs after the test. *A posteriori* segmentation only shows if a variation's performance has increased in a given segment. Kameleoon doesn't manage this type of segmentation. Your web analytics solution (for example, Google Analytics, KISSmetrics, or AT Internet) usually handles it. * ***A priori* segmentation** targets a specific fragment of your traffic because the test only applies to this segment. For instance, if you have a website with international users, you might want to run an experiment for only users in France. Similarly, you could test an offer that only displays to a specific segment (for example, first-time visitors). Kameleoon manages this type of segmentation. ### Enter segment information Fill in: * Your segment's name. * The site for which you are creating this segment. * The type of campaign for which you are creating this segment: * **Web experiment/Personalization** * **Feature flag/SDK experiment** * **All experiment types** The available conditions in the next step depend on the campaign type you choose. Segment creation pop-in with name, site, and campaign type fields You can also add a description, tags, or choose to use the segment in [Audiences](../../ai-opportunity-detection/audiences-segment-analysis). You can update your segment information in the final step of the creation process within the recap panel. ### Select and set a condition To add a condition, drag and drop a condition from the left-hand list to the field on the right. Condition settings pop-in after dragging a condition into the segment The pop-in shows the condition's settings after you drag and drop it. Configure the condition as needed. ### Delete a condition Click the **X** icon to delete a condition. ### Narrow or add a new condition To add a new condition when you've already selected a condition, two options are available: * **Narrow an existing condition:** Further specifies a condition, similar to using mathematical parentheses: (Condition A and Condition B) and Condition C. * **Add a new condition:** Adds a condition at the same level: Condition A and Condition B and Condition C. For more information, refer to the [FAQ](../../faq). #### Quickly add conditions You can quickly add multiple conditions of the same type by clicking the small **+** icon on the right side of the condition. This feature is available for conditions frequently used in large numbers. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/segments/create-a-segment/specific-ip-address.png) When you add multiple identical conditions, Kameleoon links them with an **OR** operator. The dropdown menu updates to **is including any of** or **is not including any of**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/segments/create-a-segment/any-ip-address.png) Once you add multiple identical conditions, you can delete them individually. ### Use logical links between conditions Create effective targeting segments by combining various conditions to define your audience. The use of the logical operators AND/OR is crucial in this process. Additionally, understanding the distinction between “include” and “exclude” conditions is essential for accurate segment configuration. #### Logical operators: AND vs. OR **AND operator:** use the AND operator (select **If all** from the dropdown) when you want the visitor to meet all specified conditions to include them in your segment. This operator narrows your target audience to those who meet all defined criteria. * **Example:** If you set an AND condition for "New Visitors" AND "On Mobile," Kameleoon only includes visitors who are both new to your site and accessing it via a mobile device. **OR operator:** use the OR operator (select **If any** from the dropdown) when you want the visitor to meet any one of the specified conditions to include them in your segment. This operator broadens your target audience. * **Example:** If you set an OR condition for "New Visitors" OR "On Mobile," Kameleoon includes visitors who are either new to your site or accessing it via a mobile device. Kameleoon prioritizes AND over OR. ### Is versus Is not conditions **Is condition:** select an **Is** condition to include visitors who meet specific criteria in your target segment. * **True:** Visitor possesses the characteristic (for example, desktop device type is true). * **False:** Visitor doesn't possess the characteristic (for example, desktop device type is false). **Is not condition:** specify an **Is not** condition to exclude visitors who meet specific criteria from your target segment. * **True:** Visitor doesn't possess the characteristic (for example, desktop device type is false, meaning the device type is **not** desktop, so Kameleoon includes them). * **False:** Visitor possesses the characteristic (for example, desktop device type is true, meaning the device type **is** desktop, so Kameleoon excludes them). ### Practical examples and common pitfalls * **Including New Visitors on Smartphone:** * **Condition 1:** New Visitor (Is) * **Condition 2:** On Smartphone (Is) * **Logical link:** AND * **Outcome:** Kameleoon includes only new visitors accessing the site on mobile devices. Segment definition combining New Visitor and On Smartphone conditions with AND * **Excluding Returning Visitors on Desktop:** * **Condition 1:** New Visitors (Is) * **Condition 2:** On Desktop (Is not) * **Logical link:** AND * **Outcome:** Kameleoon excludes returning visitors on desktop. Segment definition excluding returning desktop visitors with an Is not condition * **Including Visitors from Specific Acquisition Channels:** * **Condition 1:** Acquisition Channel is SEO (Is not) * **Condition 2:** Acquisition Channel is SEA (Is) * **Logical Link:** AND * **Outcome:** Kameleoon includes only visitors arriving via SEA campaigns (excluding SEO). Segment definition targeting SEA traffic while excluding SEO with AND * **Excluding Checkout Process Pages:** * **Condition 1:** URL contains /cart (Is not) * **Condition 2:** URL contains /login (Is not) * **Condition 3:** URL contains /payment (Is not) * **Logical Link:** OR * **Outcome:** Kameleoon excludes pages related to the checkout process. If you aren't sure if you configured everything correctly, [use the simulation panel](../../experimentation/web-experimentation/configure-and-launch/simulate-a-campaign). ## Review and save After you click **Next**, a summary panel opens on the right. Review the settings and definition. * **Settings:** Displays the segment name, project, campaign type, description, tags, and whether an audience uses the segment. Click the **pencil** icon to edit any of these settings, then click **Save changes**. * **Definition:** Displays your segment's definition. You can view a visual summary under the **Block Summary** tab or a text-based summary under the **Written Summary** tab. Click **Create segment** to finish. ### Targeting conditions Kameleoon offers targeting conditions to include or exclude specific visitor types. The targeting conditions offered in the Segment Builder depend on the chosen campaign type. #### Visitor characteristics * **Device type:** includes or excludes visitors based on their device (tablet, smartphone, computer). You can also find this feature in the personalization creation flow. * **Browser:** includes or excludes visitors based on their web browser. You can specify the browser version or target versions that are newer or older to other versions. * **Geolocation:** includes or excludes visitors according to geolocation data (country, state, city). * **Kameleoon segment:** This condition lets you include or exclude visitors based on their segment. You can import an existing segment into the Segment Builder, which then associates its targeting conditions with the new segment you're creating. For example, suppose you import the Kameleoon segment Returning visitors on mobile that you created and often use as a base. You adapt it to a specific campaign by adding the **Goal converted** condition, and you create this new segment. * **Note:** You can't modify the conditions that make up the imported segment: Kameleoon treats the segment as a whole. When you use the same segment as a base in several segments associated with active campaigns, any change affects all relevant campaigns. Therefore, be careful when modifying your core segment. You can only nest segments one level deep. You can't add a segment that already contains a Kameleoon segment inside another one. You can use the **Kameleoon Segment** condition several times to combine multiple segments. * **Visitor IP Address:** includes or excludes visitors based on their IP address. * **New or returning:** includes only new visitors (first visit) or those returning to your website. * **Screen resolution:** includes or excludes visitors based on the size of their screen (in pixels). * **Operating system:** includes or excludes visitors based on their operating system (for example, Windows MacOS). * **Browser language:** includes or excludes visitors based on the language set in their browser. * **Ad blocker:** includes or excludes visitors using an ad blocker. #### Campaign exposure * **Web experiment:** includes or excludes visitors exposed to a specific experiment who saw a specific variation or any of the variations (including the original). The condition considers the current visit or previous visits. The selected experiment can be Online, Paused, or Stopped. * **Personalization:** includes or excludes visitors exposed to a specific personalization. The condition considers the current visit or previous visits. The selected personalization can be Online, Paused, or Stopped. * **Feature Flag:** includes or excludes visitors exposed to a specific feature flag. * **Exclusive Campaign:** excludes visitors already taking part in an experiment, a personalization, or both: * If another campaign already exposed a visitor (in the current or a previous visit), that visitor won't be eligible for your current experiment. * Other experiments that don't have the **Exclusive campaign** condition can still target a visitor, even if they're part of an exclusive campaign. In other words, the **Exclusive campaign** condition doesn't prevent participation in other experiments; it only stops visitors from entering experiments that share the same exclusivity requirement. You can't delete a campaign or variation that a segment uses as a targeting condition. #### Conversions * **Converted goal:** includes or excludes visitors based on whether they converted on a certain goal during the current visit or during previous visits. * **Likelihood to convert:** Kameleoon's machine-learning algorithms predict each visitor's purchase or engagement intention in real-time. You can use this condition to automatically trigger personalization campaigns. For more information, refer to the [AI Predictive Targeting documentation](../../ai-predictive-targeting/target-users-based-on-likelihood-to-convert). #### Technical * **Custom JS:** Define a custom JavaScript function that returns true or false to determine whether Kameleoon includes a visitor in an experiment. There are three available options: * **Run the condition in loop until it returns True, False or Timeout:** The function executes immediately and continues running until it returns a decision. It runs every 75 milliseconds before DOM Ready, then every 250 milliseconds after DOM Ready. To stop the execution, return `true` to target the visitor or return `false` to exclude them. For example: ```javascript theme={null} if (window.dataLayer && window.dataLayer.some(layer => layer.pageType === "homepage")) return true; if (window.dataLayer && window.dataLayer.some(layer => layer.pageType !== "homepage")) return false; ``` * **Wait until the page has loaded:** Kameleoon only checks the condition after DOM Ready, executing every 250 milliseconds until it returns a decision. To stop the execution, return `true` to target the visitor or return `false` to exclude them. If the function returns `undefined`, it keeps running in a loop at the defined intervals. * **Run the condition asynchronously:** Use this option when waiting for a response from an API or external service before deciding whether to include the visitor. Instead of returning a value, call `setTargeting(true)` or `setTargeting(false)`. For example: ```javascript theme={null} const userId = localStorage.getItem("user\_id"); if (!userId) { setTargeting(false); return; } const apiUrl = `https://api.example.com/check-segment?userId=${userId}`; fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json" } }) .then(response => response.json()) .then(data => { setTargeting(data?.isInSegment === true); }) .catch(() => { setTargeting(false); }); ``` * **Custom data:** This set of conditions lets you include or exclude visits based on custom data. For more information, see the [advanced targeting tools](../advanced-targeting-tools/manage-advanced-targeting-tools) article. If you set values when creating your custom data, you can select them in the Segment Builder. Choose **is among the values** in the dropdown and check the values. You can also select custom data directly as a targeting condition for specific visitors in a personalization, without creating a segment. This option isn't yet available for experiments or feature flags. * **Browser cookie:** includes or excludes visitors based on the presence of a cookie in their browser and, if possible, its value. * **SDK Type:** includes or excludes visitors based on the SDK type used. * **App version:** includes or excludes visitors based on the version of the app. #### Acquisition * **Landing page URL:** includes or excludes visitors based on the URL of their landing page. * **Traffic type:** includes or excludes visitor based on the website they came from, such as a search engine, an email link, or another referring website. * The **traffic type** condition depends on either the landing page URL or the referrer URL: * **Emailing** if the landing page URL contains the pattern `utm_medium=email or utm_medium=newsletter`. * **Search engine (advertising, SEM)** if the landing page URL is from a Google domain and has `aclk` as path, **or** has the parameter `aclk` in the query. * **Search engine (organic, SEO)** if the referrer URL contains `google`, `bing`, `yahoo`, `yandex`, or `baidu`. * **Direct** if the referrer URL is empty (`null`). * **Referrer sites (affiliation)** if you have a landing page URL and a referrer URL that doesn't contain `google`, `bing`, `yahoo`, `yandex`, or `baidu`. * **Referring website URL:** includes or excludes visitors based on the referring URL. * **Acquisition channel:** includes or excludes visitors based on the marketing or referral source that brought them to your website. Landing page URL and Referring website URL support the **matches the regular expression** operator for pattern-based matching. See [Regular expressions](../advanced-targeting-tools/regular-expressions) for the supported syntax. # Manage targeting segments Source: https://docs.kameleoon.com/user-manual/assets/segments/manage-targeting-segments Find, filter, sort, edit, duplicate, and delete targeting segments from the Kameleoon Segments dashboard. ## Access the Segments page To access the Segments page in Kameleoon: * Click **Configure** > **Segments** in the left sidebar. ## Manage your segments Each segment is represented by a card that indicates its main information. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/segments/manage-targeting-segments/segments-dashboard.png) ### Find a segment A **search bar**, a **filter tool**, and a **sorting tool** help you find a particular segment. #### Filter segments You can filter the displayed segments based on several criteria: * Project * Tag * Campaign type * Linked to * Key date * Creator * Characteristics * Goal ##### Website Filter the displayed segments by their associated website. Check the websites and click **Apply**. ##### Campaign type *This filter is only available for Web experimentation + Feature experimentation customers.* Filter the displayed segments by their associated campaign type. Here are the conditions for these filter criteria: * **Feature flag/SDK experiment:** Segments containing the Visitor code condition or the Feature experimentation conditions, not used in any experiment or personalization. * **All experiment type:** Segments used in a Feature experiment that also contain conditions common to both Feature and Web experimentation. * **Web experiment/Personalization:** All other segments. ##### Linked to Filter the displayed segments by whether they're linked to a campaign. ##### Tag Filter the displayed segments by their associated tags. Select or search for a tag. Kameleoon displays the segments associated with those tags on the page. You can also check or uncheck all tags. Click **Apply**. ##### Key date Filter the displayed segments by their associated key date: last modification or creation date. Select a date then click **Apply**. ##### Creator Filter the displayed segments by their creator. Check a creator then click **Apply**. #### Characteristics Filter the displayed segments by whether they're used in Audiences. Check a filter then click **Apply**. #### Sort segments The sorting option lets you sort your segments according to several criteria: * Name * Linked to (Campaign) * Edition date * Segment ID * Creation date * Website ##### Name Hover over **Name** and click the chevron to sort segments in alphabetical (or reverse alphabetical) order. ##### Linked to Hover over **Linked to** and click the chevron to rank segments based on campaign type. ##### Edition date Hover over **Edited** and click the chevron to sort segments from newest edition to oldest or vice versa. ##### Segment ID Hover over **ID** and click the chevron to sort segment IDs in ascending or descending order. ##### Creation date Hover over **Created** and click the chevron to sort segments from newest creation to oldest or vice versa. ##### Website Hover over **Website** and click the chevron to organize segments by website, in alphabetical or reverse alphabetical order. ### Columns Click **Columns** to edit which columns appear in the Segments table. Uncheck a box to remove the associated column from the table. Select your sorting criteria under **Sort by**. Click **Apply** to apply your changes. Click **Save as view** to save your selection as a view. You can then select the view from the **Views** menu. ### Edit a segment To edit an existing segment: 1. Navigate to a segment's card. 2. Click **Edit**. A pop-up opens. 3. Make your changes in the **Segment Builder**. 4. Click **Edit** to save your changes once they're complete. ### Duplicate a segment To duplicate a segment: 1. Click the **three-dots menu** on the right side of a segment's card. 2. Click **Duplicate**. The **Duplicate** sidebar opens. 3. Click **Save** to validate the segment's duplication. ### Delete a segment To delete a segment: 1. Click the **three-dots menu** on the right side of a segment's card. 2. Click **Delete**. ### Multiple selection You can select multiple segments at the same time and apply a common action to them. For example, you can delete several segments simultaneously. To select multiple segments: 1. Click the **three-dots menu** on the right side of a segment's card. 2. Click **Select**. The segments become selectable and the **Grouped actions** sidebar opens. Several actions are possible: #### Select all/deselect all To select all segments, check the box next to **Select all**. To deselect all segments, click the **checkbox** again. Kameleoon applies the actions you perform to all selected segments. #### Manage tags In the **Grouped actions** sidebar, click **Manage tags**. #### Export segments In the **Grouped actions** sidebar, click **Export**. Select a format: PDF or Excel. Set the export options if needed. Then, click **Export Dashboard**. Only the checked segments appear in the file. # Set up a segment in a campaign Source: https://docs.kameleoon.com/user-manual/assets/segments/set-up-a-segment-in-a-campaign Associate a targeting segment with an experiment or personalization to control which visitors your campaign includes. ## Create a new segment [To learn how to create a segment, read this article](./create-a-segment) ## Set up a segment in an experiment In the finalization panel, select **Target a segment** in the **Targeting** step. In the list of existing segments, select the segment you created. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/segments/set-up-a-segment-in-a-campaign/targeting.png) Hover over the **information** icon to display the segment's targeting conditions. Information icon tooltip showing the segment's targeting conditions Click the **X** icon to dissociate the segment from the experiment. To associate a segment, you can also select **Target specific visitors** in the **Targeting** step. Select the segment and connectors you need. Using this option allows you to create quickly targeting conditions without re-creating a segment. Click **Next**. Kameleoon associates this segment with your experiment. ## Set up a segment in a personalization Once you create a segment, you must associate it with a personalization. In the **Targeting** step, click **Target a segment** in the **Segments** panel. Then, click the segment you want to target. Segments panel in the personalization Targeting step with a segment selected The segment your experiment is targeting displays at the top of the page. Proceed with [finalization](../../experimentation/web-experimentation/configure-and-launch/finalizing-an-experiment) normally. # Create a theme Source: https://docs.kameleoon.com/user-manual/assets/themes/create-a-theme Build a standardized design system in the Theme Builder by defining brand colors, fonts, and reusable components for your team's widgets. When your team builds widgets without a shared design system, colors, fonts, and components can drift from your brand guidelines across projects. Creating a theme in the Theme Builder gives your team a standardized design system: you set specific colors, upload brand fonts, and pre-build components that every widget uses consistently. ## Create a new theme 1. Go to **Studio** > **Themes** in the left sidebar. 2. Click **Add a theme**. 3. Enter a name for the theme and select the **Projects** it affects. 4. (Optional) Add a description and tags. 5. Click **Next** to open the **Theme Builder**. ### Define colors Colors section of the Theme Builder Use the **Colors** section to define the palette available to users. To restrict users to specific brand colors, toggle **Define theme colors** to **ON**. * **Add a color:** Enter a hex code or click the **+** icon. You can select a color from the gradient, adjust opacity, or use the eyedropper tool to sample from your screen. * **Manage colors:** Drag and drop colors to reorder them. Click the **trash** icon to remove a color. If you add a component to the theme while **Define theme colors** is **ON**, Kameleoon automatically adds that component's default colors to your theme colors list. Colors added here are available under **Theme Colors** in the Widget Studio. #### Modifying locked colors If a component in your theme currently uses a color, you can't delete it. However, you can edit the color. When you modify a color, the change automatically applies to every component in the theme that uses that color. ### Add fonts Fonts section of the Theme Builder In the **Fonts** section, determine which distinct typography users can access. Check a font from the list to include it in your theme. Use the search bar to search for a specific font. You must select at least one font to add components to your theme. If you remove a font that's currently in use, the system defaults to the first font in your list. To add a font: Add font dialog in the Theme Builder 1. Click **+ Add font**. 2. Select your font source: * **From your website:** Enter the Font Family name. * **Link to a font file:** Enter the Font Family name and the URL to the file. * **Embed code:** Enter the Font Family name and the import code. 3. Click **Preview & add**. 4. Select the font weights you want to include. 5. Click **Add**. ### Add and manage components The **Components** section allows you to pre-configure elements (like buttons, images, or forms) for your team and define which components belong in the theme. You can add the same component type multiple times to create variations, for example, a distinct "Submit" button and a "Cancel" button. #### Add a component ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/themes/create-a-theme/add-components.png) 1. Click **+ Add** next to **Components**. 2. Search for a component or browse categories in the left sidebar. 3. Select the components you want to include. 4. Click **Add components**. #### Edit and lock components After you add a component to your list, click the **secondary actions** (three dots) menu to manage it: Secondary actions menu for a theme component * **Rename:** Give the component a descriptive name (for example, "Primary CTA"). * **Edit:** Open the component editor to adjust styles using [Widget Studio tools](../widgets/create-a-widget-with-the-widget-studio/edit-your-widgets). * **Lock:** Restrict editing permissions for this component. Alternatively, click the **lock** icon next to the **Secondary actions** menu to lock a component. * **When locked:** Users can only modify the content (text or images), position, and size. They can't design elements like colors or borders. * **When unlocked:** Users have full control to change the design. ## Activate your theme When you finish configuring your theme, click **Activate** in the top right corner. The theme is now available for use in your experiments and personalizations. To deactivate a theme later, click the status dropdown (**Active**) and select **Deactivate**, or use the Themes dashboard. # Manage themes Source: https://docs.kameleoon.com/user-manual/assets/themes/manage-themes Search, sort, filter, and configure theme usage settings to enforce brand guidelines across projects in the Widget Studio. When different teams build widgets across projects without a shared reference, colors, fonts, and components can drift from your brand guidelines. Themes let you enforce brand guidelines across projects: by defining specific colors, fonts, and reusable components, you ensure that every widget built in the Widget Studio stays consistent with your brand identity. This article explains how to access, organize, and configure usage settings for your themes. ## Access the themes dashboard To access the Themes dashboard, click **Studio** > **Themes** in the left sidebar. From the dashboard, you can organize your view: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/themes/manage-themes/sort.png) * **Search:** Use the search bar to find a specific theme by name. * **Sort:** Click the **Sort** icon to order themes by **Modification date** (newest to oldest or oldest to newest) or alphabetically (**A to Z**, **Z to A**). * **View:** Switch between **Block view** (grid) and **List view** using the icons next to the sort options. * **Filter:** Open the **Filters** sidebar to refine the list by **Project** or **Status** (Active/Inactive). Click **Apply** to update the list. Filters sidebar for the Themes dashboard ## Configure theme usage and defaults Administrators can control how strict theme usage is for each project. You can require users to apply a theme or allow them to create widgets without one. 1. On the **Themes** dashboard, click the **settings** (cog wheel) icon. 2. In the **Theme Usage & Default Themes** panel, locate the project you want to configure. 3. Select the usage mode: * **Optional:** Users can choose whether to apply a theme when creating widgets. * **Mandatory:** Users must select a theme to create a widget. 4. (Optional) Select a **Default Theme**. Kameleoon pre-selects this theme for users creating new widgets in this project. 5. Click **Save**. Theme Usage and Default Themes panel ## Edit and manage existing themes Each theme card provides quick access to management actions: * **Edit:** Click the **Pencil** icon to open the Theme Builder and modify colors, fonts, or components. * **Secondary actions:** Click the **secondary actions** (three dots) menu to: * **Preview theme:** View how the theme appears. * **Rename theme:** Change the internal display name. * **Manage tags:** Add or remove tags for organization. * **Duplicate theme:** Create a copy of the theme. * **Activate/Deactivate:** Toggle the theme's availability. * **Delete:** Permanently remove the theme. # Create a trigger Source: https://docs.kameleoon.com/user-manual/assets/triggers/create-a-trigger Learn how to use the Kameleoon Trigger Builder to define when and where an experiment or personalization should activate based on real-time visitor behavior. A **trigger** defines the specific conditions visitors must meet for an experiment or personalization to activate. Use triggers to target visitors based on their real-time behavior or visit characteristics. ## Access the Trigger Builder ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/create-a-trigger/trigger.png) To start creating a new trigger: 1. In the left sidebar, click **Settings** > **Triggers**. 2. Click **New trigger**. 3. In the **New Trigger** window, enter a name for your trigger. 4. Select the **Project** you want to associate with this trigger. 5. (Optional) Add a description and tags to help organize your triggers. 6. Click **Next**. ## Define conditions ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/create-a-trigger/trigger-builder.png) The Trigger Builder is where you define your trigger's logic. Select conditions from the sidebar and configure how they relate to one another. 1. **Select a condition:** In the left sidebar, scroll through the list or use the search bar to find a condition (for example, **Number of visits**). Click the condition to add it to the builder. 2. **Configure the condition:** Enter the relevant values for the condition (for example, **3 visits**). 3. **Add more conditions:** To add another layer of targeting, click **Refine** on an existing condition or select a new condition from the sidebar. ### Manage logic (AND/OR) When you use multiple conditions, define how they work together using the dropdown menu at the top of the Trigger Builder or [condition group](#create-complex-groups): * **If all (AND):** Kameleoon fires the trigger only if the visitor meets **every** condition in the group. * **If any (OR):** Kameleoon fires the trigger if the visitor meets **at least one** of the conditions in the group. ### Create complex groups Group conditions together to create advanced logic (nested conditions). * **Reorder:** Click and drag a condition to move it up or down in the list. * **Nest (Group):** Click and drag one condition on top of another to combine them into a subgroup with its own independent logic (**If any**/**If all**). * For example, you can create a condition that targets visitors who have visited the site three times today **AND** viewed a certain product page at least once. ### Copy or delete conditions * **Copy:** Click the **Copy** icon to clone a condition. * **Delete:** Click the **X** icon to remove a condition. #### Quickly add conditions You can quickly add multiple conditions of the same type by clicking the small **+** icon on the right side of the condition. This feature is available for conditions frequently used in large numbers. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/create-a-trigger/specific-ip-address-triggers.png) When you add multiple identical conditions, Kameleoon links them with an **OR** operator. The dropdown menu updates to **is including any of** or **is not including any of**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/create-a-trigger/any-ip-address-triggers.png) Once you add multiple identical conditions, you can delete them individually. *** When you are satisfied with your conditions, click **Next**. ## Review and save After you click **Next**, a summary panel opens on the right. Review the settings and definition. * **Settings:** Displays the trigger name, project, campaign type, description, and tags. Click the **pencil** icon to edit any of these settings, then click **Save changes**. * **Definition:** Displays your trigger's definition. You can view a visual summary under the **Block Summary** tab or a text-based summary under the **Written Summary** tab. Click **Create trigger** to finish. *** After you save your trigger, it becomes available for use in experiments and personalizations. ## Conditions This section provides a comprehensive list of available conditions and their functions. ### Page * **Page URL:** includes or excludes visitors based on their current page URL. * **Element on the page:** includes or excludes visits based on the presence of an element on the page. The condition performs a `querySelectorAll` loop until the DOM ready event. After that, the condition returns `false`. * **Landing Page URL:** includes or excludes visitors based on the URL of the page where their visit started. * **Page Title:** includes or excludes visitors based on their current page title. * **Previous page:** includes or excludes visitors based on the URL of their previous page. * **Key pages:** includes or excludes visitors who have visited specific pages that you defined as key pages. * **Visited product pages:** includes or excludes visitors based on which/how many product pages they visited. * **Price of the product page displayed:** includes or excludes visitors based on the price of the product they're currently viewing. ### Browsing behavior * **Elapsed time since page loaded:** includes or excludes visitors based on the time since the page loaded. * **Website exit:** includes or excludes visitors whose cursor has left the browser window. Use the Website exit condition to display retention-focused content, such as a discount offer or an email sign-up form, before a visitor navigates away. For a step-by-step example, see [Create an exit intent widget](../widgets/create-a-widget-with-the-widget-studio/create-an-exit-intent-widget). * **Number of pages viewed:** includes or excludes visitors who have seen a certain number of pages. * **Total number of visits:** includes or excludes visitors based on the total number of their visits. * **Number of visits by page:** includes or excludes visitors based on their number of visits to a specific page. * **Number of visits today:** includes or excludes visitors based on their number of visits today. * **Number of visited product pages:** includes or excludes visitors based on how many product pages they have visited. * **Elapsed time:** includes or excludes visitors based on the time elapsed during the session. * **Elapsed time since first visit:** includes or excludes visitors based on the time since their first visit. * **Elapsed time since last visit:** includes or excludes visitors based on the time since their last visit. * **Elapsed time since session became active:** includes or excludes visitors based on the time since the beginning of the session. ### Campaign exposure * **Web Experiment:** includes or excludes visitors that Kameleoon has exposed to a specific experiment and who have seen a **specific variation** OR **any of the variations** (original included). The condition considers current or previous visits. The selected experiment can be **Online**, **Paused**, or **Stopped**. * **Personalization:** includes or excludes visitors that Kameleoon has exposed to a specific personalization. The condition considers current or previous visits. The selected personalization can be **Online**, **Paused**, or **Stopped**. * **Feature Flag:** includes or excludes visitors that Kameleoon has exposed to a specific feature flag and assigned to **any variation** or a **specific variation**, who have seen **any of the variations**, **On**, or **Off**. * **Exclusive Campaign:** excludes visitors already participating in an experiment, a personalization, or both, meaning that: * If another campaign has already exposed the visitor (in a current or previous visit), Kameleoon doesn't target them in the current experiment. * Other experiments that don't have the **Exclusive campaign** condition can still target the visitor, even if the visitor is currently part of an exclusive campaign. In other words, this condition doesn't prevent participation in other experiments. It lets visitors participate in experiments that don't have the same exclusivity requirement. Kameleoon prevents the deletion of campaigns or variations used as targeting conditions in a segment. ### Conversions * **Converted goal:** includes or excludes visitors based on whether they converted on a certain goal during the current visit or during previous visits. ### Technical * **Custom JS:** Define a custom JavaScript function that returns `true` or `false`. If the result is `true`, Kameleoon includes the visitor in the experiment. Kameleoon evaluates this function each time the page displays, or you can run it asynchronously. * **Check condition immediately or when the page has loaded:** If the function returns `undefined`, Kameleoon retries execution every 75 ms for the first 3 seconds, then every 250 ms. * **Run the condition asynchronously:** Kameleoon evaluates your JavaScript for each experiment. * If you use the asynchronous condition, the callback function is `setTargeting`. Replace it in the code with `setTargeting(true)` or `setTargeting(false)`. * **Custom data:** includes or excludes visitors based on specific custom data. To use this condition, you must first [create custom data](../custom-data/create-custom-data). * **Custom event:** starts a personalization only when your code triggers a specific event using the [`Kameleoon.API.Events.trigger(eventName)`](../../../developer-docs/apis/activation-api-js/api-reference/api-reference#trigger) method. * **Browser cookie:** includes or excludes visitors based on a cookie's presence in their browser and its value. * **Explicit trigger:** explicitly triggers a campaign using the Kameleoon Activation API. Use explicit triggers for campaigns you implement with a modern JS framework (for example, React, Vue, Angular). * **Kameleoon trigger:** includes or excludes visitors based on whether they fired another trigger. ### Acquisition * **Traffic type:** includes or excludes visitors from specific sources (for example, search engine, emailing, website tributary). * **Referring Website URL:** includes or excludes visitors based on the referring URL. * **Acquisition channel:** includes or excludes visitors based on their acquisition channel. ### External environment * **Day / Night:** includes or excludes visits during the day or night. * **Weekday:** includes or excludes visitors on a specific day of the week. * **Timeslot:** includes or excludes visitors within a given time period. * **Date range:** includes or excludes visitors to the website during a given date range. * **Sky condition:** includes or excludes visitors based on the weather in their location. * **Temperature:** includes or excludes visitors based on their location's temperature. # Manage your targeting triggers Source: https://docs.kameleoon.com/user-manual/assets/triggers/manage-your-targeting-triggers Find, filter, sort, edit, duplicate, and delete targeting triggers from the Kameleoon Triggers page. ## Access the Triggers page To access the **Trigger** page, click **Configure** > **Triggers**. ## Manage your triggers Each trigger is represented by a card that indicates its main information. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/manage-your-targeting-triggers/triggers-page.png) ### Find a trigger A search bar, a filter tool, and a sorting tool help you find a particular trigger. #### Filter triggers You can filter the displayed triggers based on several criteria: * Project * Linked to * Tag * Key date * Creator * Characteristics * Goal Filters panel with Project, Key date, Creator, Characteristics, and Goal filter options ##### Project You can filter the displayed triggers based on the projects with which they are associated. Check the projects and click **Apply**. ##### Campaign type *This filter is only available for Web experimentation + Feature experimentation customers.* You can filter the displayed triggers based on the type of campaign with which they are associated. You can select from the following campaign types: * **Feature flag / SDK experiment**: Triggers containing the “Visitor code” condition or Feature experimentation conditions not used in any experiment or personalization. * **All experiment types:** Triggers used in a Feature experiment that also contain conditions common to both Feature experimentation and Web experimentation. * **Web experiment / Personalization:** All other segments. ##### Linked to You can filter the displayed triggers based on whether they are associated with a campaign. ##### Tag You can filter the triggers based on the tags with which they are associated. Select or search for a tag. Kameleoon displays the triggers associated with these tags on the page. You can also check or uncheck all tags. Then, click **Apply**. ##### Key date You can filter the displayed triggers based on the key date with which they are associated: last modification or creation date. Select a date and click **Apply**. ##### Creator You can filter the displayed triggers based on their creator. Check the creators and click **Apply**. ##### Characteristics You can display triggers used in Audiences or with AI Predictive targeting. Check the filter and click **Apply**. #### Sort triggers You can sort triggers based on several criteria: * Name * Linked to (Campaign) * Edition date * Segment ID * Creation date * Project ##### Name Hover over **Name** and click the chevron to sort triggers in alphabetical (or reverse alphabetical) order. ##### Linked to Hover over **linked to** and click the chevron to rank triggers based on the campaign type. ##### Edition date Hover over **Edited** and click the chevron to sort triggers from newest edition to oldest or vice versa. ##### Segment ID Hover over **ID** and click the chevron to sort the triggers' IDs in ascending or descending order. ##### Creation date Hover over **Created** and click the chevron to sort triggers from newest creation date to oldest or vice versa. ##### Project Hover over **Project** and click the chevron to sort triggers based on the website on which they are configured in alphabetical (or reverse alphabetical) order. ### Edit a trigger To edit a trigger: 1. Navigate to the trigger's card. 2. Click the **pencil** icon. The Trigger Builder opens. 3. Make your changes and click **Save** to save them. ### Duplicate a trigger To duplicate a trigger: 1. Navigate to the trigger's card. 2. Click the **three-dots menu** > **Duplicate**. The **Duplicate** sidebar opens. 3. Click **Save** to validate your trigger's creation. ### Delete a trigger 1. Navigate to the trigger's card. 2. Click the **three-dots menu** > **Delete**. ### Multiple selection You can select multiple triggers at the same time and apply a common action to them. For example, it is possible to delete several triggers simultaneously. To apply multiple actions to triggers: 1. Navigate to a trigger's card. 2. Click the **three-dots menu** > **Select**. Triggers become selectable and the **Grouped actions** sidebar opens. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/manage-your-targeting-triggers/grouped-actions.png) Several actions are available. #### Select/deselect all To select all triggers, click the **checkbox** next to **Select all**. Click the **checkbox** again to deselect all triggers. #### Manage tags Click **Manage tags** in the **Grouped actions** sidebar. # Set up a trigger in a campaign Source: https://docs.kameleoon.com/user-manual/assets/triggers/set-up-a-trigger-in-a-campaign Add a trigger condition to an experiment to control when your campaign activates based on visitor events. ## Create a new trigger Read [this article](./create-a-trigger) to learn how to create a trigger. ## Set up a trigger in an experiment For more information on defining segments and triggers, refer to [this article](../../experimentation/web-experimentation/configure-and-launch/define-your-experiments-targeting). To set up a trigger in an experiment: 1. Click **Targeting** > **Target specific visitors** in the Graphic editor's finalization panel. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/triggers/set-up-a-trigger-in-a-campaign/0.png) 2. Select **Trigger** from the list of conditions. Conditions dropdown with Trigger selected 3. Select the event with which you want to associate your trigger. Select a trigger field showing the search result Has visited 3 or more pages 4. Click **Next** to associate the trigger with your experiment. Click the **X** icon to dissociate a trigger from the experiment. You can add multiple conditions to your experiments. # Analyze your widget results Source: https://docs.kameleoon.com/user-manual/assets/widgets/analyze-your-widget-results Use the widget results page to track exposure and completion metrics, read AI-generated learnings, and browse individual responses to your widget. The widget results page gathers all the data collected by a widget across the campaigns it is linked to. It shows how many users saw and completed the widget, AI-generated learnings, and every individual response. ## Access the widget results page To open a widget's results page, go to the [Widgets dashboard](./manage-your-widgets) and click the **three-dots menu** on the widget's card, then select **See results**. The header of the results page displays the widget's name, ID, and status. From the header, you can: * Click **Export** to export the widget's data. * Select the period to analyze: a custom date range, **All days**, **30D** (last 30 days), or **7D** (last 7 days). The results page contains three tabs: **Overview**, **Learnings**, and **Responses**. ## Overview The **Overview** tab summarizes the widget's key metrics over the selected period. ![The Overview tab of the widget results page, showing key metrics, the responses over time chart, and the linked campaigns table](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/analyze-your-widget-results/overview-tab.png) * **Key metrics:** The number of users exposed to the widget across all linked campaigns, the number of users who completed it, the completion rate, and the average response time per completion. * **Widget responses over time:** A chart comparing the number of users exposed to the number of users who completed the widget, day by day. Hover over the chart to see the values for a specific date. * **Linked campaigns:** A table listing every campaign the widget is linked to, with its status, the number of users exposed, the number of users who completed the widget, and the completion rate. The last row totals these metrics across all campaigns. ## Learnings The **Learnings** tab provides an AI-generated summary of the widget's results. The date of the last update is displayed at the top of the tab. Click the **refresh** icon to regenerate the summary with the latest data. ![The Learnings tab of the widget results page, showing the AI summary with insights, recommendations, detailed insights per question or input, and a notes field](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/analyze-your-widget-results/learnings-tab.png) * **Overall insights & recommendations:** **Insights** summarize the widget's key metrics and the main takeaways from the responses. **Recommendations** suggest concrete next steps based on those insights. * **Detailed insights:** A breakdown of the responses to each question/input of the widget, highlighting the main trends and segments. * **Notes:** A free-text field where you can write down your own observations and conclusions. ## Responses The **Responses** tab lists every individual response submitted through the widget. The total number of responses is displayed next to the search bar, which you can use to search within the responses. ![The Responses tab of the widget results page, listing individual responses with their date, answers, submission URL, campaign, and variation](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/analyze-your-widget-results/responses-tab.png) By default, each response displays the visitor code, the date the widget was seen, the date it was submitted, the answers to each question, the submission URL, and the campaign and variation the response was collected on. ### Customize the responses table Use the sidebar on the right of the tab to customize the table: ![The sidebar of the Responses tab, with the Columns section open to select displayed columns and the sorting rule](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/analyze-your-widget-results/responses-sidebar.png) * **Filters:** Filter the responses. * **Columns:** Select which columns to display, and choose the criteria and order by which to sort the responses (for example, newest to oldest). Click **Apply** to apply your changes, or **Reset** to restore the default configuration. * **Views:** Manage saved views of the responses table. # Create a new widget Source: https://docs.kameleoon.com/user-manual/assets/widgets/create-a-new-widget Start building a new widget in Kameleoon using the Widget Studio, the code editor, or PBX. To create a new widget in [Kameleoon](https://login.kameleoon.com/app): * Click **Studio** > **Widgets** > **New widget**. You can choose to create a widget using an AI prompt, the Widget Studio, or code. Select an option to continue. * [Learn how to create a widget from code](./create-a-widget-from-code/creating-a-widget-from-code) * [Learn how to create a widget using the Widget Studio](./create-a-widget-with-the-widget-studio/create-a-widget) * [Learn how to create a widget using PBX](../widgets/pbx-widgets) # Creating a widget from code Source: https://docs.kameleoon.com/user-manual/assets/widgets/create-a-widget-from-code/creating-a-widget-from-code Learn how to build highly customizable widget templates by coding them directly in the Kameleoon platform. Create highly customizable widget templates by coding them directly in the Kameleoon platform. Use one of our ready-to-use widgets or ask your developer for help if you are not comfortable coding. ## Create a new template Follow these steps to create a new widget template with code: 1. Click **Studio** > **Widgets** > **New widget**. 2. Select **Using code**. 3. Name your template, associate a website, and optionally write a description and add tags. 4. Click **Create**. ## The template editor The template editor contains three distinct areas. ### Left panel The top-left corner displays your template's title. Hover over the title and click the **pencil icon** to rename the template. Access two essential steps for creating your template in this panel: * **Code to run** * **Configuration form** ### Content zone ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-from-code/creating-a-widget-from-code/code-area.png) Enter your template's code in this area. ## Code the template Use the central content area to code your widget template. This code generates the widget on your website's page. The editor provides three tabs for different code types: * **JS** (JavaScript) * **CSS** * **HTML** By default, the **JS** tab is active. Write your code in the blank area. To validate your template, enter at least 10 characters in the **JS** tab. JavaScript is sufficient to create your template, but you can also add HTML or CSS. Use this default CSS to format elements, such as lists and text fields, according to the Kameleoon stylesheet: ```css theme={null} @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;700&display=swap'); body * { font-family: "Montserrat"; font-size: 12px; } fieldset { border: none; border-bottom: 1px solid #dedede; } label { display: block; font-size: 12px; margin-bottom: 5px; } input, select, textarea { display: block; padding: 8px 12px; width: 100%; border-radius: 4px; border: 1px solid #eeeeee; outline: none; font: 400 12px "Roboto", sans-serif; color: #333333; background: white; -webkit-transition: border-color 0.25s ease; -o-transition: border-color 0.25s ease; transition: border-color 0.25s ease; } input:hover, select:hover, textarea:hover, input:focus, select:focus, textarea:focus { border-color: #c6c6c6; } textarea { resize: vertical; } input[type="checkbox"] { display: inline-block; width: auto; position: relative; top: 1px; } input[type="checkbox"] + label { display: inline-block; } ``` After writing the code, click **Configuration form** in the left panel to move to the user interface step. ### Special case: variables To use a field (a variable) specified by the user in your widget, add the `templateData` attribute to the corresponding field in the **User Interface** tab. **Example:** `input id="message" type="text" templateData="messageContent" placeholder="Type your message"` To retrieve the variable, use `UserData` in the **Code to run** tab. **Example:** `var userInput = UserData.messageContent;` `alert(userInput);` ## Create the configuration form ![Configuration form interface](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-from-code/creating-a-widget-from-code/configuration-form.png) Use the **Configuration form** tab to build the user interface that team members use to set up your widget. Only the **HTML** and **CSS** tabs are available in this section. ### HTML tab Enter the HTML code for your configuration form in the **HTML** tab. Users interact with this form to set up their template and create a widget. Every field in the form must include an attribute with the following syntax: `templateData="[NameOfYourChoice]"`. ### CSS tab Styles that you add in the **CSS** tab apply to the user interface form. This step is optional. ## Activate the template To finalize your template, click **Activate** in the top-right corner of the editor page. If you enter fewer than 10 characters in the JavaScript field, **Activate** is unavailable. ## Manage created templates After you create a template, it appears as a card on the **Widgets** dashboard. ## Use the widget in an experiment To associate a code widget with an experiment: 1. Open or create an experiment using the **Graphic editor**. 2. In the left sidebar, click **Add** next to **Elements**. 3. In the **Add element** sidebar that opens, click **Widgets**. 4. Select your custom widget from the list and configure it. ![Add widget](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-from-code/creating-a-widget-from-code/add-widget.png) ## Preview your widget Code-based widget templates don't have a direct **Simulate** action. The **Widgets** dashboard's three-dots menu offers only **Rename**, **Manage tags**, **Duplicate**, **Activate**, and **Delete** for a code-based widget, and the template editor's header offers only **Activate**. ![Three-dots menu for a code-based widget, showing no Simulate option](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-from-code/creating-a-widget-from-code/code-widget-three-dots-menu.png) To preview a code-based widget, add it to a draft experiment using the **Graphic editor**: click **Add** next to **Elements** in the left sidebar, click **Widgets**, and select your widget from the list. Then click **Simulate** in the **Graphic editor** to test the campaign, including your widget, in a production-like environment before publishing it live. For details on the simulation panel, see [Simulate a campaign](../../../experimentation/web-experimentation/configure-and-launch/simulate-a-campaign). # Add a slider to a widget Source: https://docs.kameleoon.com/user-manual/assets/widgets/create-a-widget-with-the-widget-studio/add-a-slider-to-your-widget Add a slider element to your widget, edit slides, position it, and customize transition animations and autoplay behavior. A slider lets you cycle through multiple images, offers, or messages inside a single widget element. In this article, you learn how to: * Add a slider * Edit slides * Position a slider * Customize slide transitions ## Add a slider To add a slider to your widget: 1. Click **Add element** in the left sidebar. 2. Unfold the **Media** section in the list. 3. Click **Slider**. A panel opens where you can edit the slider. ## Edit slides A new slider contains three slides by default. To add a slide, click **Add new slide** in the **Slider Content** section. To edit an existing slide, click **Edit** next to it. Slider Content panel with three slides and the Add new slide option ## Position a slider Drag the slider on the canvas to place it anywhere in your widget. ## Customize slide transitions Click **Slider Animation** to configure how your slides transition: * **First slide**: the slide that displays first. * **Speed**: the transition duration, in milliseconds. * **Autoplay**: advances slides automatically, without visitor interaction. * **Effect**: the transition style between slides. * **Direction**: the direction slides move in. * **Allow touch move**: lets visitors swipe between slides on touch devices. Slider Animation panel with First slide, Speed, Autoplay, Effect, Direction, and Allow touch move settings You can also customize the previous and next arrows and the pagination indicator from the same panel. # Associate events and actions with your widget Source: https://docs.kameleoon.com/user-manual/assets/widgets/create-a-widget-with-the-widget-studio/associate-events-and-actions-with-your-widget Learn how to define interactive behaviors for your widgets by connecting event triggers with automated actions in the Kameleoon Widget Studio. Define how and when your widget behaves by associating events and actions. Events are triggers that detect specific visitor behaviors or page states, while actions are the responses that Kameleoon executes when those events occur. For example, you can create a multi-step pop-in flow where submitting an email address in the first pop-in (Event) triggers the display of a second "thank you" pop-in (Action). ## Access the Events section To manage your widget's triggers and responses, open the Widget Studio and click **Events** in the left sidebar. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-the-widget-studio/associate-events-and-actions-with-your-widget/events-page.png) ## Manage events An event defines when Kameleoon performs one or more actions. You configure an event's conditions and its actions together in the same panel. ### Create an event To create a new event: 1. Click **Add event**. The **Create new event** panel opens. 2. Select the type of event you want to create and click **Next**. 3. Under **Conditions**, use the dropdowns to define the values that trigger the event. Click **Add device trigger** to add a condition based on the visitor's device. 4. Under **Actions**, define the action Kameleoon performs when the conditions are met. Click **Add action** to add more actions to the same event, and click the trash icon next to an action to remove it. 5. Optional: click the pencil icon next to the panel title to rename the event. 6. Click **Save** to save the event. Create new event panel with Conditions and Actions sections ### Edit or delete an event To manage an existing event, hover over its card in the **Events** section, then: * **Edit:** Click the pencil icon. * **Delete:** Click the trash can icon. ### Event types Kameleoon offers several event types to trigger actions: | Event type | Description | | :-------------------- | :---------------------------------------------------------------------------------------------- | | **Click** | Triggers when a visitor clicks a specific element. | | **URL** | Triggers when the page URL contains a specific value or matches a regular expression. | | **Display** | Triggers before or after the widget appears. | | **Survey** | Triggers based on a survey or form value. | | **Language** | Triggers based on the visitor's browser language. | | **Timer** | Triggers based on time factors, such as a delay. | | **Custom data value** | Triggers when a specific custom data value meets defined conditions. | | **Device** | Triggers based on the visitor's device type. | | **Wheel of fortune** | Triggers based on a wheel of fortune action. Requires a wheel of fortune element on the widget. | | **Scratch card** | Triggers based on a scratch card action. Requires a scratch card element on the widget. | | **Countdown** | Triggers when a countdown ends. Requires a countdown element on the widget. | | **Video** | Triggers when a video ends. Requires a video element on the widget. | ## Action types You configure an event's actions under **Actions** in the **Create new event** panel. Kameleoon offers the following action types: | Action type | Description | | :------------------- | :---------------------------------------------------------------------------------------- | | **Elements** | Shows or hides a specific element within the widget. | | **Screens** | Shows or hides a specific screen within the widget. | | **Kameleoon goals** | Converts or cancels a Kameleoon goal. | | **Kameleoon events** | Triggers a custom event for segment targeting. | | **Kameleoon data** | Sets a custom data value. | | **URL redirects** | Redirects the visitor to a specified URL. | | **JavaScript code** | Runs custom JavaScript code. | | **Survey/Form** | Enables or disables a survey or form element. | | **Browser storage** | Sets a value in local storage, session storage, or a browser cookie. | | **Wheel of fortune** | Rotates a wheel of fortune element. Requires a wheel of fortune element on the widget. | | **Translation** | Displays the widget in a specific language. Requires translations enabled for the widget. | # Create a widget Source: https://docs.kameleoon.com/user-manual/assets/widgets/create-a-widget-with-the-widget-studio/create-a-widget Create a new widget in the Widget Studio by selecting a template and content type such as banner, NPS, survey, or countdown. To view a video tutorial about the Widget Studio, visit [our academy](https://academy.kameleoon.com/get-started-with-web-experimentation/164461?utm_source=user_manual\&utm_medium=online) To create a widget: 1. Click **Studio** > **Widgets** > **New widget**. 2. Select **Using the Widget Studio**. If you prefer to build a widget by prompting an AI, view [this article](../pbx-widgets); if you prefer to create a widget from code, view [this article](../create-a-widget-from-code/creating-a-widget-from-code#create-a-new-template). 3. Enter the required information in the pop-in. 4. Click **Create**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-the-widget-studio/create-a-widget/create-widget.png) ## Choose a theme After you click **Create**, a pop-in prompts you to choose a theme for your widget. The list always includes the [master theme](../../themes/create-a-theme), plus any custom themes available to your team. Select a theme and click **Next**. ## Select a template ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-the-widget-studio/create-a-widget/Capture-decran-2025-02-17-a-16.02.17-1536x908.png) Select a template for your widget. You can search for a specific template, apply filters, and access your custom or favorite templates. You can also import a widget from another account by clicking **Create from JSON file** in the upper-right corner. ### Content types There are several content types to choose from: * **Banner:** Add a banner to your page. * **Emailing/Newsletter:** Improve return rates by incentivizing visitors with a pop-in. * **Countdown/Timer:** Create urgency for better conversions. * **Slider**: Create carousels to animate your website. * **NPS:** See what your visitors think using an NPS widget. * **Feedback/Survey:** Ask visitors for their opinions or information about their experience. * **Social:** Allow users to access your social media accounts. Both the timer and the countdown create a sense of urgency, but the first is in hours, while the second is in days. Use the timer for short-term offers (for instance, limited-time free shipping), and the countdown for long-term offers (for instance, a week-long sale). # Manage your widgets Source: https://docs.kameleoon.com/user-manual/assets/widgets/manage-your-widgets Access the Widgets dashboard to search, filter, edit, duplicate, simulate, activate, and delete your Kameleoon widgets. ## Access the Widgets dashboard To access the **Widgets** dashboard, click **Studio** > **Widgets**. ## Structure of the Widgets dashboard ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/manage-your-widgets/widgets-dashboard.png) * **Header:** Contains the **New widget** button. * **Search bar:** Searches for widgets and displays the total count of created widgets. * **Display options:** Switches the layout between **Card view** and **List view**. Sorts widgets by modification date or alphabetically. * **Cards:** Display widget details, such as status. Each card includes an **Edit** button and a **More actions** menu to manage the widget. * **Sidebar:** Filters results, customizes columns, and manages saved views. ## Widget information Each widget is represented by a card that indicates its main information. Hover over the tooltip to the right of a widget's name to access detailed information about the widget. ## Dashboard sidebar ### Filter You can filter your widgets based on several criteria: * Project * Widget type * Status * Linked to * Tag * Creator Select the filters and click **Apply**. Click **Save as view** to save your configuration as a [view](#views). ### Columns #### Display Select which columns you'd like to display on the dashboard table. Click the **checkbox** associated with a column to select or deselect it. Click **Apply** to apply your changes. #### Sort by Select the criteria by which to sort your widgets and the associated sorting rule (for example, newest to oldest, alphabetical, ascending number of tags). ### Views Select which view you'd like to apply to the dashboard. To change the default view, click a view's **three-dots** menu > **Default**. ## Edit a widget Click the **pencil** icon to edit a widget. Depending on how you set up the widget, Kameleoon takes you to the Code editor or the Widget Studio. ## Secondary actions Click the **three-dots menu** on a widget's card to access the available actions. The actions depend on your widget's status. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/manage-your-widgets/secondary-actions.png) ### See results You can view a widget's results, including exposure and completion metrics, AI-generated learnings, and individual responses. See [Analyze your widget results](./analyze-your-widget-results). The **See results** action only appears if you've enabled response collection for the widget in PBX or the Widget Studio and linked it to an active campaign. ### Edit You can edit a widget. Depending on how you set up the widget, Kameleoon takes you to the Code editor or the Widget Studio. ### Simulate You can simulate a widget and preview it in a production-like context on the URL entered when the widget was created, without needing to associate the widget with an experiment. A sidebar appears with settings to define before launching the simulation. Code-based widgets don't have a **Simulate** action. To preview a code-based widget, associate it with a draft experiment and simulate the campaign instead. See [Preview your widget](./create-a-widget-from-code/creating-a-widget-from-code#preview-your-widget). ### Rename You can rename a widget. Click **Save** to validate the widget's renaming. ### Manage tags You can manage the tags associated with a widget. Tags associated with your widget are highlighted in the **Manage tags** sidebar and widget card. You can associate tags and create new ones. Click **Save** to validate your choices. ### Duplicate You can duplicate a widget. Click **Save** to validate the widget's duplication. You can find the new widget in the dashboard. ### View code You can view a widget's code. You can change which devices the widget displays on and its position on the page. Click **View code** again to view the widget's JS and CSS code, copy the code, or expand the window. ### Export to .json Download your widget as a `.json` file. ### Activate/Deactivate You must activate a widget to use it in a campaign. A widget's status is indicated on its card. If its card is white, the widget is activated. If its card is gray, the widget is deactivated. If you deactivate a widget, you can't use it in your campaigns. ### Delete You can delete a widget. Confirm your choice in the pop-in to validate the widget's deletion. # Create a widget with PBX Source: https://docs.kameleoon.com/user-manual/assets/widgets/pbx-widgets Learn how to use PBX, an AI-powered UI generator, to build widgets using natural language prompts. # Create a widget with PBX **PBX Widgets** is an AI-powered UI generator that helps you create widgets for your experiments and personalizations created in the new personalization builder. Instead of manually writing HTML, CSS, and JavaScript, you describe the widget in natural language, and PBX generates the component automatically. This approach enables marketers, product managers, and CRO specialists to build UI components quickly without technical expertise. Typical widgets created with PBX include: * Newsletter subscription popups * Promotional banners * Exit-intent offers * Lead capture forms * Product recommendation panels * Survey or feedback modals PBX widgets are not compatible with legacy personalization. You can ask your CSM to get access to the new personalization if needed. ## Create a widget Follow these steps to generate and refine a widget using PBX. ### 1. Open PBX To start generating a widget in the Kameleoon interface: 1. Go to **Studio** > **Widgets**. 2. Click **New widget**. 3. Select **Using a prompt**. 4. Enter the required information in the pop-in menu. 5. Click **Create**. ### 2. Write a prompt Describe the widget you want to build in the prompt field. PBX uses this description to generate the widget UI automatically. **Example prompt:** ```text theme={null} Create a centered modal popup widget. - Layout: Two columns with an image placeholder on the left and a subscription form on the right. - Content: - Title: Join an Exclusive Community - Description: Subscribe for exclusive discounts and get 15% off your next purchase. - Form: Email input field with placeholder "Enter your email address" - CTA button: Subscribe now - Secondary link: No thanks - Style: - Modern ecommerce design - Rounded UI elements - Light beige background - Dark CTA button - Behavior: - Display after 5 seconds - Close button in top right corner - Close popup when clicking "No thanks" - Tracking: Track a goal called "Newsletter Signup" when the CTA button is clicked. - Image: Use the following image in the image placeholder on the left: [https://www.whereyourimagelives.com](https://www.whereyourimagelives.com) ``` ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-pbx/create-a-widget-with-pbx/create-widget.png) ### 3. Preview and iterate The fastest way to reach your desired result is to use a prompt-iteration workflow. Review the generated widget and refine it by writing follow-up prompts, such as: * "Make the CTA button larger" ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-pbx/create-a-widget-with-pbx/make-cta-button-larger.png) * "Change the background color to a darker beige" ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-pbx/create-a-widget-with-pbx/make-background-dark-beige.png) * "Make the popup appear after 3 seconds" ### 4. Edit the widget's code Edit a generated widget's JS or CSS code directly, instead of refining it through prompts. 1. Click the **code** icon in the header to open the **View code** tab. 2. Click **Edit code**. 3. Edit the widget's JS or CSS code. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/assets/widgets/create-a-widget-with-pbx/create-a-widget-with-pbx/pbx-widget-code.png) If you generate multiple versions of the widget, select the one you want to edit from the **Selected version** dropdown. You can edit only the last version. For older versions, you can view and copy the code, but you can't edit it. ### 5. Add goals Track user interactions—such as email submissions, button clicks, form completions, or widget displays—by adding goals to your prompt. You must configure the goals in [**Settings**](../goals/create-a-goal) before you can track them in a PBX widget. **Example prompts for goals:** * Newsletter signup: `Track a goal called "Newsletter Signup" when the user clicks the Subscribe button.` * CTA click: `Track a goal when the CTA button is clicked. Goal name: Promo CTA Click` * Widget engagement: `Track a goal when the popup is displayed. Goal name: Widget Viewed` ## Collect widget responses in Kameleoon (optional) If your widget asks visitors a question, such as an NPS score or a feedback comment, you need a way to gather those answers without setting up an external tool like Google Sheets. PBX detects when a widget contains a question or input and lets you collect the resulting responses directly in Kameleoon (currently in beta), then export them as a CSV file. ### 1. Enable response collection When PBX generates a widget that contains a question or input, it prompts you to choose whether to collect the responses. 1. In the **Collect widget responses?** prompt, select an option: * **Yes, collect responses via Kameleoon:** Kameleoon collects each response and makes it available for CSV export. * **No, don't collect responses:** Kameleoon doesn't collect widget response data. 2. Click **Apply**. Collect widget responses prompt with Yes and No options and an Apply button ### 2. Name the widget fields Kameleoon uses the widget field names as the column headers in the exported CSV file, so give each field a clear, descriptive name. 1. Open the **Widget response settings** panel. 2. Review the automatically generated field names, listed as **Field 1**, **Field 2**, and so on. 3. Hover over a field name and click the pencil icon to edit it. 4. Enter a name, then click the checkmark to confirm or the cancel icon to discard the change. 5. Click **Apply** to save your changes. Widget response settings panel with the Widget field names list and Apply button **Apply** stays disabled until you make at least one change to a field name. ### What's included in the export Kameleoon exports responses collected through a PBX widget under the same conditions as [survey CSV exports](./create-a-widget-with-the-widget-studio/create-csat-nps-and-surveys/survey-data-export#kameleoon-csv). The exported file includes: * Response timestamps * Visitor code * Answers per question * Campaign and variation details ## Send widget responses to Google Sheets (optional) You can configure a PBX widget to [send survey and form responses directly to Google Sheets](./create-a-widget-with-the-widget-studio/create-csat-nps-and-surveys/send-survey-responses-to-google-sheets). You must first configure your Google Sheet as described in [this guide](./create-a-widget-with-the-widget-studio/create-csat-nps-and-surveys/send-survey-responses-to-google-sheets). When writing the prompt to connect your widget to Google Sheets, follow these guidelines: 1. **Define a clear submission trigger:** Specify which element triggers the request. The trigger is typically the main CTA button (for example, `SubmitButton`). 2. **Use an external HTTP POST request:** Set the method to **POST** and include your Google Apps Script Web App URL. 3. **Provide the Web App URL explicitly:** Always include the full URL (for example, `https://script.google.com/macros/s/XXXXXXXX/exec`). PBX cannot infer or generate this automatically. 4. **List all elements to send:** Explicitly name every field (for example, `email`, `rating`, `comment`). Do not rely on implicit behavior. PBX requires exact field names. 5. **Match parameter names exactly:** Ensure the names match exactly across your widget element names, the request payload, and the Google Sheets column headers. **Example prompt for Google Sheets integration:** ```text id="example_pbx_google_sheets" theme={null} Add a system to send widget form data to a Google Sheet using an HTTP request. - Submission trigger: Use the button named "SubmitButton" - Request configuration: - Request name: SendToGoogleSheets - Method: POST - Action URL: https://script.google.com/macros/s/AKfycbxxxxxxx/exec - Data to send: Send the values of the following elements as parameters: - email - rating - comment - Payload rules: - Each parameter key must exactly match the element name - Keys must also match the column headers in the Google Sheet - Behavior: - Trigger the request when the SubmitButton is clicked - After successful submission, display a confirmation message: "Thank you for your response" - Optionally handle errors if the request fails ``` ## Write effective prompts Well-written prompts significantly improve the quality of the generated widget. ### Use a clear structure The best prompts follow a logical structure: ```text theme={null} - Widget type - Layout - Content - Style - Behavior ``` **Example:** ```text theme={null} Create a modal popup widget. - Layout: Two columns with an image on the left and a form on the right. - Content: - Title: Join our community - Description: Get 15% off your first order - Email field - CTA button: Subscribe now - Style: - Modern ecommerce design - Rounded buttons - Dark beige background - Behavior: - Display after 5 seconds - Close button in top right corner - Image: Use the following image in the image placeholder on the left: https://www.whereyourimagelives.com ``` ### Be specific Specific prompts produce more accurate layouts and styles. **Bad prompt:** `Create a popup for a newsletter` **Better prompt:** `Create a centered modal popup for newsletter subscription with a modern ecommerce design. Include an email input field and a large CTA button offering 15% off.` ### Define UI elements explicitly Always specify the elements you need, such as titles, buttons, inputs, secondary links, icons, or images. **Example:** ```text theme={null} Add: - Email input field - Large CTA button - Small legal disclaimer text - "No thanks" link below the button ``` ### Describe behavior PBX can generate widget logic if you specify it. **Examples:** * `Show popup after 5 seconds` * `Close popup when clicking outside` * `Close popup when clicking "No thanks"` ### Iterate instead of rewriting Rather than rewriting the entire prompt, refine your widget step-by-step. Iterative prompting improves results efficiently. **Examples:** * `Make the CTA button darker` * `Increase padding inside the popup` * `Add rounded corners to the input field` # Define feature variables Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables Define feature variables to dynamically control your feature flag's content across environments without redeploying code. You can define feature variables to remotely update your feature flags' content in your chosen environment. A feature variable defines a single piece of content, such as a discount percentage or a button color, that you can update without redeploying code. A feature variation is a named combination of variable values that Kameleoon serves to a specific group of users. You must create feature variables [to create feature variations](./define-feature-variations). There is no limit on the number of feature variables you can create. ## Creating feature variables ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/configure-your-feature-flags/define-feature-variables/feature-variables.png) 1. Navigate to your feature flag or create a new one. 2. In the left sidebar, click **Set Up** > **Variables** > **Add Variable**. 3. Select your variable's **Type**. * If you select **Enum**, enter a list of allowed values separated by commas. This type creates a predefined list of options that you can select from when you define feature variations, rather than typing values manually. 4. Enter a **Variable Key**. 5. Set the **Default Value**. * For **Enum** variables, select the default value from the list you defined in step 3. 6. Click **Save**. To retrieve a feature variable from your source code, you must call the `getVariation()` method. If you use feature variations, Kameleoon automatically buckets the user in a variation and returns the value that variation defines for the variable, so you don't need to call the `getVariation()` method in that case. ## Benefits of feature variables Feature variables let you variabilize parts of your feature flag code, so you can dynamically assign values to variables without coding them in your source code. Once the feature variable's code is in production, changing it doesn't require redeployment. For example, if your feature is a discount offer for premium customers, but you're not sure what the discount percentage will be, you can create a **Discount Percentage** variable in your code and update it from the Kameleoon app without changing or pushing new code. Developers and product owners can work together to variablize parts of your feature that might change, so product updates aren't blocked by development and deployments. Choose your SDK language and use the sample code provided to set up the SDK in your app in any of the following languages: * [Android](/developer-docs/sdks/mobile-sdks/android-sdk) * [iOS](/developer-docs/sdks/mobile-sdks/ios-sdk) * [Java](/developer-docs/sdks/web-sdks/java-sdk) * [C#](/developer-docs/sdks/web-sdks/csharp-sdk) * [Node.js](/developer-docs/sdks/web-sdks/nodejs-sdk) * [PHP](/developer-docs/sdks/web-sdks/php-sdk) * [Ruby](/developer-docs/sdks/web-sdks/ruby-sdk) * [Go](/developer-docs/sdks/web-sdks/go-sdk) * [Flutter](/developer-docs/sdks/mobile-sdks/flutter-sdk) * [Python](/developer-docs/sdks/web-sdks/python-sdk) * [Rust](/developer-docs/sdks/web-sdks/rust-sdk) * [Elixir](/developer-docs/sdks/web-sdks/elixir-sdk) # Define feature variations Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variations Create and customize feature variations to control which feature version appears for each user based on your flag's rules. You can create variations of your feature and decide which one to show each user, based on the criteria you define in the feature flag. A feature variation is a named combination of values assigned to your feature variables. A feature variable defines a single piece of content, such as a discount percentage or a button color, that you can update remotely without redeploying code. You define variables first, then assign their values within each variation. ## Creating feature variations ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/configure-your-feature-flags/define-feature-variations/feature-variations.png) You can customize any variable you create in the **Variables** page for any variation. You don't need to code any values into your source code. To create a feature variation: 1. Navigate to your feature flag or create a new one. 2. In the left sidebar, click **Set up** > **Variations** > **Add variation**. 3. Edit your variation's **Name** and **Variation Key**. 4. Click **Save**. ## Default feature variations When you create a new feature flag, Kameleoon generates two default variations: **On** and **Off**. * **Off Variation:** Users assigned to this variation see your app or website as it appears when you turn off the feature flag. This variation lets you test new features on a limited group of users. Keep in mind that any custom variables you've created for the feature flag won't be available in the Off variation. * **On Variation:** The On variation presents your flagged feature's default or control version. This variation serves as the baseline experience for your feature and will only use the default values for all defined feature variables. Utilizing these default variations helps you manage user experience for different segments of your audience. You can view a summary of which variations Kameleoon serves to your users in the [Rollout Summary](../using-the-rollout-planner/rules-and-targeting/add-a-new-rollout-rule#rollout-summary). The `getVariation()` method returns the variation key associated with the user. # Manage environments Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments Create and manage feature flag environments to separate development, staging, and production deployments within your projects. Kameleoon Environments are used to categorize and manage your application's deployment locations. They help organize your technical infrastructure by separating development, testing, and production environments. Environments provide a structured approach for managing feature flags and deployments across platforms. When you create a new project, Kameleoon automatically creates three feature environments: Production, Staging, and Development. These environments are intended to iterate, test, and refine software, minimizing risks and ensuring a safe release cycle. ## Customizing Environments Navigate to the **Set Up** menu and click **Environments** to see your current project environments. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments/0.png) You’re free to customize these environments (except Production, which can only be renamed) or create additional environments. TO create a new environment, click **Add an Environment**. Enter a name and key for your new environment. You can also reorder the environments to your liking. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments/1.png) ## Scope of Environments You can use feature variables, variations, and goals across all environments—you don't have to create them separately for each environment when setting up a feature. However, environments apply to the entire project, not just the flag you're working on. Any changes made to environments will affect all feature flags in the project. You can filter flags by project and environment key in the Feature Flags dashboard. Hover over the ⓘ icon to see the projects on which a key has been used. To use Kameleoon’s multi-environments feature flagging, you must use the parameter called “environment” within the YAML configuration file to define the environment key. If there are no environments specified within your YAML file, the configuration associated with the Production environment will be applied by default. Read your SDK’s [documentation](../../../../developer-docs/feature-experimentation/get-started/overview#supported-platforms) to learn more. # Approvals for flag changes Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/approvals-for-flag-changes Implement a request-and-review workflow to control and audit feature flag changes before they go live. Enabling approvals for flag changes introduces a request-and-review mechanism for feature flag edits. Team members can request changes, and designated approvers can review and approve or reject these requests before they take effect. Flag approvals give you greater control over which changes apply to your flags. After you configure approval settings, changes that certain users make pass through a request-and-review process, so only approved changes reach end users. ## Activate approvals for a project Only users with **Admin** rights can configure approvals. Read [User roles](/user-manual/account-and-team-management/users-and-teams/user-roles) to learn what each role can do. To activate flag approvals: 1. Click **Features** > **Flags & Experiments** in the left sidebar. 2. Click the **Settings** tab, then select **Approval settings**. ![Settings tab](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/approvals-for-flag-changes/settings%20tab.png) 3. Select the project for which you want to enable flag approvals from the dropdown. 4. Select the users who can review requests, then click **Save**. If you select the option to trigger approvals only for the production environment, Kameleoon applies the workflow only to the environment with the key `production`. However, changes that indirectly affect the production environment, such as updates to variables, variations, or goals, also trigger the approval workflow. Approvals don't control environments, integrations, or the flag toggle. Only users with **Production** permissions can change a flag's state (on/off). ## Request approval for flag changes After you activate approvals for a project, non-reviewer users see an approval request popup when they save their changes. You can customize the title of the request and add a comment that the approver can see. ![Request change](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/approvals-for-flag-changes/request%20change.png) Requesters can make and submit changes in bulk across different sections of the setup or environments. Kameleoon notifies approvers of pending requests so they can review the proposed changes. All approval requests are visible in the **Review** section. To withdraw an individual request that's still **Pending**, click the request, then click **Withdraw request**. ## Approve change requests On the main dashboard, approvers can see an overview of each flag's review status: ![Pending](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/approvals-for-flag-changes/pending.png) * **Pending**: The flag has at least one pending request. * **Reviewed**: An approver has approved or rejected every request. Hover over the review stage to display additional information. When editing a feature flag, reviewers can navigate to the **Review** section to see all requests. Click a request to view its details, including proposed changes and comments. Requests can have one of four states: * **Pending**: Awaiting review. * **Approved**: Kameleoon approved and applied the change. * **Rejected**: The approver declined the change. * **Invalid**: A newer change makes this request no longer applicable. Kameleoon rejects it by default. For each request, approvers can approve or decline the proposed changes. Kameleoon applies or discards the associated changes accordingly. Approvers can only review requests in top-down order (newest request first). ## FAQs No. After you enable approvals, all flags in a project must go through the approval workflow. If the process seems too stringent, consider revising the list of approvers. Kameleoon discards rejected requests, but requesters can always resubmit their changes. A newest-first review process flags and ignores invalidated requests, eliminating any risk of data conflicts. # Create a feature flag Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag Create a new feature flag in Kameleoon and configure it using the Rollout Planner to control feature delivery across environments. This guide shows you how to create a new feature flag. ## Access the Feature flag creation page 1. Click **Features** > **Flags & Experiments** > **New feature flag**. 2. Enter a name for your flag. 3. Choose the project on which you want to activate the flag. 4. Choose a flag type: **Temporary** for a flag you plan to remove after launch, or **Permanent** for one that stays in your codebase long-term. Read [Manage feature flag technical debt](./manage-feature-flag-technical-debt) to learn how Kameleoon tracks each type. 5. Add a description (optional). 6. Click **Save**. Kameleoon generates a flag key automatically based on your flag's name, but you can edit it before you save. The flag key is the unique ID that identifies the feature flag in your source code. Be sure to update the key in Kameleoon before editing it in your source code. ![New feature flag dialog showing the flag name, flag key, project, flag type, and description fields](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag/fe-creation-pop-in.png) By default, your flag is **OFF**. You can activate it once it's configured. ## Structure of the Rollout Planner ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag/rollout-planner.png) The Rollout planner consists of several blocks. * A **header** indicating the flag's name, the associated sitecode (you can copy and paste it directly from here), and the flag key. Click the flag's name to edit its details. * A **Setup** menu, to create and manage your feature variables and feature variations URL. * A **Rollout Planner** specific to each environment. Changes to a feature flag apply only to the environment you select in this section. Kameleoon lets you link your production and staging/QA environments to the app. * A **toggle** that turns your feature flag **On** or **Off**. * An area for you to add rollout rules. * A **toolbar** for accessing the AI assistant, reverting your changes, and opening secondary actions (**Simulate** and **Calculate duration**). * A **Save** button. Confirm the flag exists in Kameleoon and that you can retrieve it from your source code with a Kameleoon SDK's `isFeatureActive()` or `getVariation()` method. Then go to your feature flag in Kameleoon and continue configuration. # Feature Experimentation dashboard Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/feature-experimentation-dashboard Navigate the Feature Experimentation dashboard to manage flags, review approvals, monitor flag health, and access holdout settings. Managing flags across multiple projects and environments can get cluttered fast. The Feature Experimentation dashboard centralizes project, environment, and flag management into a clear layout. This article outlines the dashboard's sections and how to use them. ![Feature Experimentation dashboard showing the flags list with the Overview, Approvals, Health, and Settings tabs](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/feature-experimentation-dashboard/fe-dashboard.png) ## Dashboard structure ### Tabs ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/feature-experimentation-dashboard/tabs.gif) The dashboard has four main tabs: * **Overview:** The standard view, which lets you navigate your flags and their rules. * **Approvals:** Lists flags or rules waiting for approval before rollout. * **Health:** Displays flag health, last evaluation, and last modification to better manage technical debt. * **Settings:** Access to approval and holdout settings. You can access holdout reporting from this tab. ### Environment selector and views ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/feature-experimentation-dashboard/selector-views.gif) At the top of the flag list, you can switch between your projects' environments, or select **All environments** for a consolidated view across all of them. The view toggle also lets you adjust how flags appear: * **Collapsed:** Compact list of all flags, similar to the previous dashboard. * **Expanded:** Shows details at a rule level for a more complete picture of the rollouts and experiments in your flag's environments. ### Flag actions ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/feature-experimentation-dashboard/flag-actions.gif) From the dashboard list, you can also: * Toggle a flag **On** or **Off**. * Open the flag configuration using the edit icon. * Click the edit or results icon at the end of a rule's row to open its configuration or reporting page. # Feature flag comments Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/feature-flag-comments Add, reply to, and manage comments directly on feature flags to centralize team discussions and decisions in Kameleoon. Feature flag comments let your team centralize discussions and decisions directly within Kameleoon. Instead of relying on external tools, you can easily communicate about specific feature flags, ensuring everyone has the necessary context, leading to faster decisions and better collaboration. ## Access the Comments panel 1. In the left sidebar, click **Features** > **Flags & Experiments**. 2. **Edit** an existing feature flag or **create a new one** (for more information on creating feature flags, see [this article](./create-a-feature-flag)). 3. On the feature flag configuration screen, click the **text bubble icon** in the top-right corner. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/flag-comments/comments.png) The **Comments** panel opens on the right side of the screen. The panel has two tabs: **Active** and **Archived**. ## Use comments to collaborate ### Add a new comment 1. In the **Active** tab, click **Add comment** and type your comment in the text field. * To notify a teammate and help them find the comment, use the **@** symbol to tag them (for example, `@JaneDoe`). 2. Click **Save**. ### Reply to a comment 1. Find the comment you want to respond to. 2. Click **Add reply** beneath the comment. 3. Enter your response. 4. Click **Save**. #### View and hide replies * To view replies, click **Show reply** or **Show replies** (the number of replies is shown in brackets). * To hide replies, click **Hide reply** or **Hide replies**. ### Edit or delete your comments You can edit or delete comments that you posted. 1. Click the **three-dots menu** associated with your comment. 2. Click **Edit** to modify the text **or** **Delete** to permanently remove the comment. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/flag-comments/edit-delete.png) ## Manage comments ### Resolve and archive comments Once a discussion is complete, mark the comment as resolved to declutter your active list. * In the **Active** tab, click the **Checkmark icon** next to the comment you want to resolve. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/flag-comments/checkmark.png) The comment is marked as resolved and automatically moved to the **Archived** tab. ### View and unresolve archived comments 1. Click the **Archived** tab to view all resolved comments. 2. To move a resolved comment back to the active list, click the **checkmark icon** next to it. The comment is marked as unresolved and moves back to the **Active** tab. ### Search and filter comments Use the search bar above the comments list to quickly find specific discussions in either the **Active** or **Archived** tab. ### Adjust display settings Customize how comments display in the list. 1. Click the **cog icon**. 2. Choose a sorting option: * **Newest on top** * **Oldest on top** * **By author (A to Z)** 3. To filter the list, select the option to **only display comments tagging you**. # Manage feature flag technical debt Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/manage-feature-flag-technical-debt Track flag health and classify flags as temporary or permanent to proactively identify and clean up stale flags in your codebase. Your feature flag dashboard can help in managing technical debt in your codebase and proactively cleanup obsolete or stale flags. ## Flag creation ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-feature-flag-technical-debt/0.png) When creating a new flag, you can define whether it’s a **temporary** or **permanent** flag. This distinction is crucial for managing technical debt effectively to keep track of their health and status moving forward. * **Temporary Flags:** Used to control features that have a short-term purpose or are part of an ongoing experiment. They are ideal for situations where you need to roll out a new feature with safeguards or precisely schedule your release. Temporary flags are not intended to be part of your codebase in the long run and are thus regularly checked for staleness to ensure they are actively managed. * **Permanent Flags:** Permanent flags are intended for long-term use and are meant to be a permanent part of your codebase. They are ideal for features that are core to your application’s functionality. Unlike temporary flags, permanent flags are exempt from staleness checks and Kameleoon will not highlight them for removal. ## Dashboard utilities for flag cleanup Your dashboard features a summary of total active flags, running Experiments, and stale/permanent flag counts for your Production environment (only). ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-feature-flag-technical-debt/status-and-last-evaluation.png) You also have two columns in the dashboard regarding flag status: * **Last Evaluation:** This column displays the last time the feature flag in your code was referenced (for example, the most recent instance of an end user being evaluated by the flag in your application code to determine which variation to expose the user to), giving you a clear indication of when the flag was last in active use. * **Status:** Based on its most recent evaluation and the last time it was edited or toggled in Kameleoon, a flag will be categorized into one of four possible statuses depending on whether it's permanent or temporary: * **Healthy:** The **temporary** flag is new or has been edited or evaluated in the last 15 days, indicating active usage. * **Stale:** The **temporary** flag has not been edited or evaluated in code for more than 15 days. This status may indicate that the rollout or experiment has reached a stable point and the flag is no longer being used, or that the flag has not been referenced in the code for a while. * **Outdated:** The **temporary** flag has not been edited or evaluated in code for more than 30 days. This status is a clear sign that the flag may be outdated and no longer necessary. * **Permanent:** The flag is **permanent** and exempt from staleness checks. ## Changing flag type You can change a feature flag's type from temporary to permanent and vice versa. As features and projects evolve, the nature of certain features may change. Converting a flag’s type ensures it aligns with the current development trajectory. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-feature-flag-technical-debt/1.png) Navigate to the secondary actions menu for any flag from the dashboard, and use the **Mark as temporary** button to change the flag type. It’s useful to consider your short and long-term goals and ensure that flag types reflect them. This consideration keeps dashboard features effective for cleanup. If you’re working in a team, ensure everyone involved is aware of the rationale and importance of keeping track of technical debt in your codebase and in the Kameleoon app. # Manage your feature flags Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/create-and-manage-flags/manage-your-feature-flags Search, filter, sort, and inspect your feature flags and their rules from the Overview tab, in Collapsed or Expanded view, and perform actions on them. To learn how to create a new feature flag, read [Create a feature flag](./create-a-feature-flag). ## Access the Feature flags dashboard To access the Feature flags dashboard, click **Features** > **Flags & Experiments**. The dashboard opens on the Overview tab. Use the environment dropdown to choose which environment's data the table displays: * **Production** * **Staging** * **Development** * **All environments**, which shows data from every environment at once and changes the table's layout. Read [View flags across all environments](#view-flags-across-all-environments) for details. Use the view toggle to switch how the table displays your flags: * **Collapsed:** A compact view showing one row per feature flag. * **Expanded:** Shows each flag's rules beneath it, including each rule's name, type, and status. ## Collapsed view columns ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-card.png) In Collapsed view, each row represents a feature flag and displays the following columns: * **Name:** The flag's name. A colored bar to the left of the name indicates the flag's project. * **Rule status:** The number of active rules out of the flag's total rules, for example **2/3 rules Active**, with a colored dot indicating the status. * **Rule type:** The number of rules of each type, shown next to a distinct icon for Targeted Delivery, [Experimentation](../using-the-rollout-planner/rules-and-targeting/create-feature-experiments), and [Progressive Delivery](../using-the-rollout-planner/rules-and-targeting/define-progressive-delivery-rules). * **Modified:** The flag's last-modified date, shown as a relative time (for example, **yesterday**) or a date depending on how recently the change occurred. * **Created:** The flag's creation date. * **Tags:** The flag's tags. * **Team:** The flag's team. Blank if the flag has no team. If the table can't display every column at once, click the arrow at the end of the row to reveal the remaining columns. ## View additional details for a flag Hover over the information icon on a flag's card to see additional details. The tooltip scrolls, so keep scrolling to see every field. It has two sections: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-info-card.png) **On this environment** shows information specific to the environment you've selected in the dropdown, including its key dates, such as the **Last state changes** date, which shows when the flag's state last changed in that environment. **On the feature flag (all environments)** shows information that applies to the flag generally, across all environments: * URL * Feature key * Feature status, which refers to the flag's health rather than its on/off state. Read [Manage feature flag technical debt](./manage-feature-flag-technical-debt) to learn how Kameleoon calculates this status. * Tags * Creator * Key dates: creation date, last content change, last modification, and last evaluation. ## Find a feature flag Use the search bar, the Filters tab, and the Columns tab's sort options to locate specific flags. Use the search bar at the top of the Overview tab to find a flag by name. ### Filter flags ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-your-feature-flags/filters.png) Click **Filters** in the right sidebar to filter the flags in the table. Filter by: * Project * Tags * State * Health * Key date * Rule state * Rule type * Integrations [Save these filters as a view](#select-a-view) for later retrieval. Kameleoon generates a unique URL with custom parameters when you apply filters. To share this URL with team members, click **Copy link to this filtered dashboard** on the right side of the dashboard. ### Sort flags by modification date, creation date, or tags ![Sort arrows on the Modified column header](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-sorting.png) Hover over the **Modified** or **Created** column header to reveal two arrows. Click the upward arrow to sort flags from least recent, or the downward arrow to sort flags from most recent. Hover over the **Tags** column header to reveal the same two arrows, and click one to sort flags alphabetically or in reverse alphabetical order. ### Choose which columns appear ![Columns sidebar tab](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-columns.png) Click **Columns** in the right sidebar to choose which columns appear in the table. Check a column to show it, or clear it to hide it. Kameleoon always shows the **Name** column, which you can't hide. In Expanded view, this selection also determines which columns appear on each rule's row. Besides the default columns covered in [Collapsed view columns](#collapsed-view-columns), you can also choose to display **ID/Key**, **Audience**, and **Traffic Allocation**. Define a sorting rule under **Sort by**. For example, select **Name** from the first dropdown and **Sort A to Z** from the second dropdown to sort flags alphabetically by name. ## Select a view Kameleoon applies the **Production environment** view by default. Click **Views** in the right sidebar to select a saved view, or create a custom view to save your current filter and column settings. To load a specific view automatically when the dashboard opens, set it as the default view by clicking the **three-dots** menu > **Default**. Kameleoon generates a unique URL with custom parameters when you create a view. To share this URL with team members, click the **three-dots** menu > **Copy URL**. ## Turn a flag on or off Click the toggle on a flag's card to turn the feature flag on or off. Read [Turn your feature flag on or off](../using-the-rollout-planner/basics/turn-your-feature-flag-on-or-off) for more detail. ## Edit a flag Hover over a flag's card and click the pencil icon to edit the flag in the [Rollout Planner](../using-the-rollout-planner/basics/rollout-planner-structure). ## Use a flag's actions menu ![Flag actions menu](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-additional-actions.png) Click the three-dots menu on a flag's card to access the following actions: ### Edit Opens the flag in the [Rollout Planner](../using-the-rollout-planner/basics/rollout-planner-structure). ### Results Opens the flag's overall Results page. Read [View your feature flag's overall results](../../../experiment-analytics/analyze-results/feature-flag-results/analyze-a-feature-flags-overall-results) to learn what it shows. ### Mark as permanent/temporary Changes the flag's type. Kameleoon shows only the option that applies to the flag's current type, for example **Mark as Permanent** for a temporary flag. Read [Manage feature flag technical debt](./manage-feature-flag-technical-debt) to learn how Kameleoon tracks flag types. ### Rename Rename an existing feature flag. The **Rename** sidebar opens. To confirm the new name, click **Save**. ### Manage tags Manage the tags associated with a feature flag. The **Manage tags** sidebar opens. The list and the flag card highlight the tags already associated with the flag. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-your-feature-flags/manage-tags.png) Associate existing tags or create new ones. Click **Save** to confirm your choices. ### Duplicate Duplicate an existing feature flag to quickly create similar flags. The **Duplicate** sidebar opens. To confirm the creation of the new feature flag, click **Save**. Duplication clones the rule configurations (for all environments), flag setup, and the original flag's tags. ### Archive Archives the flag. ### Delete Delete an existing feature flag. You must set the flag to OFF in all environments before you can delete it. To validate the deletion, confirm your choice in the pop-up. ### Select Selects multiple flags to apply a grouped action. Read [Perform grouped actions](#perform-grouped-actions) for the list of available grouped actions. ## Expanded view ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-expanded.png) In Expanded view, each flag's rules appear beneath it. Rule rows display the same columns you selected in the [Columns sidebar tab](#choose-which-columns-appear) as their parent flags, though each rule's status and type appear as text instead of an icon: * **Name:** The rule's name. * **Rule status:** Whether the rule is active or paused. * **Type:** [Targeted Delivery](../using-the-rollout-planner/rules-and-targeting/define-targeted-delivery-rules), [Experimentation](../using-the-rollout-planner/rules-and-targeting/create-feature-experiments), or [Progressive Delivery](../using-the-rollout-planner/rules-and-targeting/define-progressive-delivery-rules). * **Modified:** The rule's last-modified date. * **Created:** The rule's creation date. * **Tags:** The rule's tags. * **Team:** The rule's team. ## View additional details for a rule Hover over the information icon on a rule to see additional details: the rule's project, ID, type, status, and creation date, plus the associated experiment's ID and creation date. ## Edit a rule or view its results Hover over a rule to access two icons: * Click the pencil icon to edit the rule in the [Rollout Planner](../using-the-rollout-planner/basics/rollout-planner-structure). * Click the results icon to open the rule's own Results page. What you see there depends on the rule's type. Read [Analyze a rule's results](../../../experiment-analytics/analyze-results/feature-flag-results/analyze-a-rules-results) to learn how each type differs. ## View flags across all environments Selecting **All environments** from the environment dropdown changes the table's layout in both Collapsed and Expanded view. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experiment-analytics/feature-flag-results/ff-overview/ff-all-environments.png) In **Collapsed** view, each flag lists its environments beneath it (Production, Staging, and Development). Each environment row shows the date its state last changed and includes its own toggle to turn the flag on or off in that environment. In **Expanded** view, the layout is similar, but each environment row also shows the flag's rules beneath it. ## Perform grouped actions Apply actions to multiple feature flags simultaneously using the **Grouped actions** sidebar. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/manage-your-feature-flags/grouped-actions.png) 1. Click the **More options** (three-dots) menu on any feature flag card. 2. Click **Select**. The **Grouped actions** sidebar opens. 3. Select the checkboxes next to the feature flags you want to modify. 4. In the sidebar, click the action you want to perform: * **Manage tags** * **Archive** * **Remove** * **Mark as temporary** * **Mark as permanent** * **Turn on** * **Turn off** 5. Confirm the action in the pop-up window. # Create goals for feature flags Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/track-your-feature-experiments/create-goals-for-feature-flags Add goals to your feature flags to track experiment and delivery performance across all environments using Kameleoon's reporting tools. Goals help you measure the success of your product or feature releases and experiments. Goals are tracked across all environments (development, staging, and production) and for all delivery rules you've set. Kameleoon reporting is only available if you've added **at least one goal**. If you remove all goals from an active flag, Kameleoon will stop tracking the flag and reporting will be unavailable. Goals are not necessary for your feature flag if you're not using Kameleoon's reporting tools. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/track-your-feature-experiments/create-goals-for-feature-flags/goals.png) Click **Set up** > **Goals** > **Add goal**. Choose from existing goals or create a new one. [Learn more about creating goals](../../../assets/goals/create-a-goal). You must choose at least one goal when creating a feature flag you want Kameleoon to report. The first goal you choose is automatically the **Primary goal**. Additional goals you add will be **Secondary goals**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/track-your-feature-experiments/create-goals-for-feature-flags/image-23.png) You can untrack a goal or assign a primary goal by clicking the three dots next to each goal. # Managing third-party integrations Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/track-your-feature-experiments/managing-third-party-integrations Configure which data Kameleoon sends to your third-party analytics tools for delivery rules and feature experiments. Managing third-party analytics tools for your feature flags is simple once you've [set up your integrations](../../../integrations/integrations-overview). Integrations for third-party tools are only compatible with web SDKs. They're not available for mobile SDKs. Create or open a feature flag. Then, click **Reporting** > **Integrations**. Accessing the Reporting section requires the Marketing role. Learn more in [User roles](/user-manual/account-and-team-management/users-and-teams/user-roles). ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/track-your-feature-experiments/managing-third-party-integrations/0.png) You must identify what data you want to collect and analyze before Kameleoon relays it to your third-party analytics tool. Use the **Integrations** page to identify what data is useful for a particular flag. The **Admin** menu displays all the integrations you've linked to Kameleoon. You can also choose whether to export data for: * **Delivery Rules**: Sync data for Targeted and Progressive delivery rules only. * **Feature Experiments**: Sync data for Experiment rules only. Choosing one or both of these options gives you control over how much data goes to your analytics tool. Check your integration configurations first to ensure your analytics data is accurate, relevant, and actionable. You must implement our SDK and Kameleoon JavaScript tag to use this feature. We recommend implementing the [Kameleoon Asynchronous tag](../../../../developer-docs/web-experimentation/implementation-and-deployment/standard-implementation#asynchronous-tag-without-anti-flicker), which you can install before closing the `` tag in your HTML page. Use the Asynchronous tag for tracking purposes only. All Kameleoon SDKs use the [`getEngineTrackingCode()`](../../../../developer-docs/sdks/web-sdks/nodejs-sdk#getenginetrackingcode) method to return the JavaScript code, insert it in your page, and automatically send the exposure events to the analytics solution you're using. # Experiment on LLM prompts and AI-agent configurations Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/use-cases/ab-test-llm-prompts Use Kameleoon feature flags to run controlled experiments on the prompts, models, and configurations behind an LLM application or AI agent, and measure impact with AI-quality, operational, and business metrics. LLM-powered applications and AI agents are non-deterministic. Changing a prompt, model, retrieval strategy, tool configuration, or agent workflow can affect response quality, operating cost, latency, and user behavior in different, hard-to-predict ways. Evaluation frameworks such as RAGAS score whether an individual response meets a quality bar, but that score doesn't tell you whether the change helps users accomplish what they came to your product to do. You can use Kameleoon Feature Experimentation capabilities to customize, test, and roll out the configuration behind a generative AI application or AI agent. A feature variable holds a single piece of that configuration (a prompt, a model parameter, a retrieval strategy, a tool definition), so your team can manage it outside your application code. Each variation represents a candidate configuration, which lets you iterate, experiment, and release changes more safely, without redeploying. For example, an AI customer-support agent might expose its system prompt, model, reasoning effort, and retrieval settings as four separate variables, so you can test a new combination of all four at once instead of waiting on a deploy for each one. Split traffic between variations and compare their impact using several types of metrics: * **AI-quality metrics**, such as correctness, groundedness, relevance, context quality, safety, or compliance * **Agent-performance metrics**, such as successful tool use or task completion * **Operational metrics**, such as latency, token consumption, errors, and cost * **User and business metrics**, such as satisfaction, escalation, conversion, retention, and revenue Because the configuration lives in a feature flag instead of your source code, you can add, edit, or roll back a variation directly from the Kameleoon platform at any time. ## Evaluation and experimentation solve different problems Evaluation determines whether an individual model or agent output meets a defined quality standard. Observability tools let you inspect the prompts, responses, traces, retrieval steps, and tool calls behind that output. Experimentation determines whether a change to the underlying configuration causes a measurable improvement for users or the business: a question neither evaluation nor observability answers. For example, an LLM judge might score a customer-support agent's rewritten system prompt as more grounded than its current one. A Kameleoon experiment answers the questions your team is accountable for: whether that same configuration resolves more tickets without escalating to a human, and what it costs in latency and token spend to get there. Kameleoon doesn't replace your LLM observability or evaluation stack. Feed evaluator scores from RAGAS, an LLM judge, or a human review process into Kameleoon as a [custom goal](/user-manual/assets/goals/create-a-goal#custom-goal), and track those scores alongside the behavioral and business goals your experiment already measures. Your model-level quality signals then connect to a statistically reliable measurement of real user impact. For most AI experiments, combine several metric types rather than relying on one: * Set a user or business outcome as the primary goal. * Track AI-quality metrics as secondary goals or guardrails. * Monitor latency, cost, errors, and safety as operational guardrails. * Validate automated judges against a sample of human-reviewed examples before you trust their scores at scale. ## How it works An experiment on an LLM application or AI agent moves through five stages in Kameleoon: 1. **A feature flag holds the configuration.** Each part of the configuration, such as a prompt or a model name, becomes a feature variable on the flag. 2. **Each variation sets its own values.** A variation is one complete candidate configuration, with a value for every variable. 3. **The SDK assigns each visitor a variation.** When a visitor reaches your application, your code requests the flag and receives the assigned variation's values, then uses them to call the LLM or configure the agent. 4. **Goals record what happened.** Your application tracks a conversion against each goal attached to the flag, including a guardrail conversion that fires only when a response breaches an acceptable quality or latency threshold. 5. **The results page compares the variations.** After you collect enough traffic, compare the variations across every attached goal to decide which configuration to roll out. ## Prerequisites * A Kameleoon account with a project set up for Feature Experimentation. * Your account's client ID and client secret. To find these values, see [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials). * A server-side application where you can install a Kameleoon SDK, for example a Python application. ## Set up your AI-agent experiment in Kameleoon The following steps build one concrete experiment: does a rewritten system prompt, combined with a stronger model and a higher reasoning effort, help a customer-support AI agent resolve more tickets on its own, and does it keep latency within an acceptable range while doing so? Configure the feature flag, variations, and tracking goals in the Kameleoon platform before you touch your application code. ### Create the feature flag Create a feature flag to hold your agent's configuration and control the rollout of your experiment. 1. In the Kameleoon app, click **Features** > **Flags & Experiments** > **New feature flag**. 2. Enter a name, for example `AI support agent config`, and select the project for the flag. 3. In the **Description** field, note what the flag controls, for example "Controls the system prompt, model, reasoning effort, and retrieval settings for the support chatbot," so other members of your team understand its purpose. 4. Click **Validate**. 5. Kameleoon generates a feature key from the flag's name. Note the generated key, or edit it to `ai_support_agent`. Your application code identifies the flag by this key, not by its name, so the two must match. New flags start in the **OFF** state. You turn the flag on in the Rollout Planner after you finish configuring it. For more detail, see [Create a feature flag](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag). ### Store the agent configuration in feature variables Add one feature variable for each part of the agent's configuration you want to test, so you can change any of them from the Kameleoon platform without editing your application code. This example tests four variables: | Variable key | Type | Example default value | | ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `system_prompt` | String | `You are the support assistant for Kameleoon's help center. Answer only using the article excerpts provided below, and say you'll escalate to a human agent if the answer isn't in them.` | | `model` | String | `claude-sonnet-5` | | `reasoning_effort` | String | `low` | | `retrieval_top_k` | Number | `3` | 1. On the flag's page, in the left sidebar, click **Set Up** > **Variables** > **Add Variable**. 2. Set the variable's **Type** to match the table, either **String** or **Number**. 3. Enter the **Variable Key** from the table, for example `system_prompt`. 4. Set the **Default Value** to the value your application uses in production today. Every variation you create later starts pre-filled with these values, so accurate defaults save you work and give you a known-good configuration to fall back on. 5. Click **Save**. 6. Repeat these steps for each remaining variable in the table. ![The Variables setup screen showing four variables named system\_prompt, model, reasoning\_effort, and retrieval\_top\_k, each with its default value.](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/developers/images/feature-experimentation/integrations/llm/variables.png) Kameleoon also offers an **Enum** type, where you enter the allowed values as a comma-separated list and then pick from a drop-down when you define variations. Consider it for a variable that only accepts a fixed set of values, since a drop-down prevents a typo from reaching your LLM provider. In this example, you could define `model` as an Enum with the list `claude-sonnet-5,claude-opus-5`, and `reasoning_effort` as an Enum with the list `low,medium,high`. For more detail, see [Define feature variables](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables). ### Create your variations A delivery rule can only serve **Off** or a variation you created yourself, so an A/B comparison needs a variation for your current configuration as well as one for each new configuration you want to test. This example creates two variations: `Baseline`, which mirrors what your application already serves in production, and `Grounded, high reasoning`, a challenger that combines a rewritten, more explicit prompt with a stronger model and more reasoning effort. Don't use **Off** as the comparison arm of an experiment, even if your application already has a hardcoded fallback for when it can't read the flag. **Off** represents your application with the flag turned off, and it carries none of the flag's feature variables, so code that reads `system_prompt` or `model` from an **Off** assignment gets nothing back. A hardcoded fallback doesn't solve that problem either: it lives in your source code, not in Kameleoon, so promoting a winning configuration later still means a deploy, and nothing keeps it in sync if the flag's variables change. Create an explicit `Baseline` variation instead, so your current configuration stays visible and editable alongside the challenger you're testing against it. | Variation | `system_prompt` | `model` | `reasoning_effort` | `retrieval_top_k` | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------ | ----------------- | | Baseline | Same as the default value | `claude-sonnet-5` | `low` | `3` | | Grounded, high reasoning | `You are the support assistant for Kameleoon's help center. Using only the article excerpts provided below, answer the visitor's question and cite the specific article title for each claim you make. If the excerpts don't fully answer the question, say so explicitly and escalate to a human agent instead of guessing.` | `claude-opus-5` | `high` | `5` | 1. In the left sidebar, click **Set Up** > **Variations** > **Add variation**. 2. Enter a **Name**, for example `Baseline`, and a matching **Variation Key**, for example `baseline`. Each variable is pre-filled with its default value, so leave all four unchanged. 3. Click **Save**. 4. Repeat these steps for a second variation named `Grounded, high reasoning` (key `grounded_high_reasoning`), editing each of the four variables to match the value in the table for this variation. 5. Click **Save**. ![The Variations setup screen showing two variations, Baseline left at its default values and Grounded, high reasoning with system\_prompt, model, reasoning\_effort, and retrieval\_top\_k each set to their overridden values.](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/developers/images/feature-experimentation/integrations/llm/fe-variations.png) For more detail, see [Define feature variations](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variations). ### Attach goals for business impact, AI quality, and latency Attach several goals to the feature flag so you can compare variations across the outcomes your team is accountable for, not just response quality. Because Kameleoon is a unified platform, you can attach any goal that already exists in your organization, or create a goal specific to your LLM-powered feature. This example attaches three goals, one from each of the metric categories that matter for an AI agent: | Goal | Metric category | Type | What it answers | | ------------------------------------ | ----------------- | ----------- | ------------------------------------------------------------------- | | `Ticket resolved without escalation` | User and business | Custom goal | Did the agent actually solve the visitor's problem? | | `Response groundedness score` | AI quality | Custom goal | Did more responses fall below an acceptable groundedness threshold? | | `Response latency` | Operational | Custom goal | Did more responses take longer than an acceptable threshold? | Create all three as **Custom goals** that your backend triggers, since your application fires them rather than the visitor's browser. When you create each goal, select **Custom goal** as the **Type**, then choose [the option for a back-end event via SDK](/user-manual/assets/goals/create-a-goal#option-2-using-a-back-end-event-via-sdk-for-example-node-js). 1. On the flag's page, in the **Set Up** menu, click **Goals** > **Add goal**. 2. Select an existing goal, or click **Create a new goal** to define one. Add `Ticket resolved without escalation` first, since Kameleoon automatically sets the first goal you attach as the **Primary goal**. 3. Click **Save**. 4. Repeat these steps for `Response groundedness score` and `Response latency`, which Kameleoon attaches as **Secondary goals**. If a goal ends up with the wrong designation, click the three dots next to it to reassign which goal is primary. `Response groundedness score` and `Response latency` don't carry a numeric value. Your application decides whether a given response breached an acceptable threshold, either too slow or too ungrounded, and triggers the goal's conversion only when it did. Kameleoon then reports each goal's conversion rate per variation, telling you what fraction of responses breached that threshold. ![The Goals setup screen showing the feature flag's three attached goals, Ticket resolved without escalation, Response groundedness score, and Response latency.](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/developers/images/feature-experimentation/integrations/llm/fe-goals.png) For more detail on goal types, including how to trigger a custom goal from your backend, see [Create a goal](/user-manual/assets/goals/create-a-goal#custom-goal). ### Roll out the experiment Add an Experiment rule that splits traffic between your two variations, then turn the flag on to start collecting data. The **Add a rule** menu groups rules by purpose. **Feature testing** contains the Experiment rule, which splits traffic and measures a statistically significant comparison between variations, while **Feature delivery** contains Progressive delivery and Targeted delivery, which release a single variation gradually or to a specific segment without comparing arms. An A/B test needs the Experiment rule. 1. In the **Rollout Planner**, select the environment you want to target, for example **Production**. 2. Click **Add a rule**, then under **Feature testing**, select **Experiment**. 3. Under **Variations to serve**, set `Baseline` as the **Control** and add `Grounded, high reasoning` as a **Treatment**. Kameleoon measures every treatment's results against the control, so the control must be the configuration you already run in production. 4. Set the traffic distribution across the two variations, for example 50% each. 5. Set the rule's targeting to include the visitors you want to test, for example all visitors who open a support conversation. 6. In the **Then, for everyone else in production, serve** drop-down, select `Baseline`. Visitors who fall outside the rule's targeting then receive your current, validated configuration, and your application still gets a full set of variables for them. 7. Turn the flag's **ON/OFF** toggle to **ON**. 8. Click **Save**. ![The Rollout Planner for the Production environment showing an Experiment rule, under Feature testing, with Baseline set as the Control and Grounded, high reasoning added as a Treatment, splitting traffic 50/50.](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/developers/images/feature-experimentation/integrations/llm/environments.png) For more detail, see [Create feature experiments](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/create-feature-experiments). After you save the rule, Kameleoon starts assigning visitors to a configuration and serving the corresponding variables. To change a value or add a variation later, edit it directly in the Kameleoon platform. You don't need to redeploy your application to make these changes. ## Retrieve the configuration in your application Install the Kameleoon Python SDK, then retrieve the visitor's assigned configuration and track a conversion for each goal as the visitor's ticket progresses. The same pattern applies to any [Kameleoon server-side SDK](/developer-docs/feature-experimentation/get-started/overview#server-side-sdks), including Node.js, Java, and Go. Because Kameleoon only supplies the configuration values, the same pattern also works with any agent framework, such as the OpenAI Agents SDK, the Claude Agent SDK, or LangChain. Most agent code runs on Python or TypeScript, so pick whichever matches your application. 1. Install the SDK as a dependency: ```bash theme={null} pip install kameleoon-client-python ``` 2. Initialize the client with your site code and credentials. Set `environment` to the same Rollout Planner environment that holds your Experiment rule, otherwise the SDK evaluates a different environment's rules: ```python theme={null} from kameleoon import KameleoonClientConfig, KameleoonClientFactory SITE_CODE = "a8st4f59bj" FEATURE_KEY = "ai_support_agent" TASK_GOAL_ID = 12345 # "Ticket resolved without escalation" QUALITY_GOAL_ID = 12346 # "Response groundedness score" LATENCY_GOAL_ID = 12347 # "Response latency" configuration = KameleoonClientConfig( client_id="your-client-id", client_secret="your-client-secret", environment="production", ) kameleoon_client = KameleoonClientFactory.create(SITE_CODE, configuration) if not await kameleoon_client.wait_init_async(): # The SDK failed to initialize. Fall back to your production configuration. ... ``` 3. Retrieve the assigned configuration before you call your LLM, and track a conversion for each goal as the visitor's ticket progresses: ```python theme={null} from anthropic import AsyncAnthropic judge_client = AsyncAnthropic() # Mirrors the flag's default values, for use when the SDK can't reach Kameleoon. FALLBACK_CONFIG = { "system_prompt": "You are the support assistant for Kameleoon's help center. ...", "model": "claude-sonnet-5", "reasoning_effort": "low", "retrieval_top_k": 3, } # A response breaches the latency guardrail above this value. LATENCY_THRESHOLD_MS = 5000 # A response breaches the groundedness guardrail below this value. QUALITY_THRESHOLD = 0.7 def get_agent_config_for_visitor(visitor_code: str) -> dict: try: variation = kameleoon_client.get_variation(visitor_code, FEATURE_KEY) # These exception classes ship with the Kameleoon SDK. except (FeatureNotFound, FeatureEnvironmentDisabled, VisitorCodeInvalid): # The visitor isn't in the experiment, so serve the known-good configuration. return FALLBACK_CONFIG variables = variation.variables if not variables: return FALLBACK_CONFIG return { "system_prompt": variables["system_prompt"].value, "model": variables["model"].value, "reasoning_effort": variables["reasoning_effort"].value, "retrieval_top_k": int(variables["retrieval_top_k"].value), } def track_ticket_resolved(visitor_code: str) -> None: kameleoon_client.track_conversion(visitor_code, TASK_GOAL_ID) # LLM-as-a-judge: extracts the factual claims in `response` and scores # the fraction that `retrieved_context` supports. async def score_response_groundedness(retrieved_context: str, response: str) -> float: judge_prompt = ( "List the factual claims in RESPONSE. For each claim, check " "whether CONTEXT supports it. Return only a number from 0 to 1: " "the fraction of RESPONSE claims that CONTEXT supports.\n\n" f"CONTEXT:\n{retrieved_context}\n\nRESPONSE:\n{response}" ) judge_reply = await judge_client.messages.create( model="claude-opus-5", max_tokens=10, messages=[{"role": "user", "content": judge_prompt}], ) return float(judge_reply.content[0].text) def track_quality_score(visitor_code: str, score: float) -> None: if score < QUALITY_THRESHOLD: kameleoon_client.track_conversion(visitor_code, QUALITY_GOAL_ID) def track_response_latency(visitor_code: str, latency_ms: float) -> None: if latency_ms > LATENCY_THRESHOLD_MS: kameleoon_client.track_conversion(visitor_code, LATENCY_GOAL_ID) ``` Call `get_agent_config_for_visitor()` with the visitor's `visitor_code` before you send a request to your LLM, and use the returned values to build the request, the system prompt, model, reasoning effort, and number of retrieved documents. Call `track_ticket_resolved()` when the agent resolves the visitor's issue without escalating to a human. After the agent responds, call `score_response_groundedness()` with the documents it retrieved and the response it generated, then pass the returned score to `track_quality_score()`. Call `track_response_latency()` with the response time in milliseconds after each LLM call. Both `track_quality_score()` and `track_response_latency()` track a conversion only when the value breaches its threshold, so a response that stays within both guardrails triggers neither goal. `score_response_groundedness()` is a minimal LLM-as-a-judge example: it asks a model to compare the response's claims against the retrieved context and return the fraction it supports. [RAGAS's Factual Correctness metric](https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/factual_correctness/) scores the same underlying idea, and you can substitute it, or another evaluation framework your team already uses, for a hand-rolled judge prompt. Kameleoon then reports each goal's conversion rate per variation, telling you what fraction of responses breached the quality or latency guardrail, rather than tracking the raw score or millisecond value itself. Always handle the case where a visitor falls outside the experiment. An LLM call built from a missing prompt or model fails at request time, so return a complete fallback configuration rather than letting a lookup raise or return `None`. Use [`get_visitor_code()`](/developer-docs/sdks/web-sdks/python-sdk#get_visitor_code) to assign a unique ID to each visitor, and [`set_legal_consent()`](/developer-docs/sdks/web-sdks/python-sdk#set_legal_consent) if your application requires visitor consent before tracking data. For the full client initialization and configuration reference, see the [Python SDK developer guide](/developer-docs/sdks/web-sdks/python-sdk#developer-guide). ## Monitor and iterate Open the feature flag's results page to compare `Baseline` and `Grounded, high reasoning` across all three attached goals. Kameleoon tracks exposures and conversions automatically whenever your application calls `get_variation()` and `track_conversion()`, so you don't need any additional instrumentation. Read the three goals together rather than in isolation. The challenger in this example runs a larger model at a higher reasoning effort and retrieves more documents, so it costs more per conversation and is more likely to breach the latency guardrail. A win on `Ticket resolved without escalation` justifies that trade only if `Response latency` and `Response groundedness score` don't convert more often for the challenger than for `Baseline`. If the primary goal moves but a guardrail's conversion rate rises past what you're willing to accept, keep serving `Baseline` and refine the challenger. You don't need to watch the results page yourself to catch a challenger that underperforms. Add a rollback condition to the experiment rule, for example turning it off when `Response groundedness score` breaches a threshold you define, and Kameleoon automatically disables the rule and returns all visitors to `Baseline` once the condition triggers. See [Automatically rollback a feature](../using-the-rollout-planner/validation-and-rollback/automatically-rollback-a-feature). When a challenger wins, promote it: update each variable's **Default Value** to the winning configuration so it becomes the new known-good baseline, then retire the experiment rule or reuse the variation for your next hypothesis. For more detail, see [Analyze a feature flag's overall results](/user-manual/experiment-analytics/analyze-results/feature-flag-results/analyze-a-feature-flags-overall-results). ## Next steps * Read the [Python SDK reference](/developer-docs/sdks/web-sdks/python-sdk) for advanced options such as custom data, cross-device experimentation, and targeting conditions. * Attach [precise segmentation criteria](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) to target the experiment at a specific audience, for example only tickets tagged with a certain product area. * Add a token-cost goal alongside latency, tracking tokens consumed per conversation as a numeric custom goal, so you can price the difference between a Sonnet and an Opus configuration directly. See [Create a goal](/user-manual/assets/goals/create-a-goal#custom-goal). * Add a context-relevance goal to check whether raising `retrieval_top_k` actually improves which documents the agent retrieves, since a grounded answer can still come from the wrong documents. See [Create a goal](/user-manual/assets/goals/create-a-goal#custom-goal). * Add a direct user-feedback goal, such as a thumbs-up or thumbs-down control after each response, to capture visitor satisfaction alongside the behavioral signals this example already tracks. See [Create a goal](/user-manual/assets/goals/create-a-goal#custom-goal). * Add more variables to test other parts of the agent's configuration, such as temperature, tool definitions, or a fallback model for retries. See [Define feature variables](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables). * Validate your `Response groundedness score` threshold by comparing a sample of automated scores against a human review before you trust it at scale. See [Create goals for feature flags](/user-manual/experimentation/feature-experimentation/track-your-feature-experiments/create-goals-for-feature-flags). # Build whitelists to roll out a feature to internal users Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/use-cases/build-whitelists-to-rollout-a-feature-for-internal-users Use visitor code whitelists and targeted delivery rules to QA feature experiments with specific internal users before a broader release. If you want to QA a feature experiment or roll out a feature variation to an internal group of users, you can combine a user whitelist with a targeted delivery rule. This combination is one of the easiest ways to do it. For example, suppose you want to release a new feature with two variations: Feature A and Feature B. Before launching the experiment, you want to QA its live behavior by showing Feature A to a group of internal users and Feature B to a group of stakeholders. Follow these steps to set up that QA test: 1. Create segments of whitelisted users (Group A and Group B). 2. Create a targeted delivery rule for each group of users. Avoid setting many user IDs (fewer than 20 per variation): the size of your data file increases, and so does the latency, since the SDK must fetch a larger file. If you want to target more users, you can set a custom data attribute for the whole audience (for example, internal users only), then target the experience with that audience. ## Create a targeted delivery rule for each group of users 1. Click **Activate** > **Feature flags** and edit your feature flag. 2. Add a new rule and select **Targeted delivery**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/use-cases/build-whitelists-to-rollout-a-feature-for-internal-users/0.png) 3. On the right side of the panel, create a new segment (Group A) and look for the **Visitor code** condition. Add the user IDs that you want to be part of Group A. 4. Choose the variation you want to serve, for example, Variation A. All users from Group A receive Variation A of your flag. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/use-cases/build-whitelists-to-rollout-a-feature-for-internal-users/1.png) 5. Add a second **Targeted delivery** rule and replicate the steps for Variation B and Group B. # Run feature experiments with feature flags Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/use-cases/run-feature-experiments-with-feature-flags Run data-driven feature experiments using feature flags to validate hypotheses, reduce risk, and optimize user experiences at scale. ## Are feature flags all you need to experiment? Feature experiments using feature flags enable a powerful and data-driven approach for all teams to test hypotheses and validate assumptions before fully releasing new features to users. By gradually rolling out experiments to targeted user segments, teams can gather valuable insights and fine-tune their products to meet user needs effectively. This article will guide you through running a feature experiment using Kameleoon, helping you optimize user experiences and drive engagement for your products. ## Is feature experimentation the right option for you? Feature experiments using feature flags offer several advantages over traditional web experimentation approaches. Here are some key reasons why you should consider making the switch: * **Advanced server-side experimentation:** Feature experiments offer all the benefits you may associate with server-side testing such as reduced client-side dependencies, improved performance, enhanced security, and granular targeting. Feature experimentation also ensures consistency across different platforms, version independence, and compliance with data privacy regulations – making it vastly scalable for larger user bases. * **Real-time Control and Safety:** With feature flags, you can control the rollout of a new feature in real-time. This control provides a safety net to quickly disable the feature if any issues arise, ensuring a smooth user experience and minimizing potential negative impacts—all without having to write or deploy any new code. * **Gradual Rollout and Risk Reduction:** Feature flags allow for gradual feature rollout to a subset of users. This controlled release mitigates risks associated with full deployment, letting you test the feature in a controlled environment before exposing it to your entire user base. * **Iterative Development:** Feature experiments using dynamic variables facilitate iterative development. Product teams can make continuous improvements to a feature based on user feedback and data insights, without requiring a full redeployment. * **Faster Experimentation Cycles:** Feature flagging speeds up experimentation cycles since you can quickly introduce, modify, or remove features without redeploying the entire application. * **Reduced Technical Debt:** Traditional web experimentation may require maintaining multiple code branches to support different variations of the experiment. Depending on your team’s practices, feature flags centralize this control, reducing technical debt and code complexity. * **Enhanced Collaboration:** Feature flagging fosters collaboration between product, engineering, and business teams. It enables cross-functional teams to work together seamlessly on feature development and experimentation. Feature experimentation is also more well-suited for modern development practices. Overall, feature experiments using feature flags give your teams greater control, flexibility, and speed, making them a more efficient and effective approach to validating hypotheses and improving product features. ## Step-by-step guide Getting started with feature flags for experimentation may seem complex, but a simple, systematic approach can ensure successful implementation and testing and set you up for long-term success. It helps to clearly outline your experimentation goals and what you want to achieve with feature flagging. Identify specific features or hypotheses you wish to test and validate. This planning will give you a clear direction and purpose for using feature flags in your experimentation process. ### Step 1: Identify the hypothesis Begin by clearly defining the hypothesis you want to test, for example, a change you believe should move your metrics in an expected direction. For instance, if you have new ideas for a streamlined checkout process that will increase conversion rates, formulate your hypothesis accordingly. ### Step 2: Create a feature flag Now, [create a feature flag in Kameleoon](../create-and-manage-flags/create-a-feature-flag) for the new checkout process feature and keep it off for the time being, so the new checkout flow is initially hidden from all users. ### Step 3: Variabilize your feature (optional) When your developer codes your new checkout process, they can [use Kameleoon’s feature variables](../configure-your-feature-flags/define-feature-variables) to ensure they won’t have to revisit the source code again. They can use the Kameleoon app to dynamically update values and parameters inside your code using variables if you are testing different versions of the same feature. ### Step 4: Targeting and segmentation [Decide which user groups or segments](../using-the-rollout-planner/rules-and-targeting/add-a-new-rollout-rule) will be part of the experiment. Kameleoon lets you target specific demographics, user attributes, or even beta testers to receive the new feature. ### Step 5: Start your experiment [Initiate the experiment by rolling out the variants of your streamlined checkout process to a small percentage of the targeted users](../using-the-rollout-planner/rules-and-targeting/define-progressive-delivery-rules). Controlling the rollout percentage helps you minimize risks and measure the feature’s impact, while ensuring you can still test your ideas. ### Step 6: Monitor and analyze As users interact with the new checkout flow, monitor their behavior using the Results page. Track essential metrics such as conversion rates, user engagement, and drop-off points to assess performance. You can now easily [compare the performance](../../../experiment-analytics/analyze-results/feature-flag-results/analyze-a-rules-results) of the experiment group (users with the new feature) against the control group (users without the feature). Analyze the data to determine if the streamlined checkout process positively influenced user behavior. If needed, you can also export your experiments' results to any of the several partnered third-party tools Kameleoon allows integrations with. ### Step 7: Iterate and make decisions Based on the experiment's results, make informed decisions about whether you want to roll out the best-performing checkout variant to a larger percentage of your users. If the data supports the hypothesis, proceed with a full rollout to all users. Alternatively, if the results are inconclusive or disappointing, iterate on the feature by tweaking your variables and variations from the app, and conduct additional experiments to optimize its conversion rates. ## Conclusion Running feature experiments using feature flags equips your teams with valuable insights, helping them validate hypotheses and optimize user experiences. By leveraging feature management tools to control feature rollout and collect real-time data, teams can refine their products with confidence and deliver exceptional user satisfaction. Remember to utilize data-driven insights from feature experiments to continuously enhance your product, staying responsive to user needs and preferences as your product evolves. # Rollout Planner structure Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/basics/rollout-planner-structure Understand the Rollout Planner layout, including the header, setup menu, delivery rules section, and rollout summary. The Rollout Planner consists of several blocks. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/using-the-rollout-planner/rollout-planner-structure/rollout-planner-structure.png) * A **header** in the top left indicating the flag's name, associated site code, and feature key. * A **Setup** menu to create and manage your feature variables and feature variations. * A **Rollout Planner** specific to each of your environments. Changes made to a feature flag are specific to the environment you select in this section. * A **reporting section** which lists the Results pages for your created experiments. * A **toggle** that turns your feature flag on or off. * A **Delivery rules** section, where you add your rollout rules. * A **rollout summary** that summarizes the variations you're deploying through your rollout rules and the segments your rollout rules target. Accessing the reporting section requires the Marketing role. Learn more in [User roles](/user-manual/account-and-team-management/users-and-teams/user-roles). # Turn your feature flag on or off Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/basics/turn-your-feature-flag-on-or-off Toggle your feature flag on or off and configure the default variation served to users not targeted by any rollout rule. You can turn your feature flags on or off. ## Choosing a default variation Default variation dropdown at the end of the rollout rules queue At the end of your rollout rules queue, there's a dropdown list that defines which variation Kameleoon delivers to users that aren't targeted by any rule. * **Example 1:** If you want to roll out a new feature to 10% of your audience, Kameleoon delivers this default variation to 90% of users. * **Example 2:** If you have two delivery rules running with each targeting 40% of your total audience, the remaining 20% receive this default variation. Kameleoon automatically sets the default variation to the **Off** variation. However, you can change which variation appears by default. Changing the default variation has no effect when you turn your flag off. ## Manual toggle Manual toggle for turning a feature flag on or off The manual toggle is the primary method of controlling your flag's state (on or off). When you turn your feature flag toggle **on**, Kameleoon evaluates all delivery and experiment rules you've defined in the rules queue. However, while **off**, Kameleoon doesn't evaluate any delivery rules. # Using the Activity Log Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/basics/using-the-activity-log Monitor and troubleshoot feature releases using the Activity Log's chronological record of actions performed on a feature flag. The Activity Log is available for each feature flag you create. You can use the Activity Log to monitor and troubleshoot feature releases. Create or edit a feature flag, and click **Reporting** > **Activity Log**. Accessing the Reporting section requires the Marketing role. Learn more in [User roles](/user-manual/account-and-team-management/users-and-teams/user-roles). Activity Log menu showing a chronological record of actions on a feature flag The **Activity Log** menu provides a chronological record of actions performed on this feature flag. Click the **eye** icon to view changes' details. ## Filters The available filters are on the right side of the screen: * Filter by environment * Filter by date range * Filter by event type * Filter by flag contributor # Multi-armed bandit optimizations for feature experiments Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/optimizations-and-scheduling/multi-armed-bandit-optimizations-for-feature-experiments Learn how to use multi-armed bandits and contextual bandits to maximize experiment performance through dynamic traffic allocation. Kameleoon offers two types of dynamic traffic allocation algorithms to help maximize experiment performance: **multi-armed bandits (MABs)** and **contextual bandits**. Both approaches use real-time performance data to allocate more traffic to better-performing variations but differ in how they treat user data. The following sections explain how these algorithms work, when to use them, and how to activate them in your experiments. To enable dynamic traffic allocation, create a new experiment, or open an existing one. Variation to serve dropdown with Multi-armed bandit and Contextual bandit optimization options In the **Variation to serve** section, select the preferred allocation from the dropdown menu. Choose between **Multi-armed bandit** or **Contextual bandit** optimization. Kameleoon updates the allocation based solely on the lift of the primary goal. ## Multi-armed bandits When using dynamic allocation (such as MABs), you can't manually edit exposure rates. Instead, Kameleoon automatically measures improvement over the original variation and estimates the gain in total conversions using the Epsilon Greedy algorithm. Kameleoon repeats this process hourly. The MAB algorithm redirects traffic to higher-performing variations, even without statistical significance, which can drastically reduce the time required to identify winning or losing variations. Auto-optimized experiments rely on the original variation ("off" for Feature Experiments) to optimize the deviations. If the original variation doesn't receive traffic, the deviation might not update, causing the allocation to remain at 50/50 despite a clear winning variation. MABs don't rely on a control or baseline experience. Unlike A/B tests, MABs prioritize improvement over an initial equal allocation and dynamically adjust traffic based on real-time performance. In cases where statistical analysis is less important and you must minimize "exploration" time, MABs are useful because they focus more on "exploitation." ## Contextual bandits Contextual bandits dynamically optimize traffic allocation in experiments using machine learning. They adapt in real-time to redistribute traffic based on variation performance and user context to maximize effectiveness. Key differences distinguish multi-armed bandits from contextual bandits: * **Multi-armed bandits:** These optimize traffic distribution among multiple variations (arms) to maximize a defined goal, such as click rates or conversions. They treat all users equally, with no distinction based on user attributes, which suits scenarios where user-specific data is unavailable or unnecessary and the focus remains on finding the highest-performing variation for the overall audience. Kameleoon places no limit on the number of variations you can use with multi-armed bandits. * **Contextual bandits:** These incorporate additional user-specific data, such as device type, location, or behavior, into decision-making. They facilitate more personalized decisions by tailoring variations to specific users for improved outcomes. The variability introduced by user attributes allows contextual bandits to optimize decisions in dynamic environments. Contextual bandits support a maximum of 5 variations, including the original, because of how the underlying machine learning model works. While multi-armed bandits optimize traffic allocation uniformly across users, contextual bandits leverage contextual data to make more personalized, data-driven decisions. Read the [Dynamic traffic allocation](https://www.kameleoon.com/blog/dynamic-traffic-allocation) article to learn more about how MAB optimization works, or read the [Kameleoon statistical paper](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/pdf/statistics-at-kameleoon.pdf) to dive deeper into the technical details of the MAB algorithm. # Reshuffle traffic for a rule Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/optimizations-and-scheduling/reshuffle-a-traffic-rule Manually redistribute user traffic across variations for an active rule, re-bucketing users to adjust your experiment's distribution. Reshuffling lets you manually redistribute your user segment's traffic towards the chosen variations for any active rule. You initiate a complex re-bucketing process by reshuffling, meaning that Kameleoon may expose users to different variations than before. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/reshuffle-a-traffic-rule/ezgif.com-optimize-2.gif) To reshuffle traffic for a rule: 1. Navigate to the delivery or experiment rule for which you want to reshuffle traffic. 2. Click the three-dots menu. 3. Click **Reshuffle**. **Reshuffle only takes effect after saving**. You can dequeue the reshuffle action by repeating the same steps and choosing **Undo reshuffle**. Once saved, Kameleoon redistributes traffic among the rule’s variations. Some users may see a different variation than they previously encountered, while others may continue to see the same variation. If you are monitoring results and metrics for an ongoing feature experiment or delivery, reshuffling traffic skews your results and may impact user behavior and key metrics. Use reshuffling when results prior to it are irrelevant or unnecessary. Avoid reshuffling any rule that's active for a substantial amount of time since results are likely invalidated after reshuffling. # Schedule a feature's rollout Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/optimizations-and-scheduling/schedule-a-features-rollout Schedule the start and end dates for rollout rules to automatically activate or deactivate a feature at a specified time. You can schedule when a rule should activate or reactivate. Calendar for scheduling a rollout rule's start or end date A calendar opens when you select a start or end date. Use the calendar to pick the date and time for the rule to take effect or expire. # Add a new rollout rule Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/add-a-new-rollout-rule Add and configure rollout rules in the Rollout Planner to control feature delivery with targeted, progressive, or experiment-based strategies. ## What are rollout rules? The Rollout Planner gives you granular control over your releases and tests. You can activate or deactivate any feature flag at any time for specific users. Rollout rules are like conditional statements for your releases and experiments. You can set up a sequence of actions that triggers under certain conditions. You can schedule these rules, apply them immediately, target them to specific groups, reorder them, and customize them to align with your goals. ## Add a new rule Click **Add a rule** to open the rule configuration menu. Here, you can choose between delivery or testing rules, and name your rule. Rule configuration menu for adding a delivery or testing rule You can combine rules of any type to implement your feature's rollout strategy. Kameleoon lets you add rules for [Targeted Delivery](./define-targeted-delivery-rules), [Progressive Delivery](./define-progressive-delivery-rules) and [Feature Experiments](./create-feature-experiments). ## How does the rule queue work? Rule queue showing rollout rules evaluated in order Once you’ve added your rollout rules, it's important to understand how Kameleoon evaluates the queue: * Kameleoon checks if your flag is on or off. If it's off, visitors see the default variation (which you can choose in the "*Then, for everyone else in production, serve*" dropdown list). * If you turn the flag on, Kameleoon evaluates experiments and rules in the queue in a top-to-bottom order. Keep the evaluation order in mind when planning your rollout strategy to prevent unforeseen targeting overlaps. * Kameleoon adds new rules to the bottom of the queue by default. However, you can manually reorder rules by clicking and dragging its card to a new position. Reordering rules in the queue also changes the order in which Kameleoon reads and evaluates the rule. Be mindful when delivering rules that share the same audience. Paused rule in the rule queue * Paused rules are simply ignored at runtime. ### User targeting and bucketing Exposing a user to a variation follows a two-step process: **targeting** and **bucketing**. Targeting means the user meets the conditions specified in a rule. Bucketing means Kameleoon has assigned the user to the variation associated with that rule. Kameleoon handles targeting and bucketing differently for delivery and experiment rules. Consider these two scenarios: #### Scenario 1: Delivery rule at the top Delivery rule placed at the top of the rule queue If Kameleoon first targets a visitor for a Targeted/Progressive Delivery rule: * They receive the selected variation if Kameleoon buckets them. * If targeted, but not bucketed, the visitor receives the "Everyone else" variation, and Kameleoon doesn't evaluate the visitor against any other rule. ```mermaid theme={null} flowchart TD A["Delivery rules"] --> B(["🎯 Targeted"]) A --> C(["✕ Not targeted"]) B --> D["✅ Exposed"] B --> E["⬜ Not exposed"] C --> F["⏭️ Go to the next rule"] D --> G["📊 Bucketed into a variant"] E --> H["📊 See default variation *"] ``` The variant marked with an asterisk (\*) is the one selected in the "Then, for everyone else in production, serve" dropdown list. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/add-a-new-rollout-rule/Capture-decran-2023-12-13-a-12.53.57.png) #### Scenario 2: Experiment rule at the top Experiment rule placed at the top of the rule queue * If Kameleoon targets a visitor with an Experiment rule: * They receive the selected variation if Kameleoon buckets them. * If Kameleoon targets them, but doesn't bucket them, it checks the next rule until the end of the queue. All users whom experiment rules don't bucket move down the queue, so it's always a good practice to keep experiments at the top of your queue. ```mermaid theme={null} flowchart TD A["Experiment rule"] --> B(["🎯 Targeted"]) A --> C(["✕ Not targeted"]) B --> D["✅ Exposed"] B --> E["⬜ Not exposed"] C --> F["⏭️ Go to the next rule"] D --> G["📊 Bucketed into a variant"] E --> H["⏭️ Go to the next rule"] ``` ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/add-a-new-rollout-rule/Capture-decran-2023-12-13-a-12.53.34.png) Changing the traffic split for an experiment doesn't change the total size of your experiment bucket. For example, say you launch an experiment that targets **20%** of a user segment with a variation split of 50/50 for its two variations. If you update the split to 60/40 or 90/10, but don't change the percentage of traffic included in the experiment sample (**20%**), the total bucketed users don't increase or decrease. Instead, some bucketed users get exposed to a different variation. When there are no more rules in the queue, all remaining users receive the "Everyone else" variation. ### Rule states All rules in the queue display one of these states: * **Active** (is live) * **Paused** * **Planned** (scheduled to start and/or stop at a given date/time). Planned rule changes to *active* at the defined schedule or *outdated* at the end of it. * **Unsaved changes** (this also includes new rules that are in the draft state) * **Outdated** (a planned rule that automatically turned off at scheduled end date). The default view hides outdated rules, but you can use filters to view them. ## Pause or resume a rule To pause a rule, click the three-dots menu on an experiment's card and click **Pause**. Kameleoon ignores paused rules at runtime. You can resume the rule later in the same menu. ## Copy rules from another environment You can also add rules you created in another environment. Copying rules lets you replicate your rollout/experiment setup from your **staging** environment to your **production** environment, for example. The rule configuration menu offers these rule types: | Category | Rule type | Description | | ---------------- | -------------------- | --------------------------------------------------------------------- | | Feature testing | Experiment | Run a test to compare the KPI impact of different feature variations. | | Feature delivery | Targeted Delivery | Target a custom audience to roll out a feature variation to. | | Feature delivery | Progressive Delivery | Gradually roll out a feature variation to a custom audience. | | Other | Copy | Copy rules from another environment. | Select **Copy** when adding rules in your destination environment and select the source environment in the next step. You'll have the option to append the copied rules at the bottom of your rules queue, or replace all existing rules with the copied rules. ## Rollout summary This section provides an overview of **variations** and **segments**. Changing the cards' order, deleting a rule, or changing a rule's status affects the ordering and values inside summary cards. While the summary section accounts for untargeted (remaining) users that Kameleoon can pass down to the next rule, it doesn't account for or identify any partial or complete overlaps between different user segments across different rules. ### Variations summary ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/add-a-new-rollout-rule/5.png) This section provides an overview of which variations your delivery and experiment rules have used. Variations that target multiple segments have multiple bars displaying the percentage of the audience you have targeted. ### Segments summary ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/add-a-new-rollout-rule/6.png) This section summarizes the different user segments that your rollout rules target. Variations assigned to a segment combine to reach a maximum of 100%. If you exceed this limit, Kameleoon ignores any remaining variations. ## Using outdated SDKs If your SDK's version doesn't support [Feature Variations](../../../../../developer-docs/feature-experimentation/get-started/overview#supported-platforms), keep a few things in mind when adding new rules for a feature flag: * Kameleoon checks only the rules defined in the Production environment and ignores rules you created in the staging or development environments. * Kameleoon evaluates only the first rule in the queue and ignores any rules below it. If there are no rules at all, Kameleoon serves the default variation defined in your rollout. * Kameleoon only supports the default variations **On** and **Off**. * Kameleoon doesn't support start/stop scheduling of rules. Visit Kameleoon's [Github](https://github.com/Kameleoon) and subscribe to notifications to stay up to date with all SDK releases. # Create feature experiments Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/create-feature-experiments Create experiment rules to deliver and compare multiple feature variations, measuring their performance against your defined goals. With Experiments, you can create different variations of your features and test them to compare their performance. By combining variations and experiments, you can efficiently and continuously test and improve your features. Experiment rule configuration Experiments let you deliver various feature variations to set up your feature test and define the distribution among them. You must associate at least one [goal](../../track-your-feature-experiments/create-goals-for-feature-flags) with your experiment. # Define Progressive Delivery rules Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/define-progressive-delivery-rules Configure progressive delivery rules to gradually roll out a feature to an increasing audience using linear or custom ramp-up schedules. Progressive Delivery rule configuration Progressive rollouts let you spread out large feature releases by gradually rolling them out to an increasing audience. You can customize the feature variation you want to roll out, the audience you want to target, and the interval and increment size of the ramp-up. When setting up the exposure for your Progressive Delivery rule, you can select from two types of ramp-ups: **linear** or **custom**. ## Linear ramp-up Linear ramp-up configuration for a Progressive Delivery rule When configuring your linear ramp-up, you must define the following custom parameters: * The rollout's start date. * Initial exposure (audience percentage immediately exposed to the feature). * Increment percentage for rollout audience. * Time interval between increments. * Final exposure limit: define the percentage at which you want the rollout to stop. Below this section, a confirmation shows when and at what exposure percentage the rollout stops. ## Custom ramp-up Custom ramp-up configuration for a Progressive Delivery rule Click **Add step** to add a custom increment. You must select a date/time and an exposure rate for each step you add. # Define Targeted Delivery rules Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/define-targeted-delivery-rules Enable a feature variation for specific user segments using Targeted Delivery rules based on attributes like location, device, or demographics. Targeted Delivery is the easiest way to enable a feature variation for specific users or your entire audience. You can use multiple Targeted Delivery rules to implement advanced rollout strategies that suit your needs. Targeted Delivery rule configuration Use Targeted Delivery rules to gain rollout flexibility based on user attributes, such as geographical region, demographic data, or operating system. # Target groups of users with rules Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/target-groups-of-users-with-rules Select or create segments to target specific user groups in your rollout rules using Kameleoon's available SDK targeting conditions. You must select a segment or create a new one when creating a rule in the **Targeting** section. Select one of the three targeting conditions available: * **Target all visitors**: Includes your entire user base. Chosen by default. * **Target a segment:** You can choose from a drop-down of existing segments or create a new segment in the segment builder by clicking **+**. * **Target specific visitors:** This method is a lite version of the segment builder, allowing you to create common targeting conditions using *Custom Data* or *Visitor Code* attributes to define an audience without creating and saving a new segment. Don't use this method if the target audience requires sophisticated targeting. ## SDK conditions available If you want to create a new segment or add precise targeting conditions for choosing your audience, you can use Kameleoon’s pre-existing conditions available [depending on which SDK you’re using](../../../../../developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation): * **Browser:** Includes or excludes visitors based on their browser. * **Device:** Includes or excludes visitors based on their device type: phone, tablet, or desktop. * **Conversion:** Includes or excludes visits during which a visitor converts a specific goal. * **Custom Data:** Includes or excludes visits based on a custom data's value. * **Page URL:** Includes or excludes visitors based on the displayed page's URL. * **Page Title:** Includes or excludes visitors based on the displayed page's title. * **SDK Language:** Includes or excludes visitors based on their languages. * **App Version**: Includes or excludes visitors based on their app's version. * **Visitor Code:** Includes or excludes visitors based on their visitor code. * **Browser Cookie:** Includes or excludes visitors based on the presence of a cookie in their browser, and possibly its value. * **Operating System:** Includes or excludes visitors based on their operating system. * **IP Geolocation:** Includes or excludes visitors based on their location. * **Segment:** Includes or excludes visitors based on their Kameleoon segment. * **Previous Page:** Includes or excludes visitors based on the previous page's URL. * **Number of Page Views:** Includes or excludes visitors that have seen a specific number of pages. * **Time since First Visit:** Includes or excludes visitors based on the time elapsed since their first visit. * **Time since Last Visit:** Includes or excludes visitors based on the time elapsed since their last visit. * **Number of Visits Today:** Includes or excludes visitors based on their number of visits today. * **Total Number of Visits:** Includes or excludes visitors based on their total number of visits. * **New or Returning Visitors:** Allows you to include only new visitors or only returning visitors on your website. * **Exclusive Feature Flag:** Only includes users who aren't targeted by any ongoing rollout or feature/web experiment. * **Targeted Feature Flag:** Includes or excludes visitors that another feature flag has already targeted and exposed to a variation. * **Likelihood to Convert**: Target visitors based on their Kameleoon Conversion Score (KCS). Requires the Contextual Bandit and AI Targeting add-on. Next, choose the rule's exposure rate: the percentage of your chosen segment that Kameleoon exposes while the flag stays on. See Kameleoon's [developer documentation](../../../../../developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) to learn how to use targeting conditions in a Kameleoon SDK. Kameleoon also integrates with [Mixpanel](../../../../integrations/as-a-destination/analytics/mixpanel/setting-up-mixpanel), [Segment](../../../../integrations/as-a-destination/cdp/segment/setting-up-segment), or [GA4](../../../../integrations/as-a-destination/analytics/google-analytics-4/setting-up-google-analytics-4), allowing you to use segments (or cohorts) that you've defined in those tools. # Automatically roll back a feature Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/automatically-rollback-a-feature Set up automatic rollback conditions to turn off a rule or environment when a feature's performance drops below defined thresholds. Automatic rollbacks help you manage feature releases. You can set up custom rollback conditions, or "triggers," that monitor your features' performance to automatically turn off the appropriate rule or flag. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/create-and-manage-flags/using-the-rollout-planner/automatically-rollback-a-feature/rollback.png) ## Setting up rollback conditions Create a new rule or click an existing one. Then, navigate to the Rollback Condition at the bottom of the edit pane for your desired rule. Here, add a new condition and fill in the fields: 1. Choose whether to turn off only the selected **rule** or turn off the toggle for the entire **environment**. Turning off the environment stops all active rules, including deliveries and experiments, in that environment. 2. Choose the performance indicator to check against: **Uplift**, **Downlift**, or **Conversion Rate**. 3. Define the condition (**higher than** or **lower than**) and enter the threshold percentage which triggers the automatic rollback. 4. Select which **goal** to monitor and evaluate for performance. Here, you can pick from any Kameleoon goal that you’ve created for this project. 5. Select the **minimum visitors** to evaluate before Kameleoon can execute the rollback. Automatic rollback condition configuration Once saved, Kameleoon continuously monitors the metrics you specified against the defined conditions. You can choose to add as many additional conditions as you’d like by repeating the same steps; however, Kameleoon executes whichever condition triggers first and ignores the rest. ## Setting up email alerts Set up email alerts to receive notifications when a rollback occurs. These alerts ensure your team stays informed about unexpected adverse effects on your key performance metrics. To enable alerts, select **Send email alerts** and enter the recipients' email addresses. Click **Save** to finalize. # Using simulation mode Source: https://docs.kameleoon.com/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode Preview and test feature experiment configurations in a controlled environment before deploying them to production. Simulation mode enables you to preview and test experiments in a controlled environment before you deploy them to production. For Feature Experimentation, Kameleoon primarily uses the simulation panel for testing and debugging. It doesn't offer the full capability available in Web Experimentation. For more details, see [What the simulation panel does](#what-the-simulation-panel-does). ## Prerequisites If you implement Kameleoon Web Experimentation or use Feature Experimentation in [Hybrid mode](../../../../../developer-docs/feature-experimentation/get-started/hybrid-experimentation), simulation mode works automatically on all major web browsers. You don't need any additional setup. If you *only* use Feature Experimentation, you must manually inject the Kameleoon tag to test your configuration. To inject the tag, use the Kameleoon Chrome extension. Simulation mode only supports web SDKs. It doesn't support mobile SDKs. To manually inject the Kameleoon tag: 1. Install the [Kameleoon Chrome Extension](https://chromewebstore.google.com/detail/kameleoon/nimncjjomfcjmfnghgaopccmdggjfoam). 2. Open the extension in your browser console and follow the on-screen instructions to log in. 3. Open the **DevTools** panel and click **Tag Injection**. 4. Enter your **Sitecode**, select your **Environment**, and select the **Add Anti-Flicker** checkbox. 5. Apply the changes and refresh the page. 6. In **DevTools**, open the **Network** tab and verify the `kameleoon.js?injected=true` request appears. This request confirms successful tag injection. ## Access the simulation panel After you confirm the tag injection, click the **three dots** menu for a rule, then click **Simulate**. To ensure the SDK recognizes the flag configuration, verify the flag toggle is **ON** and the rule is **Active**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/feature-experimentation/using-the-rollout-planner/using-simulation-mode/2.png) Alternatively, open the **three dots** menu in the top right of the editor, click **Simulate**, and select a rule from any environment to launch. The simulation panel appears in the bottom half of the page. ## What the simulation panel does * **Displays variants:** The simulation panel enables you to select and preview a variant in your browser for testing. * To test a variant in production without exposing it to users, set the allocated traffic for the variant to 0%. * **Overrides targeting behavior:** The simulation panel ignores targeting rules. The SDK manages targeting, but the simulation panel overrides these rules to force the display of the selected variant. # Comment mode Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-features/comment-mode Use Comment mode to exchange feedback on experiment variations directly in Kameleoon, keeping team discussions organized and in context. Feedback on experiment variations often lives outside Kameleoon, scattered across Slack threads, emails, or sticky notes that lose their context the moment the conversation moves on. **Comment mode** (in beta) keeps that feedback where the work happens, letting you and your team exchange comments directly on the variations you're discussing. Whether you're reviewing variations, discussing changes, or resolving issues, Comment mode keeps conversations organized and tied to the exact variation they're about. This article covers how to access, use, and manage comments. ## Accessing Comment mode To access Comment mode, click the **text bubble icon** in the mode toolbar. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/comment-mode-tab.png) The **Comments** sidebar opens. ## Adding a comment To add a new comment: 1. Select your desired variation in the **Variations** sidebar. 2. Click **+Add** in the **Comments** sidebar. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/Screenshot-2025-05-20-at-8.20.19AM.png) 3. Type your comment in the text field and click **Save**. Your comment is now visible in the **Active** comments tab of the Comments sidebar. A visual indicator noting the number of comments appears next to the relevant variation or element’s name. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/Screenshot-2025-05-20-at-8.24.23AM.png) ## Actions in the Comments sidebar ### Editing and deleting a comment To edit a comment: 1. Click the **three-dots menu** > **Edit comment**. 2. Edit the comment’s text. 3. Click **Save**. To delete a comment: * Click the **three-dots menu** > **Delete comment** > **YES**. You can delete a comment only during the first 15 minutes after its creation, and only if it has no replies. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/Screenshot-2025-05-20-at-8.23.29AM.png) ### Reply to a comment To reply to a comment: 1. Click **Add reply**. 2. Type your reply into the text field. 3. Click **Save**. ### Resolve a comment To resolve a comment: * Hover over the comment and click the **checkmark icon**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/Screenshot-2025-05-20-at-8.36.31AM.png) You've now resolved the comment. You can view resolved comments by clicking **Archived**. Click a comment’s **checkmark icon** in the Archived tab to un-resolve it. ## Settings You have access to a variety of settings in the Comments sidebar. Click the **cog icon** to view the available settings. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/comment-mode/Screenshot-2025-05-20-at-8.23.05AM.png) You can: * **Sort comments** * You can sort comments newest to oldest, oldest to newest, or alphabetically by the author’s name. * **Display in list** * You can choose to display only comments in which you’re mentioned or all replies. # Contextual bandits Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-features/contextual-bandits Learn how contextual bandits use real-time user signals to dynamically allocate traffic to the best-performing variation for each visitor. Personalization is no longer a luxury—it’s an expectation. But delivering the right experience to each visitor, at the right time, is a daunting task, especially when you’re operating at scale. Traditional methods like A/B and multivariate testing (MVT) offer structured ways to improve user experiences, but they come with trade-offs: slow learning curves, limited flexibility, and static assumptions. To address these limitations, there are **contextual bandits** (CBs)—a machine learning-based approach that offers a dynamic, data-driven alternative to traditional experimentation. In this article, we’ll explore: * How contextual bandits work * What kind of data powers them * What “behavior” really means in this context * Real-world use cases where CBs shine. ## What are contextual bandits? Contextual bandits are a machine learning approach that balances **exploration** (trying new options) and **exploitation** (using the best-known option). Crucially, the algorithm now checks the status of the Learning phase every hour, similar to AI Predictive Targeting, letting it adapt more quickly and intelligently. Contextual bandits use the same machine learning model as [AI Predictive Targeting](../../../ai-predictive-targeting/overview). By default, the model does not use custom data as input. To use custom data with contextual bandits, subscribe to the AI Predictive Targeting add-on. Unlike A/B or MVT tests, which are static and split traffic evenly across fixed variants, CBs are **dynamic**. They adjust variant allocation in real time based on what’s working best for different types of users. It’s also important to distinguish CBs from traditional multi-armed bandits (MABs). While MABs consider only the performance of each option overall, CBs factor in real-time user context (like behavior, device type, referral source, and more) to make smarter decisions for each visitor. ## Learning phase Because contextual bandits use the same machine learning model as AI Predictive Targeting, they go through the same learning phase before the algorithm starts allocating traffic dynamically. The model requires both 7 days of data and 100,000 visits together, so reaching only one of these thresholds isn't enough to complete the learning phase. During the learning phase, Kameleoon splits traffic evenly across the selected variations, the same way a classic A/B test does. Once the learning phase completes, the contextual bandit starts allocating traffic to the best-performing variation for each visitor. The number of variations in an experiment doesn't affect the length of the learning phase. However, an experiment can include a maximum of 5 variations, including the original, because of how the underlying machine learning model works. ## How contextual bandits work At the heart of CBs is a continuously improving decision loop. Here’s how it works: 1. A user lands on your website. 2. The CB algorithm evaluates real-time contextual signals (for example, device, location, past behavior). 3. It selects the variant most likely to result in a positive outcome. 4. It observes the result (conversion, click, engagement). 5. It learns and updates its models accordingly. This feedback loop happens constantly and is powered by live user data. Kameleoon uses contextual multi-armed bandits (CMABs), an advanced form of CB that incorporates multiple layers of user context to inform decisions. ## Understanding behavior in contextual bandits In Kameleoon, behaviors include any interaction or signal from a user that can help predict their intent or preferences, which includes: * Page views * Scroll depth * Time on site * Clicks on CTAs * Products browsed * Cart contents These behaviors feed into both our CB algorithm and our Kameleoon Conversion Score (KCS), creating a standardized behavioral foundation across all personalization tools. ## What makes contextual bandits smart? The intelligence of a CB system comes down to the quality of data it receives. High-quality, real-time behavioral signals lead to better predictions and faster optimization. Kameleoon’s real-time data pipeline ensures that CBs make informed decisions by: * Capturing clean, relevant contextual signals instantly * Feeding them into the model without delay * Enabling hour-by-hour updates of the Learning phase status. Kameleoon ensures your personalization efforts improve quickly and accurately. ## Use cases for contextual bandits CBs are incredibly versatile. Here are a few ways you can put them to work: ### Optimizing homepage layouts Show different homepage modules (like banners, featured products, or CTAs) depending on real-time user behavior. The CB continuously adapts to determine which layout performs best for different types of users. ### Saving personalized promotional offers Instead of manually creating audience segments for promotions, let the CB do the heavy lifting. It analyzes signals such as cart value (which is a [custom data](../../../assets/custom-data/create-custom-data)), browsing history, or traffic source and shows the most effective offer automatically. ### Rolling out new features dynamically Gradually introduce a new feature or UI component to users who are more likely to respond positively, based on how they interact with your site in real time. ## When to use contextual bandits Contextual bandits are a powerful tool, but they aren’t always the right choice. Here’s a short guide: **Best for:** * Personalization at scale * Continuous learning environments * High-traffic pages with lots of user interaction **Not ideal for:** * Experiments where strict control and interpretability are essential. * Low-traffic environments where learning would take too long. ## Summary Contextual bandits are a smarter, more adaptive approach to experimentation. By continuously learning from high-quality real-time data, CBs let you deliver better experiences faster—no rigid tests or manual segmentation required. Importantly, the behaviors that power Kameleoon’s contextual bandits are the same ones that fuel our AI propensity scores. This shared behavioral foundation means smart and consistent personalization across your experiments and optimizations. In short, contextual bandits help you meet your users where they are—intelligently, efficiently, and at scale. For more information on integrating CBs in your web experiments, refer to [this](../configure-and-launch/finalizing-an-experiment#contextual-bandits) article. Read [this](../../feature-experimentation/using-the-rollout-planner/optimizations-and-scheduling/multi-armed-bandit-optimizations-for-feature-experiments#contextual-bandits) article to learn more about CBs in feature experiments. # Predefined tags Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-features/predefined-tags Apply predefined tags like TECHNICAL, DELAYED, LIVE-UPDATE, HOLDOUT, and MUTUALLY EXCLUSIVE GROUP to modify how Kameleoon runs your experiments. You can apply predefined tags to your experiments to change Kameleoon's default behavior: * **TECHNICAL**:\ When you add a `TECHNICAL` tag to an experiment, Kameleoon displays the variation but doesn't collect or send data to an analytics platform until the visitor gives [consent](../../../project-management/consent-management-policy#consent-policy).\ This tag is typically used for experiments or personalization campaigns that display the same variant to all users. * **DELAYED**:\ Adding the `DELAYED` tag to an experiment allows you to delay non-essential experiments until after the first page load. Experiments with this tag are intelligently managed:\ Kameleoon doesn't download the configuration until at least 10 seconds of idle time pass after the initial page load, **or** until it targets the visitor and allocates a variant other than the control variant. * **LIVE-UPDATE**:\ By default, Kameleoon downloads active experiments and includes them in the app file (`kameleoon.js`).\ The browser stores and caches this file for 90 minutes. If you pause, stop, or modify an experiment, targeted visitors returning to a page with active experiments within the 90-minute caching period may still see the experiment because their browser serves the cached version. Outside that caching window, a paused or stopped experiment no longer assigns its variation, and visitors revert to the original experience on their next page evaluation.\ To update more frequently than browser caching normally allows, add the `LIVE-UPDATE` tag: it refreshes any experiment within **1 minute**, which is particularly useful for campaigns that require regular updates. * **HOLDOUT**:\ Create a control group that remains unaffected by any active campaigns, so you can establish a reliable baseline that measures the aggregate impact of all changes against a group that experiences no modifications.\ Set up an experiment in Kameleoon and tag it with the `HOLDOUT` label.\ [More about this tag](/user-manual/experiment-analytics/analyze-results/advanced-analysis/create-reliable-baselines-with-holdouts) * **MUTUALLY EXCLUSIVE GROUP**:\ Group campaigns so that each visitor sees only one campaign per group, reducing overlap and improving test clarity.\ Follow the naming convention `ME-GROUP-{GROUP NAME}` to tag each campaign in the group, such as `ME-GROUP-A`.\ This naming signals Kameleoon to treat the campaigns within each group as mutually exclusive.\ [More about this tag](/user-manual/experiment-analytics/analyze-results/advanced-analysis/prevent-overlapping-campaigns-with-mutually-exclusive-groups) # Using Shadow DOM elements in the Graphic editor Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-features/using-shadow-dom-elements-in-the-graphic-editor Select and modify Shadow DOM elements in the Kameleoon Graphic editor using shadow root paths and the ::SHADOW-ROOT:: selector syntax. Shadow DOM is an advanced web development feature that lets developers encapsulate a part of their webpage's structure, creating a separate "shadow" section. Think of it like creating a miniature webpage within a component of your main site. This "mini-page" operates independently of the rest of your page, preventing conflicts between styles and scripts used within the component and those used on the main page. If you're working in the Kameleoon Graphic editor, you'll need to know how the DOM (Document Object Model) works to use Shadow DOM elements. Since Shadow DOM elements aren't visible in the main DOM by default, things like placing widgets or personalizing experiments can be trickier than working with standard DOM elements. This feature assumes development experience. If you're not comfortable working with the DOM or Shadow DOM encapsulation, reach out to a developer or look into online resources about Web Components and Shadow DOM fundamentals. ## Limitation of standard selectors Standard CSS selectors or JavaScript methods like `querySelector()` or `$()` won't work on elements inside a Shadow DOM. The shadow root hides these elements from the main page, meaning: * You can't target elements inside a Shadow DOM using the usual selection methods in the Kameleoon Graphic editor. * Targeting these elements using traditional CSS or jQuery-style selectors in your experiment might fail. * Any modifications (like text changes, style updates, or content insertion) may not apply unless you take special steps to pierce the Shadow DOM boundary. If you’re unsure whether you’re dealing with a Shadow DOM, inspect the element using your browser’s developer tools. Look for `#shadow-root` in the **Elements** panel. ## Working with Shadow DOM in the Graphic editor In most cases, users can select elements inside a shadow root directly from the preview zone in the Kameleoon Graphic editor. ### When the additional "Shadow root path" input appears In some cases, when you attempt to select or edit an element inside a Shadow DOM using the Kameleoon Graphic editor, you may see an additional input field labeled **Shadow root path**. This field appears when Kameleoon detects that the target element exists within a shadow tree that standard selectors can't reach. The Shadow root path lets you define a specific path through nested Shadow DOM layers so Kameleoon can reach and modify the element during experiment execution. **Mock connection:** the Graphic editor uses this path to “mock” the connection between the main DOM and the element inside the Shadow DOM, which lets you visually edit and preview changes as if the element were part of the regular page structure. ## Examples ### Targeting a button inside a shadow root In the Kameleoon Graphic editor, you can target elements inside a shadow root: * **Directly in the Preview zone:** If the element is accessible, you can select it within the editor preview, just like any standard element. * **Using “Edit selector”:** For more complex structures you can manually construct the selector path using the Edit selector input. Use the `::SHADOW-ROOT::` separator to indicate a step into shadow root. ### Inspecting Shadow DOM in DevTools 1. Right-click the element > **Inspect**. 2. In the **Elements** panel, look for `#shadow-root (open)` under the custom element. 3. Expand it to view the inner elements. For example: ```html theme={null} #shadow-root (open) ``` ## Visual cues or labels in the UI While working with web pages that use Shadow DOM, the Kameleoon Graphic editor provides several visual indicators to help you identify when elements are part of a shadow root. ### `#shadow-root` in the Elements panel ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/using-shadow-dom-elements-in-the-graphic-editor/element-panel.png) In the editor’s **Elements** panel, components that use Shadow DOM display `#shadow-root` as part of their CSS selector, showing that the shadow root encapsulates the element rather than exposing it in the main DOM tree. ### "Shadow root path" field in the selector panel ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-features/using-shadow-dom-elements-in-the-graphic-editor/selector-panel.png) When selecting an element inside a shadow root, an additional **Shadow root path** input appears in the **Edit CSS selector** panel. This input lets you build a full path through shadow roots using the `::SHADOW-ROOT::` syntax. ## How to write a selector path for Shadow DOM elements When working with Shadow DOM elements in Kameleoon’s Graphic editor, a standard CSS selector isn't sufficient. You must define a selector path that includes the shadow root. To target an element inside a **single-level shadow root**, use the `::SHADOW-ROOT::` separator: `custom-element::SHADOW-ROOT::.target-button` The `::SHADOW-ROOT::` separator causes Kameleoon to enter the shadow root of the specified element and continue targeting inside it using standard CSS selectors. Kameleoon supports navigating only **one level** into the Shadow DOM. If the element sits within multiple nested shadow roots, you can't target it using a full selector path. ### Example ```html theme={null} #shadow-root ``` Your selector path is `my-element::SHADOW-ROOT::.cta` This selector path directs Kameleoon to: * Select the `my-element` element. * Enter its shadow root. * Target the `.cta` button inside it. ## Limitations and considerations When working with Shadow DOM, keep the following limitations in mind: * **Closed Shadow DOMs:** You can't access or modify elements in a closed shadow root using the Graphic editor or standard JavaScript. * **Dynamic or script-generated content:** If a script generates or updates content inside a shadow root dynamically, your changes may not persist or take effect as expected. * **Styling restrictions:** Styles applied from outside the shadow root often won’t affect inner elements due to Shadow DOM’s style encapsulation. You may need to apply CSS rules directly within the component’s internal structure instead, which isn’t possible via the Graphic editor. ### Shadow root (closed) Some web components use a closed shadow root, meaning their internal DOM is completely hidden from the browser’s developer tools and JavaScript. Unlike an open shadow root, which you can access using `.shadowRoot`, a closed shadow root **doesn't** expose its content through the DOM, making it inaccessible to: * The Graphic editor * JavaScript-based targeting * Selector paths or custom scripts Because a closed shadow root hides its internal elements, you can't select or modify them in Kameleoon. Even if you see the outer wrapper component, its internal structure remains sealed. #### What can you do? If a key element is inside a closed shadow root, you’ll need help from a developer. They may: * Rebuild the component using an open shadow root. * Expose specific elements for external targeting. * Provide a hook or workaround for targeting. If you can’t inspect the inside of a component in browser DevTools, it’s likely using a closed shadow root. ### Nested shadow roots You can nest Shadow DOMs multiple layers deep (for example, a shadow root inside another shadow root). This case isn't supported in Kameleoon's current version. If an element isn’t responding to edits, double-check if it’s inside a closed shadow root or if you’re missing part of the path. ### Adding widgets to shadow root You can’t add widgets inside Shadow DOM elements in Kameleoon’s Graphic editor because the shadow root isolates its content from the DOM nodes the editor can reach. ### Add click tracking from the Graphic editor You can't add a click tracking goal directly from the Graphic editor for Shadow DOM elements. However, you can create and manage one using Kameleoon's [API](../../../../developer-docs/apis/activation-api-js/api-reference/api-reference#runwhenshadowrootelementpresent). # Multi-language experiments Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-types/multi-language-experiments Create multi-language A/B test variations in the Kameleoon Graphic editor to test content changes across different languages simultaneously. Kameleoon makes it quick and easy to implement multi-language experiments. In this tutorial, you will learn how to create a multi-language variant using the Graphic editor, using the [Fossil website](https://www.fossil.com/) as an example. ## Goal The goal of this guide is to change the header label "Order Status" ("Statut de la commande" in French) to "Order Tracking" ("Suivi de commande" in French). ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-language-experiments/header.png) ## Steps 1. Launch the Graphic editor. 2. Select "Order Status" in the header and generate the selector based on **Content**. If the label you want to edit is in a Shadow DOM see [this article](../advanced-experiment-features/using-shadow-dom-elements-in-the-graphic-editor#working-with-shadow-dom-in-the-graphic-editor) 3. Enter "Order Tracking". 4. Change the language to French by clicking **Ship to** > **Europe** > **France**. 5. Repeat step **2** and **3**, entering "Statut de la commande" in the header. 6. Change the text to "Suivi de commande". 7. Simulate the experiment to verify your variant. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-language-experiments/demo.gif) # Multi-page experiments Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments Design experiment variations that span multiple pages and configure URL-based targeting to run tests across entire user journeys. Testing a change across a user journey, such as a checkout flow or a multi-step signup, usually means building and coordinating a separate experiment for each page involved. Multi-page experiments let you design variations that span multiple pages within a single experiment, so you can test the whole journey as one unit. Multi-page experiments offer the following benefits: * **Holistic testing:** Evaluate the impact of your changes across user journeys that span multiple pages. * **Unified experiment management:** Create and manage variations that affect different pages within the same experiment. * **Streamlined workflow:** Design variations for multiple pages at once, reducing the need for separate experiments. **Creating multi-page variations:** 1. Launch the Graphic editor on all desired pages. 2. Design your variations and incorporate changes for each page within the same variation. 3. Define the targeting criteria for your multi-page experiment during finalization. Kameleoon also supports the [simultaneous selection of multiple elements on one or several pages](../graphic-based-experiments/getting-started-with-the-graphic-editor). Multiple selection enables efficient edits to elements with identical IDs or classes across multiple targeted pages. For example, you can automatically apply a change to an "Add to Cart" CTA on one page to all other "Add to Cart" CTAs on targeted pages in the experiment. ## Target pages according to their URL Once your variations are complete, click **Finalize** to proceed to the experiment configuration. In the finalization flow, the **Targeting** step defines the conditions under which your experiment exposes visitors to your variations. This step contains two sections: **Segments**, where you build audience conditions such as page URL or device type, and **Triggers**, where you define when and where the experience fires. This article covers the page-based and entire-site targeting options, which live in the **Triggers** section. ### Several pages sharing the same URL To target several distinct pages by URL: 1. In the finalization flow, click **Target a segment** > **Create a new segment**. The segment creation window opens. 2. Double-click the **Page URL** condition or drag and drop to select it. You can add this condition as many times as you need. 3. Indicate the URL or URL fragment you want to target on your website, and specify whether you want to include or exclude it. A wide range of targeting conditions are available. For more information about targeting conditions, see [Manage targeting segments](../../../assets/segments/manage-targeting-segments). ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/Capture-decran-2021-06-08-174105-1920x556.png) Kameleoon offers three options: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/Capture-decran-2021-06-08-174147.png) * **Contains:** Runs the test on all URLs containing a URL fragment. * **Corresponds exactly to:** Runs the test only on the URL you choose. If your URL contains changing parameters, use the **Matches the regular expression** option. * **Matches the regular expression:** Use this option if your A/B test runs on a group of pages with specific parameters, such as `?x=exemple&y=exemple2`. Take this regular expression as an example: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/Capture-decran-2021-06-08-174330-1920x589.png) The regular expression above tells Kameleoon to run the test on pages containing the path `mywebsite.com/article/january` and the parameter `x=y`. Among the five following examples, only the first two URLs count toward the A/B test: `http://www.mywebsite.com/article/january?x=y` `https://mywebsite.com/article/january?a=b&x=y&c=d` `http://www.mywebsite.com/article/january` `http://www.mywebsite.com/article/january/?x=y` `http://www.mywebsite.com/article/january/monday?x=y` Explanation: * The third URL does not contain the `x=y` parameter. * The fourth URL contains a `/` after `january`. * The fifth URL has `monday` added at the end. ### Several pages with the same URL fragment To target several pages that contain the same URL fragment, either build a segment with a **Page URL** condition set to **Contains**, or select **The URLs containing a specific fragment** trigger option in the **Triggers** section of the **Targeting** step. For example, to change the color of the add-to-basket button on each of your product pages, where all of the URLs contain one identical fragment, select this trigger option: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/multi-page-experiment-targeting.png) When you select **The URLs containing a specific fragment**, a field appears where you can enter the fragment. If all of your pages contain the `/product/` fragment, enter `/product/` in this field so the experiment runs only on pages that contain this fragment. ### From an element on the page URL-based targeting doesn't work in every case. Two common scenarios require a different approach: * All the URLs are different. * You want to test only the last step of a multi-step process, such as registration or checkout, where every step shares one URL. Targeting that URL would include every step in the A/B test, not just the last one. To target pages accurately despite dynamic content, use the **Presence of an element on the page** condition. 1. Create a new segment within your experiment. 2. Select the **Technical** category > **Element on the page**. 3. Define the element that must be present on the page to include visitors in the experiment. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/Capture-decran-2021-06-08-174842.png) If your page contains a unique element, particularly for a multi-step process that shares one URL, use this method: indicate the HTML element's ID or CSS selector to define the element. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/Capture-decran-2021-06-08-174926.png) For optimal performance, choose an element located near the top of the page when using **Presence of an element on the page**. Placing the element lower on the page increases the likelihood of flickering, because Kameleoon waits for the element to load before triggering the experiment. ### On the whole website You can also run the experiment on every page of your website. To do so, select **The entire site** trigger option in the **Triggers** section of the **Targeting** step. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/multi-page-experiments/multi-page-experiments-the-entire-site.png) Selecting **The entire site** targets all pages within the project scope. Confirm that your variations are compatible with every page before using this option. # Running an A/B test on a Shopify store Source: https://docs.kameleoon.com/user-manual/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store Run A/B tests on Shopify using content swapping, the Graphic editor, or the Kameleoon JS SDK in Liquid templates. You might want to test different variations of your product page and measure how visitors interact with it based on key KPIs, such as the number of "Add to cart" actions, entries into the checkout funnel, and, ultimately, the number of completed orders. Imagine a typical product page, as shown in the image below. A user can either choose to buy the product immediately or add it to their cart. But what if most of your visitors typically only purchase one product? In that case, you might consider showing only a single "Buy Now" CTA to streamline the checkout process and reduce friction for your users. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/product-page.png) There are several ways to create experiments using Kameleoon. For example, you can: * **Content swap test:** Each variant of your experiment lives on a different Shopify template, which you can access through a dedicated page URL (for example, `www.mystore.com/products/products/produc-name?view=version-b`). The A/B testing tool lets you swap your content with the variant, such as pages B or C. * **Use a Visual editor:** Create variants directly on your product page using the Graphic editor, or customize them by injecting JS/CSS. * **Leverage Shopify's Liquid and a JS SDK:** Add logic in your Liquid template to activate or deactivate specific variants for a subset of users. The following sections explore each method in more detail. Install Kameleoon on your Shopify store before you start. Every experiment requires this installation. * If you're running **web experiments**, use the [**Shopify x Kameleoon app**](https://apps.shopify.com/kameleoon-abtesting-app), which simplifies setup. See the [documentation](../../../integrations/cms-e-commerce/shopify/shopify-plus) for step-by-step instructions. * If you're running **feature flags or feature experiments**, follow the guidelines outlined in the Feature Experimentation [documentation](../../../../developer-docs/sdks/web-sdks/js-sdk). ## Content swapping testing A **Content Swapping test** is an A/B/n experiment that shows users different content under the same URL, with each version representing a variant of the same page. Unlike URL redirects, it doesn't create a [Sample Ratio Mismatch](../../../experiment-analytics/statistical-methods/sample-ratio-mismatch). With Shopify, content swapping tests are easy to set up using the `?view` URL parameter, which lets you serve alternate templates for the same page. For example: * `https://kameleoon-segment-test.myshopify.com/products/vans-sh-8-hi` * `https://kameleoon-segment-test.myshopify.com/products/vans-sh-8-hi?view=version-b` In this case, `?view=version-b` uses [Shopify's alternate template feature](https://www.shopify.com/partners/blog/shopify-alternate-templates), letting you create multiple versions of templates for products, collections, carts, and more, which is ideal for content swapping experiments. This section walks through how to configure a content swap test in Shopify and Kameleoon. In your Shopify store, go to **Online Store** > **Themes** > **Actions** > **Edit Code**, and open the `product.liquid` template. The template includes two main sections: * `product-template`: contains the product page's source code. * `product-recommendations`: contains the code for the "You may also like" recommendation block. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/0.png) To set up your content swap experiment, you must create an alternate template for **variant B** and duplicate the `product-template` section, removing the code related the **Add to Cart** button. Here's how: 1. Click **Add a new template** in your Shopify theme editor. 2. Choose the **Product** template type and name it (this example uses `version-b`, but you can use any name). ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/1.png) The name you choose will appear in the URL (for example, `?view=version-b`), so pick something that makes sense to your team but isn't confusing to users. 3. Click **Add a new section**; name it **`product-template-version-b`**, and **copy and paste** the code from your original `product-template.liquid` file into this new section. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/2.png) You should now have four liquid files. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/3.png) 4. Remove the **Add to cart** button code from the **`product-template-version-b.liquid`** section. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/4.png) 5. Call your modified section (`product-template-version-b`) from the alternate product template you created (`product.version-b.liquid`). Your updated code should look something like this: ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/5.png) Once you've saved your changes, you have a second variant of your product page. Shopify lets you load alternate template files using the `?view=` URL parameter. As long as the value matches a template file in your theme, Shopify renders that version of the page. For example, visiting `https://kameleoon-store.myshopify.com/products/vans-sh-8-hi?view=version-b` now displays the product page **without the Add to Cart button**, as intended. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/buy-now.png) With the template updates complete, configure your content swap experiment in Kameleoon so that 50% of your visitors see **variation B** instead of the original product page. It's a simple process: 1. Go to the [Kameleoon app](https://app.kameleoon.com/). 2. Click **New experiment** > **With the code editor**. 3. Enter the required information and click **Create**. The Code editor will open. From there, you can set up and launch your experiment. 4. Click the **chevron** next to **Add variation** > **Add code variation**. ![](https://storage.googleapis.com/kameleoon-storage-documentation/user-manual/images/experimentation/web-experimentation/advanced-experiment-types/running-an-ab-test-on-a-shopify-store/add-url-redirection.png) 5. Add the **following code**, adapting the `VIEW` and the `SELECTOR` variables to suit your use case ```javascript theme={null} (function () { var VIEW = 'version-b'; // Alternate template handle, e.g. 'version-b' var SELECTOR = '#MainContent'; // Main container selector function executeScripts(container) { container.querySelectorAll('script').forEach(function (oldScript) { var newScript = document.createElement('script'); for (var i = 0; i < oldScript.attributes.length; i++) { var attr = oldScript.attributes[i]; newScript.setAttribute(attr.name, attr.value); } newScript.textContent = oldScript.textContent; oldScript.replaceWith(newScript); }); } Kameleoon.API.Core.runWhenElementPresent( SELECTOR, function () { fetch(window.location.pathname + '?view=' + encodeURIComponent(VIEW), { credentials: 'same-origin' }) .then(function (r) { if (!r.ok) { throw new Error('Unexpected status ' + r.status); } return r.text(); }) .then(function (html) { var doc = new DOMParser().parseFromString(html, 'text/html'); var newContent = doc.querySelector(SELECTOR); if (!newContent) { console.error('[Kameleoon] Swap failed: selector not found in fetched template'); return; } var main = document.querySelector(SELECTOR); main.replaceWith(newContent); executeScripts(newContent); }) .catch(function (err) { console.error('[Kameleoon] Swap failed:', err); }); } ); })(); ``` Content swapping only pulls the HTML from the alternate template. Browsers never execute ` ``` You also need to [activate](/user-manual/experimentation/feature-experimentation/track-your-feature-experiments/managing-third-party-integrations) your chosen integration (for example, Google Analytics 4 or Mixpanel) when configuring your feature experiment. The relevant data (such as experiment ID, experiment name, variation ID, variation name) will be automatically sent to the third party platform by the Kameleoon JavaScript engine. ### Using front-end data points and events in feature experiments If you have implemented Kameleoon in hybrid mode, goals and [data points](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience) will be automatically tracked on the front end and will appear in Kameleoon reports as filtering and breakdown criteria. In addition, several [targeting conditions](../targeting-and-segmentation/native-segmentation) will be available in the Kameleoon SDK, ready to be used for targeting purposes. ### Managing consent in hybrid mode When using Kameleoon in Hybrid mode and [consent is set to "Required"](/user-manual/project-management/consent-management-policy), ensure that both the JavaScript Activation API and SDK methods are called upon collecting consent to ensure that Kameleoon can collect user events from both the Kameleoon Application File and the SDK. Additionally, as Kameleoon is restricted from storing the visitorCode key in a cookie, ensure that the visitorCode key generated by the SDK for all first requests is shared with the Kameleoon engine running client-side. Read the [technical considerations](/user-manual/project-management/consent-management-policy#important-technical-considerations). Consent information is synchronized between the Kameleoon Engine (`engine.js`) and the JavaScript SDK **only when either component is reinitialized** (for example, on page reload or when calling `API.Core.load`). As a result, we strongly recommend explicitly managing consent using the dedicated methods provided by the engine and the SDK, rather than relying on this synchronization mechanism. # Overview Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/get-started/overview Explore Kameleoon's server-side feature management and experimentation support across web, mobile, TV, and gaming platforms using SDKs. In addition to the client-side capabilities, Kameleoon has extensive support for server-side feature management and experimentation on web apps, mobile apps, TV and gaming apps, or any other apps connected to the internet. To install and initialize an SDK, see the [quickstart guide](../../sdks/quickstart). Here are a few additional features available using Kameleoon SDKs: * Use [feature flags and feature experiments](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag) with advanced rollout and targeted delivery rules. * Combine server-side experiments with client-side native capabilities using [Hybrid experimentation](./hybrid-experimentation). Join the [Slack Community](https://join.slack.com/t/kameleooncommunity/shared_invite/zt-1s6m8s09e-~yA1EUgn5pLWW_mrgf8TrQ) to ask questions about product features and get updates. ## Supported platforms Kameleoon continually adds support for new languages and enhances existing SDKs with new features and methods. The minimum version required for specific SDK features varies depending on the language. The SDK compatibility table shows which features are available in each language and the minimum SDK version required for implementation. For comprehensive version history, see the changelog file in the [SDK Github repository](https://github.com/Kameleoon). Subscribe to receive email notifications. ### Server-side SDKs | **SDK** | [Java](../../sdks/web-sdks/java-sdk) | [C#](../../sdks/web-sdks/csharp-sdk) | [PHP](../../sdks/web-sdks/php-sdk) | [NodeJS](../../sdks/web-sdks/nodejs-sdk) | [Ruby](../../sdks/web-sdks/ruby-sdk) | [Python](../../sdks/web-sdks/python-sdk) | [Go](../../sdks/web-sdks/go-sdk) | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------ | ---------------------------------- | ---------------------------------------- | ------------------------------------ | ---------------------------------------- | -------------------------------- | | [Real-time streaming](../technical-reference/technical-considerations#streaming-premium-option) | 2.0.6 | 2.2.0 | - | 1.0.0 | 2.1.0 | 2.0.0 | 2.1.0 | | [Feature experiments](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/create-feature-experiments) | 3.0.0 | 3.0.0 | 3.0.0 | 1.0.0 | 2.0.0 | 2.1.0 | 2.0.0 | | [Multi-environment feature flags](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) | 2.0.4 | 2.1.4 | 2.0.9 | 1.0.0 | 1.0.10 | 1.0.2 | 1.0.5 | | [Feature flag variables](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables) | 2.0.4 | 2.1.7 | 2.0.9 | 1.0.0 | 1.0.10 | 1.0.2 | 1.0.5 | | [Predefined targeting conditions](../targeting-and-segmentation/native-segmentation) | 2.0.6 | 2.2.0 | 2.1.3 | 1.0.0 | 2.0.0 | 2.0.0 | 2.0.0 | | Retrieve all targeted feature flags | 2.0.6 | 2.1.7 | 3.0.0 | 1.0.0 | 2.0.0 | 2.0.0 | 2.0.0 | | Retrieve all feature flags variables | 3.0.0 | 2.1.9 | 3.0.0 | 1.0.0 | 2.0.0 | 2.0.0 | 2.0.0 | | [Retrieve data for a user key (Data API Map endpoint)](../targeting-and-segmentation/use-external-data-to-target-users) | 2.0.5 | 2.1.5 | 2.1.0 | 1.0.0 | 1.1.0 | 1.0.3 | 1.0.6 | | [Retrieve browsing data for a user (Data API Visitor endpoint)](../targeting-and-segmentation/native-segmentation#handling-data-in-kameleoon-sdks) | 3.2.0 | 3.3.0 | 3.3.0 | 2.2.0 | 2.2.0 | 2.4.0 | 2.3.0 | | [Analytics integration](./hybrid-experimentation#sending-exposure-events-to-third-party-analytics) | 3.1.0 | 3.1.0 | 3.1.0 | 2.1.0 | 2.1.0 | 2.2.0 | 2.1.0 | | [Legal consent management](/user-manual/project-management/consent-management-policy#for-web-experiments-/-feature-experiments-/-hybrid-experiments) | 4.0.0 | 4.0.0 | 4.0.0 | 3.1.0 | 3.0.0 | 3.0.0 | 3.0.0 | | [Data warehouse integration](/user-manual/?s=data+warehouse) | 4.1.0 | 4.3.0 | 4.1.0 | 2.8.0 | 3.1.0 | 3.1.0 | 3.1.0 | | [Cross-device experimentation](../../cross-device-experimentation) | 4.1.0 | 4.3.0 | 4.1.0 | 2.8.0 | 3.1.0 | 3.1.0 | 3.1.0 | ### Client-side or mobile SDKs | **SDK** | [Android (Kotlin/Java)](../../sdks/mobile-sdks/android-sdk) | [iOS (Swift)](../../sdks/mobile-sdks/ios-sdk) | [JavaScript/TypeScript](../../sdks/web-sdks/js-sdk) | [Flutter](../../sdks/mobile-sdks/flutter-sdk) | [React/React Native](../../sdks/web-sdks/react-js-sdk) | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------ | | [Real-time streaming](../technical-reference/technical-considerations#streaming-premium-option) | 2.1.0 | 2.1.0 | 1.0.0 | 2.0.0 | 6.0.0 | | [Feature experiments](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/create-feature-experiments) | 3.0.0 | 3.0.0 | 1.0.0 | 2.0.0 | 6.0.0 | | [Multi-environment feature flags](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) | 2.0.12 | 2.0.14 | 1.0.0 | 1.0.3 | 6.0.0 | | [Feature flag variables](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables) | 2.0.12 | 2.0.14 | 1.0.0 | 1.0.3 | 1.0.0 | | [Predefined targeting conditions](../targeting-and-segmentation/native-segmentation) | 2.1.1 | 2.1.2 | 1.0.0 | 2.0.0 | 4.0.0 | | Retrieve all targeted feature flags | 2.1.1 | 2.1.2 | 1.0.0 | 2.0.0 | 6.0.0 | | Retrieve all feature flags variables | 2.1.1 | 2.1.3 | 1.0.0 | 2.0.0 | 1.0.0 | | [Retrieve data for a user key (Data API Map endpoint)](/api-reference/map/get-data-for-a-key) | 2.0.13 | 2.0.15 | 1.0.0 | 1.0.3 | 2.1.0 | | [Retrieve browsing data for a user (Data API Visitor endpoint)](../targeting-and-segmentation/native-segmentation#handling-data-in-kameleoon-sdks) | 3.2.0 | 4.1.0 | 1.4.0 | 7.2.2 | N/A | | [Legal consent management](/user-manual/project-management/consent-management-policy#for-web-experiments-/-feature-experiments-/-hybrid-experiments) | 4.0.0 | 4.0.0 | 2.1.0 | 3.0.0 | 8.1.0 | | [Data warehouse integration](/user-manual/?s=data+warehouse) | 3.3.0 | 3.1.0 | 1.8.0 | 3.0.0 | 7.6.0 | | [Cross-device experimentation](../../cross-device-experimentation) | 3.3.0 | 3.1.0 | 1.8.0 | 3.0.0 | 7.6.0 | All Kameleoon SDKs are zero-latency SDKs, which means client-side code performs all campaign allocations. Because the allocations don't use a remote server call, the impact on website performance is under a millisecond compared to \~50 ms on average for API-based technologies. ### Serverless edge compute platforms Kameleoon also supports feature management and experimentation at the edge by providing a set of starter kits to implement feature flags and experiments using [serverless edge compute platforms](../implementation-and-deployment/serverless-edge-compute-starter-kits) offered by modern Content Delivery Networks (CDNs), such as **Akamai EdgeWorkers**, **AWS Lambda\@Edge**, **Cloudflare Workers**, **Fastly Compute\@Edge** and **Vercel Edge Functions**. | **Module** | **Current Version** | **Release Date** | | -------------------------------------------------------------------------------- | ------------------- | ---------------- | | [Fastly starter kit](https://github.com/Kameleoon/fastly-compute-starter-kit) | 0.2.2 | September 2023 | | [Cloudflare Workers](https://github.com/Kameleoon/cloudflare-worker-starter-kit) | 1.0.2 | October 2024 | | [Akamai EdgeWorkers](https://github.com/Kameleoon/akamai-starter-kit) | 0.2.0 | December 2024 | | [AWS Lambda@Edge](https://github.com/Kameleoon/aws-lambda-edge-starter-kit) | 2.1.1 | July 2024 | | [Vercel Edge Functions](https://github.com/Kameleoon/vercel-examples) | 0.1.0 | April 2023 | ### OpenFeature providers Kameleoon supports OpenFeature, an open source standard for feature flag management backed by the CNCF. OpenFeature ensures seamless compatibility across tools and custom solutions. Kameleoon provides OpenFeature support for several SDKs. The full list is available in the [dedicated article](../technical-reference/open-feature-providers). ### Web server modules | **Module** | **Current Version** | **Release Date** | | ------------------------------------------------------------------------------ | ------------------- | ---------------- | | [Nginx web server module](../implementation-and-deployment/web-server-modules) | 1.0.24 | September 2022 | The web server module for Nginx only supports experiments. ### API If Kameleoon SDKs don't support the language used in the app, build a service using one of the existing SDKs or implement a custom integration with the API. When low latency is a priority for updating targeting conditions and feature flag configurations in real time, opting for an API implementation instead of an SDK may result in increased latency. Use the SDKs whenever possible, as they provide better performance, automatic error handling, and ensure a more seamless integration with the codebase. Kameleoon supports all modern JavaScript frameworks, such as Angular, Vue.js, and Next.js, so you can always use a [JavaScript-based SDK](#selecting-a-javascript-sdk) for front-end development instead of relying on the API approach. Visit the [API documentation](https://customers.kameleoon.com/apisdk/doc) to learn more about the available methods. ### Selecting a JavaScript SDK In addition to the JavaScript/TypeScript SDK, Kameleoon also provides other JavaScript-based SDKs: the React SDK, React Native SDK, and NodeJS SDK. Most JavaScript frameworks work with the JavaScript/TypeScript SDK, but some are better suited to one of the other SDKs depending on the implementation. For example, apps that use server-side rendering (SSR) or [Edge integrations](../implementation-and-deployment/serverless-edge-compute-starter-kits) should use the NodeJS SDK. Here are the recommended SDKs based on JavaScript frameworks. | Framework | Recommended SDK | | --------------------------------- | -------------------------------------- | | Edge integrations (any framework) | NodeJS SDK | | Angular/AngularJS | JavaScript/TypeScript SDK | | Express | NodeJS SDK | | Deno | NodeJS SDK | | Ionic | JavaScript/TypeScript SDK | | JQuery | JavaScript/TypeScript SDK | | NestJS | NodeJS SDK | | NextJS (with SSR) | NodeJS SDK | | NextJS (without SSR) | React SDK or JavaScript/TypeScript SDK | | NodeJS | NodeJS SDK | | Plain JavaScript | JavaScript/TypeScript SDK | | React Native | React Native SDK | | React | React SDK or JavaScript/TypeScript SDK | | Svelte (with SSR) | NodeJS SDK | | Svelte (without SSR) | JavaScript/TypeScript SDK | | Vue (with SSR) | NodeJS SDK | | Vue (without SSR ) | JavaScript/TypeScript SDK | For working examples of the NodeJS SDK integrated with Next.js, see the [Next.js starter kits repository](https://github.com/Kameleoon/nextjs-starter-kits). # SDK execution flow Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/get-started/sdk-execution-flow Understand the three stages of Kameleoon SDK execution: initialization, user identification and attribute assignment, and variation assignment and tracking. ## Overview Kameleoon's SDKs allow you to conduct experiments to enhance user experience in web and mobile applications. Based on the diagram below, implementing an experiment with a Kameleoon SDK involves three stages: **initialization**, **user identification and attribute assignment**, and **variation assignment and tracking**. This article will provide a detailed explanation of each of these stages. ![SDK Flow](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/sdk-flow/newImage.jpg) Methods (and their names) described in each step below, will have different syntaxes, based on the convention of the particular language of the SDK. ### 1. Initialization Upon initialization, the SDK fetches any updated feature flag configuration using Cloudflare’s Content Delivery Network via two methods: **Polling**, a default option where the SDK regularly sends a request to the CDN (default interval is every 60 minutes) to obtain the most recent configuration, and **Streaming**, a premium option where the Kameleoon SDK is notified of any real-time changes to the configuration via server-sent events (SSE). Once the configurations are fetched, the SDK begins visitor code assignment along with the variations and targeting process. For more information, refer to [technical considerations](../technical-reference/technical-considerations#feature-flags-configuration-and-bucketing). As shown in the diagram, during **Streaming** , the `on_configuration_update` method (remember, name will vary across each SDK) allows you to handle the event when configuration has updated data. It takes one input parameter handler. The handler that will be called when the configuration is updated using a real-time configuration event. For more information, please refer to the individual [Web](#4-helpful-documents) and [Mobile](#4-helpful-documents) SDKs. ### 2. User identification and attribute assignment At this stage, to calculate variation, **visitor code is allocated** and **user attributes** are incorporated: * **1. Assigning visitor code:** Initialization provides the option to assign a unique ID to a visitor. For web SDKs, the `getVisitorCode()` method retrieves a visitor code from the request headers cookie. If the visitor code does not exist, the method generates a random visitor code and stores it in a response header cookie. For mobile SDKs, the visitor code is obtained from the mobile device’s memory. * **2. Adding user attributes for targeting purposes:** Kameleoon’s targeting conditions enable activation of feature flags based on [user attributes](../targeting-and-segmentation/native-segmentation). To use these conditions effectively, set values using the `addData()` method and/or `getRemoteVisitorData()` to retrieve and use [specific criteria](../targeting-and-segmentation/native-segmentation#when-is-getremotevisitordata-required). User attributes are then stored in server memory for server-side SDKs, browser local storage for client-side SDKs, or device memory for mobile SDKs. For more details, refer to the [data storage article](../technical-reference/technical-considerations#data-storage). * **3. Adding external data for targeting purposes:** External data, such as user preferences or CRM data, can be stored using the Data API (a REST API) via the [POST endpoint](/api-reference/map/update-data-for-several-keys/). This data is then retrieved in the SDK by using the `getRemoteData()` method. ### 3. Variation assignment and tracking In Kameleoon SDKs, the `getVariation()` (`isFeatureActive()` / `getVariations()`) method retrieves the configuration of a feature experiment that contains multiple variations. It can be used to obtain the variation key(s) assigned to a specific user. When a visitor meets all the targeting conditions and exposition rules for the feature flag, Kameleoon uses a hash function to assign the visitor to a variation and then sends an experiment event to the tracking service. If the visitor doesn’t meet the conditions, an activity event is sent to indicate that the user is active on the web or mobile app. Additionally, the `flush()` method can be used to send any other stored data to the tracking service. ### 4. Helpful documents For language-specific integrations, please refer to the **Web SDK** and **Mobile SDK** articles: **Web SDKs** * [Node JS](../../sdks/web-sdks/nodejs-sdk) * [Java](../../sdks/web-sdks/java-sdk) * [JS/TS](../../sdks/web-sdks/js-sdk) * [React](../../sdks/web-sdks/react-js-sdk) * [C#](../../sdks/web-sdks/csharp-sdk) * [Go](../../sdks/web-sdks/go-sdk) * [PHP](../../sdks/web-sdks/php-sdk) * [Ruby](../../sdks/web-sdks/ruby-sdk) * [Python](../../sdks/web-sdks/python-sdk) * [Rust](../../sdks/web-sdks/rust-sdk) * [Elixir](../../sdks/web-sdks/elixir-sdk) **Mobile SDKs** * [iOS](../../sdks/mobile-sdks/ios-sdk) * [Android](../../sdks/mobile-sdks/android-sdk) * [Flutter](../../sdks/mobile-sdks/flutter-sdk) * [React Native](../../sdks/mobile-sdks/react-native-sdk) **Additional resources** * [FAQ](../technical-reference/faq-global) # SDK setup and feature flag tutorial Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/get-started/sdk-setup-and-feature-flag-tutorial Set up the Kameleoon SDK and configure feature flags using this step-by-step checklist covering SDK integration, Kameleoon app setup, and optional Hybrid Mode. Always check the [SDK documentation](../../feature-experimentation/get-started/overview) to ensure correct method names. The methods below are from the **Node.js SDK**. Some features or methods may not be available in all SDKs. *** ## SDK checklist overview This guide provides a step-by-step checklist to [set up and use the Kameleoon SDK](#one-time-setup-in-the-sdk), [configure Feature Flags](#one-time-setup-in-the-kameleoon-app), and optionally [enable Hybrid Mode](#2-hybrid-mode-optional) (SDK + `engine.js`). *** ## 1. Basics ### One-time setup (in the SDK) #### Step 1 – Install, configure, and initialize the SDK See the [quickstart guide](../../sdks/quickstart) to find the right SDK for your language, then follow its own reference page for the exact install command and initialization code. * Install the SDK and configure it, paying attention to: * `updateInterval`: Defines how often the SDK fetches the configuration from Kameleoon. * `trackingInterval`: Defines how often the SDK sends tracked data to Kameleoon. * Create a new `KameleoonClient` and call `initialize()` to load configuration. * **PHP SDK only:** Install a [cron job](../../sdks/web-sdks/php-sdk#using-the-php-sdk-without-a-cron-job) to collect data. *** #### Step 2 – set the visitor code * Use `getVisitorCode()` or a custom method to set the `kameleoonVisitorCode` cookie. *** #### Step 3 (optional) – enable [cross-device Experimentation](../../cross-device-experimentation) and [custom bucketing key](../../sdks/web-sdks/nodejs-sdk#using-a-custom-bucketing-key) * **Cross-Device Experimentation:** Share the same `kameleoonVisitorCode` across devices. * **Custom Bucketing Key:** Show the same variation to a defined group of visitors (for example, users from the same organization). *** #### Step 4 – handle consent * If an experiment requires consent, call `setLegalConsent()`. *(Not needed if the feature flag type uses Delivery Rules.)* *** #### Step 5 – send predefined data * Use `addData()` to target: * Device * Browser * Page URL * Other predefined criteria * Call `flush()` to send data to Kameleoon. *(Note: `flush` is also called implicitly by `isFeatureActive`, `getVariation`, and `trackConversion`.)* *** #### Step 6 – Exclude and tag bots * Exclude bots from results using `addData()` with the `userAgent` type. * To tag internal bots, pass `curl/8.0` to `userAgent` in `addData()`. *** ### One-time setup (in the [Kameleoon app](https://app.kameleoon.com)) #### Step 7 – Create a feature flag * Create a new feature flag in the Kameleoon App. * Add at least one **rule** (Delivery or Experiment) to the **Rollout Planner**. *** #### Step 8 – define the segment * **Option 1:** Target all visitors in the Kameleoon App and implement custom logic in the SDK. * **Option 2 (Recommended):** Use a Kameleoon segment in the Kameleoon App. * Ensure you use `addData()` in the SDK to send data used for targeting (page URL, device, browser, and other predefined criteria). *** #### Step 9 – Attach goals to the flag * Attach at least one goal to track conversions. *** #### Step 10 – Activate the flag * Turn the flag **on** in the Kameleoon App to start serving variations. *** ### Setup for each flag (In the SDK) #### Step 11 – Check feature activation * Call `isFeatureActive()` to check whether the visitor matches the targeting conditions: * Returns `false` if the variation is "off" * Returns `true` if active * Call `getVariation()` to retrieve the variation key. *** #### Step 12 – track conversions and set custom data * Call `trackConversion()` with relevant parameters to track goals. * Optionally, use `addData()` for custom targeting and segmentation. *** #### Step 13 (optional) – use [previously collected Data](../targeting-and-segmentation/native-segmentation#when-is-getremotevisitordata-required) or [external Data](../targeting-and-segmentation/use-external-data-to-target-users) * **Previously Collected Data:** Call `getRemoteVisitorData()` to reuse collected or preloaded conditions. * **External Data:** Use the **Data API** to collect external data and retrieve it in the SDK via `getRemoteData()`. *** ## 2. Hybrid mode (optional) Follow these steps only if using [**Hybrid Mode**](./hybrid-experimentation) (Client-side SDK + `engine.js`). ### One-time setup (in the SDK) #### Step 1 – Install engine.js * Add the `engine.js` script to the front-end. *** #### Step 2 – Handle frontend consent To handle consent, use one of the following approaches: * The **Activation API** * The `kameleoonQueue` via Tag Manager * A direct code snippet: ```js theme={null} window.kameleoonQueue.push(['Kameleoon.API.Core.enableLegalConsent', 'BOTH']); // or window.kameleoonQueue.push(['Kameleoon.API.Core.disableLegalConsent', 'BOTH']); ``` *** #### Step 3 – sync visitor code If your implementation requires legal consent, sync the visitor code cookie using this approach: ```js theme={null} window.kameleoonQueue.push({ level: "IMMEDIATE", command: () => Kameleoon.API.Visitor.setVisitorCode("") }); ``` *** ### Optional: Send data to external tools (hybrid mode) #### One-time setup (in the [Kameleoon app](https://app.kameleoon.com)) ##### Step 1 – Activate third-party integration * In the **Integrations** section of the Kameleoon App, enable the analytics tool you want to use (for example, GA4). *** #### Setup for each flag (in the Kameleoon app) ##### Step 2 – Turn on the integration * In the **Feature Flag Dashboard**, activate the tool for the chosen rule type (Experiment or Delivery). * Enable integration in the correct environment (Production, Development, or Staging). *** #### Setup for each flag (in the SDK) ##### Step 3 – retrieve tracking code * Call `getEngineTrackingCode()` in the SDK code within **5 seconds** of activating the feature (`isFeatureActive` or `getVariation`). *** ##### Step 4 – Inject tracking code into the page Insert the returned code into the HTML: ```js theme={null} window.kameleoonQueue = window.kameleoonQueue || []; window.kameleoonQueue.push(['Experiments.assignVariation', 244200, 962266]); window.kameleoonQueue.push(['Experiments.trigger', 244200, true]); ``` *** ##### Step 5 – Verify event transmission * On the targeted page, inspect the page elements to confirm code injection. * For GA4, verify the event in the `dataLayer`. * In the **Network tab**, confirm the event reaches the correct analytics tool (GA4 should show a “collect” event). # Serverless edge compute starter kits Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits Deploy Kameleoon feature flags and experiments at the edge using starter kits for Fastly, Cloudflare Workers, Vercel, AWS Lambda, and Akamai. A key benefit of edge computing is that it overcomes the static nature of CDNs by running code at the edge. For experimentation purposes, this means bucketing decisions for experiments occur at the edge with reduced latency and avoid caching issues. Use a starter kit to minimize latency or to run feature flags and experiments on parts of the application that are heavily cached. Kameleoon has extended and embedded the [NodeJS SDK](../../sdks/web-sdks/nodejs-sdk) into the following starter kits: * [Fastly Compute@Edge](https://github.com/kameleoon/fastly-compute-starter-kit) * [Cloudflare Workers](https://github.com/kameleoon/cloudflare-worker-starter-kit) * [Vercel Edge Functions](https://github.com/Kameleoon/vercel-starter-kit) * [AWS Lambda with CloudFront Lambda@Edge function](https://github.com/Kameleoon/aws-lambda-edge-starter-kit) * [Akamai Edge](https://github.com/Kameleoon/akamai-starter-kit) ## Tracking in serverless environments Most serverless edge compute providers, including AWS, Akamai, Vercel, and Fastly, charge based on the CPU time your functions consume. Waiting for tracking requests to complete at the edge increases execution time and costs. To optimize performance and reduce expenses, Kameleoon recommends performing tracking on the client side. To implement client-side tracking, use one of the following methods: * **Engine tracking code**: Use the `getEngineTrackingCode()` method to generate a tracking script that runs in the client browser. * **JavaScript SDK**: Load the [Kameleoon JavaScript SDK](../../sdks/web-sdks/js-sdk) on the client side for tracking. Because the edge worker handles bucketing and experiment variation decisions, the client-side SDK only manages tracking and does not require variation configuration. ### Cloudflare Workers Cloudflare Workers is an exception because it allows you to wait for asynchronous requests without additional costs. In a Cloudflare environment, you can perform tracking requests directly within the edge worker without increasing your billable CPU time. # Web server modules Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/implementation-and-deployment/web-server-modules Run Kameleoon A/B tests directly in Apache, Nginx, and other web servers to support variation delivery on heavily cached pages without back-end SDK code. Kameleoon has traditionally allowed the operation of A/B tests at the (using a JavaScript engine) or at the (using a server-side SDK). However, because extensive caching strategies are common for performance reasons, HTTP requests may never reach the back-end application servers. Instead, a front-facing web server replies directly with a cached version of the requested page. In these configurations, server-side SDKs cannot be used, since the logic of allocating a visitor to an experiment happens in the back-end application code, which means many requests cannot be processed because a cached version of the page is returned instead. In other words, the code implementing traffic repartition to variations of an experiment would not be run. To solve this challenge, Kameleoon A/B testing modules are available for the major web servers. This allows the web server to determine the variation used by each visitor. The server can reply with a cached version of the correct variation, so the application can benefit from standard caching strategies. Web testing at the **web server level** is an intermediary level between the front-end and the back-end. ## General concepts A Kameleoon web server module is a low-level component (written in C for optimal performance) that performs variation allocation whenever an HTTP request triggers an A/B experiment. It then (internally) redirects the request to a potentially different URL corresponding to the chosen variation. For example, if a visitor hits the page `https://www.shop.com/plasma-tvs.html` and there is a running experiment on that category page, with three variations, the web server redirects the HTTP request internally to either `https://www.shop.com/plasma-tvs.html` (original version), `https://www.shop.com/plasma-tvs.html?version=B` (first variation) or `https://www.shop.com/plasma-tvs.html?version=C` (second variation). It then returns a cached version of these pages (or passes the call to the application server to generate a new one if the TTL value has expired). The A/B/C test is completely transparent to end users. The visitor only sees the canonical URL `https://www.shop.com/plasma-tvs.html` in the browser. Setup and configuration of the experiment (including setting the target and redirection URLs; for example, by adding the `?version=B` parameter) are completed in the Kameleoon platform. The web server module periodically refreshes its configuration from the Kameleoon servers and database. For convenient planning and deployment of A/B tests, all usual Kameleoon features are available, including starting, pausing, and stopping tests; changing deviation; and modifying configuration. Due to the technical constraints of the client-server model on the web, using URL conditions is the only way to configure experiment targeting at the web server level. Similarly, always use URL redirects in Kameleoon to configure variations at the web server level. The negative performance and SEO impacts associated with front-end (browser) redirection are not an issue with web server redirects. Instead, internal web server redirects are perfectly safe and considered a good practice because they are processed internally on the web server (they're fast) and are transparent to search engine crawlers. ### Operating web server A/B experiments 1. In the Kameleoon app, create a new A/B experiment, with the **Code editor**. 2. In Kameleoon, create the variations you want to implement for the experiment. For each variation, choose the URL redirection option and enter the desired new URL. 3. Choose the targeting of the experiment by adding one or more URL targeting conditions. Any matched URL on the web server will trigger the web server module for the request and activate an internal redirect. The module stores a (first-party) cookie named **kameleoonVisitorCode** for matched requests. Only URL redirection options are taken into account for web-server experiments. Any other modifications on the variations, such as JavaScript or CSS code changes via the graphical editor, are ignored. You can then choose the deviation and launch the experiment as usual. Pausing or stopping the experiment also works as expected. [Traffic exclusion](/user-manual/experimentation/web-experimentation/code-based-experiments/using-the-code-editor#finalization-steps) does not work with web server A/B experiments. Analytics for the original version of the experiment will include data from visitors in the excluded bucket, instead of excluding those visitors from the experiment entirely. To help with potential treatments on the server-side (tracking, implementation of variations), the web server adds an HTTP header to any request that matches a Kameleoon experiment, when the option is turned on in Nginx config file (`kameleoon_headers on;`). The first header is named **kameleoon-experiment** and its content is in the format `experimentID=variationID`, where `experimentID` represents the triggered experiment ID, and `variationID` represents the ID of the variation assigned. The second header is named **kameleoon-redirection** and its content is in the format `variationID=redirectionURL`, where `variationID` represents the ID of the variation assigned and `redirectionURL` represents parameter used for the redirection. ## Supported platforms ### Nginx server module The module supports version **1.18.0**, **1.20.2** and **1.21.4** of Nginx; the [Docker package is available here](../../../assets/developer-docs/kameleoon-module-nginx-1.2.0-docker.zip). The latest version of the module supports version **1.27.1** of Nginx; the [Docker package is available here](../../../assets/developer-docs/kameleoon-module-nginx-1.4.0-docker.zip). The module is tested under an x86\_64 architecture. A Python script must also be installed in parallel (and launched periodically via a CRON job; 30 or 60 minute intervals are recommended). Automation API credentials (for OAuth 2.0 Authentication) are required to use the script. See the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) article for details. ### Apache HTTPd server module (deprecated) The Apache httpd module supports **version 2.4** and has been tested on a CentOS distribution with an x86\_64 CPU architecture. A Python script must also be installed in parallel (and launched periodically via a CRON job; 30 or 60 minute intervals are recommended). This script refreshes or updates the generated Nginx configuration file required by the Kameleoon module. The script also reloads the configuration file. # Headless CMS integration guide Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/integrations/headless-cms-integration-guide Integrate Kameleoon feature flags with a headless CMS to control content delivery per variation, using the React SDK as a reference implementation. ## Step 1: Defining feature variations in Kameleoon In your Kameleoon dashboard, create a new feature flag that controls which version of content is displayed. For example, if you’re experimenting with different versions of a homepage section: * **Feature flag name:** `Homepage section` * **Feature variation 1:** `section_v1` * **Feature variable 1:** `content_id = ID of the content on version 1 of your CMS.` * **Feature variation 2:** `section_v2` * **Feature variable 2:** `content_id = ID of the content on version 2 of your CMS.` These `content_id` values should correspond to the unique identifiers used in your headless CMS for each content version. ![CMS-1](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/cms-intgr/cms-1.png) ### Step 2: Fetching and displaying content from CMS Now, in your React application, use the getFeatureVariable method to retrieve the content\_id based on the active variation and use it to fetch the corresponding content from your headless CMS. ```js theme={null} // Define the type for your CMS content interface CMSContent { title: string; body: string; } function HomepageSection() { const [content, setContent] = useState(null); useEffect(() => { const fetchContent = async () => { try { // Fetch the content_id for the current variation const content_id: string = await getFeatureVariable( 'homepage_section', 'content_id' ); // Fetch the content from your headless CMS using the content_id const response = await fetch(`https://your-cms.com/api/content/${content_id}`); const data: CMSContent = await response.json(); // Update the state with the fetched content setContent(data); } catch (error) { console.error('Error fetching content:', error); } }; fetchContent(); }, []); return (
{/* Render the content fetched from your headless CMS */} {content ? (

{content.title}

{content.body}

) : (

Loading...

)}
); } export default HomepageSection; ``` **Mechanism:** * **Fetching the content id:** The `getFeatureVariable` method retrieves the `content_id` for the active variation. The `content_id` is then used to fetch the appropriate content from the CMS. * **Error handling:** A try-catch block is included to handle any errors during the content fetch process. * **Rendering:** The fetched content is rendered dynamically in your React component. ### Step 3: Managing CMS content Ensure that each content variation in your headless CMS is associated with a unique ID. These IDs should match the `content_id` feature variables set in Kameleoon. When users load the page, Kameleoon decides which variation to serve. Based on the `content_id`, the correct content is retrieved and displayed. **Conclusion:** By following these steps, Kameleoon is integrated with the headless CMS using the React SDK. This setup allows for dynamic experimentation and delivery of different content variations, enhancing personalization and the effectiveness of the user experience. ![CMS-2](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/cms-intgr/cms-2.png) # Setting up Vercel Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/integrations/setting-up-vercel Integrate Kameleoon with Vercel Edge Functions to run server-side experiments at the edge, delivering personalized content with minimal latency for Next.js applications. ## Overview By using [Vercel Edge Functions](https://vercel.com/features/edge-functions) with Kameleoon, personalized content can be efficiently delivered to website visitors. These edge functions deploy globally via the Vercel Edge Network, allowing server-side logic to reside closer to users for swift and efficient content delivery. For [NextJS](https://nextjs.org/) applications on Vercel, the Kameleoon integration facilitates experimentation directly at the edge. The Kameleoon Vercel Integration streamlines the initialization of Vercel projects by pushing Kameleoon configurations directly to Vercel's Edge Config. Vercel enforces size limits on Edge Config values. Given these limits, Vercel Enterprise and Pro customers with moderately-sized environments stand to benefit most from this integration. For further details, consult Vercel's documentation on [Edge Config Limits and pricing](https://vercel.com/docs/storage/edge-config/edge-config-limits). ## Prerequisites Before starting, ensure you have a Vercel account with at least one project. ## Installation To install the Kameleoon Integration for Vercel: 1. Navigate to the [Vercel Marketplace](https://vercel.com/integrations/kameleoon). 2. Search for the Kameleoon Integration and click **Add Integration**. 3. Choose a Vercel Account for the Integration. 4. Select the Projects for the Integration. 5. Review the Integration permission requirements and click **Add Integration**. You can now map Kameleoon projects to Edge Configs. Kameleoon will synchronize project configs with the specified Edge Config. ## Configuration To modify an existing Kameleoon Vercel Integration: 1. Visit the [Vercel Integrations Dashboard](https://vercel.com/dashboard/integrations). 2. Find the Kameleoon Integration and click **Manage**. 3. Click **Configure** to open the Kameleoon configuration panel. 4. Choose a different Edge Config destination or add more connections. ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/feature-management-and-experimentation/setting-up-vercel/integration.png) 5. Click **Save** to finalize the changes. ## Uninstallation To remove an existing Vercel Integration: 1. Access the [Vercel Integrations Dashboard](https://vercel.com/dashboard/integrations). 2. Identify the Kameleoon Integration and select the **Manage** action. 3. Click **Configure** to access the Kameleoon configuration panel. 4. Choose **Remove connection**. 5. Return to the Vercel Integration Dashboard. 6. Select **Remove Integration**. # How to collect data Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/targeting-and-segmentation/how-to-collect-data Troubleshoot missing data collection in Kameleoon feature flags and experiments with a back office and SDK-side checklist. *** ## In the back office Ensure the following conditions are met: * The **rule** is created for the correct **environment** (`production`, `staging`, or `development`). * The **rule** is **enabled** (turned **on**). * The **rule** targets **traffic** that can actually be exposed (i.e. matching segments or visitors). *** ## In the SDK Check these points carefully: * The `KameleoonClient` is created with the **correct configuration**: * `siteCode` * `environment` variable * `networkDomain` (if applicable) * `getVisitorCode()` is called **only once**, and its value is **reused** wherever the `visitorCode` is needed. * If using **Hybrid Mode** (with `engine.js` on the frontend): * The `visitorCode` is correctly **synced** between the SDK and the frontend. * For **Experiment Rules**: * `setLegalConsent(true)` is called to ensure that **data collection** is allowed. * For **Delivery Rules**: * `isFeatureActive()` or `getVariation()` is called. * The method returns `true` (or the expected variation). * For **Experiment Rules**: * `getVariation()` is called and returns the **expected variation**. *** ## Debugging tips If you’ve confirmed all of the above and data is still missing: * **Log in the console**: * The consent value * The `visitorCode` * The variation values Verify that the values match those shown in the browser. * **Enable SDK logging** and check for any **errors**. # Target users with native segmentation criteria Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation Trigger feature flags based on specific criteria and manage visitor data across SDK environments. Kameleoon targeting conditions trigger feature flags based on specific criteria. To use these conditions effectively, set their values with the `addData()` method. In some scenarios, call `getRemoteVisitorData()` to retrieve historical visitor data. This combination creates highly targeted and personalized experiences. ## Manage data in Kameleoon SDKs Accurate data ensures consistent targeting and experimentation. The following sections explain how client-side and server-side SDKs manage targeting conditions and specify when to use `getRemoteVisitorData()` to fetch data from the server. ### Key terminology * **Targeting condition**: The specific user or session attribute used for targeting, such as conversion, browser, or custom data. * **Client-side**: Data handling for standard SDKs operating in a web browser or mobile app. * **Client-side (Cross-device)**: Data handling for client-side implementations that maintain a consistent visitor profile across multiple devices. * **Server-side**: Data handling when the SDK runs on a backend server. Unlike client-side SDKs, server-side SDKs typically delete visitor information after a session ends. * **web only**: A targeting condition based on data that only a browser generates, such as the page URL or page title. Web SDKs collect this data automatically. The Android, iOS, and Flutter SDKs don't support these conditions because no browser generates the data. The React Native SDK has experimental support for them. Server-side SDKs do support these conditions: they receive the data through `addData()` or retrieve it from a prior web session with `getRemoteVisitorData()`. ## Data collection requirements The SDK requires specific visitor data to evaluate targeting conditions. The table below identifies which criteria the SDK handles automatically and which require an explicit method call. "web only" labels the origin of the data, not which SDKs can use it. web only conditions come from browser data, so most mobile SDKs don't support them, but server-side SDKs do, through `addData()` or `getRemoteVisitorData()`. The [React Native SDK](../../sdks/mobile-sdks/react-native-sdk) is the exception among mobile SDKs: it has experimental support for these conditions. | Targeting condition | Actions (Web) | Actions (Mobile) | Actions (Server-side) | | :--------------------------------- | :------------------------------------------------------------------- | :------------------------------------------------------------------- | :------------------------------------------------------------------- | | **Exclusive feature flag** | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | | **Exclusive experiment** | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | | **Target feature flag** | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | | **Target experiment** | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | | **Target personalization** | `getRemoteVisitorData()` | `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **Browser (web only)** | Auto-added / `addData()` | Not supported\* | `addData()` / `getRemoteVisitorData()` | | **Device** | Auto-added / `addData()` | Auto-added / `addData()` | `addData()` / `getRemoteVisitorData()` | | **Operating system** | Auto-added / `addData()` | Auto-added | `addData()` / `getRemoteVisitorData()` | | **Conversion goal** | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | | **Custom data** | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | | **Page URL (web only)** | Auto-added\* / `addData()` | Not supported\*\* | `addData()` / `getRemoteVisitorData()` | | **Page title (web only)** | Auto-added\* / `addData()` | Not supported\*\* | `addData()` / `getRemoteVisitorData()` | | **Previous page (web only)** | Auto-added\* / `addData()` / `getRemoteVisitorData()` | Not supported\*\* | `addData()` / `getRemoteVisitorData()` | | **Page view count (web only)** | Auto-added\* / `addData()` / `getRemoteVisitorData()` | Not supported\*\* | `addData()` / `getRemoteVisitorData()` | | **IP geolocation** | `addData()` | `addData()` | `addData()` / `getRemoteVisitorData()` | | **SDK type** | Auto-added | Auto-added | Auto-added | | **Time elapsed since first visit** | Auto-added / `getRemoteVisitorData()` | Auto-added / `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **Time elapsed since last visit** | Auto-added / `getRemoteVisitorData()` | Auto-added / `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **Visit number today** | Auto-added / `getRemoteVisitorData()` | Auto-added / `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **Visit number total** | Auto-added / `getRemoteVisitorData()` | Auto-added / `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **New or returning visitor** | Auto-added / `getRemoteVisitorData()` | Auto-added / `getRemoteVisitorData()` | `getRemoteVisitorData()` | | **Visitor code** | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | `KameleoonClientFactory.create()` | `isFeatureActive()` / `getVariation[s]()` / `getRemoteVisitorData()` | | **Segment** | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | `addData()` / `getRemoteVisitorData()` | | **Browser cookie** | `addData()` | Not supported\*\* | `addData()` | | **Application version** | `addData()` | Auto-added | `addData()` | | **Likelihood to convert** | `getRemoteVisitorData()` | `getRemoteVisitorData()` | `getRemoteVisitorData()` | * \* Automatic data collection does not work in single-page applications (SPAs). In an SPA, you must call the `addData()` method manually. * \*\* Not supported in the mobile SDKs (Android, iOS, or Flutter). The React Native SDK has experimental support via `addData()`. ## Use getRemoteVisitorData() for historical data The following table outlines when a remote call to `getRemoteVisitorData()` is required to retrieve historical data for targeting decisions. | Targeting condition | Client-side | Client-side (Cross-device) | Server-side | | :-------------------------------- | :------------- | :------------------------- | :------------- | | **Exclusive feature flag** | No | Yes | Yes | | **Exclusive experiment** | No | Yes | Yes | | **Target feature flag** | No | Yes | Yes | | **Target experiment** | No | Yes | Yes | | **Target personalization** | Yes | Yes | Yes | | **Browser (web only)** | No | No | No | | **Device** | No | No | No | | **Operating system** | No | No | No | | **Conversion goal** | No | Yes | Yes | | **Custom data** | No | Yes | Yes | | **Page URL (web only)** | No (Automatic) | No (Automatic) | No/Yes | | **Page title (web only)** | No (Automatic) | No (Automatic) | No/Yes | | **Previous page (web only)** | No (Automatic) | Yes | No/Yes | | **Page view count (web only)** | No (Automatic) | Yes | Yes | | **IP geolocation** | No | No | No/Yes | | **SDK type** | No (Automatic) | No (Automatic) | No (Automatic) | | **Time elapse since first visit** | No (Automatic) | Yes | Yes | | **Time elapse since last visit** | No (Automatic) | Yes | Yes | | **Visit number today** | No (Automatic) | Yes | Yes | | **Visit number total** | No (Automatic) | Yes | Yes | | **New or returning visitor** | No (Automatic) | Yes | Yes | | **Visitor code** | No | No | No | | **Segment** | No (Automatic) | Yes | Yes | | **Browser cookie** | No | No | No | | **Application version** | No | No | No | | **Likelihood to convert** | Yes | Yes | Yes | ### Data handling definitions #### Client-side SDKs * **No (Automatic)**: The SDK collects this data automatically. It requires no remote requests or explicit `addData()` calls. * **No**: The SDK does not collect this data automatically. You must use `addData()`, `trackConversion()`, or evaluation methods like `getVariation()` to add this data. It does not require a remote request. * **Yes**: You must call `getRemoteVisitorData()`. This requirement applies to data generated on the server (like "Likelihood to convert") or when unifying sessions across multiple devices to retrieve actions from a previous device. #### Server-side SDKs * **No (Automatic)**: Applies only to "SDK Type." The SDK collects this automatically. * **No**: Use `addData()` or `trackConversion()` to provide this data. It does not require a remote request. * **No/Yes**: You can provide data directly on the server or obtain it via a remote request. This scenario occurs if a client-side SDK has already collected information during the current visit. * **Yes**: You must call `getRemoteVisitorData()`. Because server-side SDKs have [limited data storage](../technical-reference/technical-considerations#data-storage), they require a remote call to identify historical actions, such as previous visits or exclusivity for an experiment. ### Benefits of remote data retrieval Calling `getRemoteVisitorData()` provides the following advantages: * **Up-to-date information**: Decisions use real-time data from the Data API. * **Cross-device consistency**: Accesses data collected from other devices or sessions. * **Historical access**: Retrieves previous user behavior, such as past URL visits, even if the local SDK state has cleared. Use the `VisitorDataFiltersType` parameter to specify the number of past visits to retrieve or to apply specific criteria filters. ## Edge targeting constraints Feature flags and experiments evaluated at the edge only have access to the visitor data your integration makes available to the edge worker. An edge request behaves like a server-side SDK evaluation for a brand-new visitor session: the edge worker has no guaranteed memory of any previous request from that visitor, so most targeting conditions have no data to evaluate unless you supply it explicitly. Getting visitor data into the edge worker so it can evaluate targeting conditions is covered below. Sending the resulting flag and variation decisions back to your origin server is a separate concern, not handled by the `addData()` and cookie mechanism described here. Edge frameworks expose different mechanisms for this; check your own framework's documentation. ### Where visitor data comes from at the edge Depending on your edge framework, some browser and device data, such as user agent and geolocation, is available directly from the incoming request through the platform's own request-context APIs. Check your framework's documentation for what it exposes. Every other targeting condition, including custom data and visitor history, has no path into the worker unless your integration explicitly supplies it. Whichever source the data comes from, your integration is responsible for reading it and calling `addData()` with it before evaluating a feature flag or experiment. Kameleoon doesn't collect any of this on your behalf at the edge, including data a framework exposes on the request itself. ### getRemoteVisitorData() at the edge `getRemoteVisitorData()` works at the edge the same way it does in any server-side evaluation, but it isn't practical for most edge use cases: * **Latency**: A call to `getRemoteVisitorData()` typically takes 100-200 ms to fetch historical visitor data from the Data API. Flag and variation evaluation itself normally completes in milliseconds, so adding a remote data fetch to every request pushes latency well outside what most edge use cases target. * **No durable shared memory**: An edge platform can destroy and recreate a worker instance at any time, so caching a `getRemoteVisitorData()` result across requests isn't reliable unless your framework offers durable, cross-request memory to store it in. See [Use getRemoteVisitorData() for historical data](#use-getremotevisitordata-for-historical-data) for how this method works outside the edge. For most edge use cases, supply the visitor data your targeting conditions need through `addData()` instead, using the approach below. ### Pass visitor data into the edge worker Because the edge SDK can neither fetch nor retain visitor data on its own, your integration is responsible for making that data available at request time: 1. Collect the visitor attributes upstream, for example during an earlier page load or client-side SDK call. 2. Pass the attributes into the edge request, typically as cookies, since an edge worker can read cookies from the incoming request without making an external call. 3. In the edge worker, read the attributes and call `addData()` with them before evaluating any feature flag or experiment. ```js theme={null} import { CustomData } from '@kameleoon/nodejs-sdk'; // Inside the edge worker, before evaluating any feature flag const visitorCode = getCookie(request, 'kameleoonVisitorCode'); const plan = getCookie(request, 'customerPlan'); const customData = new CustomData(1, plan); client.addData(visitorCode, customData); const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key' }); ``` Cookies have a practical size limit of approximately 4 KB. Reserve that budget for the visitor attributes your targeting conditions actually need. ### Constraints vary by edge framework and plan Edge framework capabilities change over time and can depend on your specific plan with a provider, not just which provider you use. External request support, cross-request shared memory, and compute billing all vary between frameworks, and can change between plan tiers or as a provider updates its offering. Check your framework's current documentation and your account's plan details before relying on a specific capability such as an external call or cross-request caching. See [Serverless edge compute starter kits](../implementation-and-deployment/serverless-edge-compute-starter-kits) for the frameworks Kameleoon currently supports. ## Hybrid Experimentation mode Hybrid Experimentation combines the SDK with the Kameleoon JavaScript snippet to enable advanced targeting. For more details, see the [Hybrid Experimentation](../get-started/hybrid-experimentation) guide. **Benefits:** * Streamlines the implementation process. * Accesses engine-collected client-side data, such as datalayer variables and front-end goals, directly at the SDK level. **Requirement:** * Implement both the SDK and the Kameleoon JavaScript tag. Calling `getRemoteVisitorData()` in this mode provides access to all data points automatically collected by the Kameleoon engine on the webpage. # Use external data to target users Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/targeting-and-segmentation/use-external-data-to-target-users Target users in Kameleoon experiments using external CRM or CDP data stored via the Data API Map endpoint and retrieved with the getRemoteVisitorData method. ## Using the Data API Map endpoint to store data To store additional data on Kameleoon remote servers for use with feature flags and experiments, use the Data API Map endpoint to post data for each user. For further details, refer to the [Map documentation](/api-reference/map/update-data-for-several-keys). This section explains how to retrieve and use data in feature flags and experiments. This allows for creating more targeted and personalized experiences for users based on relevant data. ### Create custom data To use any data point as a targeting criterion, you first need to create a [custom data](/user-manual/assets/custom-data/create-custom-data) entry for the data point by following these steps: 1. In the [Kameleoon app](https://app.kameleoon.com), click **Configure** > **Custom data**. 2. In the **Custom data** section, click **New custom data**. 3. Choose the project. 4. Enter a descriptive name for the custom data. 5. For the acquisition method, choose **Kameleoon SDK method**. 6. For the type, select **Single** . 7. For the data format, select **String**. 8. Choose the appropriate [scope](../../custom-data#scope-for-targeting) for the data. 9. Click **Next** and **Skip** the following screen to save the custom data. ### Retrieve data from the Data API Map endpoint Once you've created the custom data, you need to do the following: 1. Retrieve the relevant data from the Data API Map endpoint by using the `getRemoteData` method. See the documentation for the SDK being used. 2. The `getRemoteData` method returns a JSON object that contains data related to the user. You can parse this JSON response to retrieve the data value you want to use as a targeting condition and set it as the value of the custom data entry using the SDK `addData()` method. ### Use the custom data as a targeting criterion You can now use the custom data entry as a targeting criteria in your feature flags. Here's how: 1. In the **Segments** page (**Configure** > **Segments**), create a new segment or open an existing segment to open the segment builder. 2. In the \**Custom Data* section, select the custom data as a targeting criterion. 3. Save the segment and use it in the feature flags. # FAQ Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/technical-reference/faq-global This list is regularly updated with common customer questions. Use the Kameleoon Application script for web experiments created using the Graphic editor or Code editor. Use the web SDK for feature flags and feature experiments. Note that Kameleoon can run in Hybrid mode. Hybrid mode uses both Web SDKs and the Kameleoon JavaScript application file. Hybrid mode allows you to use the optimal approach for individual tasks. For example, you can implement and deploy variations more easily on the server side, while the JavaScript file tracks more effectively. Kameleoon uses CDN servers during initialization. Once the SDK receives and caches the configuration, retrieval and updates happen quickly, typically 50-70 ms depending on the latency of the server from the nearest CDN. There are two methods to get the configuration: [Polling](./technical-considerations#polling-default) and [Streaming](./technical-considerations#streaming-premium-option-premium-option). Streaming uses the [Server Side Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE or EventSource) protocol. Kameleoon purges the CDN every time you update a feature flag configuration (for example, variations, traffic expositions, and targeting), or every 24 hours. If you use a client-side SDK and the website restricts the loading of resources (scripts, images, media, CSS) via the standard Content-Security-Policy (CSP) HTTP header, update the site's CSP to allow Kameleoon resources to load: ``` script-src https://[your-site-code].kameleoon.io https://[your-site-code].kameleoon.eu https://client-config.kameleoon.com https://sdk-config.kameleoon.eu https://*.experimentation.dev 'unsafe-eval'; connect-src https://[your-site-code].kameleoon.io https://[your-site-code].kameleoon.eu https://eu-data.kameleoon.eu https://eu-data.kameleoon.io https://na-data.kameleoon.eu https://na-data.kameleoon.io https://editor.kameleoon.com https://api.kameleoon.com https://customers.kameleoon.com https://logger.kameleoon.io https://client-config.kameleoon.com https://sdk-config.kameleoon.eu https://*.experimentation.dev; ``` If you use Kameleoon in [Hybrid mode](../get-started/hybrid-experimentation), the domain for your Kameleoon scripts `https://[your-site-code].kameleoon.xx` may vary from one project to another. Kameleoon may host your projects on either `kameleoon.eu` or `kameleoon.io` depending on their creation date. Make sure you use the domain displayed in your project in the Kameleoon App. Replace \[your-site-code] with your Kameleoon [site code](/user-manual/faq#how-do-i-find-my-sitecode) in each line that it appears and add this to your configuration. Since each server instance has its own memory, it stores all collected visitor data on that instance. You need to use a framework that processes requests from the same visitor on the same server instance. Otherwise, you must add visitor data completely before each tracking request, or load it using the `getRemoteVisitorData` method. If the problem is in the SDK configuration difference, use the [Streaming](./technical-considerations#streaming-premium-option-premium-option) option. All evaluations happen locally to eliminate latency. Tracking requests then go asynchronously to Kameleoon Data API servers. To assign a visitor to an experiment variation, Kameleoon first builds an identifier using the visitor code, the experiment ID, and a potential additional element in case of respooling. Then, a synchronous implementation of the hash function [SHA-256](https://project.kameleoon.net/issues/256) computes a hash of this identifier. The integer obtained through hashing is then mapped to a floating number between zero and one to assign it to an experiment variation. The [SHA-256](https://project.kameleoon.net/issues/256) function is deterministic, so the same user (with the same visitor code) always receives the same variation for an experiment, unless you explicitly request a recomputation of the assignment. Follow the methodology detailed [here](/user-manual/experimentation/feature-experimentation/use-cases/build-whitelists-to-rollout-a-feature-for-internal-users). This [article](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/add-a-new-rollout-rule#user-targeting-and-bucketing) explains all possible cases. The results page might display no data for several reasons: * If you enabled bot filtering in the project settings, the SDK might set the user-agent value incorrectly. Refer to [this article](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results) for more details. * You configured Legal Consent as **Required** in the project settings, but haven't called the `setLegalConsent` SDK method. Refer to [this documentation](/user-manual/project-management/consent-management-policy#for-web-experiments-/-feature-experiments-/-hybrid-experiments) for more details. * You didn't use any of the SDK methods that send a tracking request to Kameleoon servers. [The following article](/user-manual/experiment-analytics/analyze-results/feature-flag-results/analyze-a-feature-flags-overall-results) provides additional help in debugging the issue. If you see a large number of visits from visitors, they could be bots. To prevent bots from affecting your experiment, enable the [Bot filtering option](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results) in your project settings. To indicate that a visitor isn't a bot, you need to pass the Kameleoon UserAgent data by using the SDK method. Calling the method prevents Kameleoon from filtering the user. If your reports show unexpected allocation (for example, 10/90 instead of 50/50) or if visitors are missing, it might be due to one of the following reasons: * **Narrowing the visitor pool for specific variations:** If you first call `getVariations(onlyActive: true, track: false)`, the SDK only returns visitors assigned to active (`ON`) variations. If you then only display experiment pages and call `getVariation(track: true)` for these specific visitors, Kameleoon tracks only the `ON` variation, which results in a report that only shows one variation. * **Insufficient time for tracking requests:** Kameleoon sends data at a specific interval. If a visitor stays on a page with an integrated Kameleoon client for the `ON` variation, but moves to a page without it for the `OFF` variation, the client might not have enough time to send the tracking request for the `OFF` variation. * **Missing configuration for specific variations:** You might have omitted the `UserAgent` or `setLegalConsent` for some variations. For example, if you only provide consent on the page for the `ON` variation, Kameleoon can't track visitors in the `OFF` variation. * **Missing visitor data:** The SDK doesn't collect visitor data automatically; you must add it explicitly for targeting and tracking to work correctly. ### Check your targeting setup If you suspect targeting issues, follow these steps: 1. Create a non-targeting rule with 100% exposure and assign your desired variation. 2. Add targeting and ensure the user stops receiving the variation. 3. Add the required Kameleoon data. The user should receive the variation again. If this fails, try using simpler targeting conditions to isolate the issue. Yes, data is immediately available for targeting. Data will be available during the visitor session for server-based SDKs and during the set lifetime for client-based SDKs (mobile and web). It depends on the type of SDK. In server SDKs, the SDK stores visitor data in operational memory during the visitor's session. You can set the session's duration, although the default is 30 minutes. The longer the session duration set with the `sessionDuration` parameter, the more data the SDK holds in memory, increasing consumption. Each time a visitor sends a request, the session extends by an additional 30 minutes. As a result, the SDK keeps data for at least 30 minutes after the last request. In client SDKs (mobile and web), the SDK stores data in local storage (web LocalStorage). Data can persist indefinitely. However, using the [dataExpirationInterval or targetingDataCleanupInterval](../../sdks/web-sdks/js-sdk#configuration-parameters) parameter, you can set the data's expiration date, after which the SDK deletes the data. Generally, you don't need to call the `flush` method manually: other calls to [SDK methods](#which-methods-trigger-http-requests-are-they-synchronous-or-asynchronous) send data along with them. However, if you need to send data to the Data API without triggering the visitor feature flag, use the `flush` method. If you have a server-based SDK, it stores data during a visitor's session. You don't need to load the data on every request if the user's session hasn't expired. In mobile SDKs, data persists indefinitely (or according to your settings). If the user session is no longer active or if the visitor is moving between devices in the client SDK, call the `getRemoteVisitorData` method with the appropriate parameters to get data sent to the Data API. After loading, the SDK includes the data in visitor targeting. An edge worker has no guaranteed memory of a visitor's previous requests, so it behaves like a server-side SDK evaluating a brand-new visitor session every time. Most targeting conditions have no data to evaluate unless your integration supplies that data explicitly, typically by reading it from a cookie and calling `addData()` before evaluating the feature flag. `getRemoteVisitorData()` works at the edge, but its latency (typically 100-200 ms) and the lack of durable, cross-request memory in most edge platforms make it impractical for most edge use cases. See [Edge targeting constraints](../targeting-and-segmentation/native-segmentation#edge-targeting-constraints) for the full explanation and a code example, and [Serverless edge compute starter kits](../implementation-and-deployment/serverless-edge-compute-starter-kits) for the frameworks Kameleoon supports. If you enable legal consent, Kameleoon collects data only with visitors' consent. If you're using the Experiment Rule and aren't receiving visitor statistics, ensure that: * **In the [Kameleoon App](https://app.kameleoon.com)** * You created the rule for the correct environment (production, staging, or development). * The rule is on. * The rule targets traffic that Kameleoon can actually expose. * If you enabled bot filtering on your project, [add User Agent to the filter](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). * **In the SDK** * You created the `KameleoonClient` with the correct configuration (siteCode, environment variable, and, if applicable, networkDomain). * You call `getVisitorCode` only once, and reuse its value wherever you need the visitorCode. * If using hybrid mode (engine.js on the frontend), the visitorCode syncs correctly with the frontend. * For experiment rules, you call `setLegalConsent(true)` to allow data collection. * For delivery rules, you call `isFeatureActive()` (or `getVariation()`), and it returns true (or the expected variation). * For experiment rules, you call `getVariation()`, and it returns the expected variation. * **Debugging Tips** * Log in the console: the consent value, the visitorCode, and the variation values, and verify they match what you see in the browser. * Enable SDK logging and check for any errors. According to GDPR rules, without visitor consent, Kameleoon only uses the technical information necessary for correct operation of product features. Without consent, Kameleoon sends variations for Targeted Delivery rules (but not for Experiment rules). Kameleoon sends all other information (for example, CustomData, Page Views, and Geolocation) only with visitor consent (when you grant permission). You can read about consent management in more detail [here](/user-manual/project-management/consent-management-policy#how-to-choose-your-consent-management-policy). By default, the SDK batches multiple events together and sends a tracking request to Kameleoon servers for analytics purposes on a configurable interval. This approach improves efficiency and reduces server load. The SDK sends a tracking request: * **Periodically:** By default, the SDK sends a request every **1000 milliseconds (1 second)**. You can change this interval by setting the **tracking interval** value. * **On demand:** Instantly, if your code calls a method like `flush(instant=true)`. Specifically, calling any of the following methods before the interval expires triggers a tracking request: * `getVariation` (when you set `track` to `true`). * `getVariations` (when you set `track` to `true`). * `isFeatureActive` (when you set `track` to `true`). * `trackConversion` * `flush` (with or without `instant=true`) In addition to the preceding method calls, **client-side SDKs** send a tracking request every **60 seconds** by default if no other activity has occurred, helping maintain the visitor session. You can change this default by setting the `activityTrackingIntervalMillisecond` (or `activity_tracking_interval_millisecond`) parameter in your SDK configuration. For **server-side SDKs**, event batching is particularly useful as each tracking request can consolidate multiple visitors' data into a single request. This approach aggregates information on all affected visitors and sends it once per interval, improving efficiency, and reducing server load. Tracking requests are subject to [Data API rate limits](../../apis/data-api-rest/overview#rate-limits). For **client-side SDKs**, in environments where multiple visitors share the same network (for example, in an office), they may appear to originate from the same IP address, which can exceed the rate limit and cause a `429 - Too Many Requests` error. Different SDKs may name these methods differently due to language peculiarities. The following is a list of SDK methods that make HTTP requests: * isFeatureActive / getFeatureVariationKey / getFeatureVariable / trackConversion / flush * These methods make asynchronous requests to the Data API to store all information about the visitor (including variations received by the user). Kameleoon uses this information to display statistics in app.kameleoon.com * getRemoteData / getRemoteVisitorData / getWarehouseAudience * These methods make synchronous requests to the Data API to obtain information about the visitor * Additionally, SDK makes asynchronous requests to obtain the configuration necessary for internal work. Call `isFeatureActive` when you need to know whether the flag is active but don't need to know the exact variation the visitor received. When using Experiment rules, it's better to call `getFeatureVariationKey` if you have two or more variations other than `off`. Yes, you can implement a hybrid integration using both a client-side SDK (such as the Kameleoon JavaScript SDK or the `engine.js` application file) and a server-side SDK. In this setup, it's essential to call the `getVisitorCode` method. Calling this method ensures consistent visitor recognition between the browser and the server, and provides consistent variation allocation when running both client-side code (for example, event tracking) and server-side code (such as feature execution) for a given feature flag. Use `getVisitorCode` in instances where you use a hybrid integration (web-site \<-> server sdk, js sdk \<-> server, engine \<-> server sdk). When you call `getVisitorCode`, it obtains the visitor code and transmits it using a cookie. If you don't use a hybrid integration, you don't need to call `getVisitorCode`. However, you can still call it to generate a random visitor code. `getVisitorCode` requires domain installation. Otherwise, you may get different variations for one visitor, since it will have different visitor codes on different subdomains of your site. If you use server and client SDKs at the same time, you need to set the consent for both SDKs. Refer to [this](../../privacy-and-compliance/consent-management) article for more details. Certain ad blockers can block Kameleoon, like many experience optimization solutions. These primarily affect the Web Experimentation application file (`engine.js`) and client-side SDKs, which rely on JavaScript code loaded on your website. Server-side SDKs, however, operate within your servers, unaffected by ad blockers. If you want to include users with ad blockers in your experiments, Kameleoon offers a premium option that allows you to use a custom domain instead of Kameleoon's default domain. Custom domains prevent ad blockers from detecting and blocking Kameleoon. Once configured, Kameleoon uses your custom domain for all outgoing network requests to Kameleoon's servers, whether for tracking purposes, or fetching SDK configuration updates. Using a custom domain isn't the same as self-hosting. When you use a custom domain, Kameleoon's infrastructure still hosts and serves all the content (for example, `engine.js`, SDK configuration, tracking calls). The difference is that Kameleoon routes these requests through a domain you control, like `experiments.mydomain.com`. To enable this option, contact your Technical Account Manager. You must provide a full domain (for example, `experiments-mydomain.com`), not a subdomain (for example, `experiments.mydomain.com`). The domain name can't contain the substring `kameleoon`. * For Web Experimentation, replace references to the default Kameleoon domain (`kameleoon.`) with your custom domain. * **Example:** `//SITE_CODE.{your-domain}/engine.js` * For client-side SDKs, use the `networkDomain` parameter in the SDK initialization. If you want to self-host instead of using a custom domain, follow [this guide](../../web-experimentation/self-hosting/simple-self-hosting-and-on-premises-options#application-file-self-hosting). # OpenFeature providers Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/technical-reference/open-feature-providers Use Kameleoon as an OpenFeature provider to standardize feature flagging across different platforms and languages. Kameleoon contributes to the OpenFeature ecosystem, an open-source initiative from the [Cloud Native Computing Foundation](https://www.cncf.io/) (CNCF). OpenFeature establishes a universal standard for feature flag management that works across various tools and custom solutions. When you use Kameleoon as your OpenFeature provider, you can switch between platforms without rewriting your code. This approach eliminates vendor lock-in. Kameleoon offers OpenFeature providers for multiple languages and frameworks, which allows you to use officially supported SDKs while benefiting from the open-source community. ### Key benefits of OpenFeature OpenFeature helps you perform the following tasks: * **Standardize implementations:** Maintain consistent feature flag logic across different providers. * **Simplify migrations:** Migrate between platforms by swapping the OpenFeature provider. * **Streamline operations:** Reduce complexity by following community-driven best practices. ### Supported SDKs Kameleoon provides [OpenFeature support](https://openfeature.dev/ecosystem/?instant_search%5Bquery%5D=kameleoon) for the following SDKs: | Language | Repository Link | | :-------------------------- | :-------------------------------------------------------------------- | | **Java** | [GitHub Repository](https://github.com/Kameleoon/openfeature-java) | | **.NET** | [GitHub Repository](https://github.com/Kameleoon/openfeature-dotnet) | | **Python** | [GitHub Repository](https://github.com/Kameleoon/openfeature-python) | | **Go** | [GitHub Repository](https://github.com/Kameleoon/openfeature-go) | | **Ruby** | [GitHub Repository](https://github.com/Kameleoon/openfeature-ruby) | | **PHP** | [GitHub Repository](https://github.com/Kameleoon/openfeature-php) | | **Swift** | [GitHub Repository](https://github.com/Kameleoon/openfeature-swift) | | **Kotlin** | [GitHub Repository](https://github.com/Kameleoon/openfeature-android) | | **JavaScript / TypeScript** | [GitHub Repository](https://github.com/Kameleoon/openfeature-js) | | **Node.js** | [GitHub Repository](https://github.com/Kameleoon/openfeature-nodejs) | # Technical considerations Source: https://docs.kameleoon.com/developer-docs/feature-experimentation/technical-reference/technical-considerations Understand how Kameleoon SDKs handle feature flag configuration updates, zero-latency bucketing, polling vs. streaming, and client-side vs. server-side initialization. ## Feature flags configuration and bucketing When you update a feature flag configuration or turn it on or off, SDKs can obtain the updated configuration from the Cloudflare Content Delivery Network (CDN) using either **polling** or **streaming**. Polling is the default option while streaming is a premium option. Kameleoon designs its SDKs to comply with a **zero latency policy**. The SDK doesn't require additional remote server calls to activate and bucket users in a feature flag. Even a fast remote server call adds a minimum of 20 ms of latency to the app, and depending on several factors, this delay can increase to hundreds of milliseconds or completely block the app loading for the end user. To prevent this added latency, Kameleoon SDKs assign visitors to experiments locally. For client-side SDKs, which run on the user's device, the app must fetch the active feature flag configurations at the start of the session. This requires a remote call with an associated callback, which means app code that uses the SDK can't run experiments or apply feature flags before this callback finishes executing. ### Polling (default) In this mode, the SDK sends a request to the CDN at regular intervals (by default, every 60 minutes) to retrieve the most recent configuration. You can customize the interval in the SDK configuration using the `refresh_interval_minute` (or `refresh_interval`) parameter. When intervals are moderately spaced, updating the configuration consumes very little memory and network resources. For web client-side SDKs, the polling strategy includes local caching logic: 1. **Initial Load**: When a visitor opens a web page, the SDK first attempts to load the configuration from the cache (if available). 2. **Background Update**: If the `refresh_interval` has elapsed, the SDK performs a background request to update the configuration and refreshes the cache. 3. **Cache Validity**: The SDK considers cached configuration valid for **1.5 hours** from the last successful update. If the visitor opens a page within this 1.5-hour window, the SDK uses the cached version. Otherwise, it ignores the cache and makes a new request immediately. This caching strategy ensures faster page loads and minimal network usage, while still keeping configuration reasonably fresh. Suppose you set `refresh_interval` to 15 minutes and a feature flag exposes a user. If you turn off the feature flag **10 minutes later** and the same user returns to the website 20 minutes later: * The SDK will first **load the configuration from cache** (still showing the flag as active). * Simultaneously, it will **make a background request** to fetch the updated configuration and refresh the cache. * This design prioritizes **performance**, ensuring quick page loads while asynchronously syncing with the updated config. So, even though there's a short window where a user might see stale configuration (depending on the interval), the SDK quickly updates it in the background. The SDK applies the updated configuration the next time the visitor opens a page. #### Impact on newly launched experiments and feature flags `dataFileCacheTtl` and `dataFileRefreshInterval` together determine how quickly the SDK evaluates visitors against a newly launched experiment or feature flag's configuration. Visitors whose cached data file has already aged past `dataFileCacheTtl`, or who have no cached data file yet, receive the new configuration immediately, because `initialize()` blocks until a fresh data file arrives. Visitors whose cached data file is still within `dataFileCacheTtl` keep evaluating against the previous configuration until that cache ages past `dataFileCacheTtl`, or until a background refresh completes and they load another page. As a result, with the default `dataFileCacheTtl` of 90 minutes, traffic entering a newly launched experiment ramps up gradually instead of reaching full volume immediately. For example, a newly launched experiment might expose about 40% of its eventual targeted population in the first hour after launch, about 57% by the second hour, and more than 95% by the third hour, once enough visitors' cached data files have aged past that 90-minute window. No visitor is permanently excluded from the experiment. Each one joins after their device fetches the updated configuration. When you compare Kameleoon exposure numbers to another analytics tool right after launching a campaign, expect a ramp-up period of roughly `dataFileCacheTtl` minutes (90 by default) before exposure numbers stabilize and match the targeted population. A lower exposed-visitor count during that window doesn't mean the SDK drops or mistargets those visitors. If your use case needs a faster start, lower `dataFileCacheTtl` (keep it higher than `dataFileRefreshInterval` to avoid adding unnecessary blocking requests to page load), or discuss [the streaming option](#streaming-premium-option) with [Kameleoon support](mailto:support@kameleoon.com), which bypasses the cache model entirely for the SDK data file. A setup that pairs a server-side or edge SDK with `getEngineTrackingCode()` and the `Engine.js` tracking script can still show a similar ramp-up in tracked results, driven by the separate browser cache for `Engine.js` itself. See [Engine.js caching behavior](../../web-experimentation/faq#will-kameleoons-script-enginejs-slow-down-my-website) for details. ### Streaming (premium option) The real-time streaming mode allows the SDK to automatically apply the new configuration without any delay. When you enable streaming, the Kameleoon SDK receives notification of any changes to the configuration in real time, thanks to server-sent events (SSE). **Main benefits:** * Update the configuration immediately (no waiting for the next polling cycle). * Use less network traffic than polling at short intervals. Streaming doesn't send periodic requests. It opens the connection once and keeps it permanently open, ready to receive data. **Availability:** * Real-time streaming is a premium option that requires a subscription. To activate it on a Kameleoon account, contact the Customer Success Manager or email [support@kameleoon.com](mailto:support@kameleoon.com). * The PHP SDK doesn't support streaming due to technical constraints. It can't listen for configuration updates in a non-blocking way because the PHP SDK doesn't persist between requests. Each request creates a new SDK instance that destroys itself immediately after it receives a response. * The real-time streaming mode isn't compatible with [serverless edge compute platforms](../implementation-and-deployment/serverless-edge-compute-starter-kits). * For other languages, see the [SDK compatibility matrix](../../feature-experimentation/get-started/overview) for the minimum supported version in the preferred language. ## Data storage Kameleoon designs its SDKs for optimal performance and user experience by managing visitor data locally, either on the server or directly on the device, instead of fetching it from a remote source. Kameleoon stores all visitor data relevant to experiments and feature flags locally, including experiment assignments, segment data, and any custom data added using methods like `addData()` or fetched from the server with `getRemoteVisitorData()`. **Where the SDK stores this data and its lifespan differs between server-side and client-side SDKs.** ### Server-side SDKs **For server-side SDKs, the SDK stores visitor data in the server's volatile memory (RAM)**, which provides fast access for experiment and feature flag evaluations. The SDK organizes data as a map, using unique `visitorCode` values as keys. * **Session-based storage:** The SDK retains data in memory only for the duration of a user's active session, which ends by default after 30 minutes of inactivity (no page views or clicks). All data collected during the session expires at the same time. * **`session_duration` parameter**: Adjust session duration using the `session_duration` parameter in the SDK configuration. This parameter lets the Kameleoon session align with your app's server-side session management. Increasing `session_duration` will require the SDK to allocate more RAM to store the increased visitor data * **Data loss on server restart:** Because the SDK stores data in RAM, it's lost if the app server restarts. Usually, data loss isn't a concern, since Kameleoon typically retrieves important visitor data (like user IDs or attributes) from a persistent database and reassigns it to the visitor upon a new request. * **Retrieving data from previous visits:** The `getRemoteVisitorData()` method lets you retrieve custom data collected during a visitor's previous visit. This method automatically associates the last data entry for a visitor, eliminating the need to call `addData()` again. ### Client-side SDKs **As opposed to server-side SDKs, client-side SDKs utilize persistent local storage on the user's device.** This mechanism ensures that visitor data persists across browser sessions, app restarts, and even device reboots. The specific storage mechanism varies by platform: * **iOS:** `UserDefaults` * **Android:** `SharedPreferences` * **JavaScript/TypeScript:** `LocalStorage` * **Note for JS SDK:** The `kameleoonVisitorCode` is also shared via cookies to ensure seamless data continuity and communication with the Kameleoon app file (`engine.js`). * **React Native:** `MMKV Storage` Unlike server-side SDKs which use a global `session_duration`, client-side SDKs don't operate on a session-based expiry for all data. Instead, they use either `targetingDataCleanupInterval` or `data_expiration_interval_minute` to control how long the SDK stores each piece of data. * **`targetingDataCleanupInterval`/`data_expiration_interval_minute`:** These parameters determine the individual lifespan of each specific data entry. By default, the SDK stores data indefinitely (until explicitly cleared) to ensure consistent user experiences across visits. Set this parameter to a specific number of minutes if you need individual data points to expire after a certain time. * **Data persistence:** This fundamental difference means that data persists even if the user closes the browser tab, navigates away from the site, or closes the mobile app. * **Naming convention rationale:** The difference in parameter terminology (`session_duration` versus `data_expiration_interval_minute`) reflects the underlying storage philosophy. `session_duration` governs the overall lifetime of all data within a server-side session. `data_expiration_interval_minute` allows granular control over the lifespan of each specific piece of data stored persistently on the client. ### Edge SDKs Edge SDKs, deployed through [serverless edge compute starter kits](../implementation-and-deployment/serverless-edge-compute-starter-kits), don't retain visitor data between requests the way server-side or client-side SDKs do. An edge platform can destroy and recreate a worker instance at any time, so a `session_duration` setting has no durable memory to apply to, and an edge worker might not see a later request from the same visitor. Because an edge worker can't retain visitor data on its own, your integration is responsible for making visitor data available to the edge worker on every request, typically by passing it in as a cookie and calling `addData()` before evaluating a feature flag or experiment. See [Edge targeting constraints](../targeting-and-segmentation/native-segmentation#edge-targeting-constraints) for the full explanation and a code example. ## Domain list The Kameleoon SDKs require access to a set of URIs that provide specific services to the SDK. If you are restricting access and need to explicitly whitelist domains, make sure that the Kameleoon SDKs can reach the following domains: | URL | Used by | | ------------------------------------------------------------------------------------------- | ------------------------------- | | `https://sdk-config.kameleoon.eu/` | Configuration file | | `https://.kameleoon.io/sdk-config` / `https://client-config.kameleoon.com/mobile` | Configuration file (deprecated) | | `https://events.kameleoon.com:8110/sse` | Real-time streaming | | `https://api.kameleoon.com/oauth/token` | Authentication | | `https://eu-data.kameleoon.io`/ `https://na-data.kameleoon.io` | Tracking | The domain for your Kameleoon scripts may vary from one project to another. Kameleoon may host your projects on either `kameleoon.eu` or `kameleoon.io` depending on their creation date. Make sure you use the domain displayed in your project in the Kameleoon App. # Developer docs overview Source: https://docs.kameleoon.com/developer-docs/home/developer-docs-overview Navigate Kameleoon's developer documentation for web experimentation, feature management, SDKs, APIs, and privacy tools. For help creating, configuring, and analyzing experiments in Kameleoon, refer to the [user manual](/user-manual/home/user-manual-overview) instead. Kameleoon provides a unified platform that combines experimentation capabilities for both web and full-stack apps. The platform includes two main solutions: * **Web Experimentation** includes the tools you need to run experiments on websites and web apps, such as prompt-based experimentation (PBX). This solution uses a Kameleoon JavaScript app file to deliver experiment changes and track events related to experiments and visitor behavior. * **Feature Management and Experimentation** supports product and engineering teams that deploy and run experiments in backend systems or mobile apps. You can also use it to incorporate feature management and experimentation into your product development and release process. This solution uses Kameleoon SDKs or APIs. You can use both Web and Feature solutions simultaneously in an approach called [**hybrid experimentation**](../feature-experimentation/get-started/hybrid-experimentation). This approach lets you run backend experiments while leveraging the Kameleoon JavaScript app file to automatically capture visitor behavior and conversion data. Kameleoon offers APIs and additional tools for integrating with your ecosystem, automating tasks, and ensuring flicker-free A/B experiments. *** ## Web Experimentation Execute tests, including AI-powered PBX, on copy, design, or full pages on your website, including single-page apps, all without affecting performance. [**Start your PBX free trial**](https://login.kameleoon.com/app/sign-up-pbx) to begin optimizing your web experience today. Learn about Kameleoon's script, A/B testing, and Web Experimentation features. Use advanced Kameleoon features, such as cross-device history reconciliation and ITP management. Use consent management features to adhere to regulatory requirements while responsibly managing data. *** ## Feature management and Experimentation Roll out new features with confidence, in real time, everywhere in your tech stack. Kameleoon provides more than a dozen SDKs. [**Try a free demo**](../feature-experimentation/get-started/demo-feature-experimentation) to see feature management in action. ### Web SDKs ### Mobile SDKs ### Serverless edge compute starter kits ### Additional resources Learn the basics of Feature Experimentation in Kameleoon SDKs and supported features and integrations. Check the prerequisites and verification steps that apply across every SDK before you pick one to install. Understand the fundamental technical concepts you need for a successful implementation. If your web server implements caching, use Kameleoon's web server modules to ensure your A/B testing's reliability. *** ## APIs Discover APIs to supercharge your experimentation. [**Get started with APIs**](../apis/overview) Use the Activation API (JavaScript) to implement A/B testing variations without flickering. Use the Automation API (REST) to programmatically configure and manage your experiments. Use the Data API (REST) to store, retrieve, and manage data on Kameleoon's remote servers. Use the Product Recommendation API (REST) to make AI-driven product recommendations to your visitors. *** ## Additional tools Learn about Kameleoon's powerful analytics and data collection tools. Integrate with your analytics solution (for example, Mixpanel, Amplitude, Snowplow, GA4, Segment). Connect AI coding assistants to Kameleoon to automate implementation and feature management. Open any page in Claude, ChatGPT, or Cursor, or configure a persistent docs MCP server for your development environment. Understand what client-side and SDK data you collect for use in your experiments. *** ## Support Join Kameleoon's [Slack Community](https://join.slack.com/t/kameleooncommunity/shared_invite/zt-1s6m8s09e-~yA1EUgn5pLWW_mrgf8TrQ) or contact [support@kameleoon.com](mailto:support@kameleoon.com) for questions about Kameleoon's features and platform updates. # Consent management Source: https://docs.kameleoon.com/developer-docs/privacy-and-compliance/consent-management Manage visitor consent in Kameleoon, including available policies, SDK and API methods for setting consent, and the impact on data collection. Refer to the [User Guide](/user-manual/project-management/consent-management-policy) for instructions on setting up the consent management policy when using Kameleoon on your web app. Kameleoon provides native built-in integrations with [Consent management platforms](/user-manual/integrations/integrations-overview). ## Consent policies For a given visitor, legal consent to use Kameleoon is considered either **granted**, **denied** or **unknown** (a special state used when consent is not defined, as the visitor has not granted nor denied it yet). To begin, you should choose how Kameleoon treats legal consent: * **Consent not required**: With this policy, consent is not explicitly required for this website and module. Kameleoon assumes it is granted automatically and starts immediately in full-featured mode. * **Consent required**: With this policy, consent must be explicitly obtained from the visitor. Until it is given, the legal consent will be considered to be "unknown" and Kameleoon will not operate normally. To inform Kameleoon when legal consent has been obtained, you must either use the Kameleoon Activation API method, which requires custom JS code to be implemented or one of the SDKs if you run feature flags. For more information on this topic, refer to the [user guide](/user-manual/project-management/consent-management-policy#how-to-define-your-consent-management-policy). To cover all possible use cases, Kameleoon's Web Experimentation behavior can be further customized when consent is considered unknown or when it is denied by the user. To learn more about the available options, read the article on [behavior when consent is unknown](/user-manual/project-management/consent-management-policy#behavior-when-consent-is-unknown). ## Reading the current consent state for a visitor This section applies only to the Kameleoon Web Experimentation solution. To obtain (read) the current legal consent state for a visitor, use the `Kameleoon.API.Visitor` object: * `Kameleoon.API.Visitor.experimentLegalConsent`: returns **true**, **false**, or **null** if the state is unknown (consent is required but has not yet been granted nor denied). * `Kameleoon.API.Visitor.personalizationLegalConsent`: returns **true**, **false**, or **null** if the state is unknown (consent is required but has not yet been granted nor denied). While the `Kameleoon.API.Core.enableLegalConsent()` and `Kameleoon.API.Core.disableLegalConsent()` methods allow you to change (write) the legal consent status for this visitor, obtaining (reading) the current state is done via `Kameleoon.API.Visitor`. If you have chosen a Consent not required policy, the legal consent can never be in an unknown state. The `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` will always return **true** by default and will be set to **false** only if the `Kameleoon.API.Core.disableLegalConsent()` method is called. ## Modes of operation for Kameleoon Web Experimentation engine This section applies only to the Kameleoon Web Experimentation solution. Kameleoon's engine works differently depending on its mode of operation. To determine the operational mode, Kameleoon considers the current value of the consent in `Kameleoon.API.Visitor.experimentLegalConsent` and `Kameleoon.API.Visitor.personalizationLegalConsent`. If the value is is **null** (unknown state) or **false**, Kameleoon takes into account the corresponding configuration value in the [project settings](/user-manual/project-management/consent-management-policy#behavior-when-consent-is-unknown) for the **Behavior when consent is unknown** or **Behavior in case of opt-out**. The following section describes all possible modes for the engine: #### Active mode This is the normal mode of operation. Kameleoon sends data to tracking servers and writes data to the device as needed. The visitor is supposed to have granted consent to the use of Kameleoon. This mode is operational when `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` is **true** #### Disabled mode In this mode, Kameleoon does nothing at all - data is neither sent out to remote servers nor written to the local device. Experiments and personalizations are never displayed. For all purposes, it's as if Kameleoon did not exist for this particular visitor. This mode is operational when: * `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` is **null**, and you selected the option "Completely block Kameleoon" when [consent is unknown](/user-manual/project-management/consent-management-policy#behavior-when-consent-is-unknown). * `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` is **false**, and the customer selected "Completely block Kameleoon" for [the opt-out behavior](/user-manual/project-management/consent-management-policy#behavior-on-opt-out). #### Delayed mode In this mode, Kameleoon does not send any data to tracking servers nor write any data to the device. However, it will still display experiments (or personalizations) normally, if the visitor triggers the associated segment. In addition, every data that should be written or sent to a remote server is kept in memory. If later Kameleoon switches to the active mode (usually because the consent is granted at some point), all the data gathered so far (in the context of this page) is written and sent at once. Thus the "delayed" name: if the full consent is obtained at some point, the end result will be that Kameleoon behaved almost like in active mode. This mode is operational when `Kameleoon.API.Visitor.experimentLegalConsent` / `Kameleoon.API.Visitor.personalizationLegalConsent` is **null**, and you selected the option "Do not block Kameleoon" when [consent is unknown](/user-manual/project-management/consent-management-policy#behavior-when-consent-is-unknown). #### Restricted mode In this mode, Kameleoon does not send any data to tracking servers nor write any data to the device. However, it will still display experiments (or personalizations) that have been flagged with the "Technical" tag in Kameleoon. All other experiments / personalizations are not displayed. This is a very useful mode that allows most Kameleoon operations to be disabled if a visitor does not want to grant consent, but that still permits critical experiments and personalizations to run for this visitor. Very often, Kameleoon is used as a quick workaround for deploying bugfixes and small improvements to production. In this particular context, data privacy regulations such as GDPR explicitely do not apply, and Kameleoon can be used without consent for such use cases. The restricted mode is equivalent to the delayed mode, but more restrictive. Since it can be chosen as a result of a final legal consent choice (a denial), it is expected that data will probably never be written nor sent in this case (whereas the delayed mode is normally only a "transitional" mode). This mode is operational when: * `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` is **null**, and you selected the option "Partially block Kameleoon" when [consent is unknown](/user-manual/project-management/consent-management-policy#behavior-when-consent-is-unknown). * `Kameleoon.API.Visitor.experimentLegalConsent` or `Kameleoon.API.Visitor.personalizationLegalConsent` is **false**, you selected the option "Partially block Kameleoon" for [the opt-out behavior](/user-manual/project-management/consent-management-policy#behavior-on-opt-out). It is very possible that Kameleoon will have different operational modes for AB experiments and personalizations. In that case, everything works as expected. For instance, experiments are displayed and tracking data, but nothing happens for personalizations. ## Advanced considerations Choosing to implement an explicit consent policy in the context of AB testing usually leads to complex issues, and there is no ideal solution. The two main problems are described here. The first thing to take into account is that if you choose a strict behavior (**disabled mode**) while consent is unknown, experiments / personalizations will be triggered "late" in the context of the first page view of a totally new visitor. With this setup, they can indeed be displayed only after the legal consent has been obtained, which is typically after a few seconds. This can unfortunately lead to some serious UX issues, depending on the underlying experiments and personalizations, and should be taken into account. The **delayed mode** can mitigate this problem, as display is immediate and only writing is delayed. However, there is a second issue arising when a visitor "stays" with an unknown legal consent status over several pages. Since Kameleoon does not write any data to the device, not even the Kameleoon **visitorCode** identifier, it keeps being randomly (re)generated on each page while final consent is not determined. This implies that for experiments, variation allocation will happen at each new page view, and no consistency is guaranteed in those allocations. It is thus important to take steps to prevent this situation from happening. Several options are possible (blocking consent banner, denying consent automatically after X seconds...), but a visitor should never stay in the unknown legal consent status forever. Either consent is granted, or it is denied. For the same technical reason (permanent variation reallocation), if the **restricted mode** is chosen for opt-out behavior, it is expected than only personalizations or experiments with a 100% traffic deviation to a variation will be flagged with the "Technical" tag. Doing otherwise can seriously deteriorate the navigational experience of users that refused, as they could be subject to several different variations in the same session. # Data collection (backend) Source: https://docs.kameleoon.com/developer-docs/privacy-and-compliance/data-collection-backend Reference guide for all data stored on Kameleoon back-end servers, covering analytics data, synchronization fields, and retention behavior. If you want to learn which data is stored **client-side**, in the visitor's browser, refer to the [data collection client-side](./data-collection-client-side) article. This article primarily applies to **Kameleoon Web Experimentation** or when using [Kameleoon Hybrid Experimentation mode](../feature-experimentation/targeting-and-segmentation/native-segmentation#hybrid-experimentation-mode). If you are only using Kameleoon's SDKs (Feature Experimentation), **no data will be collected** except: * Data that is **explicitly sent** using the `addData` method of the SDK. * All **exposure events** related to feature flag evaluations. ## List of collected data The following data is collected and stored on Kameleoon back-end servers for each visit made by an individual visitor to your website: * **Visitor code** (a unique Kameleoon identifier for this visitor) * **Visit number** (if multiple visits were made by the same visitor) * **Device type** (mobile, tablet, or desktop) * **Operating system** (Windows, macOS, Linux) * **Browser name and version** * **Screen size** * **Window size** * **Browser time zone** * **Browser language** * **Original referrer** (acquisition channel) * **Number of pages viewed** * **Time elapsed since the visitor's previous visit** (if any) * **Time spent on the website** * **Start and end time of the visit** * **Number of mouse clicks** * **Ad blocker detection** (enabled/disabled) * **List of conversions** (for example, clicks, transactions), including revenue (if applicable) * **List of personalizations and A/B experiments** seen by the visitor * **List of targeted Kameleoon segments** (requires [Audiences Explorer](/user-manual/ai-opportunity-detection/audiences-segment-analysis) * **Current weather conditions** (if the corresponding targeting condition is activated): temperature, wind, rain, and other conditions. * **Sunset and sunrise times** (required for some weather-based targeting criteria) * **Geolocation** (only if a corresponding targeting condition is activated, as some weather-related criteria require geolocation data) * **Products viewed** (only if the add-on is enabled) * **Titles and URLs of pages visited** * **Custom data** * **External segmentation data** (obtained from a third-party DMP or CRM) * **Conversion probabilities** computed by the **Kameleoon AI Predictive targeting engine** (only when at least one AI-driven personalization is active; required for generating Kameleoon Conversion Score heat ranges) By default, **Kameleoon does NOT collect any personal information** as defined by **GDPR** or similar data privacy laws. However, personal data may still be collected and stored on Kameleoon back-end servers in the following cases: * Your URLs contain parameters with personal data, as Kameleoon collects visited URLs. Kameleoon strongly recommends removing such parameters from your URLs, as they are collected by all web analytics platforms. * You explicitly define a [custom data](../custom-data) field and populate it with personal data. * You explicitly configure an integration with a third-party DMP or CRM that provides personal data. Kameleoon will never store any IP addresses on its data servers. There are no exceptions to this rule: even when legal consent has been provided by a visitor, IP addresses are not saved. ## Data retention policy Kameleoon retains visit and product event data for **24 months** for analytics purposes. The retention period begins when the first event is processed. If you require a shorter retention period, please contact your Customer Success Manager. * [Cross-Device Experimentation](../cross-device-experimentation): 2 months of prior data can be retrieved for targeting in Web Experimentation and Feature Experimentation. * [ITP Management](../web-experimentation/technical-concepts/itp-management): 2 months of prior data can be retrieved for targeting in Web Experimentation and Feature Experimentation. * Remote Synchronization of Visits: 2 months of prior data can be retrieved for targeting in Web Experimentation and Feature Experimentation. * AI Predictive Targeting: 2 months of prior data are used to compute Kameleoon Conversion Scores. * Product Targeting: 2 months of prior data can be retrieved for targeting in Web Experimentation. * Data API MAP Endpoint: 1 year of prior data can be retrieved in Web Experimentation and Feature Experimentation. # Data collection (client-side) Source: https://docs.kameleoon.com/developer-docs/privacy-and-compliance/data-collection-client-side Reference guide for all data collected client-side by the Kameleoon application file and SDKs, including cookies, local storage, and session data. To learn which data Kameleoon stores on its backend servers—primarily for analytics and reporting—read the [data collection back-end](./data-collection-backend) article. ## Introduction Like any web analytics solution, Kameleoon collects data such as URLs visited, browser version, and time spent on your website. As an experimentation and personalization platform, Kameleoon also requires real-time, persistent access to this data to trigger experiments and personalization campaigns with minimal latency. Because the JavaScript runtime environment resets on each page change, storing this data in memory alone is not possible. Instead, Kameleoon reads and writes data across different web pages (URLs). Fetching data from a backend server at the start of each page load is one alternative, but this approach has two drawbacks: * **Performance cost**: Each page load incurs an extra server call. * **Scalability cost**: Frequent server requests degrade performance at scale. Local storage is a more efficient approach. It lets Kameleoon store data directly in the browser. ### Local storage as a data storage mechanism Kameleoon does not use cookies to store data for two reasons: * Cookies are not designed to store large amounts of data. * The browser sends cookies with every HTTP request, including requests for static resources such as images, which increases bandwidth usage and reduces performance. Instead, Kameleoon uses Local Storage, a standard web technology that most browsers support. It works similarly to cookies but supports much larger storage capacity (typically a few MBs, depending on the browser). Compared to cookies, Local Storage has the following properties: * Only JavaScript can write to Local Storage—servers cannot. * Only JavaScript can read data from Local Storage. * The browser never sends Local Storage data to remote HTTP servers, which makes it more secure. However, Local Storage has one key limitation: it is scoped to a single exact subdomain. ### Local storage limitations for unified session data Unlike cookies—where a script on `http://www.example.com` can create a cookie accessible from `buy.example.com`—Local Storage is partitioned by subdomain and protocol. For example, if your e-commerce website is hosted on `https://www.randomshop.com` but your conversion funnel runs on `https://transaction.randomshop.com`, Local Storage cannot share data between these two subdomains. This can produce inconsistent experiment variations across the customer journey, affecting both user experience and experiment validity. Kameleoon provides a mechanism to unify session data across subdomains. For details, see [Unify session data across subdomains](../web-experimentation/technical-concepts/unify-session-data-storage-across-subdomains). ## List of collected data This page covers two categories of data: 1. **Data collected and stored for all visitors** ([excluding visitors who opted out](./consent-management)). 2. **Data collected for internal use** when Kameleoon users build experiments on the platform. For data privacy purposes, focus on the **first category**. The second category applies only to a small number of internal employees. Kameleoon always stores data in a **first-party** context, associated with your domain. Kameleoon **never** stores third-party data on Kameleoon-owned domains. ## Data stored for all visitors ### Data stored in Local Storage Local Storage does not have a built-in expiration mechanism. To work around this, Kameleoon emulates lifespan expiration by storing data with two fields: * `"value"`: The stored information. * `"expirationDate"`: A timestamp that defines when Kameleoon stops using the data. Example: ``` kameleoonLegalConsent: {"value": {"AB_TESTING": true, "PERSONALIZATION": true},"expirationDate":1545297630228} ``` | **Key** | **Description** | **Lifespan** | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `kameleoonVisitorCode` | Unique Kameleoon **visitorCode** identifier. | 365 days | | `kameleoonData` | Visitor and session data (for example, browsing history and session info). Kameleoon stores this data in Local Storage using a simple encoding scheme. The full list of data this key contains appears below. | 365 days | | `kameleoonExperiment-${experimentId}` | Experiment variation allocation and assigned variation ID (either "Reference", the variation ID, or "none" if traffic exclusion is configured), and the date Kameleoon assigned the variation. | 30 days (renews on revisit) | | `kameleoonPersonalization-${personalizationId}` | The personalization variation allocation for this visitor. | 30 days (renews on revisit) | | `kameleoonGlobalPersonalizationExposition` | The visitor's global exposition status for all personalizations. A value of `"false"` prevents this visitor from seeing any personalization. A value of `"true"` allows exposure when the visitor meets the required targeting conditions. By default, no global traffic exclusion applies. You can configure this value in the [project configuration settings](/user-manual/project-management/manage-your-projects#Configuration). | 365 days | | `kameleoonLegalConsent` | Legal consent for the use of Kameleoon. The value format is, for example, `{"AB_TESTING": true, "PERSONALIZATION": false}` or `{"PERSONALIZATION": true}`. | 365 days | | `kameleoonOpenTabs` | IDs of open tabs on the same website. | 365 days | | `KameleoonProducts_device_id` | A unique device ID for the visitor. Kameleoon only stores this key if you have the Product Recommendation add-on. | 365 days | | `kameleoonRequestEvents` | Failed requests that Kameleoon cannot send when the page reloads immediately. This behavior is specific to Safari. | Deleted once the page reloads. | The `kameleoonData` key stores the following data for each visit (on this device only, or all visits if you use [cross-device history reconciliation](../cross-device-experimentation)): * Custom data * Device type (mobile, tablet, or desktop) * Operating system * Browser name and version * Screen size * Window size * Browser time zone * Browser language * Original referrer (acquisition channel) * Number of pages viewed * Title and URL of pages visited * Time spent on the website * Visit start and end time * Number of open tabs * Whether an ad blocker is active * List of conversions (clicks, transactions, and other events) * List of personalizations and A/B experiments the visitor saw * Current weather conditions (if the targeting condition is active): temperature, wind, rain, and other conditions * Sunset time (if a weather targeting condition is active, because some weather criteria require it) * Weather forecast (if the targeting condition is active): temperature, wind, rain, and other conditions * Geolocation (if a geolocation or weather targeting condition is active) * IP address (if the targeting condition is active) * External segmentation data from a third-party DMP or CRM * Products seen (if the Product Targeting add-on is enabled) | **Key** | **Description** | **Lifespan** | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `kameleoonClientData` | A local copy of the remote configuration file the SDK fetches. It contains configuration details for live feature flags. | Until removed manually or by browser settings. | | `kameleoonConsentData` | Legal consent for Feature Experiments obtained from the visitor. | [targetingDataCleanupInterval](../sdks/web-sdks/js-sdk#configuration-parameters) | | `kameleoonDataInfo` | Technical information for data cleanup and linked visitor sessions. Required for [cross-device experimentation](../sdks/web-sdks/js-sdk#cross-device-experimentation). | Until a tracking request succeeds. | | `kameleoonForcedExperimentVariation` | A specific experiment variation required for custom evaluation of a given feature flag. | [targetingDataCleanupInterval](../sdks/web-sdks/js-sdk#configuration-parameters) | | `kameleoonForcedFeatureVariation` | Specific feature variations used for simulation of a given feature flag. | Session | | `kameleoonTargetingData` | Visitor and session data, including browsing history and session details, required for targeting purposes. | [targetingDataCleanupInterval](../sdks/web-sdks/js-sdk#configuration-parameters) | | `kameleoonTrackingData` | All **visitor codes** on this browser for tracking requests. | Until a tracking request succeeds. | | `kameleoonVariationData` | Assigned variations for each feature flag, used to maintain visitor consistency across sessions. | [targetingDataCleanupInterval](../sdks/web-sdks/js-sdk#configuration-parameters) | The `kameleoonTargetingData` key stores the following data associated with the visitor: * Custom data * Conversion * Page view * Unique identifier * Geolocation data * User agent * Browser * Device * Cookie * Kameleoon conversion score (AI Predictive Targeting) * Operating system * Application version * Visits data * Personalization #### Data stored in Session Storage | **Key** | **Description** | **Lifespan** | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | | `kameleoonDisabledForVisit` | Disables Kameleoon for the current visit. Kameleoon sets this key when you enable the [Disable Kameleoon for the entire visit](/user-manual/project-management/manage-your-projects#Configuration) timeout option. | Session duration | | `kameleoonAnalyticsTrackingTimes` | Optimizes the number of third-party analytics tracking calls Kameleoon makes when you use a web analytics integration. | Session duration | | `kameleoonTabId` | A unique 8-digit identifier that distinguishes a browser tab and prevents redundant tracking of the same page URL across multiple triggered events. | Session duration | | `kameleoonActiveScript` | Kameleoon sets this value when the script confirms it is installed on the page. | Session storage value | #### Data stored in cookies Kameleoon uses **only one cookie** to store a randomly generated **visitor identifier**. The **Kameleoon CDN** may also set an optional cookie when it delivers the application file, unless you [self-host the application file](../web-experimentation/self-hosting/simple-self-hosting-and-on-premises-options#application-file-self-hosting). If you use the Kameleoon Product Recommendation add-on, Kameleoon stores two additional cookies. You can contact your Customer Success Manager to enable a cookieless option that stores these data points in Local Storage instead. | **Key** | **Description** | **Lifespan** | | ------------------------------------ | ----------------------------------- | ---------------- | | `KameleoonProducts_session_code` | A unique session identifier. | Session duration | | `KameleoonProducts_session_last_act` | The timestamp of the last activity. | 1 hour | | **Key** | **Description** | **Lifespan** | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `_detectRootDomain` | Temporary test cookie (value=`testRootDomain`) that Kameleoon uses to detect the main domain of the site. | Deleted immediately after use. | | `kameleoonVisitorCode` | The unique Kameleoon **visitorCode** identifier. | 365 days | | `kameleoonReferrer` | Temporary cookie for redirect URL experiments. Kameleoon stores the `document.referrer` value before the redirect occurs. | Deleted immediately after use. | | `kameleoonSimulationFFData` | Data required to simulate a specific variation of a feature flag. | Session | ### Temporary data stored only for Kameleoon internal use #### Data stored in cookies | **Key** | **Description** | **Lifespan** | | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | `kameleoonSimulationParameters` | Kameleoon Simulation Mode uses this cookie to enable simulation across subdomains. Without it, simulation does not work correctly if your site has subdomains. All simulation query parameters must be included in URL-encoded JSON format using `JSON.stringify`. For example: | | | `KEY - kameleoonSimulationParameters`, `VALUE - {"kameleoon-experiment-id":"266118","kameleoon-simulation":"true","kameleoon-language":"en"}` | 1 hour, but deleted immediately when the simulation is closed. | | #### Data stored in Local Storage | **Key** | **Description** | **Lifespan** | | -------------------------------- | ---------------------------------------------------------------------------------- | ------------ | | `kameleoonSimulation` | Data required for simulation purposes. | 1 hour | | `kameleoonSimulationShortURL` | The short URL of the simulation. | 1 hour | | `kameleoonSimulationVisitorData` | All virtual data about the visitor and their visits, used for simulation purposes. | 1 hour | #### Data stored in Session Storage | **Key** | **Description** | **Lifespan** | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `kameleoonFullApplicationCode` | The entire Kameleoon codebase, required for simulation. The production application file is size-optimized and does not include all code, so Kameleoon stores the full version here. | 1 hour | | `kameleoonVariation-${variationId}` | Variation code and data for preview or simulation purposes. | 1 hour | | `kameleoonRedirectionURL` | The absolute URL of the page when Kameleoon triggers a redirect experiment. Kameleoon stores this to prevent infinite redirect loops. | Removed when the visitor navigates to a different page. | # Android SDK Source: https://docs.kameleoon.com/developer-docs/sdks/mobile-sdks/android-sdk Integrate the Kameleoon Android SDK to run experiments and activate feature flags in native Android applications. With the Kameleoon Android SDK, you can run feature flags on native mobile Android applications. The Android SDK is compatible with both Kotlin and Java. The SDK is easy to integrate into your applications, and its memory and network usage are low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Android SDK: 4.27.0 [Changelog](https://github.com/Kameleoon/client-android/blob/master/CHANGELOG.md) **SDK methods**: For the full reference documentation of the Android SDK methods, see the [reference](#reference) section. ## Developer guide Follow this section to install and configure the Android SDK in your Android app and learn about advanced features. ### Getting started Follow these steps to install and configure the Kameleoon Android SDK in your app. #### Installation You can install the Android SDK by adding the following dependency to the `build.gradle` file in your Android app: ```java theme={null} dependencies { implementation 'com.kameleoon:kameleoon-client-android:4.20.0' } ``` #### Additional configuration To customize the SDK's behavior, create a `.properties` configuration file. The properties file's name and location are important: * Create the file in your app's `assets/` directory. * Name the file `kameleoon-client.properties`. You can also [download a sample configuration](/assets/developer-docs/sdks/mobile-sdks/client-configs/kameleoon-client.properties.zip) file. These are the available properties you can set: | Key | Description | Default value | | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `refreshIntervalMinute` / `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, for the SDK to fetch the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments. If left unspecified, the default interval is 60 minutes. Additionally, a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) is available that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply them in real-time. | `60` minutes | | `dataExpirationIntervalMinute` / `data_expiration_interval_minute` optional | Designates the predefined time period, in minutes, that the SDK stores the visitor and their associated data. Each data instance is evaluated individually, allowing you to set the amount of time the SDK saves data before automatically deleting it. If no interval is specified, the SDK doesn't automatically delete data from the device. | `Integer.MAX_VALUE` | | `defaultTimeoutMillisecond` / `default_timeout_millisecond` optional | Specifies the time interval, in milliseconds, that it takes for network requests from the SDK to time out. Set the value to `30000` milliseconds (30 seconds) or more if you don't have a stable connection. Some methods have additional parameters for method-specific timeouts, but if you don't specify them explicitly, the default value is used. | `10000` milliseconds | | `trackingIntervalMillisecond` / `tracking_interval_millisecond` optional | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had data flushed will be included in this tracking request, which is performed once per interval. The minimum value is `1000` ms and the maximum value is `5000` ms. | `1000` ms | | `environment` / `environment` optional | For customers using multi-environment experimentation and feature flagging, this option specifies which feature flag configuration to use. By default, each feature flag has the options `production`, `staging`, and `development`. If not specified, the default value is `production`. [More information](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments). | `nil` | | `isUniqueIdentifier` / `is_unique_identifier` optional | Indicates that the specified `visitorCode` is a unique identifier. | `false` | | `networkDomain` / `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `nil` | | `defaultDataFile` / `default_datafile` optional | The `default_datafile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `default_datafile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `nil` | | `activityTrackingIntervalMillisecond` / `activity_tracking_interval_millisecond` optional | Sets how often the SDK sends an activity event to extend the visitor's session. Both the minimum and the default are `60 000` ms; any lower non-zero value is ignored and the default is applied instead. Set it to `0` to turn off periodic activity tracking, in which case a single activity event is sent at startup. Changing this value has side effects, so review [this section](#using-activitytrackingintervalmillisecond) first. | `60 000` ms | If you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK methods use the `visitorCode` value as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). The SDK links the flushed data to the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor through session merging. ##### Using `activityTrackingIntervalMillisecond` The `activityTrackingIntervalMillisecond` parameter controls how often the SDK sends an activity event to extend the visitor's session on the Data API. A longer interval reduces network usage and battery consumption. The default and minimum value is `60 000` ms (60 seconds); any lower non-zero value is ignored and the default is applied instead. Timers pause while the app is in the background, so the interval only advances while the app is in the foreground. Increasing this value has **significant side effects** you should weigh carefully: 1. **Visits with zero duration** * Reports derive visit duration from tracked events. If the interval is longer than the time a visitor spends in the app, the SDK may send only the startup event. The visit's start and last-activity timestamps are then (nearly) identical, so the report usually shows a duration close to zero. You can avoid this by sending other events during the session (such as conversions or page views), which also extend the recorded duration. 2. **Time elapsed since last visit (targeting)** * The "time elapsed since last visit" condition compares the current time against a previous visit's last recorded activity. That timestamp only refreshes once per interval, so its precision degrades as the interval grows: comparisons for durations close to or below the interval become unreliable. 3. **[Visit count](/user-manual/experiment-analytics/troubleshooting/data-discrepancies#how-visits-and-visitors-are-counted)** * A new visit starts after 30 minutes of inactivity. If you set the interval longer than 30 minutes, every activity event arrives after the previous session has already expired, so the SDK creates a new visit at each interval and inflates the visit count. Setting `activityTrackingIntervalMillisecond` to `0` disables periodic activity tracking entirely. In that case, the SDK sends only a single activity event at app startup. #### Initialize the Kameleoon client After installing the SDK in your app and setting up the app properties, you must create the Kameleoon Client. A Client is a singleton object that acts as a bridge between your app and the Kameleoon platform. It includes all of the methods and properties you need to run a feature flag. ```java theme={null} import com.kameleoon.KameleoonClient; import com.kameleoon.KameleoonClientConfig; import com.kameleoon.KameleoonClientFactory; import com.kameleoon.KameleoonException; public class MyApplication extends Application { private KameleoonClient kameleoonClient; @Override public void onCreate() { super.onCreate(); try { KameleoonClientConfig config = new KameleoonClientConfig.Builder() .refreshIntervalMinute(15) // in minutes, 1 hour by default, optional .defaultTimeoutMillisecond(10_000) // in milliseconds, 10 seconds by default, optional .trackingIntervalMillisecond(1000) // in milliseconds, 1000 ms by default, optional .dataExpirationIntervalMinute(1440 * 365) // in minutes, infinity by default, optional .environment("staging") // optional .isUniqueIdentifier(false) // optional, false by default. Set to true if the visitorCode corresponds to your customer's unique userId. .networkDomain("example.com") // optional .defaultDataFile("{...}") // optional .activityTrackingIntervalMillisecond(20_000) // optional, 15_000 milliseconds by default .build(); String siteCode = "a8st4f59bj"; String visitorCode = "yourVisitorCode"; kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode, config, getApplicationContext()); // or, if you want, the visitor code will be generated automatically kameleoonClient = KameleoonClientFactory.create(siteCode, config, getApplicationContext()); } catch (KameleoonException.SiteCodeIsEmpty | KameleoonException.VisitorCodeInvalid exception) { // Exceptions indicate that provided siteCode is empty or visitorCode is invalid } catch (Exception exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } } public KameleoonClient getKameleoonClient() { return kameleoonClient; } } ``` ```kotlin theme={null} import com.kameleoon.KameleoonClientConfig import com.kameleoon.KameleoonClientFactory import com.kameleoon.KameleoonException class MyApplication : Application() { var kameleoonClient: KameleoonClient? = null private set override fun onCreate() { super.onCreate() try { val config = KameleoonClientConfig.Builder() .refreshIntervalMinute(15) // in minutes, 1 hour by default, optional .defaultTimeoutMillisecond(10_000) // in milliseconds, 10 seconds by default, optional .trackingIntervalMillisecond(1000) // in milliseconds, 1000 ms by default, optional .dataExpirationIntervalMinute(1440 * 365) // in minutes, infinity by default, optional .environment("staging") // optional .networkDomain("example.com") // optional .defaultDataFile("{...}") // optional .activityTrackingIntervalMillisecond(20_000) // optional, 15_000 milliseconds by default .build() val siteCode = "a8st4f59bj" val visitorCode = "yourVisitorCode" val kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode, config, applicationContext) // or if you want that visitor code will be generated automatically kameleoonClient = KameleoonClientFactory.create(siteCode, config, applicationContext) } catch (e: KameleoonException.SiteCodeIsEmpty) { // Exception indicating that provided siteCode is empty } catch (e: KameleoonException.VisitorCodeInvalid) { // Exception indicating that provided visitor code is invalid } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } } } ``` During `KameleoonClientFactory.create()`, the client starts initialization but isn't immediately ready for use. The Kameleoon Client must retrieve the current feature flag configuration, including traffic allocation, from a Kameleoon remote server. This requires network access, which isn't always available. Until the Kameleoon Client is ready, you shouldn't run SDK methods that depend on feature flag configuration. After the first feature flag configuration is fetched, the client refreshes it periodically. If a later refresh fails, the Kameleoon client continues to use the previous configuration. You can use [`isReady()`](#isready) to check whether the Kameleoon client initialization is complete. Alternatively, a **helper callback** can wrap the logic for triggering feature flags and applying variations. The best approach ([`isReady()`](#isready) or **callback**) depends on the use case. Use [`isReady()`](#isready) when the SDK is expected to be ready soon, such as for a feature flag in a dialog that users are unlikely to open during the first few seconds or minutes of app navigation. Use a callback when the SDK is likely to still be initializing, such as for a feature flag shown at app launch. It's your responsibility as the app developer to ensure the logic of your app code is correct within the context of A/B testing using Kameleoon. A good practice is to always assume that the app user can be left out of the feature flag when the Kameleoon client isn't yet ready. This exclusion is easy to implement, because this corresponds to the implementation of the default or reference variation logic. The code samples in the next paragraph show examples of this approach. You're now ready to implement feature management and feature flags. See the [Reference](#reference) section for details about additional methods. #### Best practices for initialization and usage * Initializing [`KameleoonClient`](#create) as a singleton as early as possible after the app starts is recommended, as initialization may take some time. Since initialization is asynchronous, it doesn't block or delay the app startup process. * Before using `KameleoonClient`, verify that it's initialized by calling the [`runWhenReady`](#runwhenready) method. Otherwise, attempts to use the client before it's ready will result in errors. * ⚠️ Most key methods may throw exceptions, so proper exception handling is required. Be sure to review the documentation for each method you use to understand its potential exceptions. ```java theme={null} // Initialize `KameleoonClient` on application startup and use it as a singleton later try { KameleoonClient kameleoonClient = KameleoonClientFactory.create("", getApplicationContext()); } catch (KameleoonException ignored) {} // Example: Apply a discount percentage based on a feature flag variable's value void applyDiscountIfApplicable() { kameleoonClient.runWhenReady(1000, result -> { double discount = 0.0; try { if (result.getOrThrow()) { Variation variation = kameleoonClient.getVariation("discount"); discount = (double) variation.getVariables().get("discount_value").getValue(); } } catch (Exception ignored) { } if (discount > 0) { applyDiscount(discount); } }); } ``` ```kotlin theme={null} // Initialize `KameleoonClient` on application startup and use it as a singleton later try { KameleoonClient kameleoonClient = KameleoonClientFactory.create("", applicationContext) } catch (ignored: KameleoonException) {} // Example: Apply a discount percentage based on a feature flag variable's value fun applyDiscountIfApplicable() { kameleoonClient.runWhenReady(1000) { result -> val discount = runCatching { if (result.getOrThrow()) { val variation = kameleoonClient.getVariation("discount") variation.variables["discount_value"]?.value as? Double } else { null } }.getOrNull() ?: 0.0 if (discount > 0) { applyDiscount(discount) } } } ``` ```kotlin theme={null} // Initialize `KameleoonClient` on application startup and use it as a singleton later try { val kameleoonClient = KameleoonClientFactory.create("", applicationContext) } catch (ignored: KameleoonException) {} // Example: Apply a discount percentage based on a feature flag variable's value suspend fun applyDiscountIfApplicable() { kameleoonClient.runWhenReady(1000).getOrNull() ?: return // Exit if initialization fails val discount = runCatching { val variation = kameleoonClient.getVariation("discount") variation.variables["discount_value"]?.value as? Double }.getOrNull() ?: 0.0 if (discount > 0) { applyDiscount(discount) } } ``` #### Activating a feature flag ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices, use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `goalId` parameter. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```java title="Device A" theme={null} // In this, example Custom data with index `90` was set to "Visitor" scope in Kameleoon. final int VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90; kameleoonClient.addData(new CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")); kameleoonClient.flush(); ``` ```java title="Device B" theme={null} // Before working with the data, call `getRemoteVisitorData`. kameleoonClient.getRemoteVisitorData(result -> { // After calling, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available to target and track the visitor. }); ``` ```kotlin title="Device A" theme={null} // In this example Custom data with index `90` was set to "Visitor" scope on Kameleoon Platform. val VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90 kameleoonClient.addData(CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")) kameleoonClient.flush() ``` ```kotlin title="Device B" theme={null} // Before working with the data, call the `getRemoteVisitorData` method. kameleoonClient.getRemoteVisitorData { result -> // After that the SDK on Device B will have an access to CustomData of Visitor scope defined on Device A. // So "your data" will be available for targeting and tracking for the visitor. } ``` ```kotlin title="Device A" theme={null} // In this example Custom data with index `90` was set to "Visitor" scope on Kameleoon Platform. val VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90 kameleoonClient.addData(CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")) kameleoonClient.flush() ``` ```kotlin title="Device B" theme={null} // Before working with the data, call the `getRemoteVisitorData` method. kameleoonClient.getRemoteVisitorData() // After that the SDK on Device B will have an access to CustomData of Visitor scope defined on Device A. // So "your data" will be available for targeting and tracking for the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`getRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `getRemoteVisitorData()` with passed `isUniqueIdentifier=true` to [`KameleoonClientConfig`](#additional-configuration) - to retrieve data for all linked visitors. * [`trackConversion()`](#trackconversion) or [`flush()`](#flush) with passed `isUniqueIdentifier=true` to `KameleoonClientConfig` - to track some data for specific visitor that is associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```java theme={null} // In this example, `91` represents the Custom Data's index, // configured as a unique identifier in Kameleoon. final int MAPPING_INDEX = 91; final String FEATURE_KEY = "ff123"; // 0. Initializing anonymous KameleoonClient // Assume `anonymousVisitorCode` is the randomly generated ID for that visitor. KameleoonClient anonymousKameleoonClient = KameleoonClientFactory.create(siteCode, anonymousVisitorCode, getApplicationContext()); anonymousKameleoonClient.runWhenReady(result -> { // ... }); // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. Variation anonymousVariation = anonymousKameleoonClient.getVariation(FEATURE_KEY); // 2. After the visitor is authenticated // Assume `userId` is the authenticated visitor's visitor code. anonymousKameleoonClient.addData(new CustomData(MAPPING_INDEX, userId)); anonymousKameleoonClient.flush(true); KameleoonClient userKameleoonClient = KameleoonClientFactory.create( siteCode, userId, (new KameleoonClientConfig.Builder()) .isUniqueIdentifier(true) // Indicate that `userId` is a unique identifier .build(), getApplicationContext() ); userKameleoonClient.runWhenReady(result -> { // ... }); // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. Variation userVariation = userKameleoonClient.getVariation(FEATURE_KEY); boolean isSameVariation = userVariation.getKey().equals(anonymousVariation.getKey()); // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. kameleoonClient.trackConversion(123, 10.0f); // Additionally, the linked visitors will share all fetched remote visitor data. kameleoonClient.getRemoteVisitorData(result -> { // ... }); ``` ```kotlin theme={null} // In this example, `91` represents the Custom Data's index // configured as a unique identifier in Kameleoon. val MAPPING_INDEX = 91 val FEATURE_KEY = "ff123" // 0. Initializing anonymous KameleoonClient // Assume `anonymousVisitorCode` is the randomly generated ID for that visitor. val anonymousKameleoonClient = KameleoonClientFactory.create(siteCode, anonymousVisitorCode, applicationContext) anonymousKameleoonClient.runWhenReady { result -> // ... } // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. val anonymousVariation = anonymousKameleoonClient.getVariation(FEATURE_KEY) // 2. After the visitor is authenticated // Assume `userId` is the authenticated visitor's visitor code. anonymousKameleoonClient.addData(CustomData(MAPPING_INDEX, userId)) anonymousKameleoonClient.flush(true) val userKameleoonClient = KameleoonClientFactory.create( siteCode, userId, KameleoonClientConfig.Builder() .isUniqueIdentifier(true) // Indicate that `userId` is a unique identifier .build(), applicationContext ) userKameleoonClient.runWhenReady { result -> // ... } // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. val userVariation = userKameleoonClient.getVariation(FEATURE_KEY) val isSameVariation = userVariation.getKey() == anonymousVariation.getKey() // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. userKameleoonClient.trackConversion(123, 10.0f) // Additionally, the linked visitors will share all fetched remote visitor data. userKameleoonClient.getRemoteVisitorData { result -> // ... } ``` ```kotlin theme={null} // In this example, `91` represents the Custom Data's index // configured as a unique identifier in Kameleoon. val MAPPING_INDEX = 91 val FEATURE_KEY = "ff123" // 0. Initializing anonymous KameleoonClient // Assume `anonymousVisitorCode` is the randomly generated ID for that visitor. val anonymousKameleoonClient = KameleoonClientFactory.create(siteCode, anonymousVisitorCode, applicationContext) anonymousKameleoonClient.runWhenReady() // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. val anonymousVariation = anonymousKameleoonClient.getVariation(FEATURE_KEY) // 2. After the visitor is authenticated // Assume `userId` is the authenticated visitor's visitor code. anonymousKameleoonClient.addData(CustomData(MAPPING_INDEX, userId)) anonymousKameleoonClient.flush(true) val userKameleoonClient = KameleoonClientFactory.create( siteCode, userId, KameleoonClientConfig.Builder() .isUniqueIdentifier(true) // Indicate that `userId` is a unique identifier .build(), applicationContext ) userKameleoonClient.runWhenReady() // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. val userVariation = userKameleoonClient.getVariation(FEATURE_KEY) val isSameVariation = userVariation.getKey() == anonymousVariation.getKey() // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. userKameleoonClient.trackConversion(123, 10.0f) // Additionally, the linked visitors will share all fetched remote visitor data. userKameleoonClient.getRemoteVisitorData() ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor automatically generated by the SDK is used. The visitor code can be retrieved with the [`getVisitorCode()`](#getvisitorcode) method. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```java theme={null} kameleoonClient.addData(new CustomData(index, "newVisitorCode")); ``` ```kotlin theme={null} kameleoonClient.addData(CustomData(index, "newVisitorCode")) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Error handling All methods of the **Kameleoon SDK** can **only** throw `KameleoonException` or its documented inherited exceptions (listed in the *Exceptions Thrown* section for each method). These exceptions are **expected behavior** of the SDK. If you want to handle specific scenarios differently, you can catch individual inherited exceptions; otherwise, catching `KameleoonException` will handle all SDK‑related errors. Although the SDK's **unit and integration tests** confirm that it **never throws** `Exception` or `RuntimeException`, **patching SDK versions on Android can be difficult**, and unexpected issues may arise from **third‑party libraries** that could throw a `RuntimeException`. To prevent your app from crashing in such rare cases, also **catch `Exception` (or `RuntimeException`)** as an additional safeguard. This is strictly a precaution and **not an expected behavior of the SDK**. For example: ```java theme={null} try { // Calling a method of the SDK } catch (KameleoonException e) { // Handling expected exceptions } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ```kotlin theme={null} try { // Calling a method of the SDK } catch (e: KameleoonException) { // Handling expected exceptions } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```java theme={null} // The `NONE` log level does not allow logging. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.WARNING); // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG); ``` ```kotlin theme={null} // The `NONE` log level allows no logging. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.NONE) // The `ERROR` log level allows to log only issues that may affect the SDK's main behaviour. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.ERROR) // The `WARNING` log level allows to log issues which may require an attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.WARNING) // The `INFO` log level allows to log general information on the SDK's internal processes. // It extends the `WARNING` log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.INFO) // The `DEBUG` log level allows to log extra information on the SDK's internal processes. // It extends the `INFO` log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG) ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```java theme={null} public class CustomLogger implements com.kameleoon.logging.Logger { // `log` method accepts logs from the SDK @Override public void log(com.kameleoon.logging.LogLevel level, String message) { // Custom log handling logic here. For example: switch (level) { case ERROR: android.util.Log.e("your-log-tag", message); break; case WARNING: android.util.Log.w("your-log-tag", message); break; case INFO: android.util.Log.i("your-log-tag", message); break; case DEBUG: android.util.Log.d("your-log-tag", message); break; default: } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG); // Optional, defaults to `LogLevel.WARNING`. com.kameleoon.logging.KameleoonLogger.setLogger(new CustomLogger()); ``` ```kotlin theme={null} class CustomLogger : com.kameleoon.logging.Logger { override fun log(level: com.kameleoon.logging.LogLevel, message: String) { // Custom log handling logic here. For example: when (level) { com.kameleoon.logging.LogLevel.ERROR -> android.util.Log.e("your-log-tag", message) com.kameleoon.logging.LogLevel.WARNING -> android.util.Log.w("your-log-tag", message) com.kameleoon.logging.LogLevel.INFO -> android.util.Log.i("your-log-tag", message) com.kameleoon.logging.LogLevel.DEBUG -> android.util.Log.d("your-log-tag", message) else -> { // Optional: handle default case if needed } } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG) // Optional, defaults to `LogLevel.WARNING`. com.kameleoon.logging.KameleoonLogger.setLogger(CustomLogger()) ``` ### Passing the visitor code to a WebView In some cases, you may need to pass the **visitor code** from the native app to a WebView that uses [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation) or the web [JavaScript](/developer-docs/sdks/web-sdks/js-sdk) or [React](/developer-docs/sdks/web-sdks/react-js-sdk) SDKs. The following example demonstrates the recommended way to achieve this: ```kotlin theme={null} class WebViewActivity : AppCompatActivity() { private var webView: WebView? = null private val DEFAULT_URL = "https://example.com" private val COOKIE_NAME = "kameleoonVisitorCode" private val COOKIE_DOMAIN = ".example.com" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) webView = WebView(this).also { webView -> setContentView(webView) configureWebView(DEFAULT_URL, kameleoonClient) webView.loadUrl(DEFAULT_URL) } } private fun configureWebView(url: String, kameleoonClient: KameleoonClient) { CookieManager.getInstance().apply { setCookie( url, "$COOKIE_NAME=${kameleoonClient.visitorCode}; Domain=$COOKIE_DOMAIN; Path=/; Secure" ) flush() } } } ``` ```kotlin theme={null} @Composable fun KameleoonCookieWebView(url: String, kameleoonClient: KameleoonClient) { AndroidView( factory = { context -> WebView(context).apply { configureWebView(url, kameleoonClient) loadUrl(url) } }, ) } private fun WebView.configureWebView(url: String, kameleoonClient: KameleoonClient) { val COOKIE_NAME = "kameleoonVisitorCode" val COOKIE_DOMAIN = ".example.com" CookieManager.getInstance().apply { setCookie( url, "$COOKIE_NAME=${kameleoonClient.visitorCode}; Domain=$COOKIE_DOMAIN; Path=/; Secure" ) flush() } } ``` ```java theme={null} public class WebViewActivity extends AppCompatActivity { private WebView webView; private static final String DEFAULT_URL = "https://example.com"; private static final String COOKIE_NAME = "kameleoonVisitorCode"; private static final String COOKIE_DOMAIN = ".example.com"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); webView = new WebView(this); setContentView(webView); configureWebView(webView, DEFAULT_URL, kameleoonClient); webView.loadUrl(url); } private void configureWebView(WebView webView, String url, KameleoonClient kameleoonClient) { CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setCookie( url, COOKIE_NAME + "=" + kameleoonClient.getVisitorCode() + "; Domain=" + COOKIE_DOMAIN + "; Path=/; Secure" ); cookieManager.flush(); } } ``` ## Reference This is the full reference documentation for the Kameleoon Android SDK. ### Initialization Once you have [installed the SDK](#installation) in your app, the first step is initializing Kameleoon. All of your app's interactions with the SDK, such as triggering an experiment, are accomplished using this Kameleoon client object. #### create() Call this method before any others to initialize the SDK. This method is in `com.kameleoon.KameleoonClientFactory`. Your app conducts all interactions with the SDK using the resulting `KameleoonClient` object that this method creates. You can customize the SDK's behavior (for example, the environment, credentials, and so on) by providing a [configuration object](#additional-configuration). Otherwise, the SDK tries to find and use your configuration file instead. ```java theme={null} String siteCode = "a8st4f59bj"; try { // pass client configuration and visitorCode as arguments KameleoonClientConfig config = new KameleoonClientConfig.Builder() .refreshIntervalMinute(15) // in minutes, 1 hour by default, optional .defaultTimeoutMillisecond(10_000) // in milliseconds, 10 seconds by default, optional .dataExpirationIntervalMinute(1440 * 365) // in minutes, infinity by default, optional .isUniqueIdentifier(false) // optional, false by default. Set to true if the visitorCode corresponds to your customer's unique userId. .environment("staging") // optional .build(); String visitorCode = "yourVisitorCode"; KameleoonClient kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode, config, getApplicationContext()); } catch (KameleoonException.SiteCodeIsEmpty | KameleoonException.VisitorCodeInvalid exception) { // Exception indicates that the provided siteCode is empty or the visitorCode is invalid } catch (Exception exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } try { // generate visitorCode automatically and read client configuration from a file 'kameleoon-client.properties' KameleoonClient kameleoonClient = KameleoonClientFactory.create(siteCode, getApplicationContext()); } catch (KameleoonException.SiteCodeIsEmpty | KameleoonException.VisitorCodeInvalid exception) { // Exception indicates that the provided siteCode is empty or the visitorCode is invalid } catch (Exception exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ```kotlin theme={null} val siteCode = "a8st4f59bj" try { // pass client configuration and visitor code as arguments val config = KameleoonClientConfig.Builder() .refreshIntervalMinute(15) // in minutes, 1 hour by default, optional .defaultTimeoutMillisecond(10_000) // in milliseconds, 10 seconds by default, optional .dataExpirationIntervalMinute(1440 * 365) // in minutes, infinity by default, optional .environment("staging") // optional .build(); val visitorCode = "yourVisitorCode" val kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode, config, applicationContext) } catch (e: KameleoonException.SiteCodeIsEmpty) { // Exception indicating that the provided siteCode is empty } catch (e: KameleoonException.VisitorCodeInvalid) { // Exception indicating that the provided visitorCode is invalid } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } try { // generate visitorCode automatically and read client configuration from the 'kameleoon-client.properties' file val kameleoonClient = KameleoonClientFactory.create(siteCode, applicationContext) } catch (e: KameleoonException.SiteCodeIsEmpty) { // Exception indicating that the provided siteCode is empty } catch (e: KameleoonException.VisitorCodeInvalid) { // Exception indicating that the provided visitorCode is invalid } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | siteCode required | `String` | A [unique key](/user-manual/faq#how-do-i-find-my-sitecode) identifying the Kameleoon project used with the SDK. | | | visitorCode optional | `String` | An optional visitor identifier. If available, use your internal **user ID**; otherwise, the SDK will generate one automatically. | `nil` | | config optional | `KameleoonClientConfig` | Optional SDK configuration. If provided, it's used instead of reading from an external [configuration file](#additional-configuration). If not provided, the SDK attempts to read the file, but if the file is missing, it falls back to default behavior. | `nil` | | applicationContext required | `Context` | The app's [context](https://developer.android.com/reference/android/content/Context). | | ##### Return value | Type | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | `KameleoonClient` | An instance of the `KameleoonClient` class that your app can then use to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `SiteCodeIsEmpty` | Exception indicating that the specified site code is empty string which is invalid value. | #### isReady() For mobile SDKs, the Kameleoon Client can't initialize immediately because it must retrieve the current configuration for active feature flags from a Kameleoon remote server. Call `isReady()` before triggering feature flags to check whether the SDK is ready. Alternatively, you can use a callback (see the [`runWhenReady()`](#runwhenready) method for details). ```java theme={null} boolean ready = kameleoonClient.isReady(); ``` ```kotlin theme={null} val ready = kameleoonClient.isReady ``` ##### Return value | Type | Description | | ------- | -------------------------------------------------------------------------------- | | boolean | `true` if the client is fully initialized; `false` if it's not yet ready to use. | #### runWhenReady() * 🔄 *Performs an asynchronous request (if the configuration is outdated or missing)* For mobile SDKs, the `KameleoonClient` can't initialize immediately, as it must perform a server call to retrieve the current configuration for all feature flags. Use the [`runWhenReady()`](#runwhenready) method to handle the time until the client is ready for use. Additionally, you can set a maximum timeout period to control how long the client will wait before it becomes ready. If `result.getOrThrow()` is `true`, the `KameleoonClient` is initialized and ready, and feature flags will be triggered with their respective variations. If the result is `false` or a timeout occurs, the initialization won't complete successfully. The callback or the coroutine-based code should include logic to apply the reference variation, as the user will be excluded from the feature flag if a timeout occurs. Since the initial configuration may require a server call, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the `KameleoonClient` is fully initialized and ready for use. * Use coroutines to handle asynchronous operations. ```java theme={null} kameleoonClient.runWhenReady(1000, result -> { int recommendedProductsNumber = 5; // Default control number for recommended products try { if (result.getOrThrow()) { Variation variation = kameleoonClient.getVariation("featureKey"); recommendedProductsNumber = (int) variation.getVariables().get("recommendedProductsNumber").getValue(); } } catch (Exception ignored) { // The user will not be included in the experiment results and should see the control variation } applyVariation(recommendedProductsNumber); }); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | --------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | timeoutMilliseconds optional | `int` | Timeout for the initialization process | [`defaultTimeoutMillisecond`](#create) or [`default_timeout_millisecond`](#additional-configuration) | | completion required | `ResultCompletion` | The callback that processes the received data. | | ```kotlin theme={null} kameleoonClient.runWhenReady(1000) { result -> val recommendedProductsNumber = runCatching { if (result.getOrThrow()) { val variation = kameleoonClient.getVariation("featureKey") variation.variables["recommendedProductsNumber"]?.value as Int } else { null // The user will not be included in the experiment results and should see the control variation } }.getOrDefault(5) // Default control number for recommended products applyVariation(recommendedProductsNumber) } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | --------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | timeoutMilliseconds optional | `Int` | Timeout for the initialization process | [`defaultTimeoutMillisecond`](#create) or [`default_timeout_millisecond`](#additional-configuration) | | completion required | `ResultCompletion` | The callback that processes the received data. | | A common mistake is using suspended functions inside `mapCatching`, `runCatching`, or a `try-catch` block without properly re-throwing `CancellationException`, which can interfere with coroutine cancellation. To ensure correct behavior, try to avoid calling suspend functions within these blocks. ```kotlin theme={null} viewModelScope.launch { kameleoonClient.runWhenReady(1000).getOrNull() ?: return@launch val recommendedProductsNumber = runCatching { val variation = kameleoonClient.getVariation("featureKey") variation.variables["recommendedProductsNumber"]?.value as? Int }.getOrNull() ?: 5 // Default control number for recommended products applyVariation(recommendedProductsNumber) } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | ----- | -------------------------------------- | ---------------------------------------------------------------------------------------------------- | | timeoutMilliseconds optional | `Int` | Timeout for the initialization process | [`defaultTimeoutMillisecond`](#create) or [`default_timeout_millisecond`](#additional-configuration) | ##### Return value | Type | Description | | -------------- | ----------------------------------------------------------------------------------------- | | `Result` | A Kotlin `Result` that contains either the success result or the exception that occurred. | ### Feature flags and variations #### isFeatureActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* This method was previously called `activateFeature`, which was removed in SDK version `4.0.0`. Call this method to activate a feature toggle. This method accepts a `featureKey` as a required argument to check if the specified feature will be active for a visitor. If the visitor has never been associated with this feature flag, the method returns a random boolean value (`true` if the visitor should be shown this feature, otherwise `false`). If the visitor is already registered with this feature flag, this method returns the previous `featureFlag` value. Ensure you properly set up error handling as shown in the example code to catch potential exceptions. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```java theme={null} String featureKey = "new_checkout"; boolean hasNewCheckout = false; try { hasNewCheckout = kameleoonClient.isFeatureActive(featureKey); // disabling tracking hasNewCheckout = kameleoonClient.isFeatureActive(featureKey, false); } catch (KameleoonException.SDKNotReady e) { // Exception indicating that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound e) { // SDK not initialized, or feature toggle not yet activated in Kameleoon - we consider the feature inactive } catch (Exception exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } if (hasNewCheckout) { // Implement new checkout code here } ``` ```kotlin theme={null} val featureKey = "new_checkout" var hasNewCheckout = false try { hasNewCheckout = kameleoonClient.isFeatureActive(featureKey) // disabling tracking hasNewCheckout = kameleoonClient.isFeatureActive(featureKey, false) } catch (e: KameleoonException.SDKNotReady) { // Exception indicating that the SDK has not completed its initialization yet. hasNewCheckout = false } catch (e: KameleoonException.FeatureNotFound) { // SDK not initialized or feature toggle not yet activated on Kameleoon's side - we consider the feature inactive hasNewCheckout = false } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries hasNewCheckout = false } if (hasNewCheckout) { // Implement new checkout code here } ``` The `isFeatureActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters | Name | Type | Description | | ---------- | ------- | --------------------------------------------------------------------------------------------------- | | featureKey | String | Unique key of the feature you want to expose to a user. This field is required. | | track | boolean | An optional parameter to enable or turn off tracking of the feature evaluation (`true` by default). | ##### Return value | Type | Description | | ------- | ----------------------------------------------------- | | Boolean | Value of the feature that's registered for a visitor. | ##### Exceptions thrown | Type | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature ID wasn't found in the SDK's internal configuration. This exception usually means the feature flag hasn't been activated on the Kameleoon side (but code implementing the feature is already deployed in the app). | #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```java theme={null} final String featureKey = "featureKey"; Variation variation = null; try { variation = kameleoonClient.getVariation(featureKey); // disabling tracking variation = kameleoonClient.getVariation(featureKey, false); } catch (KameleoonException.SDKNotReady ex) { // Exception indicating that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound ex) { // The feature key is not in the configuration file that has been fetched by the SDK. } catch (KameleoonException.FeatureEnvironmentDisabled ex) { // The feature flag is disabled for the environment. } if (variation != null) { String title = (String) variation.getVariables().get("title").getValue(); switch (variation.getKey()) { case "on": // Main variation key is selected for visitorCode break; case "alternative_variation": // Alternative variation key break; default: // Default variation key break; } } ``` ```kotlin theme={null} val featureKey = "featureKey" var variation: Variation? = null try { variation = kameleoonClient.getVariation(featureKey) // disabling tracking variation = kameleoonClient.getVariation(featureKey, false) } catch (e: KameleoonException.SDKNotReady) { // Exception indicating that the SDK has not completed its initialization yet. } catch (e: KameleoonException.FeatureNotFound) { // The feature key is not yet in the configuration file that has been fetched by the SDK. } catch (e: KameleoonException.FeatureEnvironmentDisabled) { // The feature flag is disabled for the environment } val title = variation?.variables?.get("title")?.value as? String when (variation?.key) { "on" -> { // Main variation key is selected for visitorCode } "alternative_variation" -> { // Alternative variation key } else -> { // Default variation key } } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `featureKey` required | `String` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `onlyActive` and `track` as optional arguments. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```java theme={null} try { Map variations = kameleoonClient.getVariations(); // only active variations Map variations = kameleoonClient.getVariations(true); // disable tracking Map variations = kameleoonClient.getVariations(false, false); } catch (KameleoonException.SDKNotReady ex) { // Exception indicating that the SDK has not completed its initialization yet. } ``` ```kotlin theme={null} try { val variations = kameleoonClient.getVariations() // only active variations val variations = kameleoonClient.getVariations(true) // disable tracking val variations = kameleoonClient.getVariations(false, false) } catch (e: KameleoonException.SDKNotReady) { // Exception indicating that the SDK has not completed its initialization yet. } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. ```java theme={null} final int experimentId = 9516; try { // Forcing the variation "on" for the experiment 9516 for the visitor kameleoonClient.setForcedVariation(experimentId, "on"); // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment kameleoonClient.setForcedVariation(experimentId, "on", false); // Resetting the forced variation for the experiment 9516 for the visitor kameleoonClient.setForcedVariation(experimentId, null); } catch (KameleoonException e) { // Handling the exception } ``` ```kotlin theme={null} val experimentId = 9516 try { // Forcing the variation "on" for the experiment 9516 for the visitor kameleoonClient.setForcedVariation(experimentId, "on") // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment kameleoonClient.setForcedVariation(experimentId, "on", false) // Resetting the forced variation for the experiment 9516 for the visitor kameleoonClient.setForcedVariation(experimentId, null) } catch (e: KameleoonException) { // Handling the exception } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `experimentId` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `String` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Exceptions thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```java theme={null} try { kameleoonClient.evaluateAudiences(); } catch (KameleoonException e) { // Handling the exception } ``` ```kotlin theme={null} try { kameleoonClient.evaluateAudiences() } catch (e: KameleoonException) { // Handling the exception } ``` ##### Exceptions thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```java theme={null} try { DataFile dataFile = kameleoonClient.getDataFile(); } catch (KameleoonException.SDKNotReady e) { // Exception indicates that the SDK has not completed its initialization yet. } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ```kotlin theme={null} try { val dataFile = kameleoonClient.dataFile val dateModified = dataFile.dateModified } catch (e: KameleoonException.SDKNotReady) { // Exception indicates that the SDK has not completed its initialization yet. } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ##### Errors thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | ### Goals #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track conversions. This method requires `goalId` to track conversion on this particular [goal](/user-manual/assets/goals/create-a-goal). In addition, this method also accepts `revenue`, `metadata` and `negative` arguments. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```java theme={null} final int goalId = 83023; kameleoonClient.trackConversion(goalId); // default revenue kameleoonClient.trackConversion(goalId, 10); // provided revenue == 10 kameleoonClient.trackConversion(goalId, new CustomData(1, "metadata")); // Add metadata ``` ```kotlin theme={null} val goalId = 83023 kameleoonClient.trackConversion(goalId) // default revenue kameleoonClient.trackConversion(goalId, 10f) // provided revenue == 10 kameleoonClient.trackConversion(goalId, CustomData(1, "metadata")) // Add metadata ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData...` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `new CustomData[0]` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```java theme={null} kameleoonClient.addData(new CustomData(5, "Credit Card"), new CustomData(9, "Express Delivery")); kameleoonClient.trackConversion(1000, new CustomData(5, "Amex Credit Card")); ``` ```kotlin theme={null} kameleoonClient.addData(CustomData(5, "Credit Card"), CustomData(9, "Express Delivery")) kameleoonClient.trackConversion(1000, CustomData(5, "Amex Credit Card")) ``` ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```java theme={null} import com.kameleoon.events.DataFileUpdateEvent; import com.kameleoon.events.EventType; kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, event -> { DataFileUpdateEvent.Source source = event.getSource(); // POLLING or STREAMING long dateModified = event.getDateModified(); // Data file modification date in milliseconds. // React to the data file update. }); // Clear the handler. kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, null); ``` ```kotlin theme={null} import com.kameleoon.events.DataFileUpdateHandler import com.kameleoon.events.EventType kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, DataFileUpdateHandler { event -> val source = event.source // POLLING or STREAMING val dateModified = event.dateModified // Data file modification date in milliseconds. // React to the data file update. }) // Clear the handler. kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, null) ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateEvent.Source` | The update source. [`POLLING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`STREAMING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `long` | The modification date of the updated data file, in milliseconds. | ```java theme={null} import com.kameleoon.events.EventType; import com.kameleoon.events.HttpRequestFailure; import com.kameleoon.events.HttpRequestHandler; import com.kameleoon.events.HttpRequestHandler.RequestType; kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, new HttpRequestHandler() { @Override public void onRequestSucceeded(RequestType requestType, int httpStatus, long durationMillis) { // The SDK request completed successfully. } @Override public void onRequestFailed(RequestType requestType, HttpRequestFailure failure, long durationMillis) { HttpRequestFailure.Reason reason = failure.getReason(); Integer httpStatus = failure.getHttpStatus(); Exception cause = failure.getCause(); // The SDK request failed. } }); // Clear the handler. kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, null); ``` ```kotlin theme={null} import com.kameleoon.events.EventType import com.kameleoon.events.HttpRequestFailure import com.kameleoon.events.HttpRequestHandler import com.kameleoon.events.HttpRequestHandler.RequestType kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, object : HttpRequestHandler { override fun onRequestSucceeded(requestType: RequestType, httpStatus: Int, durationMillis: Long) { // The SDK request completed successfully. } override fun onRequestFailed(requestType: RequestType, failure: HttpRequestFailure, durationMillis: Long) { val reason = failure.reason val httpStatus = failure.httpStatus val cause = failure.cause // The SDK request failed. } }) // Clear the handler. kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, null) ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `onRequestSucceeded()` for successful responses and `onRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `onRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `onRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ---------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `requestType` | `HttpRequestHandler.RequestType` | The SDK request type. Possible values are `DATAFILE`, `TRACKING`, `REMOTE_VISITOR_DATA`, and `REMOTE_DATA`. | | `httpStatus` | `int` | The HTTP status code returned by the request. Passed only to `onRequestSucceeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `onRequestFailed()`. | | `durationMillis` | `long` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailure.Reason` | The failure reason. Possible values are `HTTP_STATUS`, `EXCEPTION`, and `CANCELLED`. | | `httpStatus` | `Integer` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `null` for exception and cancellation failures. | | `cause` | `Exception` | The exception (error) that caused the request to fail. This value is `null` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DATAFILE_UPDATE` to react to SDK data file updates, or `EventType.HTTP_REQUEST` to react to completed SDK HTTP requests. | | `handler` required | `T extends EventHandler` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventType.DATAFILE_UPDATE`, or an `HttpRequestHandler` for `EventType.HTTP_REQUEST`. Pass `null` to remove the current handler. | ### Visitor data #### getVisitorCode() Returns unique visitor code used in SDK. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(); ``` ```kotlin theme={null} val visitorCode = kameleoonClient.visitorCode ``` ##### Return value | Type | Description | | -------- | ------------------------------------------------------ | | `String` | String representing a unique visitor code used in SDK. | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`trackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`getVariation()`](#getvariation) and [`getVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```java theme={null} // Add a single data item (tracked by default) kameleoonClient.addData(new CustomData(1, "value")); // Add multiple data items (tracked by default) kameleoonClient.addData(new CustomData(1, "value"), new Geolocation("France")); // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoonClient.addData(false, new CustomData(1, "value"), new Geolocation("France")); ``` ```kotlin theme={null} // Add a single data item (tracked by default) kameleoonClient.addData(CustomData(1, "value")) // Add multiple data items (tracked by default) kameleoonClient.addData(CustomData(1, "value"), Geolocation("France")) // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoonClient.addData(false, CustomData(1, "value"), Geolocation("France")) ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `track` optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `data` required | `Data...` | Collection of Kameleoon data types. | | #### flush() * 📨 *Sends Tracking Data to Kameleoon* `flush()` takes the Kameleoon data associated with a visitor, and sends a tracking request along with all of the data that were added previously using the `addData()` method that hasn't yet been sent when calling one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). `flush()` is non-blocking, as the server call is made asynchronously. `flush()` provides control over when the data associated with a visitor is sent to the servers. For instance, if `addData()` is called a dozen times, sending data to the server after each `addData()` invocation would be inefficient. Call `flush()` once at the end. The `flush()` method uses `visitorCode` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). If you set the `isUniqueIdentifier` configuration parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. ```java theme={null} kameleoonClient.addData(Device.phone()); kameleoonClient.addData(new Conversion(32, 10f, false)); kameleoonClient.flush(); // Interval tracking (most performant tracking method) kameleoonClient.flush(true); // Instant tracking ``` ```kotlin theme={null} kameleoonClient.addData(Device.phone()) kameleoonClient.addData(Conversion(32, 10f, false)) kameleoonClient.flush() // Interval tracking (most performant tracking method) kameleoonClient.flush(true) // Instant tracking ``` ##### Parameters | Name | Type | Description | | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | instant | boolean | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). This field is optional. The default value is `false`. | #### getRemoteData() * 🔄 *Performs an asynchronous request* This method was previously called `retrieveDataFromRemoteSource`, which was removed in SDK version `4.0.0`. Use this method to retrieve data from a remote Kameleoon server based on the active `siteCode` and the `key` argument (or the active `visitorCode` if the `key` is omitted). The `visitorCode` and `siteCode` are specified in `KameleoonClientFactory.create()`. Data can be stored quickly and conveniently on highly scalable remote servers using the Kameleoon Data API. The app can then retrieve the data using this method. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched. * Use coroutines for asynchronous handling. ```java theme={null} kameleoonClient.getRemoteData("key", result -> { try { JSONObject jsonObject = result.getOrThrow(); // jsonObject contains result of request } catch (Exception ex) { // request failed with an exception } }); ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | ----------------------------------------- | ----------------------------------------------------------- | ------- | | key optional | `String` | The key that the data you're retrieving is associated with. | `null` | | completion required | `ResultCompletion` | The callback that processes the received data. | | ```kotlin theme={null} kameleoonClient.getRemoteData("key") { result -> try { val jsonObject: JSONObject = result.getOrThrow() // jsonObject contains result of request } catch (ex: Exception) { // request failed with an exception } } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | ----------------------------------------- | ----------------------------------------------------------- | ------- | | key optional | `String` | The key that the data you're retrieving is associated with. | `null` | | completion required | `ResultCompletion` | The callback that processes the received data. | | A common mistake is using suspended functions inside `mapCatching`, `runCatching`, or a `try-catch` block without properly re-throwing `CancellationException`, which can interfere with coroutine cancellation. To ensure correct behavior, try to avoid calling suspend functions within these blocks. ```kotlin theme={null} viewModelScope.launch { val jsonObject = kameleoonClient.getRemoteData("key").getOrNull() ?: return@launch } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------- | -------- | ----------------------------------------------------------- | ------- | | key optional | `String` | The key that the data you're retrieving is associated with. | `null` | ##### Return value | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------- | | `Result` | A Kotlin `Result` that contains either the fetched value (`JSONObject`) or the exception that occurred. | #### getRemoteVisitorData() * 🔄 *Performs an asynchronous request* `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the visitor from the Kameleoon Data API. The method adds data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as custom data collected during previous visits. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor without having to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). Checking only for failed results is recommended. However, if necessary, it can be verified that the data has been added to the visitor and is available for targeting purposes (or for debugging, though using [logging](#logging) is better for debugging). Additionally, data can be managed manually if the `shouldAddData=false` parameter is passed. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched and added to the visitor. * Use coroutines for asynchronous handling. ```java theme={null} // Visitor data will be fetched and automatically added for `visitorCode`. kameleoonClient.getRemoteVisitorData(result -> { if (result.isSuccess()) { // Data was successfully retrieved from the Kameleoon servers and added to the visitor. } else { // The request failed due to an exception. } }); // If you only want to fetch data and add it yourself manually, set shouldAddData == `false`. kameleoonClient.getRemoteVisitorData(false, result -> { try { List visitorData = result.getOrThrow(); // visitorData contains the fetched visitor data from Kameleoon servers, which can be manually added. } catch (Exception ex) { // The request failed due to an exception. } }); // If you want to fetch custom list of data types RemoteVisitorDataFilter filter = RemoteVisitorDataFilter.builder() .previousVisitAmount(25) .experiments(true) .conversion(true) .build(); kameleoonClient.getRemoteVisitorData(filter, result -> { if (result.isSuccess()) { // Data was successfully retrieved from the Kameleoon servers and added to the visitor. } else { // The request failed due to an exception. } }); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | filter optional | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, the method retrieves `CustomData` from the current and latest previous visit. | `null` | | shouldAddData optional | `boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. | `true` | | completion required | `ResultCompletion, Exception>` | The callback that processes the received visitor data. | | ```kotlin theme={null} // Visitor data will be fetched and automatically added to the visitor. kameleoonClient.getRemoteVisitorData { result -> if (result.isSuccess) { // Data was successfully retrieved from the Kameleoon servers and added to the visitor. } else { // The request failed due to an exception. } } // If you only want to fetch data and add it yourself manually, set shouldAddData == `false` kameleoonClient.getRemoteVisitorData(false) { result -> try { val visitorData = result.getOrThrow() // visitorData contains the fetched visitor data from Kameleoon servers, which can be manually added. } catch (e: Exception) { // The request failed due to an exception. } } val filter = RemoteVisitorDataFilter.builder() .previousVisitAmount(25) .experiments(true) .conversion(true) .build() kameleoonClient.getRemoteVisitorData(filter) { result -> if (result.isSuccess) { // Data was successfully retrieved from the Kameleoon servers and added to the visitor. } else { // The request failed due to an exception. } } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | filter optional | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, the method retrieves `CustomData` from the current and latest previous visit. | `null` | | shouldAddData optional | `Boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. | `true` | | completion required | `ResultCompletion, Exception>` | The callback that processes the received visitor data. | | A common mistake is using suspended functions inside `mapCatching`, `runCatching`, or a `try-catch` block without properly re-throwing `CancellationException`, which can interfere with coroutine cancellation. To ensure correct behavior, try to avoid calling suspend functions within these blocks. ```kotlin theme={null} // Visitor data will be fetched and automatically added for `visitorCode` viewModelScope.launch { kameleoonClient.getRemoteVisitorData() ?: return@launch } // If you only want to fetch data and add it yourself manually, set shouldAddData == `false` viewModelScope.launch { kameleoonClient.getRemoteVisitorData(shouldAddData = false) .onSuccess { visitorData -> // visitorData contains the fetched visitor data from Kameleoon servers, which can be manually added. }.onFailure { ex -> // request failed with exception } } viewModelScope.launch { val filter = RemoteVisitorDataFilter.builder() .previousVisitAmount(25) .experiments(true) .conversion(true) .build() // In general, we recommend checking only if the request fails. kameleoonClient.getRemoteVisitorData(filter) ?: return@launch } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | filter optional | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, the method retrieves `CustomData` from the current and latest previous visit. | `null` | | shouldAddData optional | `Boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. | `true` | ##### Return value | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------- | | `Result>` | A Kotlin `Result` that contains either the fetched value (`List`) or the exception that occurred. | ##### Using parameters of `RemoteVisitorDataFilter` The `getRemoteVisitorData()` method offers flexibility by letting you define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to `5` and `conversions` to `true`. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition. | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | #### getVisitorWarehouseAudience() * 🔄 *Performs an asynchronous request* Retrieves all audience data associated with the visitor in your data warehouse. The optional `warehouseKey` parameter is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched and added to the visitor. * Use coroutines for asynchronous handling. Checking only for failed results is recommended. However, if necessary, it can be verified that the data has been added to the visitor and is available for targeting purposes (or for debugging, though using [logging](#logging) is better for debugging). ```java theme={null} // Visitor data will be fetched and automatically added for the visitor kameleoonClient.getVisitorWarehouseAudience(customDataIndex, result -> { if (result.isSuccess()) { // Due to method called before this callback, data was automatically added to the visitor. } else { Exception exception = result.failure(); // The request failed due to an exception. } }); // If you need to specify warehouse key kameleoonClient.getVisitorWarehouseAudience("warehouseKey", customDataIndex, result -> { // Due to method called before this callback, data was automatically added to the visitor, // but you can evaluate the added data if necessary. try { CustomData data = result.getOrThrow(); } catch (Exception exception) { // The request failed due to an exception. } }); ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | | warehouseKey optional | `String` | A unique key to identify the warehouse data (usually, your internal user ID). | `null` | | customDataIndex required | `int` | An integer representing the custom data index you want to use to target your BigQuery Audiences. | | | completion required | `ResultCompletion` | The callback that processes the received data. | | ```kotlin theme={null} // Visitor data will be fetched and automatically added for the visitor kameleoonClient.getVisitorWarehouseAudience(customDataIndex) { result -> if (result.isSuccess) { // Due to method called before this callback, data was automatically added to the visitor. } else { val exception = result.failure() // The request failed due to an exception. } } // If you need to specify warehouse key kameleoonClient.getVisitorWarehouseAudience("warehouseKey", customDataIndex) { result -> // As a result of the method before this callback is called, data was automatically added to the visitor // but you can evaluate the added data if you need to check it. try { val data = result.getOrThrow() } catch (e: Exception) { // The request failed due to an exception. } } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | | warehouseKey optional | `String` | A unique key to identify the warehouse data (usually, your internal user ID). | `null` | | customDataIndex required | `Int` | An integer representing the custom data index you want to use to target your BigQuery Audiences. | | | completion required | `ResultCompletion` | The callback that processes the received data. | | A common mistake is using suspended functions inside `mapCatching`, `runCatching`, or a `try-catch` block without properly re-throwing `CancellationException`, which can interfere with coroutine cancellation. To ensure correct behavior, try to avoid calling suspend functions within these blocks. ```kotlin theme={null} // Visitor data will be fetched and automatically added for the visitor viewModelScope.launch { // As a result of the method call, the data was automatically added to the visitor. kameleoonClient.getVisitorWarehouseAudience(customDataIndex = customDataIndex) ?: return@launch } // If you need to specify warehouse key viewModelScope.launch { // As a result of the method call, the data was automatically added to the visitor. kameleoonClient.getVisitorWarehouseAudience("warehouseKey", customDataIndex) .onSuccess { customData -> // But you can evaluate the added data if you need to check it. } .onFailure { ex -> // The request failed due to an exception. } } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ | ------- | | warehouseKey optional | `String` | A unique key indentifying the warehouse data (usually your internal user ID). | `null` | | customDataIndex required | `Int` | An integer representing the custom data index you want to use to target your BigQuery Audiences. | | ##### Return value | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------- | | `Result` | A Kotlin `Result` that contains either the fetched value (`CustomData`) or the exception that occurred. | #### setLegalConsent() You must use this method to specify whether the visitor has given legal consent to use their personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```java theme={null} kameleoonClient.setLegalConsent(true); ``` ```kotlin theme={null} kameleoonClient.setLegalConsent(true) ``` ##### Parameters | Name | Type | Description | | ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | legalConsent | boolean | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | ### Data types This section lists the `com.Kameleoon.Data` types supported by Kameleoon. Several standard data types are provided, as well as the `CustomData` type for defining custom data types. #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | --------------- | ----------------------------------------------- | ------------------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData...` | Metadata of the conversion. | `new CustomData[0]` | ```java theme={null} kameleoonClient.addData(new Conversion(32, 10f)); kameleoonClient.addData(new Conversion(33, 0f, true)); kameleoonClient.addData( new Conversion(34, 5f, new CustomData(3, "metadata1", "md2"), new CustomData(5, "md3")) ); ``` ```kotlin theme={null} kameleoonClient.addData(Conversion(32, 10f)) kameleoonClient.addData(Conversion(33, 0f, true)) kameleoonClient.addData( Conversion(34, 5f, CustomData(3, "metadata1", "md2"), CustomData(5, "md3")) ) ``` #### Device Since Android SDK `4.13.0`, the `Device` is automatically detected based on the [`android.content.Context`](https://developer.android.com/reference/android/content/Context). However, you can still manually override it if needed. Store information about the user's device. | Name | Type | Description | | ------------------------------ | --------- | ---------------------------------------------------- | | device required | `Devices` | List of devices: **phone**, **device**, **desktop**. | ```java theme={null} kameleoonClient.addData(Device.tablet()); ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. ```java theme={null} kameleoonClient.addData(new Geolocation("France", "Île-de-France", "Paris")); ``` ```kotlin theme={null} kameleoonClient.addData(Geolocation("France", "Île-de-France", "Paris")) ``` | Name | Type | Description | | ------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String` | The country of the visitor. | | `region` optional | `String` | The region of the visitor. | | `city` optional | `String` | The city of the visitor. | | `postalCode` optional | `String` | The postal code of the visitor. | | `latitude` optional | `float` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `float` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. #### CustomData Define your own custom data types in the Kameleoon app or the Data API and use them from the SDK. | Name | Type | Description | Default | | ---------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `int`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `String...`/`Collection` | Values of the custom data to be stored. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * The index of the custom data is available in the **Custom data configuration** page of the Kameleoon app. Be careful: this index starts at 0, so the first custom data you create for a given site would have the index 0, not 1. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```java theme={null} kameleoonClient.addData(new CustomData(1, "value")); // With several values kameleoonClient.addData(new CustomData(1, "value1", "value2")); // To set the 'overwrite' flag to false kameleoonClient.addData(new CustomData(1, false, "value")); // To use a name instead of the index kameleoonClient.addData(new CustomData("my-custom-data", "value")); ``` ```kotlin theme={null} kameleoonClient.addData(CustomData(1, "value")) // With several values kameleoonClient.addData(CustomData(1, "value1", "value2")) // To set the 'overwrite' flag to false kameleoonClient.addData(CustomData(1, false, "value")) // To use a name instead of the index kameleoonClient.addData(CustomData("my-custom-data", "value")) ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `long` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```java theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. Map featureFlags = dataFile.getFeatureFlags(); // Retrieves the last modification timestamp of the DataFile. // The value is a long representing milliseconds since the Unix epoch. long dateModified = dataFile.getDateModified(); ``` ```kotlin theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. val featureFlags = dataFile.featureFlags // Retrieves the last modification timestamp of the DataFile. // The value is a long representing milliseconds since the Unix epoch. val dateModified = dataFile.dateModified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `String` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `List` | A list of `Rule` objects | ```java theme={null} // Check whether the feature flag is enabled in the current environment boolean isEnvironmentEnabled = featureFlag.isEnvironmentEnabled(); // Retrieve the key of the default variation String defaultVariationKey = featureFlag.getDefaultVariationKey(); // Retrieve the default variation object Variation defaultVariation = featureFlag.getDefaultVariation(); // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) Map variations = featureFlag.getVariations(); // Retrieve all targeting rules associated with the feature flag List rules = featureFlag.getRules(); ``` ```kotlin theme={null} // Check whether the feature flag is enabled in the current environment val isEnvironmentEnabled = featureFlag.isEnvironmentEnabled // Retrieve the key of the default variation val defaultVariationKey = featureFlag.defaultVariationKey // Retrieve the default variation object val defaultVariation = featureFlag.defaultVariation // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) val variations = featureFlag.variations // Retrieve all targeting rules associated with the feature flag val rules = featureFlag.rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```java theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) Map variations = rule.getVariations(); ``` ```kotlin theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) val variations = rule.variations ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | name | `String` | The name of the variation. | | key | `String` | The unique key identifying the variation. | | id | `Integer` | The ID of the assigned variation (or `null` if it's the default variation). | | experimentId | `Integer` | The ID of the experiment associated with the variation (or `null` if default). | | variables | `Map` | A map containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```java theme={null} // Retrieving the variation name String variationName = variation.getName(); // Retrieving the variation key String variationKey = variation.getKey(); // Retrieving the variation id Integer variationId = variation.getId(); // Retrieving the experiment id Integer experimentId = variation.getExperimentId(); // Retrieving the variables map Map variables = variation.getVariables(); ``` ```kotlin theme={null} // Retrieving the variation name val variationName = variation.name // Retrieving the variation key val variationKey = variation.key // Retrieving the variation id val variationId = variation.id // Retrieving the experiment id val experimentId = variation.experimentId // Retrieving the variables map val variables = variation.variables ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | `String` | The unique key identifying the variable. | | type | `String` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**. | | value | `Object` | The value of the variable, which can be of the following types: **boolean**, **int**, **long**, **double**, **String**, **JSONObject**, **JSONArray**, **null**. | ```java theme={null} // Retrieving the variables map Map variables = variation.getVariables(); // Variable type can be retrieved for further processing String type = variables.get("isDiscount").getType(); // Get the Boolean value of "isDiscount" Boolean isDiscount = (Boolean) variables.get("isDiscount").getValue(); // Get the numeric value of "number" as an Integer Integer number = (Integer) variables.get("number").getValue(); // Get the String value of "title" String title = (String) variables.get("title").getValue(); ``` ```kotlin theme={null} // Retrieving the variables map val variables = variation.variables // Variable type can be retrieved for further processing val type = variables["isDiscount"]?.type // Get the Boolean value of "isDiscount" val isDiscount = variables["isDiscount"]?.value as? Boolean // Get the numeric value of "number" as an Integer val number = variables.get("number").value as? Int // Get the String value of "title" val title = variables["title"]?.value as? String ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `5.0.0`. #### getFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Use this method to get the feature variation key for a visitor. This method takes a `featureKey` as a required argument to retrieve the variation key for the specified user. If the visitor has never been associated with this feature flag, the SDK returns a randomly assigned variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, this method returns the previous variation key. If the user doesn't match any of the rules, the default value will be returned, which is defined in your customer's account. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. ```java theme={null} String featureKey = "new_checkout"; String variationKey = ""; try { variationKey = kameleoonClient.getFeatureVariationKey(featureKey); } catch (KameleoonException.SDKNotReady e) { // Exception indicates that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; feature flag isn't found in current configuration. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } switch (variationKey) { case "on": //main variation key is selected for visitorCode break; case "alternative_variation": //alternative variation key break; default: //default variation key break; } ``` ```kotlin theme={null} val featureKey = "new_checkout" var variationKey = "" try { variationKey = kameleoonClient.getFeatureVariationKey(featureKey) } catch (e: KameleoonException.SDKNotReady) { // Exception indicates that the SDK has not completed its initialization yet. } catch (e: KameleoonException.FeatureNotFound) { // Exception indicates that the SDK not initialized or the feature toggle is not yet activated on Kameleoon's side. We consider the feature inactive. } catch (e: KameleoonException.FeatureEnvironmentDisabled) { // The feature flag is disabled for the environment } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } when (variationKey) { "on" -> {} "alternative_variation" -> {} else -> {} } ``` #### getFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Use this method to get the feature variation key for a visitor. This method takes a `featureKey` as a required argument to retrieve the variation key for the specified user. If the visitor has never been associated with this feature flag, the SDK returns a randomly assigned variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, this method returns the previous variation key. If the user doesn't match any of the rules, the default value will be returned, which is defined in your customer's account. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. ```java theme={null} String featureKey = "new_checkout"; String variationKey = ""; try { variationKey = kameleoonClient.getFeatureVariationKey(featureKey); } catch (KameleoonException.SDKNotReady e) { // Exception indicates that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; feature flag isn't found in current configuration. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } switch (variationKey) { case "on": //main variation key is selected for visitorCode break; case "alternative_variation": //alternative variation key break; default: //default variation key break; } ``` ```kotlin theme={null} val featureKey = "new_checkout" var variationKey = "" try { variationKey = kameleoonClient.getFeatureVariationKey(featureKey) } catch (e: KameleoonException.SDKNotReady) { // Exception indicates that the SDK has not completed its initialization yet. } catch (e: KameleoonException.FeatureNotFound) { // Exception indicates that the SDK not initialized or the feature toggle is not yet activated on Kameleoon's side. We consider the feature inactive. } catch (e: KameleoonException.FeatureEnvironmentDisabled) { // The feature flag is disabled for the environment } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } when (variationKey) { "on" -> {} "alternative_variation" -> {} else -> {} } ``` #### getActiveFeatures() * Use [`getVariations()`](#getvariations) instead. * Previously called `getFeatureListForVisitorCode`, which was removed in SDK version `4.0.0` release. `getActiveFeatures` method retrieves information about the active feature flags that are available for the visitor. ```java theme={null} Map listActiveFeatureFlags = kameleoonClient.getActiveFeatures(); ``` ```kotlin theme={null} val listActiveFeatureFlags = kameleoonClient.getActiveFeatures() ``` ##### Return value | Type | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `Map` | A dictionary that contains the assigned variations of the active features using the keys of the corresponding active features. | #### getFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. This method gets a variable value of variation key for a specific user. It takes a `featureKey`, and `variableKey` as required arguments. If the visitor has never been associated with the `featureKey`, the SDK returns a randomly assigned variable value for the specified variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, the method returns the variable value for the previously registered variation. If the user doesn't match any of the rules, the default variable value is returned. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. ```java theme={null} String featureKey = "feature_key"; String variableKey = "variableKey"; try { Object variableValue = kameleoonClient.getFeatureVariable(featureKey, variableKey); // your custom code, depending on variableValue } catch (KameleoonException.SDKNotReady e) { // Exception indicates that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; feature flag isn't found in current configuration. } catch (KameleoonException.FeatureVariableNotFound e) { // Requested variable not defined in Kameleoon } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ```kotlin theme={null} val featureKey = "new_checkout" val variableKey = "var" try { val variableValue = kameleoonClient.getFeatureVariable(featureKey, variableKey) // your custom code depending on variableValue } catch (e: KameleoonException.SDKNotReady) { // Exception indicating that the SDK has not completed its initialization yet. } catch (e: KameleoonException.FeatureNotFound) { // The error has happened; feature flag isn't found in current configuration } catch (e: KameleoonException.FeatureVariableNotFound) { // Requested variable not defined on Kameleoon's side } catch (e: KameleoonException.FeatureEnvironmentDisabled) { // The feature flag is disabled for the environment } catch (e: Exception) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------- | | featureKey | String | Key of the feature you want to display to a user. This field is required. | | variableName | String | Name of the variable you want to get a value for. This field is required. | ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | object | Value of variation variable that's registered for the specified `visitorCode` for this feature flag. Valid types: `boolean`, `int`, `double`, `String`, `JSONObject`, `JSONArray` | ##### Exceptions thrown | Type | Description | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature key wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't been activated on Kameleoon's side (but code implementing the feature is already deployed in the app). | | FeatureVariableNotFound | Exception indicating that the specified variable wasn't found. Check that the variable key in the Kameleoon app matches the one in your code. | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getFeatureVariationVariables() * Use [`getVariation()`](#getvariation) instead. * This method was previously called `getFeatureAllVariables`, which was removed in SDK version `4.0.0` release. To retrieve all of a feature's variables, call this method. You can modify your feature variables in the Kameleoon app. This method takes one input parameter: `featureKey`. It returns the data as a `Map` type, as defined in the Kameleoon app. It throws an exception (`FeatureNotFound`) if the requested feature wasn't found in the SDK's internal configuration. ```java theme={null} String featureKey = "myFeature"; String variationKey = "variation1"; try { Map variables = kameleoonClient.getFeatureVariationVariables(featureKey, variationKey); } catch (KameleoonException.SDKNotReady e) { // Exception indicating that the SDK has not completed its initialization yet. } catch (KameleoonException.FeatureNotFound e) { // The feature is not yet activated on Kameleoon's side } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment } catch (Exception e) { // This is a generic Exception handler which will handle all exceptions. System.out.println("Exception occurred"); } ``` ```kotlin theme={null} val featureKey = "myFeature" val variationKey = "variation1" try { val variables = kameleoonClient.getFeatureVariationVariables(featureKey, variationKey) } catch (e: KameleoonException.SDKNotReady) { // Exception indicating that the SDK has not completed its initialization yet. } catch (e: KameleoonException.FeatureNotFound) { // The feature is not yet activated on Kameleoon's side } catch (e: KameleoonException.FeatureEnvironmentDisabled) { // The feature flag is disabled for the environment } catch (e: Exception) { // This is a generic Exception handler which will handle all exceptions. println("Exception occurred") } ``` ##### Parameters | Name | Type | Description | | ---------- | ------ | ---------------------------------------------------------------------------- | | featureKey | String | Unique identifier of the feature you need to obtain. This field is required. | ##### Return value | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Data representing the variables associated with this feature flag. Values can be `int`, `String`, `boolean`, `JSONObject` or `JSONArray` (depending on the types defined in the web interface). | ##### Exceptions thrown | Type | Description | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't yet been activated on Kameleoon's side. | | FeatureVariableNotFound | Exception indicating that the specified variable wasn't found. Check that the variable key in the Kameleoon app matches the key in your code. | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getFeatureList() If you want to iterate over all feature flags and call [`getVariation()`](#getvariation) on each, use the [`getVariations()`](#getvariations) method instead. Returns a list of feature flag keys currently available for the SDK. ```java theme={null} List allFeatureFlagListId = kameleoonClient.getFeatureList(); ``` ```kotlin theme={null} val allFeatureFlagListId = kameleoonClient.getFeatureList() ``` ##### Return value | Type | Description | | -------------- | ------------------------- | | `List` | List of feature flag keys | #### onUpdateConfiguration() This method was previously named `updateConfigurationHandler`, which was removed in SDK version `4.0.0` release. The `onUpdateConfiguration()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **completion**. The completion that will be called when the configuration is updated using a real-time configuration event. ##### Parameters | Name | Type | Description | | ------------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------- | | `completion` | `ResultCompletion` | The handler that will be called when the configuration is updated using a real-time configuration event. | ```java theme={null} kameleoonClient.onUpdateConfiguration(result -> { if (result.isSuccess()) { // result value contains the value of Unix time (number of seconds that have elapsed since January 1, 1970) when configuration was updated } }); ``` ```kotlin theme={null} kameleoonClient.onUpdateConfiguration { result -> if (result.isSuccess) { // result value contains the value of Unix time (number of seconds that have elapsed since January 1, 1970) when configuration was updated } } ``` # Flutter SDK Source: https://docs.kameleoon.com/developer-docs/sdks/mobile-sdks/flutter-sdk Integrate the Kameleoon Flutter SDK to run experiments and activate feature flags across all platforms targeted by the Flutter framework. With the Kameleoon Flutter SDK, experiments can run and feature flags can activate on all platforms targeted by the Flutter app framework. Integrating the SDK into applications is easy, and the footprint (in terms of memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Flutter SDK: 3.9.0 [Changelog](https://github.com/Kameleoon/client-flutter/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the Flutter SDK methods, see the [reference](#reference) section. ## Developer guide ### Getting started This guide helps integrate the SDK and start running experiments in Flutter applications. This tutorial explains the setup of a simple A/B test to change the number of recommended products based on different variations. #### Install the Flutter client To install the Kameleoon Flutter client, declare a dependency in your `pubspec.yaml` file: ```ruby theme={null} kameleoon_client_flutter: ^3.0.0 ``` In **release** mode, the JS library loads automatically. However, in **debug** mode, issues (due to the Dart Development Compiler) may occur when importing the JS library. To avoid potential problems, importing the JS library explicitly is strongly recommended. Add the following script line to the `` section of your `index.html`: ```html theme={null} ``` #### Initialize the Kameleoon client After installing the SDK into your app and setting up a server-side experiment in the Kameleoon app, the next step is creating the Kameleoon client. A `KameleoonClient` is a singleton object (per `siteCode`) that acts as a bridge between your app and the Kameleoon platform. It includes all the methods and properties you need to run an experiment. ```dart theme={null} import 'package:kameleoon_client_flutter/kameleoon_client_flutter.dart'; class _HomePage extends State { KameleoonClient kameleoonClient @override void initState() { super.initState(); try { // pass client configuration and visitorCode as arguments final config = KameleoonClientConfig( refreshIntervalMinutes: 15, // 60 minutes by default, optional defaultTimeoutMilliseconds: 10000, // 10_000 milliseconds by default, optional dataExpirationIntervalMinutes: 1440 * 365, // infinity by default, optional trackingIntervalMilliseconds: 500, // 1000 milliseconds by default, optional environment: "staging", // optional isUniqueIdentifier: false, // false by default, optional domain: "example.com", // web only option, optional networkDomain: "company.com", // web only option, optional defaultDataFile: "{...}", // optional activityTrackingIntervalMilliseconds: 60_000, // 60_000 milliseconds by default, optional ); final visitorCode = "yourVisitorCode"; final kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode: visitorCode, config: config); // or, if you want, visitor code will be generated automatically final kameleoonClient = KameleoonClientFactory.create(siteCode, config: config); } on SiteCodeIsEmpty catch (ex) { // Exception indicates that the provided siteCode is empty } on VisitorCodeInvalid catch (ex) { // Exception indicates that the provided visitorCode is invalid } on Exception catch (ex) { // Any other error } } } ``` During `KameleoonClientFactory.create()`, the client starts initialization but isn't immediately ready for use. The Kameleoon Client must retrieve the current feature flag configuration, including traffic allocation, from a Kameleoon remote server. This requires network access, which isn't always available. Until the Kameleoon Client is ready, you shouldn't run SDK methods that depend on feature flag configuration. After the first feature flag configuration is fetched, the client refreshes it periodically. If a later refresh fails, the Kameleoon client continues to use the previous configuration. You can use [`isReadyAsync()`](#isreadyasync) to check whether the Kameleoon client initialization is complete. Alternatively, a **helper callback** can wrap the logic for triggering feature flags and applying variations. The best approach ([`isReadyAsync()`](#isreadyasync) or **callback**) depends on the use case. Use [`isReadyAsync()`](#isreadyasync) when the SDK is expected to be ready soon, such as for a feature flag in a dialog that users are unlikely to open during the first few seconds or minutes of app navigation. Use a callback when the SDK is likely to still be initializing, such as for a feature flag shown at app launch. It's your responsibility as the app developer to ensure the logic of your app code is correct within the context of A/B testing using Kameleoon. A good practice is to always assume that the app user can be left out of the feature flag when the Kameleoon client isn't yet ready. This exclusion is easy to implement, as it corresponds to the implementation of the default or reference variation logic. The code samples in the next paragraph show examples of this approach. You're now ready to implement feature management and feature flags. See the [Reference](#reference) section for details about additional methods. #### Activating a feature flag ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create a [feature flag](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag) in your Kameleoon account. To determine if a feature flag is active for a specific user, you must retrieve its configuration. Use the [`getFeatureVariationKey()`](#getfeaturevariationkey) or [`isFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. Use the `isFeatureActive()` method if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. The `getFeatureVariationKey()` method retrieves the configuration of a feature experiment with several feature variations. You can use the method to get a variation key for a given user by providing the `visitorCode` and `featureKey` as mandatory arguments. Feature flags can have associated variables that are used to customize their behavior. To retrieve these variables, use the [`getFeatureVariationVariables()`](#getfeaturevariationvariables) method after calling `getFeatureVariationKey()`, as you must obtain the `variationKey` for the user. To check if a feature flag is active, you only need to use **one** method. Choose `isFeatureFlagActive` if you want to know if a feature flag is on or off. For more complex scenarios, like dynamically changing the feature's behavior, use `getFeatureFlagVariables`. ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you’ve added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user’s profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. Call `getRemoteVisitorData()` **before** retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation of a feature flag. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Remember to call the `flush()` method to send saved data to the Kameleoon servers. If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data lets you capture and analyze specific information relevant to your experiments. Don't forget to call the `flush()` method to send the collected data to Kameleoon servers for analysis. ##### Tracking flag exposition and goal conversions Kameleoon will automatically track visitors’ exposition to flags as soon as you call one of these methods: * `getFeatureVariationKey()` * `getFeatureVariable()` * `isFeatureActive()` When a user completes a desired action (for example, making a purchase), it counts as a conversion. To track conversions, you must use the [`trackConversion()`](#trackconversion) method, and provide the `visitorCode` and `goalId` parameters. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```dart theme={null} try { await kameleoonClient.addData(CustomData.withIndex(index, values: ["newVisitorCode"])) } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```dart theme={null} // The `none` log level does not allow logging. KameleoonLogger.setLogLevel(LogLevel.none); // The `error` log level only allows logging issues that may affect the SDK's main behaviour. KameleoonLogger.setLogLevel(LogLevel.error); // The `warning` log level allows logging issues which may require additional attention. // It extends the `error` log level. // The `warning` log level is a default log level. KameleoonLogger.setLogLevel(LogLevel.warning); // The `info` log level allows logging general information on the SDK's internal processes. // It extends the `warning` log level. KameleoonLogger.setLogLevel(LogLevel.info); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to support // to assist with internal troubleshooting. KameleoonLogger.setLogLevel(LogLevel.debug); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```dart theme={null} import 'package:logging/logging.dart' as logging; class CustomLogger extends Logger { final logger = logging.Logger("CustomLogger"); // `log` method accepts logs from the SDK @override void log(LogLevel level, String message) { // Custom log handling logic here. For example: switch (level) { case LogLevel.error: logger.severe(message); break; case LogLevel.warning: logger.warning(message); break; case LogLevel.info: logger.info(message); break; case LogLevel.debug: logger.fine(message); break; default: break; } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. KameleoonLogger.setLogLevel(LogLevel.debug); // Optional, defaults to `LogLevel.warning`. KameleoonLogger.setLogger(CustomLogger()); ``` ### Error handling Handling errors is considered a good practice to make your app more stable and avoid technical issues. Most `KameleoonClient` methods can throw a `KameleoonException` error. Since it can be difficult to patch the SDK version on the Android client side, enclosing every SDK method in a `try` clause that catches the `KameleoonException` and the `Throwable` error type is recommended to prevent other fatal errors. For example: ```dart theme={null} try { // Calling a method of the SDK } on KameleoonException { // Handling expected exceptions } on Exception { // Any other error } ``` ## Reference This is a full reference documentation of the Flutter SDK. ### Initialization Once you have [installed the SDK](#install-the-flutter-client) in your app, you must initialize Kameleoon. All of your app's interactions with the SDK, such as triggering an experiment, are accomplished using this Kameleoon client object. #### create() Call this method before any others to initialize the SDK. This method is in `KameleoonClientFactory`. Your app conducts all interactions with the SDK using the resulting `KameleoonClient` object that this method creates. You can customize the SDK's behavior (for example, the environment, the credentials, and so on) by providing a [configuration object](#additional-configuration). Otherwise, the SDK tries to find your configuration file and uses it instead. ```dart theme={null} import 'package:kameleoon_client_flutter/kameleoon_client_flutter.dart' final siteCode = "a8st4f59bj"; try { // pass client configuration and visitorCode as arguments final config = KameleoonClientConfig( refreshIntervalMinutes: 15, // 60 minutes by default, optional defaultTimeoutMilliseconds: 10000, // 10_000 milliseconds by default, optional dataExpirationIntervalMinutes: 1440 * 365, // infinity by default, optional trackingIntervalMilliseconds: 500, // 1000 milliseconds by default, optional environment: "staging", // optional isUniqueIdentifier: false, // false by default, optional domain: "example.com" // web only option, optional networkDomain: "company.com", // web only option, optional defaultDataFile: "{...}", // optional activityTrackingIntervalMilliseconds: 60_000, // 60_000 milliseconds by default, optional ); final visitorCode = "yourVisitorCode"; final kameleoonClient = KameleoonClientFactory.create(siteCode, visitorCode: visitorCode, config: config); } on SiteCodeIsEmpty catch (ex) { // Exception indicates that the provided siteCode is empty } on VisitorCodeInvalid catch (ex) { // Exception indicates that the provided visitorCode is invalid } on Exception catch (ex) { // Any other error } try { // generate visitorCode automatically and use default Kameleoon client config final kameleoonClient = KameleoonClientFactory.create(siteCode); } on SiteCodeIsEmpty catch (ex) { // Exception indicates that the provided siteCode is empty } on Exception catch (ex) { // Any other error } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | | siteCode required | `String` | A [unique key](/user-manual/faq#how-do-i-find-my-sitecode) identifying the Kameleoon project used with the SDK. | | | visitorCode optional | `String` | An optional visitor identifier. If available, use your internal **user ID**; otherwise, the SDK will generate one automatically. | `nil` | | config optional | `KameleoonClientConfig` | If the SDK configuration isn't provided, the SDK will automatically use the default settings. | `nil` | ##### Return value | Type | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | `KameleoonClient` | An instance of the `KameleoonClient` class that your app can then use to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `SiteCodeIsEmpty` | Exception indicating that the specified site code is empty string which is invalid value. | #### isReadyAsync() For mobile SDKs, the Kameleoon Client can't initialize immediately because it must retrieve the current configuration for active feature flags from a Kameleoon remote server. Call `isReadyAsync()` before triggering feature flags to check whether the SDK is ready. Alternatively, you can use a callback (see the [`runWhenReady()`](#runwhenready) method for details). ```dart theme={null} final ready = await kameleoonClient.isReadyAsync(); ``` ##### Return value | Type | Description | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `Future` | Resolves to `true` once the SDK is fully initialized; resolves to `false` while initialization is still in progress or if it has failed. | #### runWhenReady() For mobile SDKs, the Kameleoon Client can't initialize immediately as it must perform a server call to retrieve the current configuration for all active feature flags. Use the [`runWhenReady()`](#runwhenready) method of the `KameleoonClient` class to pass a callback that will be executed as soon as the SDK is ready for use. You can also set a timeout. The callback given as the first argument to this method must be an instance of a type of `Function(bool ready)`. If the `ready` equals `true`, the Kameleoon client is ready and should contain code that triggers a feature flag and implements variations. Otherwise, the specified timeout will occur before the client is initialized. The callback should contain code that implements the reference variation, as the user will be excluded from the feature flag if a timeout occurs. ```dart theme={null} kameleoonClient.runWhenReady((ready) async { final defaultProductsNumber = 5; if (ready) { late int recommendedProductsNumber; try { recommendedProductsNumber = await kameleoonClient.getFeatureVariable("feature_key", "product_number"); } on Exception { recommendedProductsNumber = defaultProductsNumber; } } else { recommendedProductsNumber = defaultProductsNumber; } setState(() { _recommendedProductsNumber = recommendedProductsNumber; }); }, 2000); ``` ##### Arguments | Name | Type | Description | | -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | callback required | `Function(bool)` | Callback object with `ready` flag. | | timeout (*optional)* | `Duration` | Timeout (in milliseconds). If not provided, it will use the default value of `defaultTimeoutMilliseconds` (from `KameleoonClientConfig`) milliseconds. | ### Feature flags and variations #### isFeatureActive() * 📨 *Sends Tracking Data to Kameleoon* To activate a feature toggle, call this method. This method accepts a `featureKey` as a required argument to check if the specified feature will be active for a visitor. If the visitor has never been associated with this feature flag, the method returns a random boolean value (`true` if the visitor should be shown this feature, otherwise `false`). If the visitor is already registered with this feature flag, this method returns the previous feature flag value. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```dart theme={null} String featureKey = "new_checkout"; bool hasNewCheckout = false; try { hasNewCheckout = await kameleoonClient.isFeatureActive(featureKey); } on SDKNotReady { // Exception indicates that the SDK has not completed its initialization yet. } on FeatureNotFound { // The error has occurred; feature flag isn't found in current configuration. } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error } if (hasNewCheckout) { // Implement new checkout code here } ``` The `isFeatureActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Arguments | Name | Type | Description | | ---------- | -------- | --------------------------------------------------------------------------------------------------- | | featureKey | `String` | Unique key of the feature you want to expose to a user. This field is required. | | track | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation (`true` by default). | ##### Return value | Type | Description | | -------------- | ----------------------------------------------------- | | `Future` | Value of the feature that's registered for a visitor. | ##### Exceptions thrown | Type | Description | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature ID wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't been activated on the Kameleoon side (but code implementing the feature is already deployed in the app). | | PlatformException | Exception indicating that the native plugin integration works incorrectly. | #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```dart theme={null} final String featureKey = "featureKey"; Variation? variation; try { variation = await client.getVariation(featureKey); // disabling tracking variation = await client.getVariation(featureKey, track: false); } on SDKNotReady { // Exception indicating that the SDK has not completed its initialization yet. } on FeatureNotFound { // The feature key is not in the configuration file that has been fetched by the SDK. } on FeatureEnvironmentDisabled { // The feature flag is disabled for the environment. } String? title = variation?.variables['title']?.value; switch (variation?.key) { case "on": // Main variation key is selected for visitorCode break; case "alternative_variation": // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `featureKey` required | `String` | Key of the feature you want to expose to a visitor. | | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------- | ------------------------------------------------------------------------------------- | | `Future` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `onlyActive` and `track` as optional arguments. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```dart theme={null} try { Map variations = await kameleoonClient.getVariations(); // only active variations Map variations = await kameleoonClient.getVariations(onlyActive: true); // disable tracking Map variations = await kameleoonClient.getVariations(onlyActive: false, track: false); } on SDKNotReady { // Exception indicating that the SDK has not completed its initialization yet. } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------- | ------- | | `onlyActive` optional | `bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Future>` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | #### getFeatureList() Returns a list of feature flag keys currently available for the SDK. ```dart theme={null} try { final allFeatureFlagKeys = await kameleoonClient.getFeatureList(); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` ##### Return value | Type | Description | | ---------------------- | ------------------------- | | `Future>` | List of feature flag keys | #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```dart theme={null} try { final dataFile = await kameleoonClient.getDataFile(); final dateModified = dataFile.dateModified; } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ##### Errors thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. ```dart theme={null} final experimentId = 9516; try { // Forcing the variation "on" for the experiment 9516 for the visitor await kameleoonClient.setForcedVariation(experimentId, "on") // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment await kameleoonClient.setForcedVariation(experimentId, "on", forceTargeting: false) // Resetting the forced variation for the experiment 9516 for the visitor await kameleoonClient.setForcedVariation(experimentId, null); } on KameleoonException { // Handling the exception } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `experimentId` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `String` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `bool` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Errors thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```dart theme={null} try { await kameleoonClient.evaluateAudiences(); } on KameleoonException { // Handling the exception } ``` ##### Errors thrown | Type | Description | | ------------- | --------------------------------------------------- | | `SDKNotReady` | Indicates that the SDK isn't yet fully initialized. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. ### Goals #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track conversions. This method requires `goalId` to track conversion on this particular [goal](/user-manual/assets/goals/create-a-goal). In addition, this method also accepts `revenue`, `metadata` and `negative` arguments. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```dart theme={null} kameleoonClient.trackConversion(goalId); // default revenue kameleoonClient.trackConversion(goalId, 10); // provided revenue == 10 // Add metadata kameleoonClient.trackConversionWithOptParams(goalId, metadata: [CustomData.withIndex(1, values: ["true"])]); kameleoonClient.trackConversionWithOptParams(goalId, revenue: 10, metadata: [CustomData.withIndex(1, values: ["true"])]); ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `double` | Revenue of the conversion. | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `List` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `[]` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```dart theme={null} kameleoonClient.addData([ CustomData.withIndex(5, values: ["Credit Card"]), CustomData.withIndex(9, values: ["Express Delivery"]) ]); kameleoonClient.trackConversionWithOptParams(1000, metadata: [CustomData.withIndex(5, values: ["Amex Credit Card"])]); ``` ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```dart theme={null} kameleoonClient.setEventHandler(EventType.dataFileUpdate, (event) { final source = event.source; // DataFileUpdateSource.polling or DataFileUpdateSource.streaming final dateModified = event.dateModified; // Data file modification date in milliseconds. // React to the data file update. }); // Clear the handler. kameleoonClient.setEventHandler(EventType.dataFileUpdate, null); ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource.polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource.streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `int` | The modification date of the updated data file, in milliseconds. | ```dart theme={null} kameleoonClient.setEventHandler(EventType.httpRequest, (event) { if (event.success) { final httpStatus = event.httpStatus; // The SDK request completed successfully. } else { final failure = event.failure!; final reason = failure.reason; // HttpRequestFailureReason.httpStatus, .error, or .cancelled final httpStatus = failure.httpStatus; final cause = failure.cause; // The SDK request failed. } }); // Clear the handler. kameleoonClient.setEventHandler(EventType.httpRequest, null); ``` `HttpRequestEvent` contains information about a completed SDK HTTP request. The SDK reports the event once per each actual HTTP request attempt, including retries. Check the `success` field to tell a successful request from a failed one. ##### Event data fields | Name | Type | Description | | ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `success` | `bool` | `true` if the SDK HTTP request completed successfully, `false` if it failed because of an HTTP status, error, or cancellation. | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `HttpRequestType.dataFile`, `HttpRequestType.tracking`, `HttpRequestType.remoteVisitorData`, and `HttpRequestType.remoteData`. | | `httpStatus` | `int?` | The HTTP status code returned by the request. Not `null` only if `success` is `true`. | | `failure` | `HttpRequestFailure?` | Details about why the request failed. Not `null` only if `success` is `false`. | | `duration` | `int` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason.httpStatus`, `HttpRequestFailureReason.error`, and `HttpRequestFailureReason.cancelled`. | | `httpStatus` | `int?` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `null` for exception and cancellation failures. | | `cause` | `String?` | The exception (error) that caused the request to fail. This value is `null` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.dataFileUpdate` to react to SDK data file updates, or `EventType.httpRequest` to react to completed SDK HTTP requests. | | `handler` required | `void Function(E event)?` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEvent` for `EventType.dataFileUpdate`, or `HttpRequestEvent` for `EventType.httpRequest`. Pass `null` to remove the current handler. | ### Visitor data #### getVisitorCode() Returns unique visitor code used in SDK. ```dart theme={null} final visitorCode = await kameleoonClient.getVisitorCode(); ``` ##### Return value | Type | Description | | ---------------- | ------------------------------------------------------ | | `Future` | String representing a unique visitor code used in SDK. | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`trackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`getVariation()`](#getvariation) and [`getVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```dart theme={null} try { await kameleoonClient.addData([ Device(Devices.phone), CustomData.withIndex(1, values: ["some custom value"]), Conversion(32, 10f, false), ]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `track` optional | `bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `data` required | `List` | Collection of Kameleoon data types. | | ##### Exceptions | Type | Description | | ------------------- | -------------------------------------------------------------------------- | | `PlatformException` | Exception indicating that the native plugin integration works incorrectly. | #### flush() * 📨 *Sends Tracking Data to Kameleoon* Data associated with the current user via the `addData()` method isn't sent immediately to the server. It's stored and accumulated until it's sent automatically by the `trackConversion()` method, or manually sent by calling the `flush()` method, giving control over exactly when the data is flushed to the servers. For example, if the `addData()` method is called a dozen times, sending data to the server after each `addData()` invocation would waste resources. Call `flush()` once at the end. The `flush()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```dart theme={null} kameleoonClient.flush(); // Interval tracking (most performant tracking method) kameleoonClient.flush(instant: true); // Instant tracking ``` ##### Exceptions thrown | Type | Description | | ----------------- | ------------------------------------------------------------------------------- | | PlatformException | Exception indicating that the native plugin integration doesn't work correctly. | #### getRemoteData() This method was previously called `retrieveDataFromRemoteSource`, which was removed in SDK version `3.0.0` release. Use this method to retrieve data from a remote Kameleoon server based on the active `siteCode` and the `key` argument (or the active `visitorCode` if the `key` is omitted). The `visitorCode` and `siteCode` are specified in `KameleoonClientFactory.create()`. Data can be stored quickly and conveniently on highly scalable remote servers using the Kameleoon Data API. The app can then retrieve the data using this method. Note that since a server call is required, this mechanism is asynchronous. ```dart theme={null} try { final data = await kameleoonClient.getRemoteData("test"); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } ``` ##### Parameters | Name | Type | Description | | ---- | ------ | -------------------------------------------------------------------------------- | | key | String | The key that the data you try to get is associated with. This field is optional. | ##### Return value | Type | Description | | ----------------- | -------------------------------------------------------------------------------------- | | `Future` | Future with retrieving data for specific `key` (or `visitorCode` if `key` is omitted). | | Type | Description | | ----------------- | ------------------------------------------------------------------------------- | | PlatformException | Exception indicating that the native plugin integration works incorrectly. | | Exception | Exception indicating that the request timed out or any other reason of failure. | #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. This method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as custom data collected during previous visits. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without having to call `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). ```dart theme={null} // Visitor data will be fetched and automatically added for `visitorCode`. try { final visitorData = await kameleoonClient.getRemoteVisitorData(); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } // If you only want to fetch data and add it yourself manually, set addData == `false`. try { final visitorData = await kameleoonClient.getRemoteVisitorData(addData: false); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } // If you want to fetch custom list of data types final filter = RemoteVisitorDataFilter.withValues( previousVisitAmount: 25, currentVisit: true, conversions: true, ); try { final visitorData = await kameleoonClient.getRemoteVisitorData(filter: filter, addData: false); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } ``` ##### Parameters | Name | Type | Description | | ------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | filter | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, `getRemoteVisitorData` retrieves `CustomData` from the current and latest previous visit (`RemoteVisitorDataFilter()`). All other filters parameters default to `false`. This field is optional. | | addData | boolean | A boolean indicating whether the method should automatically add retrieved data for a visitor. If unspecified, the default value is **true**. This field is optional. | ##### Return value | Type | Description | | -------------------- | ----------------------------------------------------- | | `Future>` | An array containing the fetched data for the visitor. | ##### Exceptions thrown | Type | Description | | ----------------- | ------------------------------------------------------------------------------- | | PlatformException | Exception indicating that the native plugin integration doesn't work correctly. | | Exception | Exception indicating that the request timed out or any other reason of failure. | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by letting you define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to `5` and `conversions` to `true`. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved. | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | pageViews (*optional*, *web only*) | `boolean` | If true, page data will be retrieved. | `false` | | device (*optional*, *web only*) | `boolean` | If true, device data will be retrieved. | `false` | | browser (*optional*, *web only*) | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem (*optional*, *web only*) | `boolean` | If true, operating system data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | #### getVisitorWarehouseAudience() Retrieves all audience data associated with the visitor in your data warehouse. The optional `warehouseKey` parameter is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method returns the result as a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. Since a server call is required, this mechanism is asynchronous. ```dart theme={null} try { final customData = await kameleoonClient.getVisitorWarehouseAudience(customDataIndex); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } // If you need to specify warehouse key try { final customData = await kameleoonClient.getVisitorWarehouseAudience(customDataIndex, "warehouseKey"); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } ``` ##### Parameters | Name | Type | Description | | --------------- | ------ | ------------------------------------------------------------------------------------------------------- | | visitorCode | string | A unique visitor identification string, can't exceed 255 characters. | | customDataIndex | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | warehouseKey | string | A unique key to identify the warehouse data (usually, your internal user ID). This field is optional. | ##### Return value | Type | Description | | -------------------- | ------------------------------------------------------------------------------- | | `Future` | A `CustomData` instance confirming that the data has been added to the visitor. | ##### Exceptions thrown | Type | Description | | ----------------- | ------------------------------------------------------------------------------- | | PlatformException | Exception indicating that the native plugin integration doesn't work correctly. | | Exception | Exception indicating that the request timed out or any other reason of failure. | #### setLegalConsent() You must use this method to specify whether the visitor has given legal consent to use their personal data. Setting the `consent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```dart theme={null} try { final customData = await kameleoonClient.setLegalConsent(true); } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error (including network issues). } ``` ##### Parameters | Name | Type | Description | | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | consent | boolean | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | ##### Exceptions thrown | Type | Description | | ----------------- | -------------------------------------------------------------------------- | | PlatformException | Exception indicating that the native plugin integration works incorrectly. | ##### Consent revocation behavior This is applicable only to the Flutter Web SDK. When you call `setLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Data types This section lists the `Data` types supported by Kameleoon. Several standard data types are provided, as well as the `CustomData` type for defining custom data types. #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | ------------------ | ----------------------------------------------- | ------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `double` | Revenue of the conversion | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `List` | Metadata of the conversion. | `[]` | ```dart theme={null} try { final conversion = Conversion(32, 10); final conversionMetadata = Conversion(32, 10, false, [CustomData.withIndex(1, values: ["true"])]); final conversionOptParams = Conversion.withOptParams(32, metadata: [CustomData.withIndex(1, values: ["true"])]); await kameleoonClient.addData([conversion, conversionMetadata, conversionOptParams]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### CustomData This data type is available for both types of SDKs: Mobile & Web. `CustomData` allows any type of data to be easily associated with each visitor. `CustomData` can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | | | ---------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | | index/name required | `int`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | values optional | `List` | Values of the custom data to be stored. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing `CustomData`. * The custom data `index` can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the **Use this data only locally for targeting purposes** option when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```dart theme={null} try { await kameleoonClient.addData([CustomData.withIndex(1, values: ["some custom value"])]); // With several values await kameleoonClient.addData(CustomData.withIndex(1, values: ["value 1", "value 2"])) // To set the 'overwrite' flag to false await kameleoonClient.addData(CustomData.withIndex(1, overwrite: false, values: ["first value", "second value"])) // To use a name instead of the index await kameleoonClient.addData(CustomData.withName("my-custom-data", values: ["value"])) } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### Device This data type is available for both types of SDKs: Mobile & Web. Store information about the user's device. | Name | Type | Description | | ------ | ------- | ----------------------------------------------------------------------- | | device | Devices | List of devices: `phone`, `tablet`, `desktop`. This field is mandatory. | ```dart theme={null} try { await kameleoonClient.addData([Device(Devices.phone)]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### Geolocation This data type is available for both types of SDKs: Mobile & Web. `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String` | The country of the visitor. | | `region` optional | `String?` | The region of the visitor. | | `city` optional | `String?` | The city of the visitor. | | `postalCode` optional | `String?` | The postal code of the visitor. | | `latitude` optional | `double?` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `double?` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```dart theme={null} try { await kameleoonClient.addData([Geolocation("France", region: "Île-de-France", city: "Paris")]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### Browser The data type is available only for Web SDK The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | --------------------------------- | ---------- | ----------------------------------------------------------------------------------------------- | | `browser` required | `Browsers` | List of browsers: `chrome`, `internetExplorer`, `firefox`, `safari`, `opera`, `other`. | | `version` optional | `double?` | Version of the browser, floating point number represents major and minor version of the browser | ```dart theme={null} try { await kameleoonClient.addData([Browser(Browsers.chrome)]); await kameleoonClient.addData([Browser(Browsers.chrome, 10.0)]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### PageView This data type is only available for Web SDKs. | Name | Type | Description | | --------- | ----------- | -------------------------------------------------- | | url | String | URL of the page viewed. This field is mandatory. | | title | String | Title of the page viewed. This field is mandatory. | | referrers | `List` | Referrers of viewed pages. This field is optional. | The referrer's index (ID) is available in the Acquisition channel configuration page of the Kameleoon app. Be careful: this index starts at 0, so the first [acquisition channel](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) you create for a given site will have the ID 0, not 1. ```dart theme={null} try { await kameleoonClient.addData([PageView("https://url.com", "title", [3])]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### OperatingSystem This data type is only available for Web SDKs. `OperatingSystem` contains information about the operating system on the visitor's device. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. | Name | Type | Description | | ---- | ------------------ | ---------------------------------------------------------------------------------------------------------------- | | type | `OperatingSystems` | List of operating systems: `windows`, `mac`, `ios`, `linux`, `android`, `windowsPhone` . This field is required. | ```dart theme={null} try { await kameleoonClient.addData([OperatingSystem(OperatingSystem.linux)]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` #### Cookie This data type is only available for Web SDKs. `Cookie` contains information about the cookie stored on the visitor's device. | Name | Type | Description | | ------- | --------------------- | --------------------------------------------------------------------------------- | | cookies | `Map` | A string object map consisting of cookie keys and values. This field is required. | Each visitor can only have one `Cookie`. Adding second `Cookie` overwrites the first one. ```dart theme={null} try { await kameleoonClient.addData([Cookie({ "my_key1": "my_value1", "my_key2": "my_value2" })]); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `int` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```dart theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. final featureFlags = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is an int representing milliseconds since the Unix epoch. final dateModified = dataFile.dateModified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `String` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `List` | A list of `Rule` objects | ```dart theme={null} // Check whether the feature flag is enabled in the current environment final isEnvironmentEnabled = featureFlag.environmentEnabled // Retrieve the key of the default variation final defaultVariationKey = featureFlag.defaultVariationKey // Retrieve the default variation object final defaultVariation = featureFlag.defaultVariation // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) final variations = featureFlag.variations // Retrieve all targeting rules associated with the feature flag final rules = featureFlag.rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```dart theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) final variations = rule.variations ``` #### Variation `Variation` contains information about the visitor's assigned variation (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | name | `String` | The name of the variation. | | key | `String` | The unique key identifying the variation. | | id | `int?` | The ID of the assigned variation (or `null` if it's the default variation). | | experimentId | `int?` | The ID of the experiment associated with the variation (or `null` if default). | | variables | `Map` | A map containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```dart theme={null} // Retrieving the variation name var variationName = variation.name // Retrieving the variation key var variationKey = variation.key; // Retrieving the variation id var variationId = variation.id; // Retrieving the experiment id var experimentId = variation.experimentId; // Retrieving the variables map var variables = variation.variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | `String` | The unique key identifying the variable. | | type | `String` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**. | | value | `dynamic` | The value of the variable, which can be of the following types: **bool**, **int**, **double**, **String**, **Map\** (json object), **\[dynamic]** (json array). | ```swift theme={null} // Retrieving the variables map var variables = variation.variables; // Variable type can be retrieved for further processing var type = variables["isDiscount"]?.type ?? ""; // Retrieving the variable value by key var isDiscount = variables["isDiscount"]?.value as bool? ?? false; // Variable value can be of different types var title = variables["title"]?.value as String? ?? ""; ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `4.0.0`. #### isReady() Use [`isReadyAsync()`](#isreadyasync) instead. On iOS and Android, [`isReady()`](#isready) may return incorrect (`false`) results even if the SDK has already been initialized with [`defaultDataFile`](#initialize-the-kameleoon-client). For mobile SDKs, the Kameleoon Client can't initialize immediately because it must retrieve the current configuration for active feature flags from a Kameleoon remote server. Call `isReady()` before triggering feature flags to check whether the SDK is ready. Alternatively, you can use a callback (see the [`runWhenReady()`](#runwhenready) method for details). ```dart theme={null} final ready = kameleoonClient.isReady(); ``` ##### Return value | Type | Description | | ---- | ----------------------------------------------------------------- | | bool | `true` if the SDK is ready; `false` if it's not yet ready to use. | #### onUpdateConfiguration() Use [`setEventHandler()`](#seteventhandler) with the `EventType.dataFileUpdate` event type instead, which also reports the update source and the data file modification date. This method was previously called `updateConfigurationHandler`, which was removed in SDK version `3.0.0` release. The `onUpdateConfiguration()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```dart theme={null} kameleoonClient.onUpdateConfiguration((timestamp) { // timestamp value contains the value of Unix time (number of seconds elapsed since January 1, 1970) when configuration was updated. }); ``` ##### Parameters | Name | Type | Description | | --------- | ---------------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `Function(int)?` | The handler that will be called when the configuration is updated using a real-time configuration event. | #### getFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Use this method to get the feature variation key for a visitor. This method takes `featureKey` as a required argument to retrieve the variation key for the specified user. If the visitor has never been associated with this feature flag, the SDK returns a randomly assigned variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, this method returns the previous variation key. If the user doesn't match any of the rules, the default value will be returned, which is defined in your customer's account. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. ```dart theme={null} String featureKey = "new_checkout"; String variationKey = ""; try { variationKey = await kameleoonClient.getFeatureVariationKey(featureKey); } on SDKNotReady { // Exception indicates that the SDK has not completed its initialization yet. } on FeatureNotFound { // The error has occurred; feature flag isn't found in current configuration. } on FeatureEnvironmentDisabled { // The feature flag is disabled for the environment. } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error. } switch(variationKey) { case 'on': // Main variation key is selected for visitorCode break; case 'alternative_variation': // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | | ---------- | ------ | ------------------------------------------------------------------------- | | featureKey | String | Key of the feature you want to expose to a user. This field is mandatory. | ##### Return value | Type | Description | | ---------------- | ----------------------------------------------------------------- | | `Future` | Variation key of the feature flag that's registered to a visitor. | ##### Exceptions thrown | Type | Description | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature ID wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't been activated on the Kameleoon side (but code implementing the feature is already deployed in the app). | | PlatformException | Exception indicating that the native plugin integration don't work correctly. | #### getActiveFeatures() * Use [`getVariations()`](#getvariations) instead. * Previously called `getFeatureListForVisitorCode`, which was removed in SDK version `4.0.0` release. `getActiveFeatures` method retrieves information about the active feature flags that are available for the visitor. ```dart theme={null} try { final activeFeatures = await kameleoonClient.getActiveFeatures(); } on KameleoonException { // Generic exception in native plugin integration occurred. } ``` ##### Return value | Type | Description | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `Future>` | Map that contains the assigned variations of the active features using the keys of the corresponding active features. | #### getFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* - Use [`getVariation()`](#getvariation) instead. - This method was previously called `obtainFeatureVariable`, which was removed in SDK version `3.0.0`. This method gets a variable value of variation key for a specific user. It takes a `featureKey` and `variableKey` as required arguments. If the visitor has never been associated with the `featureKey`, the SDK returns a randomly assigned variable value for the specified variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, this method returns the variable value for previously registered variation. If the user doesn't match any of the rules, the default variable value is returned. Ensure you set up proper error handling as shown in the example code to catch potential exceptions. ```dart theme={null} String featureKey = "feature_key"; String variableKey = "product_number"; int recommendedProductsNumber = 5; try { recommendedProductsNumber = await kameleoonClient.getFeatureVariable(featureKey, variableKey); } on SDKNotReady { // Exception indicates that the SDK has not completed its initialization yet. } on FeatureNotFound { // The error has occurred; feature flag isn't found in current configuration. } on FeatureEnvironmentDisabled { // The feature flag is disabled for the environment. } on FeatureVariableNotFound { // Requested variable not defined in Kameleoon. } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error. } setState(() { _recommendedProductsNumber = recommendedProductsNumber; }); ``` ##### Parameters | Name | Type | Description | | ----------- | ------ | -------------------------------------------------------------------------------- | | featureKey | String | Key of the feature you want to expose to a user. This field is mandatory. | | variableKey | String | Name of the variable for which you want to get a value. This field is mandatory. | ##### Return value | Type | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Future` | Value of the variation's variable that's registered to a visitor for this feature flag. Possible types: `bool`, `int`, `double`, `String`, `List`, `Map` | ##### Exceptions thrown | Type | Description | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SDKNotReady | Exception indicating that the SDK hasn't completed its initialization. | | FeatureNotFound | Exception indicating that the requested feature ID wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't been activated on the Kameleoon side (but code implementing the feature is already deployed in the app). | | FeatureEnvironmentDisabled | Exception indicating that the feature flag is off for the visitor's current environment (for example, production, staging, or development). | | FeatureVariableNotFound | Exception indicating that the specified variable wasn't found. Check that the variable key in the Kameleoon app matches the key in your code. | | PlatformException | Exception indicating that the native plugin integration works incorrectly. | #### getFeatureVariationVariables() * Use [`getVariation()`](#getvariation) instead. * This method was previously called `getFeatureAllVariables`, which was removed in SDK version `4.0.0` release. To retrieve all of a feature's variables, call this method. You can modify your feature variables in the Kameleoon app. This method takes one input parameter: `featureKey`. It returns the data as a `Map` type, as defined in the Kameleoon app. It throws an exception (`FeatureNotFound`) if the requested feature wasn't found in the SDK's internal configuration. ```dart theme={null} final featureKey = "featureKey"; final variationKey = "variationKey"; try { final allVariables = await client.getFeatureVariationVariables(featureKey, variationKey); } on SDKNotReady { // Exception indicates that the SDK has not completed its initialization yet. } on FeatureNotFound { // The error has occurred; feature flag isn't found in current configuration. } on FeatureEnvironmentDisabled { // The feature flag is disabled for the environment. } on KameleoonException { // Generic exception in native plugin integration occurred. } on Exception { // Any other error. } ``` ##### Parameters | Name | Type | Description | | ------------------------------------ | ------ | ------------------------------------------------- | | featureKey required | String | Identifier key of the feature you want to obtain. | | variationKey required | String | The key of the variation you want to obtain. | ##### Return value | Type | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Future>` | Data associated with this feature flag. The values of can be an int, double, bool, String, List or Map (depending on the type defined on the web interface). | ##### Exceptions thrown | Type | Description | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't been activated on Kameleoon's side. | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | FeatureVariationNotFound | Exception indicating that the requested variation ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the variation's corresponding experiment hasn't been activated on Kameleoon's side. | | PlatformException | Exception indicating that the native plugin integration doesn't work correctly. | # iOS SDK Source: https://docs.kameleoon.com/developer-docs/sdks/mobile-sdks/ios-sdk Integrate the Kameleoon iOS SDK to run experiments and activate feature flags in native Swift iOS applications. With the Kameleoon iOS (Swift) SDK, experiments can run and feature flags can activate on native mobile iOS applications. Integrating the SDK into Swift apps is easy, and the footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Swift SDK: 4.28.0 [Changelog](https://github.com/Kameleoon/client-swift/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the iOS SDK methods, see the [reference](#reference) section. ## Developer guide Follow these steps to install and configure the Kameleoon iOS SDK in your app for the first time. ### Getting started #### Starter kit To help with getting started, Kameleoon provides a starter kit and demo app to test the SDK. The starter kit includes a fully configured app with examples demonstrating how SDK methods can be used in an app. The starter kit, demo app, and detailed instructions are available at [Starter kit for iOS](https://github.com/Kameleoon/ios-examples) #### Prerequisites * Use Swift version 5.0 or higher on the iOS platform. Support for earlier iOS applications (including earlier Swift versions and Objective-C apps) isn't planned. A Universal Framework version is available, compatible both with x86\_64 (for the iOS Simulator) and ARM (for production deployment into the App Store). #### Installation You can install the iOS SDK using CocoaPods or Swift Package Manager: With CocoaPods, paste the following code in your Podfile and replace `YOUR_TARGET_NAME` with the value for your app: ```swift theme={null} # Podfile use_frameworks! target 'YOUR_TARGET_NAME' do pod 'kameleoonClient' end ``` Then, in a command prompt, in the `Podfile` directory, run the install command: ```custom_code theme={null} pod install ``` With Swift Package Manager, add a package dependency to your Xcode project. Select **File > Swift Packages > Add Package Dependency** and enter the repository URL: `https://github.com/Kameleoon/client-swift`. Alternatively, you can modify your `Package.swift` file directly: ```swift theme={null} dependencies: [ .package(url: "https://github.com/Kameleoon/client-swift.git", from("3.0.3")) ] ``` *** #### Additional configuration To customize the SDK's behavior, create a `kameleoon-client-swift.plist` configuration file in the root directory of your Xcode project. You can also [download a sample configuration](/assets/developer-docs/sdks/mobile-sdks/client-configs/kameleoon-client-swift.plist.zip) file. These are the keys you can set: | Key | Description | Default value | | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `refreshIntervalMinute` / `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, for the SDK to fetch the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments. If left unspecified, the default interval is 60 minutes. Additionally, a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) is available that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply them in real-time. | `60` minutes | | `dataExpirationIntervalMinute` / `data_expiration_interval_minute` optional | Designates the predefined time period, in minutes, that the SDK stores the visitor and their associated data. Each data instance is evaluated individually, allowing you to set the amount of time the SDK saves data before automatically deleting it. If no interval is specified, the SDK doesn't automatically delete data from the device. | `Date.distantFuture` | | `defaultTimeoutMillisecond` / `default_timeout_millisecond` optional | Specifies the time interval, in milliseconds, that it takes for network requests from the SDK to time out. Set the value to `30000` milliseconds (30 seconds) or more if you don't have a stable connection. Some methods have additional parameters for method-specific timeouts, but if you don't specify them explicitly, the default value is used. | `10000` ms | | `trackingIntervalMillisecond` / `tracking_interval_millisecond` optional | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had data flushed will be included in this tracking request, which is performed once per interval. The minimum value is `1000` ms and the maximum value is `5000` ms. | `1000` ms | | `environment` / `environment` optional | For customers using multi-environment experimentation and feature flagging, this option specifies which feature flag configuration to use. By default, each feature flag has the options `production`, `staging`, and `development`. If not specified, the default value is `production`. [More information](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments). | `nil` | | `isUniqueIdentifier` / `is_unique_identifier` optional | Indicates that the specified `visitorCode` is a unique identifier. | `false` | | `networkDomain` / `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `nil` | | `defaultDataFile` / `default_datafile` optional | The `default_datafile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `default_datafile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `nil` | | `activityTrackingIntervalMillisecond` / `activity_tracking_interval_millisecond` optional | Sets how often the SDK sends an activity event to extend the visitor's session. Changing it can have side effects. See [this section](#using-activitytrackingintervalmillisecond) before adjusting it. Minimum and default is `60 000` ms; any lower non-zero value is ignored and the default is applied. Set it to `0` to turn off periodic activity tracking, in which case a single activity event is sent at startup. | `60 000` ms | If you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK methods use the `visitorCode` value as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). The SDK links the flushed data to the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor through session merging. ##### Using `activityTrackingIntervalMillisecond` The `activityTrackingIntervalMillisecond` parameter controls how often the SDK sends an activity event to extend the visitor's session on the Data API. A longer interval reduces network usage and battery consumption. The default and minimum value is `60 000` ms (60 seconds); any lower non-zero value is ignored and the default is applied instead. Timers pause while the app is in the background, so the interval only advances while the app is in the foreground. Increasing this value has **significant side effects** you should weigh carefully: 1. **Visits with zero duration** * Reports derive visit duration from tracked events. If the interval is longer than the time a visitor spends in the app, the SDK may send only the startup event. The visit's start and last-activity timestamps are then (nearly) identical, so the report usually shows a duration close to zero. You can avoid this by sending other events during the session (such as conversions or page views), which also extend the recorded duration. 2. **Time elapsed since last visit (targeting)** * The "time elapsed since last visit" condition compares the current time against a previous visit's last recorded activity. That timestamp only refreshes once per interval, so its precision degrades as the interval grows: comparisons for durations close to or below the interval become unreliable. 3. **[Visit count](/user-manual/experiment-analytics/troubleshooting/data-discrepancies#how-visits-and-visitors-are-counted)** * A new visit starts after 30 minutes of inactivity. If you set the interval longer than 30 minutes, every activity event arrives after the previous session has already expired, so the SDK creates a new visit at each interval and inflates the visit count. Setting `activityTrackingIntervalMillisecond` to `0` disables periodic activity tracking entirely. In that case, the SDK sends only a single activity event at app startup. #### Initialize the Kameleoon client After you've set up the SDK in your app, you must create the Kameleoon Client. A Client is a singleton object that acts as a bridge between your app and the Kameleoon platform. It includes all the methods and properties you need to run an experiment. ```swift theme={null} import kameleoonClient let visitorCode = "visitorCode" let siteCode = "a8st4f59bj" do { // pass client configuration as an argument let config = try KameleoonClientConfig( clientId: "clientId", // optional clientSecret: "clientSecret", // optional refreshIntervalMinute: 15, // optional, 60 minutes by default dataExpirationIntervalMinute: 60*24*365, // optional, `Date.distantFuture` by default defaultTimeoutMillisecond: 10_000, // optional, 10_000 milliseconds by default trackingIntervalMillisecond: 500, // optional, 1000 milliseconds by default environment: "production", // optional isUniqueIdentifier: false, // optional, false by default. Set to true if the visitorCode corresponds to your customer's unique userId. networkDomain: "example.com", // optional, nil by default defaultDataFile: "{...}", // optional, nil by default activityTrackingIntervalMillisecond: 20_000 // optional, 15_000 milliseconds by default ) let kameleoonClient = try KameleoonClientFactory.create( siteCode: siteCode, visitorCode: visitorCode, // optional config: config // optional ) } catch KameleoonError.visitorCodeInvalid { // Provided visitor code is invalid } catch KameleoonError.siteCodeIsEmpty { // Indicates that provided site code is empty } catch { // Unexpected error occurred } do { // read client configuration from a file 'kameleoon-client-swift.plist' // visitor code isn't provided, so SDK generates a random visitor code which it will use in the future let kameleoonClient = try KameleoonClientFactory.create(siteCode: siteCode) } catch KameleoonError.visitorCodeInvalid { // Provided visitor code is invalid } catch { // Unexpected error occurred } ``` During `KameleoonClientFactory.create()`, the client starts initialization but isn't immediately ready for use. The client must retrieve the current experiment and feature flag configuration, including traffic allocation, from a Kameleoon remote server. This requires network access, which isn't always available. Until the Kameleoon Client is ready, you shouldn't run SDK methods that depend on experiment or feature flag configuration. After the first configuration is fetched, the client refreshes it periodically. If a later refresh fails, the Kameleoon client continues to use the previous configuration. You can use the `.ready` property to check whether the Kameleoon client initialization is complete. Alternatively, a **helper callback** can wrap the logic for triggering experiments and applying variations. The best approach (`.ready` or **callback**) depends on the use case. Use `.ready` when the SDK is expected to be ready soon, such as for an experiment in a dialog that users are unlikely to open during the first few seconds or minutes of app navigation. Use a callback when the SDK is likely to still be initializing, such as for an experiment shown at app launch. It's your responsibility as the app developer to ensure the client is ready before calling any methods. A good practice is to always assume that the app user should be left out of the experiment if the Kameleoon client isn't yet ready. This exclusion is easy to do, as it corresponds to the implementation of the default reference variation logic as shown in the code sample preceding. You're now ready to implement feature management and feature flags. See the [Reference](#reference) section for details about additional methods. #### Best practices for initialization and usage * Initializing [`KameleoonClient`](#create) as a singleton as early as possible after the app starts is recommended, as initialization may take some time. Since initialization is asynchronous, it doesn't block or delay the app startup process. * Before using `KameleoonClient`, verify that it's initialized by calling the [`runWhenReady`](#runwhenready) method. Otherwise, attempts to use the client before it's ready will result in errors. * ⚠️ Most key methods may throw errors, so proper exception handling is required. Be sure to review the documentation for each method you use to understand its potential errors. ```swift theme={null} // Initialize `KameleoonClient` on application startup and use it as a singleton later do { let kameleoonClient = try KameleoonClientFactory.create(siteCode: ""); } catch {} // Example: Apply a discount percentage based on a feature flag variable's value func applyDiscountIfApplicable() { client.runWhenReady(timeoutMilliseconds: 1000) { ready in guard ready else { return } if let variation = try? client.getVariation(featureKey: "discount"), let discount = variation.variables["discount_value"]?.value as? Double { applyDiscount(value: discount) } } } ``` ```swift theme={null} // Initialize `KameleoonClient` on application startup and use it as a singleton later do { let kameleoonClient = try KameleoonClientFactory.create(siteCode: ""); } catch {} // Example: Apply a discount percentage based on a feature flag variable's value func applyDiscountIfApplicable() async throws { try await client.runWhenReady(timeoutMilliseconds: 1000) if let variation = try client.getVariation(featureKey: "discount"), let discount = variation.variables["discount_value"]?.value as? Double { applyDiscount(value: discount) } } ``` #### Activating a feature flag ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices, use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `goalId` parameter. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```swift title="Device A" theme={null} // In this example, Custom data with index `90` was set to "Visitor" scope in Kameleoon. let visitorScopeCustomDataIndex = 90 kameleoonClient.addData(CustomData(id: visitorScopeCustomDataIndex, values: "your data")) kameleoonClient.flush() ``` ```swift title="Device B" theme={null} // Before working with the data, call `getRemoteVisitorData`. kameleoonClient.getRemoteVisitorData { result in // After calling, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available to target and track the visitor. } ``` ```swift title="Device A" theme={null} // In this example, Custom data with index `90` was set to "Visitor" scope in Kameleoon. let visitorScopeCustomDataIndex = 90 kameleoonClient.addData(CustomData(id: visitorScopeCustomDataIndex, values: "your data")) kameleoonClient.flush() ``` ```swift title="Device B" theme={null} // Before working with the data, call `getRemoteVisitorData`. try await kameleoonClient.getRemoteVisitorData() // After calling, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available to target and track the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`getRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `getRemoteVisitorData()` with passed `isUniqueIdentifier=true` to [`KameleoonClientConfig`](#additional-configuration) - to retrieve data for all linked visitors. * [`trackConversion()`](#trackconversion) or [`flush()`](#flush) with passed `isUniqueIdentifier=true` to `KameleoonClientConfig` - to track some data for specific visitor that is associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```swift theme={null} // In this example, `91` represents the Custom Data's index, // configured as a unique identifier in Kameleoon. let mappingIndex = 91; let featureKey = "ff123"; // 0. Initializing anonymous KameleoonClient // Assume `anonymousVisitorCode` is the randomly generated ID for the visitor. let anonymousKameleoonClient = KameleoonClientFactory.create( siteCode: siteCode, visitorCode: anonymousVisitorCode ) anonymousKameleoonClient.runWhenReady { result in // ... } // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. let anonymousVariation = anonymousKameleoonClient.getVariation(featureKey) // 2. After the visitor is authenticated // Assume `userId` is the visitor code of the authenticated visitor. anonymousKameleoonClient.addData(CustomData(id: mappingIndex, values: userId)) anonymousKameleoonClient.flush(instant: true) KameleoonClient userKameleoonClient = KameleoonClientFactory.create( siteCode: siteCode, visitorCode: userId, config: KameleoonClientConfig( isUniqueIdentifier: true // Indicate that `userId` is a unique identifier ) ) userKameleoonClient.runWhenReady { result in // ... } // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. let userVariation = userKameleoonClient.getVariation(featureKey: featureKey) let isSameVariation = userVariation.key == anonymousVariation.key // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. userKameleoonClient.trackConversion(goalId: 123, revenue: 10.0); // Additionally, the linked visitors will share all fetched remote visitor data. userKameleoonClient.getRemoteVisitorData { result in // ... } ``` ```swift theme={null} // In this example, `91` represents the Custom Data's index, // configured as a unique identifier in Kameleoon. let mappingIndex = 91; let featureKey = "ff123"; // 0. Initializing anonymous KameleoonClient // Assume `anonymousVisitorCode` is the randomly generated ID for the visitor. let anonymousKameleoonClient = KameleoonClientFactory.create( siteCode: siteCode, visitorCode: anonymousVisitorCode ) try await anonymousKameleoonClient.runWhenReady() // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. let anonymousVariation = anonymousKameleoonClient.getVariation(featureKey) // 2. After the visitor is authenticated // Assume `userId` is the visitor code of the authenticated visitor. anonymousKameleoonClient.addData(CustomData(id: mappingIndex, values: userId)) anonymousKameleoonClient.flush(instant: true) KameleoonClient userKameleoonClient = KameleoonClientFactory.create( siteCode: siteCode, visitorCode: userId, config: KameleoonClientConfig( isUniqueIdentifier: true // Indicate that `userId` is a unique identifier ) ) try await userKameleoonClient.runWhenReady() // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. let userVariation = userKameleoonClient.getVariation(featureKey: featureKey) let isSameVariation = userVariation.key == anonymousVariation.key // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. userKameleoonClient.trackConversion(goalId: 123, revenue: 10.0); // Additionally, the linked visitors will share all fetched remote visitor data. try await userKameleoonClient.getRemoteVisitorData() ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor automatically generated by the SDK is used. The visitor code can be retrieved with the [`getVisitorCode()`](#getvisitorcode) method. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```swift theme={null} try? kameleoonClient.addData(CustomData(id: index, values: "newVisitorCode")) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```swift theme={null} // The `none` log level does not allow logging. KameleoonLogger.logLevel = .none // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. KameleoonLogger.logLevel = .error // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. KameleoonLogger.logLevel = .warning // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. KameleoonLogger.logLevel = .info // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to support // to assist with internal troubleshooting. KameleoonLogger.logLevel = .debug ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```swift theme={null} public struct CustomLogger: Logging { let customLogger = Logger(subsystem: "com.yourcompany.app", category: "app") public func log(level: LogLevel, message: String) { switch level { case .error: customLogger.error("\(message)") case .warning: customLogger.warning("\(message)") case .info: customLogger.info("\(message)") case .debug: customLogger.debug("\(message)") default: break } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. KameleoonLogger.logLevel = .debug // Optional, defaults to `LogLevel.WARNING`. KameleoonLogger.logger = CustomLogger() ``` ### Passing the visitor code to a WebView In some cases, you may need to pass the **visitor code** from the native app to a WebView that uses [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation) or the web [JavaScript](/developer-docs/sdks/web-sdks/js-sdk) or [React](/developer-docs/sdks/web-sdks/react-js-sdk) SDKs. The following example demonstrates the recommended way to achieve this: ```swift theme={null} struct WebView: UIViewRepresentable { let url: URL let kameleoonClient: KameleoonClient private let kameleoonCookieName = "kameleoonVisitorCode" func makeUIView(context: Context) -> WKWebView { let config = WKWebViewConfiguration() let webView = WKWebView(frame: .zero, configuration: config) let cookieStore = webView.configuration.websiteDataStore.httpCookieStore let cookie = makeVisitorCookie() cookieStore.setCookie(cookie) { webView.load(URLRequest(url: url)) } return webView } func updateUIView(_ webView: WKWebView, context: Context) {} private func makeVisitorCookie() -> HTTPCookie { let properties: [HTTPCookiePropertyKey: Any] = [ .domain: ".example.com", .path: "/", .name: kameleoonCookieName, .value: kameleoonClient.visitorCode, .secure: true, .expires: Date(timeIntervalSinceNow: 60 * 60 * 24 * 365) ] return HTTPCookie(properties: properties)! } } ``` ```swift theme={null} struct WebView: View { let url: URL let kameleoonClient: KameleoonClient private let websiteDataStore: WKWebsiteDataStore private let kameleoonCookieName = "kameleoonVisitorCode" @State private var page: WebPage init(url: URL, kameleoonClient: KameleoonClient) { self.url = url self.kameleoonClient = kameleoonClient let websiteDataStore = WKWebsiteDataStore.default() var configuration = WebPage.Configuration() configuration.websiteDataStore = websiteDataStore self.websiteDataStore = websiteDataStore _page = State(initialValue: WebPage(configuration: configuration)) } var body: some View { WebView(page) .webViewBackForwardNavigationGestures(.enabled) .task(id: url) { let cookieStore = websiteDataStore.httpCookieStore let properties: [HTTPCookiePropertyKey: Any] = [ .domain: ".example.com", .path: "/", .name: kameleoonCookieName, .value: kameleoonClient.visitorCode, .secure: true ] if let cookie = HTTPCookie(properties: properties) { await cookieStore.setCookie(cookie) } page.load(URLRequest(url: url)) } } } ``` ### Apple privacy compliance Starting May 1, 2024, Apple requires apps that include a third-party SDK, such as the Kameleoon iOS SDK, that include a privacy manifest file. The latest version of the SDK is code-signed, compliant, and includes a valid manifest file with the SDK. No action is required if you're using the Kameleoon iOS SDK version 4.2 or later. ## Reference This is the full reference documentation for the Kameleoon iOS (Swift) SDK. ### Initialization Once you have [installed the SDK](#installation) in your app, you must initialize Kameleoon. All of your app's interactions with the SDK, such as triggering an experiment, are accomplished using this Kameleoon client object. #### create() Call this method before any others to initialize the SDK. This method is in `KameleoonClientFactory`. `create()` creates an instance of `KameleoonClient` to manage all interactions between the SDK and your app. You can customize the SDK's behavior (for example, the environment, the credentials) by providing a [configuration object](#additional-configuration). Otherwise, the SDK tries to find your configuration file and will use it instead. ```swift theme={null} let visitorCode = "visitorCode" let siteCode = "a8st4f59bj" do { // pass client configuration as an argument let config = try KameleoonClientConfig( clientId: "clientId", // optional clientSecret: "clientSecret", // optional refreshIntervalMinute: 15, // optional, 60 minutes by default dataExpirationIntervalMinute: 60*24*365, // optional, `Date.distantFuture` by default defaultTimeoutMillisecond: 10_000, // optional, 10_000 milliseconds by default trackingIntervalMillisecond: 500, // optional, 1000 milliseconds by default environment: "production", // optional isUniqueIdentifier: false, // optional, false by default. Set to true if the visitorCode corresponds to your customer's unique userId. networkDomain: "example.com", // optional, nil by default defaultDataFile: "{...}", // optional, nil by default activityTrackingIntervalMillisecond: 20_000 // optional, 15_000 milliseconds by default ) let kameleoonClient = try KameleoonClientFactory.create( siteCode: siteCode, visitorCode: visitorCode, // optional config: config // optional ) } catch KameleoonError.visitorCodeInvalid { // Provided visitor code is invalid } catch KameleoonError.siteCodeIsEmpty { // Indicates that provided site code is empty } catch { // Unexpected error occurred } do { // read client configuration from a file 'kameleoon-client-swift.plist' // visitor code isn't provided, so SDK generates a random visitor code which will be used in the future let kameleoonClient = KameleoonClientFactory.create(siteCode: siteCode) } catch KameleoonError.visitorCodeInvalid { // Provided visitor code is invalid } catch KameleoonError.siteCodeIsEmpty { // Indicates that provided site code is empty } catch { // Unexpected error occurred } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | siteCode required | `String` | A [unique key](/user-manual/faq#how-do-i-find-my-sitecode) identifying the Kameleoon project used with the SDK. | | | visitorCode optional | `String?` | An optional visitor identifier. If available, use your internal **user ID**; otherwise, the SDK will generate one automatically. | `nil` | | config optional | `KameleoonClientConfig?` | Optional SDK configuration. If provided, it's used instead of reading from an external [configuration file](#additional-configuration). If not provided, the SDK attempts to read the file, but if the file is missing, it falls back to default behavior. | `nil` | ##### Return value | Type | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | `KameleoonClient` | An instance of the `KameleoonClient` class that your app can then use to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `KameleoonError.visitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `KameleoonError.siteCodeIsEmpty` | Exception indicating that the specified site code is empty string which is invalid value. | #### ready For mobile SDKs, the Kameleoon Client's initialization isn't immediate because it must retrieve the current configuration for active experiments and feature flags from a Kameleoon remote server. Read the `ready` property before triggering an experiment to check whether the SDK is ready. Alternatively, you can use [`runWhenReady()`](#runwhenready) with a callback. ```swift theme={null} let ready = kameleoonClient.ready ``` ##### Return value | Name | Type | Description | | ----- | ---- | ----------------------------------------------------------------------------- | | ready | Bool | `true` if the SDK is fully initialized; `false` if it's not yet ready to use. | #### runWhenReady() * 🔄 *Performs an asynchronous request (if the configuration is outdated or missing)* For mobile SDKs, the `KameleoonClient` can't initialize immediately, as it needs to perform a server call to retrieve the current configuration for all feature flags. Use the [`runWhenReady()`](#runwhenready) method to wait until the client is ready for use. Additionally, you can set a maximum timeout period to control how long the client will wait before it becomes ready. If `ready` is `true`, the `KameleoonClient` is fully initialized, and feature flags will be evaluated and assigned their respective variations. If the result is `false` or a timeout occurs, the initialization won't complete. The callback or asynchronous code should handle applying the reference variation, as a timeout will exclude the user from the feature flag. Since the initial configuration may require a server call, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the `KameleoonClient` is fully initialized and ready for use. * Use asynchronous operations. ```swift theme={null} func applyRecommendedProductsVariation() { let defaultValue = 5 // Default control number for recommended products kameleoonClient.runWhenReady(timeoutMilliseconds: 1000) { ready in guard ready else { applyVariation(recommendedProductsNumber: defaultValue) return } let variation = try? kameleoonClient.getVariation(featureKey: "featureKey") let recommendedProductsNumber = variation.variables["recommendedProductsNumber"]?.value as? Int ?? defaultValue applyVariation(recommendedProductsNumber: recommendedProductsNumber) } } ``` ##### Arguments | Name | Type | Description | Default | | ------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | timeoutMilliseconds optional | `Int` | Timeout for the initialization process | [`defaultTimeoutMillisecond`](#create) or [`default_timeout_millisecond`](#additional-configuration) | | callback required | `(Bool) -> Void` | Callback object. It's a lambda expression that will get a `Bool` argument representing whether the `KameleoonClient` became ready before the timeout was reached. | | A common mistake is calling `async` functions with `try?` or inside a **nested** `do-catch` block **without rethrowing `CancellationError`**. This can interfere with task cancellation, causing unexpected behavior. To ensure correct task cancellation, avoid using `try?` or `do-catch` around `async` functions unless you explicitly rethrow `CancellationError`. ```swift theme={null} func applyRecommendedProductsVariation() async throws { let defaultValue = 5 // Default control number for recommended products try await kameleoonClient.runWhenReady(timeoutMilliseconds: 1000) let variation = try kameleoonClient.getVariation(featureKey: "featureKey") let recommendedProductsNumber = variation.variables["recommendedProductsNumber"]?.value as? Int ?? defaultValue applyVariation(recommendedProductsNumber: recommendedProductsNumber) } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | ----- | -------------------------------------- | ---------------------------------------------------------------------------------------------------- | | timeoutMilliseconds optional | `Int` | Timeout for the initialization process | [`defaultTimeoutMillisecond`](#create) or [`default_timeout_millisecond`](#additional-configuration) | ##### Errors thrown | Type | Description | | ---------------------------- | -------------------------------------------------------------- | | `KameleoonError.sdkNotReady` | Exception indicating that the SDK isn't fully initialized yet. | ### Feature flags and variations #### isFeatureActive() * *📨 Sends Tracking Data to Kameleoon (depending on the `track` parameter)* This method was previously called `activateFeature`, which was removed in SDK version `4.0.0`. To activate a feature toggle, call this method. `isFeatureActive()` accepts `featureKey` as a required argument to check if the specified feature will be active for a visitor. If a visitor has never been associated with this feature flag, this method returns a random boolean value (`true` if the user should be shown this feature, otherwise `false`). If the visitor is already registered with this feature flag, the method returns the previous `featureFlag` value. Ensure you implement error handling as shown in the example code to catch potential errors. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```swift theme={null} let featureKey = "new_checkout" var hasNewCheckout = false do { hasNewCheckout = try kameleoonClient.isFeatureActive(featureKey: featureKey) // disabling tracking hasNewCheckout = kameleoonClient.isFeatureActive(featureKey: featureKey, track: false); } catch { switch error { case KameleoonError.sdkNotReady: // Exception indicating that the SDK has not completed its initialization yet. hasNewCheckout = false case KameleoonError.Feature.notFound: // The feature key is not in the configuration file that has been fetched by the SDK. hasNewCheckout = false default: // Any other error. hasNewCheckout = false } } if hasNewCheckout { // Implement new checkout code here } ``` The `isFeatureActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Arguments | Name | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------- | | featureKey | `String` | The key of the feature you want to expose to a user. This field is required. | | track | `Bool` | An optional parameter to enable or turn off feature evaluation tracking (`true` by default). | ##### Return value | Type | Description | | ------ | ------------------------------------------------------- | | `Bool` | Value of the feature that's registered for the visitor. | ##### Errors thrown | Type | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonError.sdkNotReady` | Exception indicating that the SDK isn't fully initialized. | | `KameleoonError.Feature.notFound` | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception usually means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed in the web-app). | #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```swift theme={null} let featureKey = "featureKey" var variation: Types.Variation? do { variation = try kameleoonClient.getVariation(featureKey: featureKey) // disabling tracking variation = kameleoonClient.getVariation(featureKey: featureKey, track: false); } catch { switch error { case KameleoonError.sdkNotReady: // Exception indicating that the SDK has not completed its initialization yet. case KameleoonError.Feature.notFound: // The feature key is not in the configuration file that has been fetched by the SDK. case KameleoonError.Feature.environmentDisabled: // The feature flag is disabled for the environment. default: // Any other error. } } let title = variation?.variables["title"].value switch variation?.key { case "on": // Main variation key is selected for visitorCode case "alternative_variation": // Alternative variation key default: // Default variation key } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `featureKey` required | `String` | Key of the feature you want to expose to a visitor. | | | `track` optional | `Bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Errors thrown | Type | Description | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `KameleoonError.visitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `KameleoonError.Feature.notFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `KameleoonError.Feature.environmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `onlyActive` and `track` as optional arguments. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```swift theme={null} do { let variations = kameleoonClient.getVariations(); // only active variations let variations = kameleoonClient.getVariations(onlyActive: true); // disable tracking let variations = kameleoonClient.getVariations(track: false); } catch KameleoonError.sdkNotReady { // Exception indicating that the SDK has not completed its initialization yet. } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------- | ------- | | `onlyActive` optional | `Bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `Bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Dict` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Errors thrown | Type | Description | | ---------------------------- | --------------------------------------------------- | | `KameleoonError.sdkNotReady` | Indicates that the SDK isn't yet fully initialized. | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. ```swift theme={null} let experimentId = 9516 do { // Forcing the variation "on" for the experiment 9516 for the visitor try kameleoonClient.setForcedVariation(experimentId: experimentId, variationKey: "on") // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment try kameleoonClient.setForcedVariation(experimentId: experimentId, variationKey: "on", forceTargeting: false) // Resetting the forced variation for the experiment 9516 for the visitor try kameleoonClient.setForcedVariation(experimentId: experimentId, variationKey: nil); } catch { // Handling the KameleoonError, KameleoonError.Feature and common Error } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `experimentId` required | `Int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `String` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `nil`, the forced variation will be reset. | | | `forceTargeting` optional | `Bool` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Errors thrown | Type | Description | | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonError.sdkNotReady` | Indicates that the SDK isn't yet fully initialized. | | `KameleoonError.Feature.experimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonError.Feature.variationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonError/KameleoonError.Feature`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```swift theme={null} do { try kameleoonClient.evaluateAudiences(); } catch { // Handling the errors } ``` ##### Errors thrown | Type | Description | | ---------------------------- | --------------------------------------------------- | | `KameleoonError.sdkNotReady` | Indicates that the SDK isn't yet fully initialized. | In most cases, only the basic error, `KameleoonError/KameleoonError.Feature`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### getFeatureList() If you want to iterate over all feature flags and call [`getVariation()`](#getvariation) on each, use the [`getVariations()`](#getvariations) method instead. Returns a list of feature flag keys currently available for the SDK. ```swift theme={null} let allFeatureList = kameleoonClient.getFeatureList() ``` ##### Return value | Type | Description | | ---------- | ------------------------- | | `[String]` | List of feature flag keys | #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```swift theme={null} do { let dataFile = try kameleoonClient.getDataFile() } catch KameleoonError.sdkNotReady { // Exception indicates that the SDK has not completed its initialization yet. } catch { // Handling the KameleoonError, KameleoonError.Feature and common Error } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ##### Errors thrown | Type | Description | | ---------------------------- | --------------------------------------------------- | | `KameleoonError.sdkNotReady` | Indicates that the SDK isn't yet fully initialized. | ### Goals #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track conversions. This method requires `goalId` to track conversion on this particular [goal](/user-manual/assets/goals/create-a-goal). In addition, this method also accepts `revenue`, `metadata` and `negative` arguments. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```swift theme={null} let goalId = 83023 kameleoonClient.trackConversion(goalId: goalId, revenue: 10.0) kameleoonClient.trackConversion(goalId: goalId, revenue: 10.0, metadata: CustomData(id: 1, values: "metadata")) ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ------- | | `goalId` required | `Int` | ID of the goal. | | | `revenue` optional | `Double` | Revenue of the conversion. | `0` | | `negative` optional | `Bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData... / [CustomData]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `[]` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```swift theme={null} kameleoonClient.addData([CustomData(id: 5, values: "Credit Card"), CustomData(id: 9, "Express Delivery")]); kameleoonClient.trackConversionWithOptParams(goalId: 1000, metadata: CustomData(5, "Amex Credit Card")); ``` ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `nil` as the `handler` removes the current handler for the selected event type. ```swift theme={null} final class CustomDataFileUpdateHandler: DataFileUpdateHandler { func onUpdate(event: DataFileUpdateEvent) { let source = event.source // .polling or .streaming let dateModified = event.dateModified // Data file modification date in milliseconds. // React to the data file update. } } kameleoonClient.setEventHandler(.dataFileUpdate, handler: CustomDataFileUpdateHandler()) // Clear the handler. kameleoonClient.setEventHandler(.dataFileUpdate, handler: nil) ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `source` | `DataFileUpdateEvent.Source` | The update source. [`.polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`.streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `Int` | The modification date of the updated data file, in milliseconds. | ```swift theme={null} import Foundation final class CustomHttpRequestHandler: HttpRequestHandler { func onRequestSucceeded(requestType: HttpRequestType, httpStatus: Int, duration: TimeInterval) { // The SDK request completed successfully. } func onRequestFailed(requestType: HttpRequestType, failure: HttpRequestFailure, duration: TimeInterval) { let reason = failure.reason // .httpStatus, .error, or .cancelled let httpStatus = failure.httpStatus let cause = failure.cause // The SDK request failed. } } kameleoonClient.setEventHandler(.httpRequest, handler: CustomHttpRequestHandler()) // Clear the handler. kameleoonClient.setEventHandler(.httpRequest, handler: nil) ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `onRequestSucceeded()` for successful responses and `onRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `onRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `onRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `.dataFile`, `.tracking`, `.remoteVisitorData`, and `.remoteData`. | | `httpStatus` | `Int` | The HTTP status code returned by the request. Passed only to `onRequestSucceeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `onRequestFailed()`. | | `duration` | `TimeInterval` | The request duration in seconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailure.Reason` | The failure reason. Possible values are `.httpStatus`, `.error`, and `.cancelled`. | | `httpStatus` | `Int?` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `nil` for exception and cancellation failures. | | `cause` | `Error?` | The exception (error) that caused the request to fail. This value is `nil` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `EventType` | The SDK event type to handle. Use `.dataFileUpdate` to react to SDK data file updates, or `.httpRequest` to react to completed SDK HTTP requests. | | `handler` required | `Handler?` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `.dataFileUpdate`, or an `HttpRequestHandler` for `.httpRequest`. Pass `nil` to remove the current handler. | ### Visitor data #### visitorCode Returns unique visitor code used in SDK. ```swift theme={null} let visitorCode = kameleoonClient.visitorCode ``` ##### Return value | Type | Description | | -------- | ------------------------------------------------------ | | `String` | String representing a unique visitor code used in SDK. | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`trackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`getVariation()`](#getvariation) and [`getVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```swift theme={null} // Add a single data item (tracked by default) kameleoonClient.addData(CustomData(index: 1, values: "value")) // Add multiple data items (tracked by default) kameleoonClient.addData( CustomData(index: 20, values: "value"), Geolocation(country: "France") ) // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoonClient.addData( track: false, CustomData(index: 20, values: "value"), Geolocation(country: "France") ) ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `track` optional | `Bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `data` required | `KameleoonData... / [KameleoonData]` | Collection of Kameleoon data types. | | #### flush() * 📨 *Sends Tracking Data to Kameleoon* The `flush()` method collects the Kameleoon data linked to the visitor. It then sends a tracking request, along with all data added using the `addData` method that hasn't yet been sent using one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). `flush()` is non-blocking as the server call is made asynchronously. `flush` provides control over when data associated with a given `visitorCode` is sent to the servers. For instance, if `addData()` is called a dozen times, sending data to the server each time `addData()` is invoked would be inefficient. Call `flush()` once. ```swift theme={null} kameleoonClient.addData(CustomData(id: 20, values: "true", "20")) kameleoonClient.addData(Conversion(goalId: 32, revenue: 10.0, negative: false)) kameleoonClient.flush() // Interval tracking (most performant way for tracking) kameleoonClient.flush(instant: true) // Instant tracking ``` ##### Parameters | Name | Type | Description | | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | instant | `Bool` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the default tracking interval (`false`) set with the SDK parameter `trackingIntervalMillisecond` in [`KameleoonClientConfig`](/developer-docs/sdks/mobile-sdks/ios-sdk#initialize-the-kameleoon-client) or [`tracking_interval_millisecond`](#additional-configuration). This field is optional. | ##### Errors thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------ | | `KameleoonError.sdkNotReady` | Error indicating that the SDK hasn't completed its initialization. | #### getRemoteData() * 🔄 *Performs an asynchronous request* This method was previously called `retrieveDataFromRemoteSource`, which was removed in SDK version `4.0.0` release. Use this method to retrieve data from a remote Kameleoon server based on the active `siteCode` and the `key` argument (or the active `visitorCode` if the `key` is omitted). The `visitorCode` and `siteCode` are specified in `KameleoonClientFactory.create()`. Data can be stored quickly and conveniently on highly scalable remote servers using the Kameleoon Data API. The app can then retrieve the data using this method. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched. * Use asynchronous operations. ```swift theme={null} struct Test1: Decodable { let value: String private enum CodingKeys: String, CodingKey { case value = "json_value" } } kameleoonClient.getRemoteData(key: "test") { (result: Result) in switch result { case .success(let test1): // test1 is a decoded value for Test1 type case .failure: // error includes information about request's failure } } kameleoonClient.getRemoteData(key: "test") { (data: Result) in switch result { case .success(let data): if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { print(json) } case .failure: // error includes information about request's failure } } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | --------------------------------------- | --------------------------------------------------------- | ------- | | key optional | `String` | The key that the data you want to get is associated with. | `""` | | completion required | `(Result) -> Void` | The callback that processes the received data. | | A common mistake is calling `async` functions with `try?` or inside a **nested** `do-catch` block **without rethrowing `CancellationError`**. This can interfere with task cancellation, causing unexpected behavior. To ensure correct task cancellation, avoid using `try?` or `do-catch` around `async` functions unless you explicitly rethrow `CancellationError`. ```swift theme={null} struct Test1: Decodable { let value: String private enum CodingKeys: String, CodingKey { case value = "json_value" } } Task { do { let test1: Test1 = try await kameleoonClient.getRemoteData(key: "test") // test1 is a decoded value for Test1 type } catch { // handle error } } Task { do { let data: Data = try await kameleoonClient.getRawRemoteData(key: "test") if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { print(json) } } catch { // handle error } } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------- | -------- | --------------------------------------------------------- | ------- | | key optional | `String` | The key that the data you want to get is associated with. | `""` | ##### Return value | Type | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | `Decodable` | A [`Decodable`](https://developer.apple.com/documentation/swift/decodable) object containing the parsed fetched value. | ##### Errors thrown | Type | Description | | ------- | ------------------------------------------------------ | | `Error` | Indicates that the request has failed due to an error. | #### getRemoteVisitorData() * 🔄 *Performs an asynchronous request* `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the visitor from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as custom data collected during previous visits. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). Checking only for failed results is recommended. However, if necessary, it can be verified that the data has been added to the visitor and is available for targeting purposes (or for debugging, though using [logging](#logging) is better for debugging). Additionally, data can be managed manually if the `addData=false` parameter is passed. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched and added to the visitor. * Use asynchronous operations. ```swift theme={null} // Fetch visitor data and automatically add it kameleoonClient.getRemoteVisitorData { result in switch result { case .success(let visitorData): // visitorData includes all retrieved data that was added for the visitor case .failure: // Handle the error, which contains information about the request failure } } // Fetch visitor data without automatically adding it kameleoonClient.getRemoteVisitorData(addData: false) { result in switch result { case .success(let visitorData): // visitorData includes all retrieved data that was added for the visitor case .failure: // Handle the error, which contains information about the request failure } } // Fetch a custom list of data types let filter = Types.RemoteVisitorDataFilter( previousVisitAmount: 25, currentVisit: true, conversions: true, experiments: true, geolocation: true ) kameleoonClient.getRemoteVisitorData(filter: filter) { result in switch result { case .success(let visitorData): // visitorData includes all retrieved data that was added for the visitor case .failure: // Handle the error, which contains information about the request failure } } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | filter optional | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, the method retrieves `CustomData` from the current and latest previous visit. | `RemoteVisitorDataFilter.default` | | addData optional | `Boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. | `true` | | completion required | `(Result<[KameleoonData], Error>) -> Void` | The callback that processes the received visitor data. | | A common mistake is calling `async` functions with `try?` or inside a **nested** `do-catch` block **without rethrowing `CancellationError`**. This can interfere with task cancellation, causing unexpected behavior. To ensure correct task cancellation, avoid using `try?` or `do-catch` around `async` functions unless you explicitly rethrow `CancellationError`. ```swift theme={null} // Fetch visitor data and automatically add it Task { do { let visitorData = try await kameleoonClient.getRemoteVisitorData() // visitorData includes all retrieved data that was added for the visitor } catch { // Handle the error, which contains information about the request failure } } // Fetch visitor data without automatically adding it Task { do { let visitorData = try await kameleoonClient.getRemoteVisitorData(addData: false) // visitorData includes all retrieved data but was not added for the visitor } catch { // Handle the error, which contains information about the request failure } } // Fetch a custom list of data types Task { do { let filter = Types.RemoteVisitorDataFilter( previousVisitAmount: 25, currentVisit: true, conversions: true, experiments: true, geolocation: true ) let visitorData = try await kameleoonClient.getRemoteVisitorData(filter: filter) // visitorData includes all retrieved data based on the specified filter } catch { // Handle the error, which contains information about the request failure } } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | filter optional | `RemoteVisitorDataFilter` | Filter that selects which data should be retrieved from visit history. By default, the method retrieves `CustomData` from the current and latest previous visit. | `RemoteVisitorDataFilter.default` | | addData optional | `Boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. | `true` | ##### Return value | Type | Description | | ----------------- | ----------------------------------------------------- | | `[KameleoonData]` | An array containing the fetched data for the visitor. | ##### Errors thrown | Type | Description | | ------- | ------------------------------------------------------ | | `Error` | Indicates that the request has failed due to an error. | ##### Using parameters with RemoteVisitorDataFilter The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to `5` and `conversions` to `true`. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `Types.RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `Int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `Bool` | If true, current visit data will be retrieved | `true` | | customData optional | `Bool` | If true, custom data will be retrieved. | `true` | | conversions optional | `Bool` | If true, conversion data will be retrieved. | `false` | | experiments optional | `Bool` | If true, experiment data will be retrieved. | `false` | | geolocation optional | `Bool` | If true, geolocation data will be retrieved. | `false` | | kcs optional | `Bool` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `Bool` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. `visitorCode` is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `Bool` | If true, personalization data will be retrieved. `personalization` is required for the personalization condition | `false` | | cbs optional | `Bool` | If true, Contextual Bandit score data will be retrieved. | `false` | #### getVisitorWarehouseAudience() * 🔄 *Performs an asynchronous request* Retrieves all audience data associated with the visitor in your data warehouse. The optional `warehouseKey` parameter is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. Since a server call is required, this mechanism is asynchronous. Therefore, you should either: * Provide a `completion` callback as an argument to the method to ensure you are notified when the data has been successfully fetched and added to the visitor. * Use coroutines for asynchronous handling. Checking only for failed results is recommended. However, if necessary, it can be verified that the data has been added to the visitor and is available for targeting purposes (or for debugging, though using [logging](#logging) is better for debugging). ```swift theme={null} // Fetch visitor warehouse audience data kameleoonClient.getVisitorWarehouseAudience(customDataIndex: 10) { result in switch result { case .success(let customData): // Data was added. You can access it from `customData` case .failure: // Handle the error, which contains information about the request failure } } // Fetch visitor warehouse audience data with a specific warehouse key kameleoonClient.getVisitorWarehouseAudience( warehouseKey: "warehouseKey", customDataIndex: 10 ) { result in switch result { case .success(let customData): // Data was added. You can access it from `customData` case .failure: // Handle the error, which contains information about the request failure } } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------- | | warehouseKey optional | `String` | The unique key to identify the warehouse data (usually your internal user ID). | `""` | | customDataIndex required | `Int` | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | | completion | `(Result) -> Void` | The callback that processes the received data. | | A common mistake is calling `async` functions with `try?` or inside a **nested** `do-catch` block **without rethrowing `CancellationError`**. This can interfere with task cancellation, causing unexpected behavior. To ensure correct task cancellation, avoid using `try?` or `do-catch` around `async` functions unless you explicitly rethrow `CancellationError`. ```swift theme={null} // Fetch visitor warehouse audience data Task { do { let customData = try await kameleoonClient.getVisitorWarehouseAudience(customDataIndex: 10) // Data was added. You can access it from `customData` } catch { // Handle the error, which contains information about the request failure } } // Fetch visitor warehouse audience data with a specific warehouse key Task { do { let customData = try await kameleoonClient.getVisitorWarehouseAudience( warehouseKey: "warehouseKey", customDataIndex: 10 ) // Data was added. You can access it from `customData` } catch { // Handle the error, which contains information about the request failure } } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- | ------- | | warehouseKey optional | `String` | The unique key to identify the warehouse data (usually your internal user ID). | `""` | | customDataIndex required | `Int` | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | ##### Return value | Type | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | `CustomData` | A [`CustomData`](#customdata) entry added for a visitor. Generally, you can ignore this value, but it may be useful for testing purposes. | ##### Errors thrown | Type | Description | | ------- | ------------------------------------------------------ | | `Error` | Indicates that the request has failed due to an error. | #### setLegalConsent() You must use this method to specify whether the visitor has given legal consent to use their personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ##### Parameters | Name | Type | Description | | ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | legalConsent | boolean | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent; `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | ```swift theme={null} kameleoonClient.setLegalConsent(true) ``` ### Data types This section lists the data types supported by Kameleoon. Several standard data types are provided, as well as the `CustomData` type for defining custom data types. #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | ------------------------------ | ----------------------------------------------- | ------- | | `goalId` required | `Int` | ID of the goal. | | | `revenue` optional | `Double` | Revenue of the conversion | `0` | | `negative` optional | `Bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData... / [CustomData]` | Metadata of the conversion. | `[]` | ```swift theme={null} kameleoonClient.addData(Conversion(goalId: 32, revenue: 10.0, negative: false)) kameleoonClient.addData(Conversion(goalId: 32, metadata: CustomData(id: 1, values: "metadata"))) ``` #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. `CustomData` can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | | | ---------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | | index/name required | `Int`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `Bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | values required | `String...` or `[String]` | Values of the custom data to be stored. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing `CustomData`. * The custom data `index` can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the **Use this data only locally for targeting purposes** option when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```swift theme={null} kameleoonClient.addData(CustomData(index: 1, values: "value")) // With several values kameleoonClient.addData(CustomData(index: 1, values: "value 1", "value 2")) // To set the 'overwrite' flag to false kameleoonClient.addData(CustomData(index: 1, overwrite: false, values: ["first value", "second value"])) // To use a name instead of the index kameleoonClient.addData(CustomData(name: "my-custom-data", values: "value")) ``` #### Device Since iOS SDK `4.14.0`, the `Device` is automatically detected based on the value of [`UIDevice.current.userInterfaceIdiom`](https://developer.apple.com/documentation/uikit/uiuserinterfaceidiom). However, you can still manually override it if needed. Store information about the user's device. | Name | Type | Description | | ------ | ------ | ---------------------------------------------------------------------- | | device | Device | List of devices: `phone`, `tablet`, `desktop`. This field is required. | ```swift theme={null} try? kameleoonClient.addData(Device.desktop); ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String` | The country of the visitor. | | `region` optional | `String?` | The region of the visitor. | | `city` optional | `String?` | The city of the visitor. | | `postalCode` optional | `String?` | The postal code of the visitor. | | `latitude` optional | `Double?` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `Double?` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```swift theme={null} kameleoonClient.addData(Geolocation(country: "France", region: "Île-de-France", city: "Paris")); ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | ----------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `[String: FeatureFlag]` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `Int` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```swift theme={null} // Retrieves the dictionary of feature flags from the DataFile. // The dictionary is keyed by feature flag identifiers, with each value being a FeatureFlag object. let featureFlags = dataFile.featureFlags // Retrieves the last modification timestamp of the DataFile. // The value is an Int representing milliseconds since the Unix epoch. let dateModified = dataFile.dateModified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | --------------------- | -------------------------------------------------------------------------- | | `environmentEnabled` | `Bool` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `String` | The key of the default variation associated with the feature flag. | | `variations` | `[String: Variation]` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `[Rule]` | A list of `Rule` objects | ```swift theme={null} // Check whether the feature flag is enabled in the current environment let isEnvironmentEnabled = featureFlag.environmentEnabled // Retrieve the key of the default variation let defaultVariationKey = featureFlag.defaultVariationKey // Retrieve the default variation object let defaultVariation = featureFlag.defaultVariation // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) let variations = featureFlag.variations // Retrieve all targeting rules associated with the feature flag let rules = featureFlag.rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | --------------------- | ------------------------------------------------------ | | `variations` | `[String: Variation]` | A map of `Variation` objects, keyed by variation keys. | ```swift theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) let variations = rule.variations ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | `String` | The name of the variation. | | key | `String` | The unique key identifying the variation. | | id | `Int` | The ID of the assigned variation (or `-1` if it's the default variation). | | experimentId | `Int` | The ID of the experiment associated with the variation (or `-1` if default). | | variables | `[String: Variable]` | A map containing the variables of the assigned variation, keyed by variable names. `variables` could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experimentId` may be `-1`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```swift theme={null} // Retrieving the variation name let variationName = variation.name // Retrieving the variation key let variationKey = variation.key // Retrieving the variation id let variationId = variation.id // Retrieving the experiment id let experimentId = variation.experimentId // Retrieving the variables map let variables = variation.variables ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | `String` | The unique key identifying the variable. | | type | `String` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**. | | value | `Any?` | The value of the variable, which can be of the following types: **Bool**, **Int**, **Double**, **String**, **\[String: Any]** (json object), **\[Any]** (json array). | ```swift theme={null} // Retrieving the variables map let variables = variation.variables // Variable type can be retrieved for further processing let type = variables["isDiscount"]?.type ?? "" // Get the bool value of "isDiscount" (default to false if missing or not a Bool) let isDiscount = variables["isDiscount"]?.value as? Bool ?? false // Get the numeric value of "number" as an Int (default to 0 if missing or not numeric) let number = (variables["number"]?.value as? NSNumber)?.intValue ?? 0 // Get the String value of "title" (default to an empty string if missing or not a String) let title = variables["title"]?.value as? String ?? "" ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `5.0.0`. #### getFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Use this method to get the [feature variation](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variations) key for a specific user. This method takes a `featureKey` as a required argument to retrieve the variation key for the specified user. If the visitor has never been associated with this feature flag, the SDK returns a randomly assigned variation key (according to the feature flag rules). If the visitor is already registered with this feature flag, this method returns the previous variation key. If the visitor doesn't match any of the rules, the default variation you defined in the Kameleoon app will be returned. Ensure you set up proper error handling as shown in the example code to catch potential errors. ```swift theme={null} let featureKey = "new_checkout" var variationKey = "" do { variationKey = try kameleoonClient.getFeatureVariationKey(featureKey: featureKey) switch variationKey { case "variation 1": // The visitor has been bucketed with variation 1 key case "variation 2": // The visitor has been bucketed with variation 2 key default: //The visitor has been bucketed with the default variation or is part of the unallocated traffic sample } } catch { switch error { case KameleoonError.sdkNotReady: // Exception indicating that the SDK has not completed its initialization yet. case KameleoonError.Feature.notFound: // The feature key is not in the configuration file that has been fetched by the SDK. Trigger the old checkout for this visitor. case KameleoonError.Feature.environmentDisabled: // The feature flag is disabled for the environment. default: // Any other error. } } ``` #### getActiveFeatureList() Use [`getVariations()`](#getvariations) instead. To get the list of feature flag keys currently available and active for the visitor. ```swift theme={null} let activeFeatureFlags = kameleoonClient.getActiveFeatureList() ``` ##### Return value | Type | Description | | --------- | ---------------------------------------------------------- | | \[String] | List of feature flag keys which are active for the visitor | #### getActiveFeatures() Use [`getVariations()`](#getvariations) instead. `getActiveFeatures` method retrieves information about the active feature flags that are available for the specified visitor code. ```swift theme={null} let activeFeatures = kameleoonClient.getActiveFeatures() ``` ##### Return value | Type | Description | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `[String: Types.Variation]` | A dictionary that contains the visitor's assigned variations for each active feature using the keys of the corresponding active features. | #### getFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* - Use [`getVariation()`](#getvariation) instead. - This method was previously called `obtainFeatureVariable()`, which was removed in SDK version `4.0.0`. Call this method to get the [feature variable](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables) of a variation key associated with a user. This method takes `featureKey`, and `variableKey` as required arguments to get the variable of the variation key for a given user. If a visitor has never been associated with this feature flag, the SDK returns a variable value for the variation key that it randomly assigns according to the feature flag rules. If the user is already registered with this feature flag, the SDK returns the variable value for the previously associated variation. If the user doesn't match any of the rules, the default variable is returned. ```swift theme={null} String featureKey = "myFeature" String variableKey = "myVariable" try { let variable = kameleoonClient.getFeatureVariable(featureKey: featureKey, variableKey: variableKey) // your custom code, depending on variableValue } catch { switch error { case KameleoonError.sdkNotReady: // Exception indicating that the SDK has not completed its initialization. case KameleoonError.Feature.notFound: // The Feature Key is not in the configuration file that has been fetched by the SDK. Trigger the old checkout for this visitor. case KameleoonError.Feature.environmentDisabled: // The feature flag is disabled for the environment. case KameleoonError.Feature.variableNotFound: // Exception indicating that the requested variable has not been found. Check that the variable's key matches the one in your code. default: // Any other error. } } ``` ##### Parameters | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------------------------------- | | featureKey | String | Identification key of the feature you want to retrieve. This field is required. | | variableKey | String | Name of the variable you want to get a value for. This field is required. | ##### Return value | Type | Description | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Any | Data associated with this feature flag. The values can be `Int`, `String`, `Bool` or `Dictionary` (depending on the type defined on the web interface). | ##### Errors thrown | Type | Description | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonError.sdkNotReady | Exception indicating that the SDK isn't fully initialized. | | KameleoonError.Feature.notFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception usually means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed in the web-app). | | KameleoonException.Feature.environmentDisabled | Exception indicating that the feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonError.Feature.variableNotFound | Exception indicating that the requested variable wasn't found. Check that the variable's key in the Kameleoon app matches the key in your code. | #### getFeatureVariationVariables() * Use [`getVariation()`](#getvariation) instead. * This method was previously called `getFeatureAllVariables`, which was removed in SDK version `4.0.0`. To retrieve all of the variables for a feature, call this method. You can modify your feature variables in the Kameleoon app. This method takes `featureKey` as an argument. It returns data with the `[String: Any]` type, as defined on the web interface. It will throw an exception (`KameleoonError.Feature.notFound`) if the requested feature hasn't been found in the SDK's internal configuration. ```swift theme={null} let featureKey = "myFeature" let variationKey = "on" do { allVariables = try kameleoonClient.getFeatureVariationVariables(featureKey: featureKey, variationKey: variationKey); } catch KameleoonError.Feature.notFound { // The feature is not activated in Kameleoon. } catch KameleoonError.Feature.environmentDisabled { // The feature flag is disabled for the environment. } catch { // This is a generic Exception handler that will handle all errors. } ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------------- | | featureKey | String | Identification key of the feature you want to retrieve. This field is required. | | variationKey | String | The key of the variation you want to retrieve. This field is required. | ##### Return value | Type | Description | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | \[String: Any] | Data associated with this feature flag. The values can be `Int`, `String`, `Bool` or `Dictionary` (depending on the type defined on the web interface). | ##### Errors thrown | Type | Description | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonError.sdkNotReady | Exception indicating that the SDK isn't fully initialized. | | KameleoonError.Feature.notFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception usually means that the feature flag hasn't been activated in the Kameleoon app (but code implementing the feature is already deployed in the web app). | | KameleoonException.Feature.environmentDisabled | Exception indicating that the feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonError.Feature.variationNotFound | Exception indicating that the requested variation key hasn't been found in the SDK's internal configuration. This exception means that the feature flag hasn't yet been retrieved by the SDK, which may happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | ```swift theme={null} let featureKey = "myFeature" let variationKey = "on" do { allVariables = try kameleoonClient.getFeatureVariationVariables(featureKey: featureKey, variationKey: variationKey); } catch KameleoonError.Feature.notFound { // The feature is not activated in Kameleoon. } catch KameleoonError.Feature.environmentDisabled { // The feature flag is disabled for the environment. } catch { // This is a generic Exception handler that will handle all errors. } ``` #### updateConfigurationHandler() The `updateConfigurationHandler()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```swift theme={null} kameleoonClient.updateConfigurationHandler { // configuration was updated } ``` ##### Parameters | Name | Type | Description | | --------- | ------------------------ | -------------------------------------------------------------------------------------------------------- | | `handler` | `Optional<(() -> Void)>` | The handler that will be called when the configuration is updated using a real-time configuration event. | # React Native SDK Source: https://docs.kameleoon.com/developer-docs/sdks/mobile-sdks/react-native-sdk Integrate the Kameleoon React SDK to run experiments and activate feature flags in React applications using hooks and components. With the Kameleoon React SDK, you can run feature experiments and activate feature flags on your front-end web and mobile app. Integrating the SDK into your web and mobile app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide) **Changelog**: Details on the latest version of the React SDK can be found in the [changelog](https://github.com/Kameleoon/client-react/blob/main/CHANGELOG.md). **SDK methods**: For the full reference documentation of the React SDK, see the [reference](#reference) section. **Requirements**: React SDK requires `React 16.8.0+` ## Developer guide Follow this section to integrate the SDK into your app and learn more about using the SDK. ### Getting started This section walks you through installing and configurating the SDK for the first time. #### Installation The Kameleoon SDK Installation tool is the preferred way to install the SDK. This **SDK Installer** helps you to install the SDK of your choice, generate a basic code sample, and configure [external dependencies](#external-dependencies) if needed. To start the SDK Installation tool, install and run it globally: ```bash theme={null} npm install --global @kameleoon/sdk-installer kameleoon-sdk ``` Or run it directly with `npx`: ```bash theme={null} npx @kameleoon/sdk-installer ``` #### Create the Kameleoon client To get started, you need to create an entry point for React SDK by creating a Kameleoon Client at the top level of your app using the `createClient()` function imported from `kameleoon` package. ```tsx theme={null} import { createClient, Environment, SDKConfigurationType, } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ```jsx theme={null} import { createClient, Environment } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` #### Wrap the app in the Kameleoon Provider The second step is connecting the previously created Kameleoon Client to `KameleoonProvider` by passing the configured client to `KameleoonProvider`: ```tsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, }); function AppWrapper(): JSX.Element { return ( ); } ``` ```jsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, }); function AppWrapper() { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```tsx theme={null} import { createClient, Environment, KameleoonProviderSSR, } from '@kameleoon/react-sdk'; function AppWrapper(): JSX.Element { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```jsx theme={null} import { createClient, Environment, KameleoonProviderSSR, } from '@kameleoon/react-sdk'; function AppWrapper() { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```tsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; // Checks if the code is running on the server (Node.js) and not in the browser. // This can be replaced with any other mechanism you use to detect server-side execution. const isServer = typeof window === 'undefined'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, externals: { // Add your external dependencies here, e.g. storage, eventSource, visitorCodeManager, etc. }, stubMode: isServer, }); function AppWrapper(): JSX.Element { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```jsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; // Checks if the code is running on the server (Node.js) and not in the browser. // This can be replaced with any other mechanism you use to detect server-side execution. const isServer = typeof window === 'undefined'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, externals: { // Add your external dependencies here, e.g. storage, eventSource, visitorCodeManager, etc. }, stubMode: isServer, }); function AppWrapper() { return ( ); } ``` ##### KameleoonProvider Use this provider on root level by wrapping your app to gain an access to `KameleoonClient`. This ensures your app doesn't flicker due to flag changes at startup time. ###### Props | Name | Type | Description | | -------------------------------- | ----------------- | ------------------------------------------------------ | | children required | `ReactNode` | child elements of the provider | | client required | `KameleoonClient` | `KameleoonClient` instance created by `createClient()` | ##### KameleoonProviderSSR Use this provider on root level by wrapping your app to gain an access to `KameleoonClient`. `KameleoonProviderSSR` differs from `KameleoonProvider` in that it creates a `KameleoonClient` instance inside the context on the first client request. This prevents the risk of creating the client on the server side. It's recommended for use in SSR-based systems, such as Next.js with SSR. ###### Props | Name | Type | Description | | ------------------------------------- | --------------- | ---------------------------------------------------------------------- | | children required | `ReactNode` | child elements of the provider | | sdkParameters required | `SDKParameters` | `SDKParameters` settings for creating an instance of `KameleoonClient` | #### Await for the client initialization `KameleoonClient` initialization is done asynchronously to make sure that Kameleoon API call was successful for that hook `useInitialize` is used. You can use `async/await`, `Promise.then()` or any other method to handle asynchronous client initialization. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); // -- Waiting for the client initialization using `async/await` const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); // -- Waiting for the client initialization using `async/await` const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureFlagActive()`](#isfeatureflagactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureFlagActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#configuration-parameters). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#configuration-parameters)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### React Native considerations React Native on `android` platform doesn't support `Real Time Update` feature. While React SDK works the same way in both React Native and React contexts, it's important to note that setup steps differ. Due to the lack of browser API in React Native, React SDK has to have different [external dependency](#external-dependencies) implementations to work correctly. For that, Kameleoon provides several dedicated npm packages that you can install and set up manually or install using [Kameleoon SDK Installation Tool](#installation) (recommended). The packages include: * `@kameleoon/react-native-storage` - built using `react-native-mmkv` library * `@kameleoon/react-native-event-source` - built using `react-native-event-source-ts` library * `@kameleoon/react-native-visitor-code-manager` - built on top of `react-native-mmkv` library * `@kameleoon/react-native-platform-analyzer` - built using `react-native` library * *optional* `@kameleoon/react-native-secure-prng` - built using `react-native-get-random-values` library If you don't want to use the listed packages, you can provide your own implementation following [the external dependencies guide](#external-dependencies). Example React SDK setup for React Native app: ```ts theme={null} import { createClient } from '@kameleoon/react-sdk'; import { KameleoonEventSource } from '@kameleoon/react-native-event-source'; import { KameleoonStorage } from '@kameleoon/react-native-storage'; import { KameleoonVisitorCodeManager } from '@kameleoon/react-native-visitor-code-manager'; import { KameleoonSecurePRNG } from '@kameleoon/react-native-secure-prng'; import { KameleoonPlatformAnalyzer } from '@kameleoon/react-native-platform-analyzer'; // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new KameleoonStorage(), eventSource: new KameleoonEventSource(), visitorCodeManager: new KameleoonVisitorCodeManager(), platformAnalyzer: new KameleoonPlatformAnalyzer(), // -- Optional -- prng: new KameleoonSecurePRNG(), }, }); ``` ```js theme={null} import { createClient } from '@kameleoon/react-sdk'; import { KameleoonEventSource } from '@kameleoon/react-native-event-source'; import { KameleoonStorage } from '@kameleoon/react-native-storage'; import { KameleoonVisitorCodeManager } from '@kameleoon/react-native-visitor-code-manager'; import { KameleoonSecurePRNG } from '@kameleoon/react-native-secure-prng'; import { KameleoonPlatformAnalyzer } from '@kameleoon/react-native-platform-analyzer'; // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new KameleoonStorage(), eventSource: new KameleoonEventSource(), visitorCodeManager: new KameleoonVisitorCodeManager(), platformAnalyzer: new KameleoonPlatformAnalyzer(), // -- Optional -- prng: new KameleoonSecurePRNG(), }, }); ``` ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```jsx theme={null} addData(visitorCode, new CustomData(index, 'newVisitorCode')); ``` [More information in addData()](#adddata) * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions supported by this SDK, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/feature-experimentation/targeting-and-segmentation\use-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk/full’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk’; const client = createClient({ siteCode: ‘my_site_code’, configuration }); // The `NONE` log level allows no logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK’s main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk/full’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```ts theme={null} import { KameleoonClient, KameleoonLogger, IExternalLogger, LogLevel } from '@kameleoon/react-sdk'; export class CustomLogger implements IExternalLogger { // `log` method accepts logs from the SDK public log(level: LogLevel, message: string): void { // Custom log handling logic here. For example: switch (level) { case LogLevel.DEBUG: console.debug(message); break; case LogLevel.INFO: console.info(message); break; case LogLevel.WARNING: console.warn(message); break; case LogLevel.ERROR: console.error(message); break; } } } const client = createClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/react-sdk'; export class CustomLogger { // `log` method accepts logs from the SDK log(level, message) { // Custom log handling logic here. For example: switch (level) { case 'DEBUG': console.debug(message); break; case 'INFO': console.info(message); break; case 'WARNING': console.warn(message); break; case 'ERROR': console.error(message); break; } } } const client = createClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ### Domain information You provide a domain as the `domain` in `KameleoonClient` \[configuration], which is used for storing Kameleoon visitor code in cookies. This is important when working with the [`getVisitorCode`](#getvisitorcode) and [`setLegalConsent`](#setlegalconsent) methods. The domain you provide is stored in the cookie as the `Domain=` key. #### Setting the domain The domain you provide indicates the URL address can use the cookie. For example, if your domain is `www.example.com`. the cookie is only available from a `www.example.com` URL. That means that pages with the `app.example.com` domain can't use the cookie. To be more flexible around subdomains, you can prefix a domain with `.`. For example, the domain `.example.com` allows the cookie to function on both `app.example.com` and `login.example.com`. You can't use regular expressions, special symbols, protocol, or port numbers in the `domain`. Additionally, a [specific list of subdomains](https://publicsuffix.org/list/public_suffix_list.dat) aren't allowed to be used with the prefix `.`. Here's a small domain cheat sheet: | Domain | Allowed URLs | Disallowed URLs | | ------------------------------ | --------------------- | -------------------- | | `www.example.com` | ✅`www.example.com` | ❌ `app.example.com` | | | ✅ `example.com` | ❌ `.com` | | | | | | `.example.com` = `example.com` | ✅ `example.com` | ❌ `otherexample.com` | | | ✅ `www.example.com` | | | | ✅ `app.example.com` | | | | ✅ `login.example.com` | | | `https://www.example.com` | ⛔ bad domain | ⛔ bad domain | | `www.example.com:4408` | ⛔ bad domain | ⛔ bad domain | | `.localhost.com` = `localhost` | ⛔ bad domain | ⛔ bad domain | #### Developing on localhost `localhost` is always considered a bad domain, making it hard to test the domain when developing on localhost. There are two ways to avoid this issue: * Don't specify the `domain` field in the SDK client while testing. This prevents `localhost` issues (the cookie will be set on any domain). * Create a local domain for `localhost`. For example: * Navigate to `/etc/hosts` on *Linux* or to `c:\Windows\System32\Drivers\etc\hosts` on *Windows* * Open `hosts` with file super user or administrator rights * Add a domain to the localhost port, for example: `127.0.0.1 app.com` * Now you can run your app locally on `app.com:{my_port}` and specify `.app.com` as your domain ### External dependencies SDK external dependencies use the *dependency injection* pattern to give you the ability to provide your own implementations for certain parts of an SDK. In the React SDK, all external dependencies have default implementations, which use a native browser API so there's no need to provide them unless another API is required for specific use cases. Here's the list of available external dependencies: | Dependency | Interface | API Used | Description | | -------------------------------------------- | ----------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage` optional | `IExternalStorage` | Browser `localStorage` | Used for storing all the existing and collected SDK data | | `requester` optional | `IExternalRequester` | Browser `fetch` | Used for performing all the network requests | | `eventSource` optional | `IExternalEventSource` | Browser `EventSource` | Used for receiving Server Sent Events for [Real Time Update](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) capabilities | | `visitorCodeManager` optional | `IExternalVisitorCodeManager` | Browser cookie | Used for storing and synchronizing visitor code | | `prng` optional | `IExternalPRNG` | `Math.random` or Browser `crypto.getRandomValues` | Used to generate unique IDs for tracking events | | `logger` optional | `ILogger` | Custom implementation | Used for custom handling of logs from the SDK. Allows to define how logs are processed and where they're output. | | `platformAnalyzer` optional | `IPlatformAnalyzer` | React Native API | Automatically detects the platform and attaches this information to the visitor data. Designed specifically for React Native. | The following example implements external dependencies. To import an interface from an SDK, create a class that implements it and pass the instantiated class to the SDK. #### Storage ```ts theme={null} import { IExternalStorage } from '@kameleoon/react-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage implements IExternalStorage { public read(key: string): T | null { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } public write(key: string, data: T): void { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/react-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage { read(key) { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } write(key, data) { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` #### EventSource ```ts theme={null} import { IExternalEventSource, EventSourceOpenParametersType, } from '@kameleoon/react-sdk'; // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource implements IExternalEventSource { private eventSource?: EventSource; public open({ eventType, onEvent, url, }: EventSourceOpenParametersType): void { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } public close(): void { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback: (error: Event) => void): void { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` ```js theme={null} // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource { eventSource; open({ eventType, onEvent, url }) { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } close() { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback) { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` #### VisitorCodeManager ```ts theme={null} import { IExternalVisitorCodeManager, SetDataParametersType, KameleoonUtils, } from '@kameleoon/react-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager implements IExternalVisitorCodeManager { public getData(key: string): string | null { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } public setData({ visitorCode, domain, maxAge, key, path, }: SetDataParametersType): void { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager { getData(key) { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } setData({ visitorCode, domain, maxAge, key, path }) { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` #### Requester ```ts theme={null} import { RequestType, IExternalRequester, KameleoonResponseType, SendRequestParametersType, } from '@kameleoon/react-sdk'; // --- External Requester Implementation export class MyRequester implements IExternalRequester { public async sendRequest({ url, parameters, }: SendRequestParametersType): Promise { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/react-sdk'; // --- External Requester Implementation export class MyRequester { async sendRequest({ url, parameters }) { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` [Return mocked result](#simulatesuccessrequest) #### Pseudo random number generator Pseudo Random Number Generator (PRNG) is a dependency that generates random floating point number between `0` and `1` (similar to `Math.random`). Default Kameleoon implementation relies on Browser's `crypto` or `Math.random` function if `crypto` isn't available. Those API are very secure and reliable, however in some edge cases (especially in some `React Native` engines) you might want to provide your own implementation or use a dedicated Kameleoon package for React Native - `@kameleoon/react-native-secure-prng` ```ts theme={null} import { IExternalPRNG } from '@kameleoon/react-sdk'; // --- External Pseudo Random Number Generator (PRNG) implementation --- class MyPRNG implements IExternalPRNG { public getRandomNumber(): number { // Return a random floating point number between `0` and `1`, like `Math.random()` does. return Math.random(); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { prng: new MyPRNG(), }, }); ``` ```js theme={null} // --- External Pseudo Random Number Generator (PRNG) implementation --- class MyPRNG { getRandomNumber() { // Return a random floating point number between `0` and `1`, like `Math.random()` does. return Math.random(); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { prng: new MyPRNG(), }, }); ``` ### Error handling Almost every React SDK callback which is returned by hooks may throw an error at some point, these errors aren't just caveats but rather deliberately predefined `KameleoonError`s that extend native JavaScript `Error` class providing useful messages and special `type` field with a type `KameleoonException`. `KameleoonException` is an enum containing all possible error types. To know exactly what type of `KameleoonException` the callbacks may throw, you can check `Throws` section of the hooks description on this page or just hover over the callback in your IDE to see jsdocs description. Overall handling the errors considered a good practice to make your app more stable and avoid technical issues. *** ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, KameleoonError, KameleoonException, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async (): Promise => { try { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); } catch (error) { // -- Type guard for inferring error type, as native JavaScript `catch` // only infers `unknown`. if (error instanceof KameleoonError) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } }, [initialize, addData, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, KameleoonException, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async () => { try { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); } catch (error) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } }, [initialize, addData, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```tsx title="Device One" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); addData('my_visitor', customData); flush(); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Device Two" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getRemoteVisitorData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available to target and track "my_visitor". }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Device One" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); addData('my_visitor', customData); flush(); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Device Two" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getRemoteVisitorData } = useData(); const init = useCallback(async () => { await initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available to target and track "my_visitor". }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows you to combine a visitor's history across each of their devices (history reconciliation). One of the powerful features that history reconciliation provides is the ability to merge different visitors sessions into one. To reconcile visit history, you can use [`CustomData`](#customdata) to provide a unique identifier for the visitor. Follow the [activating cross-device history reconciliation](/developer-docs/cross-device-experimentation#activating-cross-device-history-reconciliation) guide to set up your custom data on the Kameleoon platform When your custom data is set up, you can use it in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation and will be displayed as a single visitor in the `Visitor` view of your experiment's result pages. The configuration SDK ensures that associated sessions always see the same variation of the experiment. Afterwards, you can use the SDK normally. The following methods might be helpful in the context of session merging: * Use [`getRemoteVisitorData`](#getremotevisitordata) with `isUniqueIdentifier=true` to retrieve data for all linked visitors * Use [`trackConversion`](#trackconversion) or [`flush`](#flush) with `isUniqueIdentifier=true` to track some data for specific visitor that's associated with another visitor As the custom data you use as the identifier must be set to `Visitor` scope, you need to use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. This example uses an app with a login page. Since the user ID isn't known at the moment of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```tsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage(): JSX.Element { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; type Props = { anonymousVisitor: string; }; function ApplicationPage(props: Props): JSX.Element { const { addData, trackConversion, getRemoteVisitorData } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage() { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function ApplicationPage(props) { const { addData, trackConversion, getRemoteVisitorData } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async () => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK know the visitor is a unique identifier. isUniqueIdentifier: true, }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows you to combine a visitor's history across each of their devices (history reconciliation). One of the powerful features that history reconciliation provides is the ability to merge different visitors sessions into one. To reconcile visit history, you can use [`CustomData`](#customdata) to provide a unique identifier for the visitor. Follow the [activating cross-device history reconciliation](/developer-docs/cross-device-experimentation#activating-cross-device-history-reconciliation) guide to set up your custom data on the Kameleoon platform When your custom data is set up, you can use it in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation and will be displayed as a single visitor in the `Visitor` view of your experiment's result pages. The SDK configuration ensures that associated sessions always see the same variation of the experiment. Before using other methods make sure to let SDK know that the visitor is a unique identifier by adding [`UniqueIdentifier`](#uniqueidentifier) data to a visitor As the custom data you use as the identifier must be set to `Visitor` scope, you need to use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. This example uses an app with a login page. Since the user ID isn't known at the moment of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```tsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage(): JSX.Element { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, UniqueIdentifier, } from '@kameleoon/react-sdk'; type Props = { anonymousVisitor: string; }; function ApplicationPage(props: Props): JSX.Element { const { addData, trackConversion, getRemoteVisitorData, flush } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` flush(props.anonymousVisitor); // -- Informing the SDK that the visitor is unique identifier. addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage() { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, UniqueIdentifier, } from '@kameleoon/react-sdk'; function ApplicationPage(props) { const { addData, trackConversion, getRemoteVisitorData, flush } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async () => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` flush(props.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier. addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ### Utilities SDK has a set of utility methods that can be used to simplify the development process. All the methods are represented as static members of `KameleoonUtils` class. #### simulateSuccessRequest Method `simulateSuccessRequest` is used to simulate a successful request to the Kameleoon server. It can be useful for custom [Requester](#requester) implementations when developer needs to simulate a successful request, for example disabling tracking. ```ts theme={null} import { KameleoonUtils, IExternalRequester, SendRequestParametersType, RequestType, KameleoonResponseType, } from '@kameleoon/react-sdk'; // - Example of `Requester` with disabled tracking class Requester implements IExternalRequester { public async sendRequest({ url, parameters, requestType, }: SendRequestParametersType): Promise { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest( requestType, null, ); } return await fetch(url, parameters); } } ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; // - Example of `Requester` with disabled tracking class Requester { async sendRequest({ url, parameters, requestType }) { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest(requestType, null); } return await fetch(url, parameters); } } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------- | | requestType required | `RequestType` | A type of request | | data required | `SimulateRequestDataType[RequestType]` | A type of request data, which is different depending on `RequestType` | Data type `SimulateRequestDataType` is defined as follows: * `RequestType.Tracking` - `null` * `RequestType.ClientConfiguration` - `ClientConfigurationDataType` * `RequestType.RemoteData` - `JSONType` ##### Return value | Type | Description | | -------------------------------- | -------------------------------------------------- | | `Promise` | returns a promise with the response of the request | #### getCookieValue Method `getCookieValue` is used to parse a common cookie string (`key_1=value_1; key_2=value_2; ...`) and get the value of a specific cookie key. It's useful when working with a custom implementation of [`VisitorCodeManager`](#visitorcodemanager). ```ts theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ##### Parameters | Name | Type | Description | | ------------------------------ | -------- | ------------------------------------------------------ | | cookie required | `string` | Cookie string in a form `key_1=value_1; key_2=value_2` | | key required | `string` | String representation of a key to find a value by | ##### Return value | Type | Description | | | -------- | ----------- | ----------------------------------------------------------------------- | | \`string | null\` | returns a string with a cookie value or `null` if the key was not found | ## Reference This is the full reference documentation for the React SDK. ### Initialization This section provides the methods you use to create and initialize the Kameleoon Client in your app. #### initialize() An asynchronous `initialize` function, collected with `useInitialize` hook, that's used for KameleoonClient initialization by fetching Kameleoon SDK related data from server or by retrieving data from local source if data is up-to-date or update interval hasn't been reached. * If the SDK configuration couldn't be retrieved but there is an older configuration available in SDK storage, the SDK uses the older configuration as a fallback and the `initialize` doesn't throw an error. * Client initialization has an optional *offline mode*. It's activated by setting optional `useCache` parameter to `true`. In *offline mode* if tracking requests from any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request as soon as it detects that the internet connection has been re-established: * [flush](#flush) * [trackConversion](#trackconversion) * [getFeatureFlagVariationKey](#getfeatureflagvariationkey) * [getFeatureVariable](#getfeatureflagvariable) * [sFeatureFlagActive](#isfeatureflagactive) ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default Value | | -------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | useCache optional | `boolean` or `undefined` | parameter for activating SDK offline mode, if `true` is passed failed polls won't return error and will use cached data if such data is available | `false` | ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | a promise resolved to a boolean indicating a successful sdk initialization. Generally initialize will throw an error if the something that can not be handled will happen, so the `boolean` value will almost always be `true` and won't give as much useful information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | An asynchronous `initialize` function, collected with `useInitialize` hook, that's used for KameleoonClient initialization by fetching Kameleoon SDK related data from server or by retrieving data from local source if data is up-to-date or update interval hasn't been reached. * If the SDK configuration couldn't be retrieved but there is an older configuration available in SDK storage, the SDK uses the older configuration as a fallback and the `initialize` doesn't throw an error. * SDK supports an *offline mode*. In *offline mode* if tracking requests from any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request as soon as it detects that the internet connection has been re-established: * [flush](#flush) * [trackConversion](#trackconversion) * [getFeatureFlagVariationKey](#getfeatureflagvariationkey) * [getFeatureVariable](#getfeatureflagvariable) * [sFeatureFlagActive](#isfeatureflagactive) ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | a promise resolved to a boolean indicating a successful sdk initialization. Generally initialize will throw an error if the something that can not be handled will happen, so the `boolean` value will almost always be `true` and won't give as much useful information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing. Collect the `isReady` function with the `useInitialize` hook. It's especially useful in a deeply nested component tree, because it lets you check SDK readiness without managing a global state or passing the initialization result through component props. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent(): JSX.Element { const { isReady } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isReady()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent() { const { isReady } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isReady()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | #### isInitialized() `isInitialized` is deprecated and will be removed in the next major release. Use [`isReady()`](#isready) instead. The `isInitialized` function, collected with the `useInitialize` hook, is a small utility method that checks if the SDK initialization has completed. For example, this can be useful when dealing with a deeply nested component tree, because it allows you to quickly check the SDK readiness without having to manage a global state, or pass the initialization result using component props. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent(): JSX.Element { const { isInitialized } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isInitialized()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent() { const { isInitialized } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isInitialized()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ##### Return value A `boolean` value. Returns `true` if SDK was successfully initialized, otherwise returns `false`. #### createClient() To get started, you need to create an entry point for React SDK by creating a Kameleoon Client at the top level of your app using the `createClient()` function imported from `kameleoon` package. An instance of `KameleoonClient` is created using `createClient()` function. ```tsx theme={null} import { createClient, Environment, SDKConfigurationType, } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ```jsx theme={null} import { createClient, Environment } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ##### Parameters An object of type `SDKParameters` containing: | Name | Type | Description | | ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | siteCode required | `string` | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | configuration optional | `Partial` | client's configuration | | externals optional | `ExternalsType` | external implementation of SDK dependencies ([External dependencies](#external-dependencies)) | ##### Configuration parameters | Name | Type | Description | Default Value | | ---------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | updateInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | | environment optional | `Environment` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data; minimum value is 1 minute | `undefined` (no cleanup will be performed) | | domain optional | `string` | [domain](#domain-information) that the cookie belongs to. Deprecated, use `cookieDomain` instead | `undefined` | | cookieDomain optional | `string` | [domain](#domain-information) that the cookie belongs to. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request, which is performed once per interval. The minimum value is `100` ms and the maximum value is `1_000` ms | `1_000` (1 second) | The `domain` parameter is deprecated and will be removed in a future release. Use `cookieDomain` instead. | Name | Type | Description | Default Value | | ---------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | dataFileRefreshInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes; the minimum value is 1 minute. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. This parameter replaces the deprecated `updateInterval`; if both are specified, `dataFileRefreshInterval` takes precedence. | `60` | | dataFileCacheTtl optional | `number` | Defines, in minutes, how long the data file cached in the storage remains valid. If the cached data file is older than `dataFileRefreshInterval` but within `dataFileCacheTtl`, `initialize()` completes immediately using the cached data file and fetches the latest version in the background. If the age of the cached data file exceeds `dataFileCacheTtl`, `initialize()` waits for the latest data file before completing, so that visitors returning after a long period of inactivity are evaluated against the current configuration; if the request fails, the cached data file is used as a fallback. A value of `0` disables the background refresh: once a refresh is due, `initialize()` always waits for the latest data file. Negative values are treated as `0`.

**Note:** a lower `dataFileCacheTtl` increases the number of blocking data file requests during SDK initialization, which adds the request time to the page load for the affected visitors. Set `dataFileCacheTtl` higher than `dataFileRefreshInterval` (for example, `dataFileRefreshInterval: 5` and `dataFileCacheTtl: 10`), unless every configuration update must be applied before initialization completes. | `90` | | environment optional | `Environment \| string` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data; minimum value is 1 minute | `undefined` (no cleanup will be performed) | | cookieDomain optional | `string` | [domain](#domain-information) that the cookie belongs to. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request, which is performed once per interval. The minimum value is `1_000` ms and the maximum value is `5_000` ms | `1_000` (1 second) | | stubMode optional | `boolean` | When set to true, the client will operate in stub mode and perform no operations. In this mode, all method calls execute no actions, ensuring that no external actions or side effects occur. | `false` | | defaultDataFile optional | `string` | The `defaultDataFile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `defaultDataFile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `undefined` | | updateInterval deprecated | `number` | **Deprecated.** Use `dataFileRefreshInterval` instead; this parameter will be removed in the next major version. If both parameters are specified, `dataFileRefreshInterval` takes precedence. Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | **Option 1 (Recommended):** Use `JSON.stringify()` ```js theme={null} const dataFileJson = {"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\"make\":\"Porsche\",\"model\":\"911\"}"}}, "dateModified":1752209266000}; const dataFileString = JSON.stringify(dataFileJson); const configuration = { dataFileRefreshInterval: 20, defaultDataFile: dataFileString }; ``` **Option 2:** Raw JSON string (escape special characters) ```js theme={null} const configuration = { dataFileRefreshInterval: 20, defaultDataFile: `{"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\\"make\\":\\"Porsche\\",\\"model\\":\\"911\\"}"}, "dateModified":1752209266000}` }; ```
Lowering `dataFileRefreshInterval` increases how often the SDK checks for a newer configuration in the background, but a visitor's cached data file still stays usable until it ages past `dataFileCacheTtl` (90 minutes by default). Lower `dataFileCacheTtl` instead to shorten how long a visitor can keep evaluating against a stale cached configuration. Otherwise, traffic into a newly launched experiment or feature flag ramps up gradually rather than reaching full volume immediately. See [Impact on newly launched experiments and feature flags](/developer-docs/feature-experimentation/technical-reference/technical-considerations#impact-on-newly-launched-experiments-and-feature-flags) for details. ##### Return value | Type | Description | | ----------------- | ------------------------------- | | `KameleoonClient` | an instance of KameleoonClient. | Make sure not to use several client instances in one app as it's not fully supported yet and may overwrite the local storage configuration and cause unintended behavior (bugs). ### Feature flags and variations This section provides the methods you use to retrieve and manage the feature flags and variations assigned to the visitor. #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes `featureKey` as a mandatory argument and `track` as an optional argument. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get variation with tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } }, [initialize, visitorCode, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get variation with tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } }, [initialize, visitorCode, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `GetVariationParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.FeatureFlagConfigurationNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Method is obtained using `useFeatureFlag` hook. Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVariations } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get all feature flag variations with tracking const variations = getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } }, [initialize, visitorCode, getVariations, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVariations } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get all feature flag variations with tracking const variations = getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } }, [initialize, visitorCode, getVariations, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `GetVariationsParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | #### isFeatureFlagActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* `EventType.Evaluation` The method `isFeatureFlagActive()`, used with the `useFeatureFlag` hook, determines whether a visitor identified by `visitorCode` has the specified `featureKey` active. This method checks the targeting conditions, identifies the variation for the visitor, and saves this information to storage. Additionally, the hook sends a tracking request. There is also an overload for this method that includes a `track` parameter, allowing you to turn off the tracking of the feature evaluation. Visitor must be targeted to has feature flag active Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureFlagActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const { isFeatureFlagActive } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Add CustomData with index `0` containing visitor id to check the targeting addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Get the status of feature flag const isActive = isFeatureFlagActive(visitorCode, featureKey); // -- Check if the feature flag is active for visitor without tracking const isActive = isFeatureFlagActive({ visitorCode, featureKey: 'my_feature', track: false}); }, [initialize, visitorCode, isFeatureFlagActive, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const { isFeatureFlagActive } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function. const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Add CustomData with index `0` containing visitor id to check targeting. addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Get the feature flag's status. const isActive = isFeatureFlagActive(visitorCode, featureKey); // -- Check if the feature flag is active for visitors without tracking. const isActive = isFeatureFlagActive({ visitorCode, featureKey: 'my_feature', track: false}); }, [initialize, visitorCode, isFeatureFlagActive, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` The `isFeatureFlagActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters There are two overloads available for this method: 1. Two parameters overload: This overload is deprecated and will be removed in the next major version. Use the new overload with an object parameter. | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | 2. Object parameter overload of type `IsFeatureFlagActiveParamsType`: | Name | Type | Description | Default | | ----------------------------------- | --------- | ------------------------------------------------------------------------ | ------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | featureKey required | `string` | a unique key for feature flag | - | | track optional | `boolean` | a boolean indicator of whether to track the feature evaluation | `true` | ##### Return value | Type | Description | | --------- | ------------------------------------------------------------------------------------------------- | | `boolean` | indicator of whether the feature flag with `featureKey` is active for visitor with `visitorCode`. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | -------------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.DataInconsistency` | Allocated variation was found but there is no feature flag with according `featureKey` | *** #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { setForcedVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Forcing the variation "on" in the feature flag "featureKey1" for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); }, [initialize, visitorCode, setForcedVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { setForcedVariation } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); }, [initialize, visitorCode, setForcedVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `SetForcedVariationParametersType` with the following properties: | Name | Type | Description | Default | | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | | `experimentId` required | `number` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | | `variationKey` required | \`string | null\` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | ##### Exceptions thrown | Type | Description | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.Initialization` | Indicates that the SDK isn't yet fully initialized. | | `KameleoonException.FeatureFlagExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.FeatureFlagVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.StorageRead` | Couldn't read storage data. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { evaluateAudiences } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); evaluateAudiences(visitorCode); }, [initialize, visitorCode, evaluateAudiences, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { evaluateAudiences } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); evaluateAudiences(visitorCode); }, [initialize, visitorCode, evaluateAudiences, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getDataFile } = useFeatureFlag(); useEffect(() => { const dataFile = getDataFile(); }, [getDataFile]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getDataFile } = useFeatureFlag(); useEffect(() => { const dataFile = getDataFile(); }, [getDataFile]); } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data This section provides the methods you use to manage visitor data. #### getVisitorCode() `getVisitorCode` method collected from `useVisitorCode` hook obtains a visitor code from the browser cookie. If the visitor code doesn't exist yet, the function generates a random visitor code (or uses the `defaultVisitorCode` value if you provided one) and sets the new visitor code in a cookie. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Pass, save, and retrieve the default visitorCode. const visitorCode = getVisitorCode('default_visitor_code'); }, [initialize, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Pass, save, and retrieve the default visitorCode. const visitorCode = getVisitorCode('default_visitor_code'); }, [initialize, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ------------------------------------------ | -------- | ------------------------------------------------------------------- | | defaultVisitorCode optional | `string` | visitor code to be used in case there is no visitor code in cookies | If you don't provide a `defaultVisitorCode` and there is no visitor code stored in a cookie, the visitor code will be randomly generated. ##### Return value | Type | Description | | -------- | -------------------- | | `string` | result visitor code. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------ | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length was exceeded | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | *** #### addData() The `addData` function, used with the `useData` hook, collects targeting data to store for other hooks to determine if the current visitor is targeted. * The `addData()` function doesn't return any value and doesn't interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission via the [flush](#flush) method .This approach helps reduce the number of server calls made, as the data is typically grouped into a single server call triggered by the execution of [flush](#flush). The [trackConversion](#trackconversion) method also sends out any previously associated data, just like the [flush](#flush). The same holds true for [getFeatureFlagVariationKey](#getfeatureflagvariationkey) and [getFeatureVariable](#getfeatureflagvariable) methods if an experimentation rule is triggered. * `userAgent` data won't be stored in storage like other data, and it will be sent with every tracking request for bot filtration. * Check the list of [supported conditions](#targeting-conditions) to know what data types can be used for targeting Each visitor can only have one instance of associated data for most data types. However, `CustomData` is an exception. Visitors can have one instance of associated `CustomData` per `customDataIndex`. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create Kameleoon Data Types const customData = new CustomData(0, 'my_data'); const browserData = new Browser(BrowserType.Chrome); // -- Add a single data item (tracked by default) addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); }, [initialize, visitorCode, addData, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create Kameleoon Data Types const customData = new CustomData(0, 'my_data'); const browserData = new Browser(BrowserType.Chrome); // -- Add a single data item (tracked by default) addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); }, [initialize, visitorCode, addData, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | | | track optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it's not sent to the Kameleoon Data API. | `true` | | kameleoonData optional | `KameleoonDataType[]` | number of instances of any type of `KameleoonData`, can be added solely in array or as sequential arguments | | * `kameleoonData` is variadic argument it can be passed as one or several arguments (see the example) * The index or ID of the [custom data](/user-manual/assets/custom-data/create-custom-data) can be found in your Kameleoon account. It's important to note that this index starts at `0`, which means that the first custom data you create for a given site will be assigned `0` as its ID, not `1`. ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | See the [Data types](#data-types) reference for more details of how to manage different data types. *** #### flush() Method `flush` collected with `useData` takes the Kameleoon data associated with the visitor and sends the data tracking request along with all of the data that's been added previously using the [addData](#adddata). If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally during [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. The `isUniqueIdentifier` parameter can be useful in some edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor (fire-and-forget) flushInstant(visitorCode); // -- Instantly flush added custom data for visitor and wait for completion await flushInstant(visitorCode); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors (fire-and-forget) flushInstant(); // -- Instantly flush data for all the visitors and wait for completion await flushInstant(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; flush(internalUserId, true); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Flush data for all the visitors flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; flush(internalUserId, true); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters length, if not passed all the data will be flushed (sent to the remote Kameleoon servers) | - | | isUniqueIdentifier optional | `boolean` | an optional parameter for specifying if the visitorCode is a unique identifier | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | `flush()` takes the Kameleoon data associated with the visitor and schedules the data to be sent with the next tracking request. The time of the next tracking request is defined by SDK Configuration [`trackingInterval`](#configuration-parameters) parameter. Visitor data can be added using [addData](#adddata) and [getRemoteVisitorData](#getremotevisitordata) methods. If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally during [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. If you need to send tracking requests immediately, use `flushInstant()` (the asynchronous version of `flush` that returns `Promise`). You can `await` it when you need delivery guarantees (for example, before page navigation/unload), or call it without `await` as a fire-and-forget request: * `await flushInstant(visitorCode)` sends tracking requests immediately for a specific visitor and waits for completion * `await flushInstant()` sends tracking requests immediately for all visitors and waits for completion ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor flush({ visitorCode, instant: true }); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors flush({ instant: true }); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor flush({ visitorCode, instant: true }); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors flush({ instant: true }); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | Or an object with the type FlushParamsType, containing: | Name | Type | Description | Default | | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | | instant optional | `boolean` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). | - | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | *** #### getRemoteData() Asynchronous method `getRemoteData`, collected with the `useData` hook, returns a data stored for specified site code on a remote Kameleoon server. For example, you can use this function to retrieve user preferences, historical data, or any other data relevant to your app's logic. By storing this data on Kameleoon's highly scalable servers using the \[Data API], you can efficiently manage massive amounts of data and retrieve it for each of your visitors or users. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteData } = useData(); const getData = useCallback(async () => { // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters | Name | Type | Description | | --------------------------- | -------- | ---------------------------------------------------------- | | key required | `string` | unique key that the data you try to get is associated with | ##### Return value | Type | Description | | ---------- | --------------------------------------------- | | `JSONType` | promise with data retrieved for specific key. | ##### Exceptions thrown | Type | Description | | ------------------------------- | -------------------------------------------- | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The `isUniqueIdentifier` parameter can be useful in edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteVisitorData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteVisitorData } = useData(); const getData = useCallback(async () => { // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be set to the storage like `addData` method does | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | | isUniqueIdentifier optional | `boolean` | optional parameter that, when `true`, specifies that the visitorCode is a unique identifier | `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteVisitorData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteVisitorData } = useData(); const getData = useCallback(async () => { // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be set to the storage like `addData` method does | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | *** #### getVisitorWarehouseData() Asynchronous method `getVisitorWarehouseAudience` collected with `useData` hook retrieves all audience data associated with the visitor in your data warehouse using the specified `visitorCode` and `warehouseKey`. The `warehouseKey` is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. Refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getVisitorWarehouseAudience } = useData(); const getData = useCallback(async (): Promise => { // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getVisitorWarehouseAudience } = useData(); const getData = useCallback(async () => { // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | customDataIndex required | `number` | number representing the index of the custom data you want to use to target your Warehouse Audiences | | warehouseKey optional | `string` | unique key to identify the warehouse data (usually, your internal user ID) | ##### Return value | Type | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | `Promise` | promise containing CustomData with the associated warehouse data or `null` if there was no data | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### setLegalConsent() Method `setLegalConsent`, collected with `useVisitorCode` hook, specifies whether the visitor has given legal consent to use personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). * Consent information is in sync between the Kameleoon Engine (app file engine.js) and the React SDK. This synchronization means that once consent is set on either the Engine or the SDK, it's automatically set for both. This feature eliminates the need for manual consent handling and ensures that SDKs operate in compliance with user preferences. If you use Kameleoon in Hybrid mode, read the consent section in the [Hybrid experimentation article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/#managing-consent-in-hybrid-mode) * When handling legal consent, it's important to use [`getVisitorCode`](#getvisitorcode) method. Additionally, `getVisitorCode` doesn't accept `domain` as an argument. Instead, pass it to the [`createClient`](#createclient) function. ```ts theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode, setLegalConsent } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); setLegalConsent(visitorCode, true); }, [initialize, getVisitorCode, setLegalConsent]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode, setLegalConsent } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); setLegalConsent(visitorCode, true); }, [initialize, getVisitorCode, setLegalConsent]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | ##### Consent revocation behavior When you call `setLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. *** ### Goals and third-party analytics This section provides the methods you use to track when a visitor action achieve one of you goals (a conversion). #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* The `trackConversion()` function, used with the `useData` hook creates and adds [`Conversion`](#conversion) data to the visitor with specified parameters and executes `flush()`. Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue` argument. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. If you specify a `visitorCode` and set `isUniqueIdentifier` to `true`, the `trackConversion()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](#cross-device-experimentation) because the SDK links the flushed data with the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | -------------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `isUniqueIdentifier` optional | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | * 📨 *Sends Tracking Data to Kameleoon* The `trackConversion()` function, used with the `useData` hook creates and adds [`Conversion`](#conversion) data to the visitor with specified parameters and executes `flush()`. Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `undefined` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```ts theme={null} addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ```js theme={null} addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | *** #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getEngineTrackingCode, getVariation } = useFeatureFlag(); const [engineCode, setEngineCode] = useState(''); const init = useCallback(async (): Promise => { await initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200` and implicit experiment id is `100` getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code and set it to state setEngineCode(getEngineTrackingCode('visitor_code')); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` }, [initialize, getVariation, getEngineTrackingCode]); useEffect(() => { init(); }, [init]); useEffect(() => { if (!engineCode) { return; } // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); // -- Remove script from the page return () => { document.body.removeChild(script); }; }, [engineCode]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getEngineTrackingCode, getVariation } = useFeatureFlag(); const [engineCode, setEngineCode] = useState(''); const init = useCallback(async () => { await initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200` and implicit experiment id is `100` getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code and set it to state setEngineCode(getEngineTrackingCode('visitor_code')); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` }, [initialize, getVariation, getEngineTrackingCode]); useEffect(() => { init(); }, [init]); useEffect(() => { if (!engineCode) { return; } // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); // -- Remove script from the page return () => { document.body.removeChild(script); }; }, [engineCode]); } ``` * To use this feature, implement both the React SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | *** ### Events This section provides the methods you use to handle events. #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. Collect the `setEventHandler` method with the `useInitialize` hook. To clear the current handler for an event type, pass `null` as the `handler`: ```tsx theme={null} setEventHandler(EventType.DataFileUpdate, null); ``` ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, DataFileUpdateEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); setEventHandler(EventType.DataFileUpdate, (eventData: DataFileUpdateEventDataType) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async () => { await initialize(); setEventHandler(EventType.DataFileUpdate, (eventData) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` `DataFileUpdateEventDataType` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource.Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource.Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `number` | The modification date of the updated data file, in milliseconds. | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, HttpRequestEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); setEventHandler(EventType.HttpRequest, (eventData: HttpRequestEventDataType) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async () => { await initialize(); setEventHandler(EventType.HttpRequest, (eventData) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` `HttpRequestEventDataType` contains information about a completed SDK HTTP request. The SDK reports the event once per each actual HTTP request attempt, including retries. The type is a union of the following types. Check the `success` field to narrow the type. | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `HttpRequestSucceededEventDataType` | Describes an SDK HTTP request that completed successfully (`success: true`). Always carries `httpStatus`. | | `HttpRequestFailedEventDataType` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation (`success: false`). Always carries `failure`. | ##### Event data fields | Name | Type | Description | | ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `success` | `boolean` | `true` if the SDK HTTP request completed successfully, `false` if it failed because of an HTTP status, error, or cancellation. | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `HttpRequestType.DataFile`, `HttpRequestType.Tracking`, `HttpRequestType.RemoteVisitorData`, and `HttpRequestType.RemoteData`. | | `httpStatus` | `number` | The HTTP status code returned by the request. Present only in `HttpRequestSucceededEventDataType`. | | `failure` | `HttpRequestFailureType` | Details about why the request failed. Present only in `HttpRequestFailedEventDataType`. | | `duration` | `number` | The request duration in milliseconds. | ##### HttpRequestFailureType `HttpRequestFailureType` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason.HttpStatus`, `HttpRequestFailureReason.Error`, and `HttpRequestFailureReason.Cancelled`. | | `httpStatus` | `number` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `undefined` for exception and cancellation failures. | | `cause` | `unknown` | The exception (error) that caused the request to fail. This value is `undefined` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DataFileUpdate` to react to SDK data file updates, or `EventType.HttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `EventCallbackType` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEventDataType` for `EventType.DataFileUpdate`, or `HttpRequestEventDataType` for `EventType.HttpRequest`. Pass `null` to remove the current handler. | *** ##### Sending exposure events to external tools Kameleoon offers built-in integrations with various analytics and CDP solutions, such as [Mixpanel, Google Analytics 4, Segment...](/user-manual/integrations/integrations-overview). To ensure that you can track and analyze your server-side experiments, Kameleoon provides a method `getEngineTrackingCode()` that returns the JavasScript code to be inserted in your page to automatically send the exposure events to the analytics solution you are using. The SDK builds a tracking code for your active analytics solution based on the experiments that the visitor has triggered in the last 5 seconds. For more information about hybrid experimentation, refer to this [documentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation). To benefit from this feature, you will need to implement both the React SDK and the Kameleoon JavaScript tag. Implement the \[Kameleoon asynchronous tag], which you can install before your closing `` tag in your HTML page, as it will be only used for tracking purposes. ### Data types Kameleoon Data types are helper classes used for storing data in storage in predefined forms. During the [flush](#flush) execution, the SDK collects all the data and sends it along with the tracking request. Data available in the SDK isn't available for targeting and reporting in the Kameleoon app until you add the data. For example, by using the `addData()` method. See [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for more information. If you are using hybrid mode, you can call `getRemoteVisitorData()` to automatically fill all data that Kameleoon has collected previously. #### Browser Since React SDK `10.11.0`, `Browser` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. Browser contains browser information. Each visitor can only have one `Browser`. Adding a second `Browser` overwrites the first one. | Name | Type | Description | | ------------------------------- | ------------- | ----------------------------------------------------------------------------------------------- | | browser required | `BrowserType` | predefined browser type (`Chrome`, `InternetExplorer`, `Firefox`, `Safari`, `Opera`, `Other`) | | version optional | `number` | version of the browser, floating point number represents major and minor version of the browser | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); addData('my_visitor_code', browser); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); addData('my_visitor_code', browser); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### UniqueIdentifier `UniqueIdentifier` data is used as marker for unique visitor identification. If you add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). Associating a `UniqueIdentifier` with a visitor notify SDK that the visitor is linked to another visitor. The `UniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. Each visitor can only have one `UniqueIdentifier`. Adding another `UniqueIdentifier` overwrites the first one. | Name | Type | Description | | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | value required | `boolean` | value that specifies if the visitor is associated with another visitor, provided `false` will imply that the visitor isn't associated with any other visitor | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UniqueIdentifier } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new unique identifier to a visitor addData('my_visitor_code', new UniqueIdentifier(true)); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UniqueIdentifier } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new unique identifier to a visitor addData('my_visitor_code', new UniqueIdentifier(true)); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. `ConversionParametersType` conversionParameters - an object with conversion parameters described below | Name | Type | Description | Default | | ---------------------------------- | -------------- | ----------------------------------------------- | ----------- | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. | `undefined` | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Conversion, ConversionParametersType, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Defined conversion parameters const conversionParameters: ConversionParametersType = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); addData('my_visitor_code', conversion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Conversion, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Defined conversion parameters const conversionParameters = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); addData(visitorCode, conversion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. * Generally, the React SDK will attempt to use a `localStorage` cookie for the conditions. If not possible, SDK can use `Cookie` data as an alternative. * Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. | Name | Type | Description | | ------------------------------ | -------------- | ------------------------------------------------------------------- | | cookie required | `CookieType[]` | A list of `CookieType` objects consisting of cookie keys and values | ```ts theme={null} import { KameleoonClient, CookieType, Cookie, useInitialize, useData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new cookie data to client const cookieData: CookieType[] = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); addData('my_visitor_code', cookie); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { KameleoonClient, CookieType, Cookie, useInitialize, useData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new cookie data to client const cookieData = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); addData('my_visitor_code', cookie); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ##### Methods `Cookie` data has a static utility method `fromString` that you can use to create a cookie instantly by parsing a string that contains valid cookie data. The method accepts `string` as parameter and returns an initialized `Cookie` instance. ```ts theme={null} import { Cookie } from '@kameleoon/react-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie: Cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` ```js theme={null} import { Cookie } from '@kameleoon/react-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` #### GeolocationData `GeolocationData` contains the visitor's geolocation details Each visitor can only have one `GeolocationData`. Adding a second `GeolocationData` overwrites the first one. An object parameter with the type `GeolocationInfoType` containing the following fields: | Name | Type | Description | | ----------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------- | | country required | `string` | The country of the visitor | | region optional | `string` | The region of the visitor | | city optional | `string` | The city of the visitor | | postalCode optional | `string` | The postal code of the visitor | | coordinates optional | `[number, number]` | Coordinates array tuple of two position values (longitude and latitude). Coordinate number represents decimal degrees | ```ts theme={null} import { KameleoonClient, GeolocationData, GeolocationInfoType, useData, useInitialize, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add geolocation data const geolocationInfo: GeolocationInfoType = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); addData('my_visitor_code', geolocationData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { KameleoonClient, GeolocationData, useData, useInitialize, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add geolocation data const geolocationInfo = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); addData('my_visitor_code', geolocationData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### CustomData To retain custom data for future visits, the SDK transmits `CustomData` with a `Visitor` scope during the next tracking request. You can configure the scope in the data settings on the [custom data dashboard](https://app.kameleoon.com/customData/dashboard). `CustomData` allows you to associate any type of data with each visitor easily. This data can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `number`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | value required | `string[]` | The custom data value. It must be stringified to match the `string` type. *Note:* value is variadic. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Defined conversion parameters const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData( customDataIndex, dataItemOne, dataItemTwo, ); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add new custom data to client addData('my_visitor_code', customData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Defined conversion parameters const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData( customDataIndex, dataItemOne, dataItemTwo, ); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add new custom data to client addData('my_visitor_code', customData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### Device Since React SDK `10.11.0`, `Device` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. In React Native, the `Device` is automatically detected based on the `DPI` from `react-native.Dimensions`. Device contains information about your device. Each visitor can only have one `Device`. Adding a second `Device` overwrites the first one. | Name | Type | Description | | ---------------------------------- | ------------ | ------------------------------------------------------------- | | deviceType required | `DeviceType` | possible types for device type (`PHONE`, `TABLET`, `DESKTOP`) | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Device, DeviceType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new device data to client const device = new Device(DeviceType.Desktop); addData('my_visitor_code', device); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Device, DeviceType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new device data to client const device = new Device(DeviceType.Desktop); addData('my_visitor_code', device); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### OperatingSystem Since React SDK `10.11.0`, `OperatingSystem` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. In React Native, the `OperatingSystem` is automatically detected based on the `react-native.Platform`. `OperatingSystem` contains the visitor's operating system information. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the previous one. | Name | Type | Description | | --------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- | | operatingSystem required | `OperatingSystemType` | possible types for device type: `WINDOWS_PHONE`, `WINDOWS`, `ANDROID`, `LINUX`, `MAC`, `IOS` | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, OperatingSystem, OperatingSystemType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); addData('my_visitor_code', operatingSystem); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, OperatingSystem, OperatingSystemType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); addData('my_visitor_code', operatingSystem); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### PageView Since React SDK `10.11.0`, `PageView` is automatically detected based on the `window.location?.href` and `document.title`. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. PageView contains information about your web page. Each visitor can have one `PageView` per unique URL. Adding a `PageView` with the same URL as an existing one will notify SDK that the visitor revisited page `PageViewParametersType` pageViewParameters - an object with page view parameters described below | Name | Type | Description | | ---------------------------------- | ---------- | ---------------------------------------------------------------------------------- | | urlAddress required | `string` | url address of the page to track | | title required | `string` | title of the web page | | referrer optional | `number[]` | an optional parameter containing a list of referrers Indices, has no default value | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, PageView, PageViewParametersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Define page view parameters const pageViewParameters: PageViewParametersType = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add new page view data to client const pageView = new PageView(pageViewParameters); addData('my_visitor_code', pageView); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, PageView } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Define page view parameters const pageViewParameters = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add new page view data to client const pageView = new PageView(pageViewParameters); addData('my_visitor_code', pageView); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### UserAgent Store information on the user-agent of the visitor. Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that could otherwise skew your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. A visitor can only have one `UserAgent`. Adding a second `UserAgent` overwrites the first one. | Name | Type | Description | | ----------------------------- | -------- | ------------------------- | | value required | `string` | value used for comparison | Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Pass the user agent to be filtered by Kameleoon when running server-side experiments for each visitor browsing your website, to avoid counting bots in your analytics. If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UserAgent } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new user agent data to client const userAgent = new UserAgent('my_unique_value'); addData('my_visitor_code', userAgent); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UserAgent } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new user agent data to client const userAgent = new UserAgent('my_unique_value'); addData('my_visitor_code', userAgent); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, ApplicationVersion, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new application version data to client const applicationVersion = new ApplicationVersion('1.2'); addData('my_visitor_code', applicationVersion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, ApplicationVersion, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new application version data to client const applicationVersion = new ApplicationVersion('1.2'); addData('my_visitor_code', applicationVersion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `number` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```ts theme={null} import { FeatureFlag } from '@kameleoon/javascript-sdk'; // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags: Map = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified: number = dateFile.dateModified; ``` ```js theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified = dateFile.dateModified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Rule[]` | A list of `Rule` objects | ```ts theme={null} import { Variation, Rule } from '@kameleoon/react-sdk'; // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled: boolean = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey: string = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations: Map = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules: Rule[] = featureFlag.rules; ``` ```js theme={null} // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules = featureFlag.rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```ts theme={null} import { Variation } from '@kameleoon/react-sdk'; // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations: Map = rule.variations; ``` ```js theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations = rule.variations; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | --------------------------------------------------------------------------------------------------- | | name | `string` | name of the variation. | | key | `string` | key of the variation. | | id | `number` or `null` | id of the variation or `null` if the visitor landed on the default variation. | | experimentId | `number` or `null` | id of the experiment or `null` if the visitor landed on the default variation. | | variables | `Map` | map of variables for the variation, where key is the variable key and value is the variable object. | * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```ts theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` ```js theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | key | `string` | The unique key identifying the variable. | | type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | value | `any` | The value of the variable, which can be of the following types: **boolean**, **number**, **String**, **Record\**, **any\[]**. | ```ts theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ```js theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ### Deprecated methods These methods are deprecated and will be removed in the next major update. #### getFeatureFlagVariationKey() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` Use the [`getVariation`](#getvariation) method. The method `getFeatureFlagVariationKey()`, which is used with the `useFeatureFlag` hook, retrieves the variation key for a visitor identified by their `visitorCode`. This process includes checking the targeting criteria, identifying the appropriate variation assigned to the visitor, storing this information, and sending a tracking request. If a user has never been associated with a feature flag, the SDK will randomly return a variation key according to the rules of that feature flag. If the user is already linked to the feature flag, the SDK will identify the previously assigned variation key. If the user doesn't meet any of the specified rules, the SDK will return the default value defined in Kameleoon’s feature flag delivery rules. It’s important to note that the default value may not always be a variation key; it could also be a boolean value or another data type, depending on how the feature flag is configured. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getFeatureFlagVariationKey } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function. const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Get the variationKey for the visitor under `visitorCode` in the feature flag. const variationKey = getFeatureFlagVariationKey(visitorCode, featureKey); }, [initialize, visitorCode, getFeatureFlagVariationKey, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getFeatureFlagVariationKey } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Get the variationKey for the visitor under `visitorCode` in the found feature flag const variationKey = getFeatureFlagVariationKey(visitorCode, featureKey); }, [initialize, visitorCode, getFeatureFlagVariationKey, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | ##### Return value | Type | Description | | -------- | --------------------------------------------------------------------------------------------------- | | `string` | a string containing variable key for the allocated feature flag variation for the provided visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | #### getVisitorFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getVisitorFeatureFlags` method, utilized with the `useFeatureFlag` hook, returns a list of *active* feature flags that target the visitor associated with the `visitorCode` (the visitor must have one of the allocated variations). This method only collects the feature flags that are currently active for the visitor. As a result, it doesn't include any feature flags for which the visitor is assigned to the “off” variation (default or control). If you need to retrieve all of the visitor’s feature flags, use `getFeatureFlags` instead. For example: ```ts theme={null} // -- `getVisitorFeatureFlags` doesn't trigger feature experiments; // it only returns feature flags where visitors didn't get the `off` variation. getVisitorFeatureFlags('my_visitor').forEach(({ key }) => { // -- `getFeatureFlagVariationKey` triggers a feature experiment, // as `off` is already filtered out - visitors will never take part // in an experiment where the `off` variation was allocated. getFeatureFlagVariationKey('my_visitor', key); }); ``` For cases where you need all of the visitor's feature flags, use [`getFeatureFlags`](#getfeatureflags) instead: ```ts theme={null} // -- Both `off` and other variations are processed as expected getFeatureFlags('my_visitor').forEach(({ key }) => { getFeatureFlagVariationKey('my_visitor', key); }); ``` ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getVisitorFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = getVisitorFeatureFlags(visitorCode); }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getVisitorFeatureFlags } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = getVisitorFeatureFlags(visitorCode); }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | ##### Return value | Type | Description | | ------------------- | ---------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key`. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | *** #### getActiveFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getActiveFeatureFlags` method, collected with the `useFeatureFlag` hook, returns a `Map`, where key is feature key and value is detailed information about the visitor's variation and it's variables ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getActiveFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } init(); ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getActiveFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } init(); ``` This method only collects the visitor's *active* feature flags. This means the result excludes all the feature flags for which the visitor is assigned to the `off` (default or control) variation. When you need all of the visitor's feature flags to iterate over, use `getFeatureFlags` instead. See the [getVisitorFeatureFlags](#getvisitorfeatureflags) *CAUTION* section method for more details. ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | ##### Return value | Type | Description | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | a map of feature flags, where key is feature key and value is detailed information about the visitor's variation and it's variables | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length of 255 characters | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | | `KameleoonException.NumberParse` | Couldn't parse Number value | | `KameleoonException.JSONParse` | Couldn't parse JSON value | *** #### getFeatureFlagVariable() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` Use the [`getVariation`](#getvariation) method. The `getFeatureFlagVariable` method, collected with `useFeatureFlag` hook, returns a variable for the visitor under `visitorCode` in the found feature flag, this includes targeting check, finding the according variation exposed to the visitor and saving it to storage along with sending tracking request. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useFeatureFlag, VariableType, JSONType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariable } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get feature variable const result = getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key', variableKey: 'my_variable_key', }); // -- Infer the type of variable by its `type` switch (result.type) { case VariableType.BOOLEAN: const myBool: boolean = result.value; break; case VariableType.NUMBER: const myNum: number = result.value; break; case VariableType.JSON: const myJson: JSONType = result.value; break; case VariableType.STRING: case VariableType.JS: case VariableType.CSS: const myStr: string = result.value; break; default: break; } }, [initialize, getFeatureFlagVariable, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariable } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get feature variable const variableResult = getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key', variableKey: 'my_variable_key', }); const { type, value } = variableResult; }, [initialize, getFeatureFlagVariable, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object of type `GetFeatureFlagVariableParamsType` containing the following fields: | Name | Type | Description | | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | | variableKey required | `string` | key of the variable to be found for a feature flag with the specified `featureKey`, can be found on Kameleoon Platform | ##### Return value | Type | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureFlagVariableType` | a variable object containing `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN` then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagVariableNotFound` | No feature variable was found for the specified `visitorCode` and `variableKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | #### getFeatureFlagVariables() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getFeatureFlagVariables` method, collected with the `useFeatureFlag`, hook returns a list of variables for the visitor under `visitorCode` in the found feature flag, this includes targeting check, finding the according variation exposed to the visitor and saving it to storage along with sending tracking request. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariables } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = getFeatureFlagVariables(visitorCode, 'my_feature_key'); }, [initialize, getFeatureFlagVariables, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariables } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode('www.example.com'); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = getFeatureFlagVariables(visitorCode, 'my_feature_key'); }, [initialize, getFeatureFlagVariables, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | ##### Return value | Type | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureVariableResultType[]` | a list of variable objects containing `key`, `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN` then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagVariationNotFound` | No feature variation was found for the specified `visitorCode` and `variableKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | *** #### onConfigurationUpdate() Use the `onEvent` method with `EventType.ConfigurationUpdate` instead. Method `onConfigurationUpdate` collected with `useInitialize` hook fires a callback on client configuration update. This hook only works for server sent events of real time update ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, onConfigurationUpdate } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); // -- Define logic to execute on client configuration update onConfigurationUpdate(() => { // -- My Logic }); }, [initialize, onConfigurationUpdate]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, onConfigurationUpdate } = useInitialize(); const init = useCallback(async () => { await initialize(); // -- Define logic to execute on client configuration update onConfigurationUpdate(() => { // -- My Logic }); }, [initialize, onConfigurationUpdate]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | -------------------------------- | ------------ | ---------------------------------------------------------------------------------- | | callback required | `() => void` | callback function with no parameters that will be called upon configuration update | ##### Exceptions thrown | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | *** #### getFeatureFlags() 🚫 *Doesn't send Tracking Data to Kameleoon* The `getFeatureFlags` method collected with the `useFeatureFlag` hook returns a list of feature flags stored in the client configuration. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get list of all feature flags const featureFlags = getFeatureFlags(); }, [initialize, getFeatureFlags]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getFeatureFlags } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get list of all feature flags const featureFlags = getFeatureFlags(); }, [initialize, getFeatureFlags]); useEffect(() => { init(); }, [init]); } ``` ##### Return value | Type | Description | | ------------------- | ---------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key`. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | #### onEvent() Use [`setEventHandler()`](#seteventhandler) instead. Method `onEvent`, collected with the `useInitialize` hook, fires a callback when a specific event is triggered. The callback function has access to the data associated with the event. The SDK methods in this documentation note which event types they can trigger, if any. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, EvaluationEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, onEvent } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); // -- Define logic to execute on SDK event onEvent(EventType.Evaluation, (eventData: EventDataType) => { // -- My Logic }); }, [initialize, onEvent]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, onEvent } = useInitialize(); const init = useCallback(async () => { await initialize(); // -- Define logic to execute on SDK event .onEvent(EventType.Evaluation, (eventData) => { // -- My Logic }); }, [initialize, onEvent]); useEffect(() => { init(); }, [init]); } ``` You can only assign one callback to each `EventType`. ##### Events Events are defined in the `EventType` enum. Depending on the event type, the `eventData` parameter will have a different type. | Type | `eventData` type | Description | | ------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `EventType.Evaluation` | `EvaluationEventDataType` | Triggered when the SDK evaluates any variation for a feature flag. It's triggered regardless of the result variation | | `EventType.ConfigurationUpdate` | `ConfigurationUpdateEventDataType` | Triggered when the SDK receives a configuration update from the server (when using real-time streaming) | ##### Parameters | Name | Type | Description | | -------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | event required | `EventType` | a type of the event to associate the callback action with | | callback required | `(eventData: EventDataType) => void` | a callback function with the `eventData` parameter that will be called when a configuration update occurs | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | # Quickstart Source: https://docs.kameleoon.com/developer-docs/sdks/quickstart Compare Kameleoon's web, server-side, and mobile SDKs at a glance, then jump straight to the one you need to install and initialize. Kameleoon provides a dedicated SDK for each major web, server-side, and mobile platform. Find the SDK for your stack below and follow its link for the exact setup steps on that SDK's own reference page. ## Before you start * **A Kameleoon project.** Every SDK needs a site code to connect to your Kameleoon account. If you don't have one yet, [add a new project](../../user-manual/project-management/manage-your-projects#add-a-new-project) and choose the SDK option when prompted. * **API credentials for server-side SDKs.** Server-side SDKs authenticate with a client ID and client secret. Find or generate these in [API credentials](../../user-manual/account-and-team-management/users-and-teams/api-credentials). Client-side and mobile SDKs authenticate with just your project's site code. * **A feature flag to test against.** [Create a feature flag](../../user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag) in your account so you have something to check once the client initializes. ## Choose your SDK Select your platform, then your language. Each card links directly to that SDK's install and initialization steps. ### Web Framework-agnostic client-side SDK for any web app. Install using the SDK Installer, or a single ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code.
##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ### Events #### SetEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```csharp theme={null} using Kameleoon.Events; kameleoonClient.SetEventHandler(EventType.DataFileUpdate, new DataFileUpdateHandler()); class DataFileUpdateHandler : IDataFileUpdateHandler { public void OnUpdate(DataFileUpdateEvent updateEvent) { DataFileUpdateEvent.UpdateSource source = updateEvent.Source; // Polling or Streaming long dateModified = updateEvent.DateModified; // Data file modification date in milliseconds. // React to the data file update. } } // Clear the handler. kameleoonClient.SetEventHandler(EventType.DataFileUpdate, null); ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Source` | `DataFileUpdateEvent.UpdateSource` | The update source. [`Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `DateModified` | `long` | The modification date of the updated data file, in milliseconds. | ```csharp theme={null} using System; using Kameleoon.Events; kameleoonClient.SetEventHandler(EventType.HttpRequest, new HttpRequestHandler()); class HttpRequestHandler : IHttpRequestHandler { public void OnRequestSucceeded(RequestType requestType, int httpStatus, TimeSpan duration) { // The SDK request completed successfully. } public void OnRequestFailed(RequestType requestType, HttpRequestFailure failure, TimeSpan duration) { HttpRequestFailure.FailureReason reason = failure.Reason; int? httpStatus = failure.HttpStatus; Exception cause = failure.Cause; // The SDK request failed. } } // Clear the handler. kameleoonClient.SetEventHandler(EventType.HttpRequest, null); ``` `IHttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `OnRequestSucceeded()` for successful responses and `OnRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `OnRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `OnRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `requestType` | `RequestType` | The SDK request type. Possible values are `DataFile`, `Tracking`, `RemoteVisitorData`, `RemoteData`, and `AccessToken`. | | `httpStatus` | `int` | The HTTP status code returned by the request. Passed only to `OnRequestSucceeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `OnRequestFailed()`. | | `duration` | `TimeSpan` | The request duration. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Reason` | `HttpRequestFailure.FailureReason` | The failure reason. Possible values are `HttpStatus`, `Exception`, and `Cancelled`. | | `HttpStatus` | `int?` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `null` for exception and cancellation failures. | | `Cause` | `Exception` | The exception (error) that caused the request to fail. This value is `null` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DataFileUpdate` to react to SDK data file updates, or `EventType.HttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `THandler : IEventHandler` | The handler to register for the selected event type. Use a `IDataFileUpdateHandler` for `EventType.DataFileUpdate`, or an `IHttpRequestHandler` for `EventType.HttpRequest`. Pass `null` to remove the current handler. | ### Data types Data available in the SDK isn't available for targeting and reporting in the Kameleoon app until it's added; for example, by using the `addData()` method. See [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for more information. If you are in hybrid mode, you can call `GetRemoteVisitorData()` to automatically fill all data that Kameleoon collected previously. The following data types are available in `Kameleoon.Data.IData`. #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | --------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------- | | `browser` required | `Browser.Browsers` | List of browsers: `CHROME`, `INTERNET_EXPLORER`, `FIREFOX`, `SAFARI`, `OPERA`, `OTHER`. | | `version` optional | `float?` | Version of the browser, floating point number represents major and minor version of the browser | ```csharp theme={null} kameleoonClient.AddData(visitorCode, new Browser(Browser.Browsers.CHROME)); kameleoonClient.AddData(visitorCode, new Browser(Browser.Browsers.SAFARI, 16)); ``` #### PageView | Name | Type | Description | | --------- | ------ | -------------------------------------------------- | | url | string | URL of the page viewed. This field is mandatory. | | title | string | Title of the page viewed. This field is mandatory. | | referrers | int\[] | Referrers of viewed pages. This field is optional. | The index (ID) of the referrer is available in the Back-Office in the Acquisition channel configuration page. Be careful: this index starts at 0, so the first [acquisition channel](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) you create for a given site would have the ID 0, not 1. ```csharp theme={null} kameleoonClient.AddData( visitorCode, new PageView("https://url.com", "title", new int[] {3}) ); ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | --------------------- | ----------------------------------------------- | ------------------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `params CustomData[]` | Metadata of the conversion. | `new CustomData[0]` | ```csharp theme={null} kameleoonClient.AddData(visitorCode, new Conversion(32, 10f)); kameleoonClient.AddData(visitorCode, new Conversion(33, negative: true)); kameleoonClient.AddData( visitorCode, new Conversion(34, 5f, metadata: new CustomData(3, "metadata1", "md2"), new CustomData(5, "md3")) ); ``` #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `int`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `params string[]` | Values of the custom data to be stored. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```csharp theme={null} kameleoonClient.AddData(visitorCode, new CustomData(1, "value")); // With several values kameleoonClient.AddData(visitorCode, new CustomData(1, "value1", "value2")); // To set the 'overwrite' flag to false kameleoonClient.AddData(visitorCode, new CustomData(1, false, "value")); // To use a name instead of the index kameleoonClient.AddData(visitorCode, new CustomData("my-custom-data", "value")); ``` #### Device | Name | Type | Description | | ------ | ----------- | ----------------------------------------------------------------------------- | | device | Device.Type | List of devices: **PHONE**, **Device**, **DESKTOP**. This field is mandatory. | ```csharp theme={null} kameleoonClient.AddData(visitorCode, new Device(Device.Type.DESKTOP)); ``` #### UserAgent Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to tackle this issue and recognize known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that might distort your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. | Name | Type | Description | | ----- | ------ | ---------------------------------------------------------------------------------------- | | Value | string | The `UserAgent` value that will be sent with tracking requests. This field is mandatory. | ```csharp theme={null} kameleoonClient.AddData(visitorCode, new UserAgent("Your User Agent")); ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data with the visitor associated with the specified identifier. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. | Name | Type | Description | | ----- | ------ | ------------------------------------------------------------------------------------------- | | value | `bool` | Parameter for specifying if the visitorCode is a unique identifier. This field is required. | ```csharp theme={null} kameleoonClient.AddData(visitorCode, new UniqueIdentifier(true)); ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. | Name | Type | Description | | ---- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- | | type | `OperatingSystem.Type` | List of operating systems: `WINDOWS`, `MAC`, `IOS`, `LINUX`, `ANDROID` and `WINDOWS_PHONE`. This field is required. | ```csharp theme={null} kameleoonClient.addData(visitorCode, new OperatingSystem(OperatingSystem.Type.WINDOWS)); ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. | Name | Type | Description | | ------- | ------------------------------------- | --------------------------------------------------------------------------------- | | cookies | `IReadOnlyDictionary` | A string object map consisting of cookie keys and values. This field is required. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```csharp theme={null} Cookie cookie = new Cookie (new Dictionary() { { "k1", "v1" }, { "k2", "v2" }, }); kameleoonClient.addData(visitorCode, cookie); ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `string` | The country of the visitor. | | `region` optional | `string` | The region of the visitor. | | `city` optional | `string` | The city of the visitor. | | `postalCode` optional | `string` | The postal code of the visitor. | | `latitude` optional | `float` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `float` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```csharp theme={null} kameleoonClient.addData(visitorCode, new Geolocation("France", "Île-de-France", "Paris")); ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```csharp theme={null} client.AddData(visitorCode, new ApplicationVersion("10")) // major client.AddData(visitorCode, new ApplicationVersion("10.20")) // major.minor client.AddData(visitorCode, new ApplicationVersion("10.20.30")) // major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | ------------------------------------------ | --------------------------------------------------------------------------------- | | `FeatureFlags` | `IReadOnlyDictionary` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `DateModified` | `long` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```csharp theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. IReadOnlyDictionary featureFlags = dataFile.FeatureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a long representing milliseconds since the Unix epoch. long dateModified = dataFile.DateModified; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation if no specific assignment exists). | Name | Type | Description | | ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Key | `string` | The unique key identifying the variation. | | Id | `int` | The ID of the assigned variation (or `Variation.UndefinedId` if it's the default variation). | | ExperimentId | `int` | The ID of the experiment associated with the variation (or `Variation.UndefinedId` if default). | | Variables | `IReadOnlyDictionary` | A dictionary containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `Id` or `ExperimentId` may be `Variation.UndefinedId`, indicating a default variation. * The `Variables` dictionary might be empty if no variables are associated with the variation. ```csharp theme={null} // Retrieving the variation key string variationKey = variation.Key; // Retrieving the variation id int variationId = variation.Id; // Retrieving the experiment id int experimentId = variation.ExperimentId; // Retrieving the variables map var variables = variation.Variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | Key | `string` | The unique key identifying the variable. | | Type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | Value | `object` | The value of the variable, which can be of the following types: **bool**, **int**, **double**, **string**, **Newtonsoft.Json.Linq.JToken**. | ```csharp theme={null} // Retrieving the variables map var variables = variation.Variables; // Variable type can be retrieved for further processing string type = variables["isDiscount"].Type; // Retrieving the variable value by key bool isDiscount = (bool)variables["isDiscount"].Value; // Variable value can be of different types string title = (string)variables["title"].Value; ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `5.0.0`. #### GetFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* To get feature variation key, call `GetFeatureVariationKey()`. Use [`GetVariation()`](#getvariation) instead. This method requires a **visitorCode** and a **featureKey** (or **featureID**) to check if a user can access a specific feature. If the user has never been linked to this feature, the SDK will randomly decide whether to activate it, returning either **true** (they can access the feature) or **false** (they can't). If the user with the given **visitorCode** is already linked to this feature, the system will return the previous value of the **featureFlag**. Make sure to include proper error handling in your code, as shown in the example, to catch any potential errors. If you specify a `visitorCode`, the `GetFeatureVariationKey()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```csharp theme={null} string visitorCode = kameleoonClient.GetVisitorCode(Request, Response, "example.com"); string featureKey = "new_checkout"; string variationKey = ""; try { variationKey = kameleoonClient.GetFeatureVariationKey(visitorCode, featureKey); } catch (KameleoonException.FeatureNotFound e) { // The feature is not yet activated on Kameleoon's side. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } switch (variationKey) { case "on": // Main variation key is selected for visitorCode. break; case "alternative_variation": // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | featureKey | string | Key of the feature you want to expose to a user. This field is mandatory. | | isUniqueIdentifier (Deprecated) | bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------- | | string | Variation key of the feature flag that's registered for a given **visitorCode**. | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature key hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### GetActiveFeatureListForVisitor() * Use [`GetActiveFeatures`](#getactivefeatures) instead. * This method was previously called `ObtainFeatureListForVisitorCode()`, which was removed in SDK version `4.0.0`. This method takes a single `visitorCode` parameter. Return only the active feature flags for the specified visitor. ```csharp theme={null} var featureListIds = kameleoonClient.GetActiveFeatureListForVisitor(visitorCode) ``` ##### Parameters | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | ##### Return value | Type | Description | | -------------- | -------------------------------------------------------------------- | | `List` | List of active feature flag IDs available for specific `visitorCode` | #### GetFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* Use [`GetVariation()`](#getvariation) instead. To get variable of variation key associated with a user, call the `GetFeatureVariable()` method of the SDK. This method requires a **visitorCode** and a **featureKey** (or **featureID**) to check if a user can access a specific feature. If the user has never been linked to this feature, the SDK will randomly decide whether to activate it, returning either **true** (they can access the feature) or **false** (they can't). If the user with the given **visitorCode** is already linked to this feature, the system will return the previous value of the **featureFlag**. Make sure to include proper error handling in your code, as shown in the example, to catch any potential errors. If you specify a `visitorCode`, the `GetFeatureVariable()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```csharp theme={null} var visitorCode = kameleoonClient.GetVisitorCode(req, res, "example.com"); const string featureKey = "feature_key"; const string variableKey = "var" try { var variableValue = kameleoonClient.GetFeatureVariable(visitorCode, featureKey, variableKey); // Your custom code, depending on variableValue } catch (KameleoonException.FeatureNotFound e) { // The feature is not yet activated in the Kameleoon app } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment } catch (KameleoonException.FeatureVariableNotFound e) { // Requested variable not defined in the Kameleoon app } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | featureKey | string | Key of the feature you want to expose to a user. This field is mandatory. | | variableKey | string | Key of the variable you want to get a value. This field is mandatory. | | isUniqueIdentifier (Deprecated) | bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | object | Value of variable of variation that's registered for a given **visitorCode** for this feature flag. Possible types: bool, int, double, string, JObject, JArray | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature key hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web app's side). | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.FeatureVariableNotFound | Exception indicating that the requested variable wasn't found. Check that the variable's key in the Kameleoon app matches the one in your code. | | KameleoonException.VisitorCodeInvalid | Exception indicating that the specified visitor code isn't valid. (It's either empty or longer than 255 characters). | #### GetActiveFeatures() Use [`GetVariations()`](#getvariations) instead. `GetActiveFeatures` method retrieves information about the active feature flags that are available for the specified visitor code. The `Kameleoon.Types.Variation.Id` and `Kameleoon.Types.Variation.ExperimentId` properties of returned variations are optional. If not specified, the default value is `Kameleoon.Types.Variation.UndefinedId`. ```csharp theme={null} IReadOnlyDictionary activeFeatures = GetActiveFeatures(visitorCode); ``` ##### Parameters | Name | Type | Description | | ----------- | ------ | -------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the visitor you want to retrieve active feature flags for. This field is mandatory. | ##### Return value | Type | Description | | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `IReadOnlyDictionary` | A dictionary that contains the assigned variations of the active features using the active feature IDs as keys. | ##### Exceptions thrown | Type | Description | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | KameleoonException.VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### GetFeatureVariationVariables() * Use [`GetVariation()`](#getvariation) instead. * This method was previously called `GetFeatureAllVariables()`, which was removed in SDK version `4.0.0`. Call this method to retrieve all feature variables for a feature. You can modify feature variables in the Kameleoon app. This method takes two input parameters: `featureKey` and `variationKey`. It returns the data with the `Dictionary` type, as defined on the web interface. It will throw an exception (`KameleoonException.FeatureNotFound`) if the requested feature hasn't been found in the SDK's internal configuration. ```csharp theme={null} string featureKey = "myFeature"; try { var allVariables = kameleoonClient.GetFeatureVariationVariables(featureKey, variationKey); } catch (KameleoonException.FeatureNotFound e) { // The feature is not yet activated in the Kameleoon app. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (KameleoonException.FeatureVariationNotFound e) { // The variation is not activated in the Kameleoon app (the associated experiment is not online). } catch (Exception e) { // This is a generic Exception handler which will handle all exceptions. Console.WriteLine("Exception occurred"); } ``` ##### Parameters | Name | Type | Description | | ------------ | -------- | -------------------------------------------------------------------------- | | featureKey | `string` | Identifier key of the feature you need to obtain. This field is mandatory. | | variationKey | `string` | Key of the variation you want to obtain. This field is required. | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Dictionary` | Data associated with this feature flag. The values of can be a number, string, boolean or object (depending on the type defined on the web interface). | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side. | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that the feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.FeatureVariationNotFound | Exception indicating that the requested variation ID wasn't found in the internal configuration of the SDK. This usually means that the variation's corresponding experiment isn't activated in the Kameleoon app. | #### UpdateConfigurationHandler() The `UpdateConfigurationHandler()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```csharp theme={null} kameleoonClient.UpdateConfigurationHandler(async delegate () { // Configuration was updated }); ``` ##### Parameters | Name | Type | Description | | --------- | -------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `Action` | The handler that will be called when the configuration is updated using a real-time configuration event. | # Elixir SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/elixir-sdk Integrate the Kameleoon Elixir SDK to run experiments and activate feature flags in Elixir services and web back-ends. With the Kameleoon Elixir SDK, you can run experiments and activate feature flags in your Elixir services and web back-ends. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Version**: Latest version of the Elixir SDK: 0.9.2 [Changelog](https://docs.kameleoon.com/developer-docs/sdks/web-sdks/elixir-sdk). **SDK methods**: For the full reference documentation of the Elixir SDK, see the [reference](#reference) section. ## Developer guide This guide is designed to help you integrate the Elixir SDK quickly and start evaluating feature flags in your Elixir app. ### Getting started #### Install the Elixir client The Kameleoon Elixir SDK includes native code powered by the Kameleoon Rust core. During dependency compilation, Mix invokes Cargo to build the native library. Make sure **Rust** and **Cargo** are installed and available in your system's `PATH` before running `mix deps.get` or `mix compile`. Install Rust and Cargo: [https://rust-lang.org/tools/install](https://rust-lang.org/tools/install) The Rust core is compiled locally for your target platform and architecture, ensuring optimal compatibility with your environment. Future releases will provide prebuilt native libraries for common platforms to simplify installation. These prebuilt binaries will be suitable for most use cases, while local compilation will remain available for users who prefer to build from source. Add the SDK as a dependency in your `mix.exs` file: ```elixir title="mix.exs" theme={null} def deps do [ {:kameleoon_client, "~> 0.9.0"} ] end ``` #### Additional configuration Create a `client-elixir.json` configuration file to provide credentials and customize SDK behavior. Save this file to the default path, `/etc/kameleoon/client-elixir.conf`. The Elixir SDK can be configured either with a configuration file used by `config_path:` or by passing a `%Kameleoon.ClientConfig{}` struct with the `config:` option. The following table shows the available properties that you can set: | Key (Code / Config File) | Description | Default value | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | | `client_id` / `clientId` required | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `client_secret` / `clientSecret` required | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `session_duration_minutes` / `sessionDurationMinutes` optional | Time interval, in minutes, during which the SDK keeps a visitor and their associated data in memory. | `30` minutes | | `refresh_interval_minutes` / `refreshIntervalMinutes` optional | Interval, in minutes, used to refresh the active experiments and feature flags configuration. | `60` minutes | | `default_timeout_millis` / `defaultTimeoutMillis` optional | Default timeout, in milliseconds, for SDK network requests. | `10000` milliseconds | | `tracking_interval_millis` / `trackingIntervalMillis` optional | Interval, in milliseconds, used to batch tracking requests. Values are clamped to the `[1000, 5000]` range. | `1000` milliseconds | | `environment` / `environment` optional | Environment from which the feature flag configuration should be used. The value can be `production`, `staging`, or `development`. | `production` | | `top_level_domain` / `topLevelDomain` optional | The current top-level domain for your website. Use the format `example.com` without protocol or subdomains. | `nil` | | `proxy_host` / `proxyHost` optional | Proxy host for outgoing SDK calls. Supported formats: `https://my.prox`, `https://my.prox:4545`, `socks5://192.168.1.1:9000`. | `nil` | | `network_domain` / `networkDomain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `nil` | #### Initialize the Kameleoon client After you have installed the SDK and configured your credentials, create a `%Kameleoon.Client{}` by using `Kameleoon.ClientFactory`. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.ClientFactory site_code = "a8st4f59bj" {:ok, client} = ClientFactory.create(site_code, config_path: "/etc/kameleoon/client-elixir.conf") :ok = Client.initialize(client) ``` ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.ClientConfig alias Kameleoon.ClientFactory site_code = "a8st4f59bj" config = %ClientConfig{ client_id: "", # mandatory client_secret: "", # mandatory refresh_interval_minutes: 60, # optional (60 minutes by default) session_duration_minutes: 30, # optional (30 minutes by default) default_timeout_millis: 10_000, # optional (10000 ms by default) tracking_interval_millis: 1_000, # optional (1000 ms by default) environment: "development", # optional top_level_domain: ".example.com", # optional mandatory if you use hybrid mode (engine or web experiments) proxy_host: "http://192.168.0.25:8080", # optional network_domain: "example.com" # optional } {:ok, client} = ClientFactory.create(site_code, config: config) :ok = Client.initialize(client) ``` A `%Kameleoon.Client{}` is the main object used to evaluate feature flags, add visitor data, and send tracking requests. * It's recommended to use `%Kameleoon.Client{}` as a singleton object, as it serves as the bridge between your app and the Kameleoon platform. It exposes all required methods and properties to run experiments efficiently. * The Elixir SDK initializes asynchronously. You should call [`initialize()`](#initialize) before relying on feature evaluation in production code. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`get_visitor_code`](#get_visitor_code) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `default_visitor_code` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `get_visitor_code` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`get_variation`](#get_variation) or [`is_feature_active?`](#is_feature_active) method to retrieve the configuration based on the `feature_key`. The `get_variation` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `feature_key` and `visitor_code`. The `is_feature_active?` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `get_variation` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millis`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `get_variation` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `get_variations` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`Client.add_data`](#add_data) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`get_remote_visitor_data`](#get_remote_visitor_data) method. This method asynchronously fetches data from the servers. It's important to call `get_remote_visitor_data` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`track_conversion`](#track_conversion) method and provide the required `visitor_code` and `goal_id` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millis`](#additional-configuration)). If you prefer to send the request immediately, use the [`Client.flush_instant`](#flush) method. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`get_engine_tracking_code`](#get_engine_tracking_code) method. The `get_engine_tracking_code` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitor_code`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitor_code`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `account_id`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.CustomData :ok = Client.add_data(client, visitor_code, CustomData.new!(42, ["new_visitor_code"])) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`Client.add_data`](#add_data) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `new_visitor_code` refers to the identifier you wish to use for your bucketing (for example, the new `user_id` or `account_id`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `Client.add_data` method, all hash calculations for assigning users to variations will use this `new_visitor_code` (your custom key) instead of the default `visitor_code`. Using the `new_visitor_code` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `new_visitor_code` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitor_code`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String.t()`. * It must be unique for the entity you intend to bucket (for example, if using a `user_id`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `get_remote_visitor_data` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `get_remote_visitor_data`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`get_remote_visitor_data`](#get_remote_visitor_data) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitor_code`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```elixir title="Device A" theme={null} # In this example, a Custom data with index `90` was set to "Visitor" scope in Kameleoon. alias Kameleoon.Client alias Kameleoon.Data.CustomData visitor_scope_custom_data_index = 90 :ok = Client.add_data(client, visitor_code, CustomData.new!(visitor_scope_custom_data_index, ["your data"])) :ok = Client.flush_instant(client, visitor_code) ``` ```elixir title="Device B" theme={null} # Before working with the data, call the `get_remote_visitor_data` method. :ok = Client.get_remote_visitor_data(client, visitor_code) # After calling the method, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. # So, "your data" will be available for targeting and tracking the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`get_remote_visitor_data`](#get_remote_visitor_data) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `get_remote_visitor_data` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`track_conversion`](#track_conversion) or [`flush`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`get_remote_visitor_data`](#get_remote_visitor_data) method on each device. Here's an example of how to use custom data for session merging. ```elixir theme={null} # In this example, 91 represents the Custom Data's index configured as a unique identifier in Kameleoon. alias Kameleoon.Client alias Kameleoon.Data.CustomData alias Kameleoon.Data.UniqueIdentifier mapping_index = 91 feature_key = "ff123" anonymous_visitor_code = "anonymous-visitor" user_id = "authenticated-user" # 1. Before the visitor is authenticated # Retrieve the variation for an unauthenticated visitor. # Assume anonymousVisitorCode is the randomly generated ID for that visitor. {:ok, anonymous_variation} = Client.get_variation(client, anonymous_visitor_code, feature_key) # 2. After the visitor is authenticated # Assume `userId` is the visitor code of the authenticated visitor. :ok = Client.add_data(client, anonymous_visitor_code, CustomData.new!(mapping_index, [user_id])) :ok = Client.flush_instant(client, anonymous_visitor_code) # Indicate that `userId` is a unique identifier. :ok = Client.add_data(client, user_id, UniqueIdentifier.new!(true)) # 3. After the visitor was authorized # Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. {:ok, user_variation} = Client.get_variation(client, user_id, feature_key) is_same_variation = user_variation.key == anonymous_variation.key # true # `userId` and `anonymousVisitorCode` are now linked and can be tracked as a single visitor. :ok = Client.track_conversion(client, user_id, 123) # Additionally, the linked visitors share all fetched previously tracked remote data. :ok = Client.get_remote_visitor_data(client, user_id) ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`get_visitor_code`](#get_visitor_code) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```elixir theme={null} alias Kameleoon.Logger # The `:none` log level does not allow logging. :ok = Logger.set_log_level(:none) # The `:error` log level only allows logging issues that may affect the SDK's primary behaviour. :ok = Logger.set_log_level(:error) # The `:warning` log level allows logging issues which may require attention. # It extends the `:error` log level. # The `:warning` log level is the default log level. :ok = Logger.set_log_level(:warning) # The `:info` log level allows logging general information on the SDK's internal processes. # It extends the `:warning` log level. :ok = Logger.set_log_level(:info) # The `:debug` level logs additional details about the SDK's internal processes. :ok = Logger.set_log_level(:debug) ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```elixir theme={null} defmodule CustomLogger do @behaviour Kameleoon.Logger require Logger @impl true def log(level, message) do Logger.log(level, message) end end # Log level filtering is applied separately from log handling logic. # The custom logger will only accept logs that meet or exceed the specified log level. :ok = Kameleoon.Logger.set_log_level(:debug) :ok = Kameleoon.Logger.set_logger(CustomLogger) ``` ## Reference This is the full reference documentation for the Elixir SDK. ### Initialization #### create() To use the SDK, create a `%Kameleoon.Client{}` from a `%Kameleoon.ClientConfig{}` instance with `Kameleoon.ClientFactory.create()`. ```elixir theme={null} alias Kameleoon.ClientConfig alias Kameleoon.ClientFactory config = %ClientConfig{ client_id: "", # mandatory client_secret: "", # mandatory refresh_interval_minutes: 60, # optional (60 minutes by default) session_duration_minutes: 30, # optional (30 minutes by default) default_timeout_millis: 10_000, # optional (10000 ms by default) tracking_interval_millis: 1_000, # optional (1000 ms by default) environment: "development", # optional top_level_domain: ".example.com", # optional mandatory if you use hybrid mode (engine or web experiments) proxy_host: "http://192.168.0.25:8080", # optional network_domain: "example.com" # optional } {:ok, client} = ClientFactory.create(site_code, config: config) ``` ```elixir theme={null} alias Kameleoon.ClientFactory {:ok, client} = ClientFactory.create("a8st4f59bj", config_path: "/etc/kameleoon/client-elixir.conf") ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | ---------------------------- | ---------------------------------------------------- | | `site_code` required | `String.t()` | Unique key of the Kameleoon project used by the SDK. | | `config_path` optional | `String.t()` | Path to the configuration file. | | `config` optional | `Kameleoon.ClientConfig.t()` | SDK configuration struct. | ##### Return value | Type | Description | | ---------------------------------------------------- | ---------------------------------------------------------------- | | `{:ok, Kameleoon.Client.t()} \| {:error, Error.t()}` | A client instance on success, otherwise an initialization error. | ##### Errors | Type | Description | | -------------------------- | -------------------------------- | | `ConfigCredentialsInvalid` | The SDK credentials are missing. | | `SiteCodeIsEmpty` | The provided site code is empty. | #### initialize() Use `initialize()` when your app should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call uses either the configured [`default_timeout_millis`](#additional-configuration) or the provided `timeout`. The call returns `:ok` when the client is initialized. If initialization fails or no initialization result is available before the timeout expires, it returns `{:error, Error.t()}`. ```elixir theme={null} alias Kameleoon.Client # Initializes the client using the configured default timeout :ok = Client.initialize(client) # Initializes the client with a custom timeout of 5 seconds :ok = Client.initialize(client, timeout: 5_000) ``` ##### Parameters | Name | Type | Description | | --------------------------------- | ------------------- | --------------------------------------------------------- | | `timeout` optional | `non_neg_integer()` | Maximum time to wait for initialization, in milliseconds. | ##### Return value | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | `:ok` once the SDK is ready, or `{:error, Error.t()}` if initialization fails or the timeout expires first. | ##### Errors | Type | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------- | | `Initialization` | The SDK couldn't finish initialization, or no initialization result was available before the timeout expired. | #### is\_ready?() `is_ready?()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. This method returns immediately without blocking. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```elixir theme={null} alias Kameleoon.Client ready? = Client.is_ready?(client) if ready? do # The client is ready end ``` ##### Return value | Type | Description | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean()` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | #### forget() Removes the cached SDK client associated with the specified `site_code`. ```elixir theme={null} alias Kameleoon.ClientFactory # Removes the cached client for the given site code :ok = ClientFactory.forget("a8st4f59bj") # Removes the cached client for the given site code and environment :ok = ClientFactory.forget("a8st4f59bj", environment: "production") ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | ------------ | -------------------------------------------------- | | `site_code` required | `String.t()` | Unique identifier of the Kameleoon project. | | `environment` optional | `String.t()` | Environment key associated with the cached client. | ##### Return value | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether the cached client was successfully removed or an error occurred. | ### Feature flags and variations #### is\_feature\_active() * 📨 *Sends tracking data to Kameleoon (depending on the `track` option)* Determines whether a feature flag is active for a given user. If the visitor hasn't yet been evaluated for this feature flag, the SDK evaluates the targeting rules and returns the result. If the visitor already has a stored evaluation for the feature, the SDK reuses the existing result to ensure consistency. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `is_feature_active?`, `get_variation` or `get_variations`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `get_variations` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. The `is_feature_active?` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ```elixir theme={null} alias Kameleoon.Client feature_key = "new_checkout" # Evaluates the feature flag and sends tracking data (default behavior) {:ok, active?} = Client.is_feature_active?(client, visitor_code, feature_key) # Evaluates the feature flag without sending tracking data {:ok, active_without_tracking?} = Client.is_feature_active?(client, visitor_code, feature_key, track: false) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------ | ------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | Unique identifier of the user. | | | `feature_key` required | `String.t()` | Key of the feature to evaluate for the user. | | | `track` optional | `boolean()` | Enables or disables tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------------------------- | | `{:ok, boolean()} \| {:error, Error.t()}` | Indicates whether the feature flag is active for the specified `visitor_code`, or returns an error. | ##### Errors | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_variation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitor_code` and `feature_key` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```elixir theme={null} alias Kameleoon.Client feature_key = "new_checkout" # Retrieves the variation assigned to the visitor (with tracking enabled by default) {:ok, variation} = Client.get_variation(client, visitor_code, feature_key) # Retrieves the variation without sending tracking data {:ok, variation_without_tracking} = Client.get_variation(client, visitor_code, feature_key, track: false) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------ | ------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | | | `feature_key` required | `String.t()` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean()` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `{:ok, Variation.t()} \| {:error, Error.t()}` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag on success, otherwise an error. | ##### Errors | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | `FeatureEvaluationBlocked` | Exception indicating that feature evaluation is blocked. The reason is described in the error message. This usually occurs due to GDPR restrictions when the visitor hasn't provided legal consent. | #### get\_variations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitor_code` as a mandatory argument, while `only_active` and `track` are optional. * If `only_active` is set to `true`, the method `get_variations` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```elixir theme={null} alias Kameleoon.Client # Retrieves all variations assigned to the visitor (with default options) {:ok, variations} = Client.get_variations(client, visitor_code) # Retrieves only active variations for the visitor {:ok, only_active_variations} = Client.get_variations(client, visitor_code, only_active: true) # Retrieves variations without sending tracking data {:ok, variations_without_tracking} = Client.get_variations(client, visitor_code, track: false) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | | | `only_active` optional | `boolean()` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean()` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `{:ok, %{String.t() => Variation.t()}} \| {:error, Error.t()}` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features on success, otherwise an error. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### set\_forced\_variation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `force_targeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#get_visitor_code)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```elixir theme={null} alias Kameleoon.Client experiment_id = 202_387 # Forces the visitor into "variation_2" for the given experiment :ok = Client.set_forced_variation(client, visitor_code, experiment_id, "variation_2") # Removes any previously forced variation for the visitor in this experiment :ok = Client.set_forced_variation(client, visitor_code, experiment_id, nil) # Forces the visitor into "variation_2" with custom options. # In this case, targeting rules are respected (force_targeting = false). :ok = Client.set_forced_variation( client, visitor_code, experiment_id, "variation_2", force_targeting: false ) ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | | | `experiment_id` required | `non_neg_integer()` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variation_key` required | `String.t() \| nil` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `nil`, the forced variation will be reset. | | | `force_targeting` optional | `boolean()` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Return value | Type | Description | | ---------------------------- | --------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether the forced variation was successfully set or an error occurred. | ##### Errors | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | #### evaluate\_audiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluate_audiences` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```elixir theme={null} alias Kameleoon.Client :ok = Client.evaluate_audiences(client, visitor_code) ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | ------------ | --------------------------------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ---------------------------- | --------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether audience evaluation succeeded or an error occurred. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_datafile() To evaluate all feature flags, use [`get_variations`](#get_variations). This method is more efficient than calling `DataFile` and iterating through flags with [`get_variation`](#get_variation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```elixir theme={null} alias Kameleoon.Client {:ok, datafile} = Client.get_datafile(client) ``` ##### Return value | Type | Description | | -------------------------------------------- | -------------------------------------------------------------------------------------------- | | `{:ok, DataFile.t()} \| {:error, Error.t()}` | The [`DataFile`](#datafile) containing the SDK configuration on success, otherwise an error. | ##### Errors | Type | Description | | ---------------- | --------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | ### Visitor data #### get\_visitor\_code() Use `get_visitor_code()` to obtain the current visitor's Kameleoon `visitor_code`. The method works with any cookie store that implements the `Kameleoon.CookieAccessor` behaviour. The implementation logic is as follows: 1. The SDK checks whether a `kameleoonVisitorCode` cookie is already available through the provided accessor. 2. If the cookie is absent, the SDK uses `default_visitor_code` when you provide one. 3. Otherwise, the SDK generates a new visitor code and stores it through the accessor. For more information, refer to [Hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). If you provide your own `visitor_code`, its uniqueness must be guaranteed on your side. Also note that the length of `visitor_code` is limited to `255` characters. The `get_visitor_code` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#set_forced_variation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```elixir theme={null} alias Kameleoon.Client defmodule MemoryCookies do @behaviour Kameleoon.CookieAccessor @impl true def get(cookies, key), do: Map.get(cookies, key) @impl true def set(cookies, key, value, _max_age, _top_level_domain) do Map.put(cookies, key, value) end end # Generate or retrieve a visitor code using an auto-generated value. cookies = %{} {:ok, visitor_code, cookies} = Client.get_visitor_code(client, {MemoryCookies, cookies}) # Generate or retrieve a visitor code using a predefined user ID. cookies = %{} {:ok, visitor_code, cookies} = Client.get_visitor_code(client, {MemoryCookies, cookies}, "user_id") ``` ```elixir theme={null} alias Kameleoon.Client defmodule PlugConnCookies do @behaviour Kameleoon.CookieAccessor @impl true def get(conn, key), do: conn.req_cookies[key] @impl true def set(conn, key, value, max_age, top_level_domain) do opts = [path: "/", max_age: max_age] opts = if top_level_domain, do: Keyword.put(opts, :domain, top_level_domain), else: opts Plug.Conn.put_resp_cookie(conn, key, value, opts) end end def get_visitor_code(conn, client) do cookies = {PlugConnCookies, conn} with {:ok, visitor_code, conn} <- Client.get_visitor_code(client, cookies) do {:ok, visitor_code, conn} end end ``` ##### Parameters | Name | Type | Description | | ---------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------- | | `cookies` required | `{module(), term()}` | Cookie accessor module and framework-specific state used to read and store the visitor cookie. | | `default_visitor_code` optional | `String.t() \| nil` | Visitor code to use when no cookie is present. | ##### Return value | Type | Description | | -------------------------------------------------- | ------------------------------------------------------------------------------------- | | `{:ok, String.t(), term()} \| {:error, Error.t()}` | String representing a unique visitor code used in SDK on success, otherwise an error. | ##### Errors | Type | Description | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `InvalidSimVarCookieFormat` | Indicates that the `kameleoonSimulationFFData` cookie contains malformed JSON and the simulated variations couldn't be parsed. | #### add\_data() The `Client.add_data` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `Client.add_data` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush`. The [`track_conversion`](#track_conversion) method also sends out any previously associated data, just like the `flush`. The same holds true for [`get_variation`](#get_variation) and [`get_variations`](#get_variations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Browser alias Kameleoon.Data.PageView alias Kameleoon.Data.UserAgent :ok = Client.add_data(client, visitor_code, Browser.new!(:chrome, version: 123.0)) :ok = Client.add_data(client, visitor_code, [ PageView.new!("https://example.com/pricing", title: "Pricing", referrers: [3]), UserAgent.new!("Mozilla/5.0") ]) :ok = Client.add_data( client, visitor_code, PageView.new!("https://example.com/checkout", title: "Checkout"), track: false ) ``` ##### Parameters | Name | Type | Description | Default value | | -------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | | | `data` required | `struct() \| [struct()]` | Collection of Kameleoon data types. | | | `track` optional | `boolean()` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether data was successfully added or an error occurred. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * *📨 Sends tracking data to Kameleoon* The `flush()` method aggregates all Kameleoon data associated with a visitor and sends a tracking request to the server. This request includes any data previously added via the [`add_data`](#add_data) method that hasn't yet been transmitted through other tracking mechanisms (see the referenced methods for details). The `flush()` operation is non-blocking, as the server call is performed asynchronously. This method provides control over when data linked to a specific `visitor_code` is transmitted. For example, if `add_data()` is called multiple times, sending a request after each invocation would be inefficient. Instead, you can batch these updates and call `flush()` once to send all accumulated data in a single request. The `flush()` method uses the provided `visitor_code` as the unique visitor identifier. * `flush()`: Queues a flush operation according to the configured tracking interval. * `flush_instant()`: Sends tracking data immediately without waiting for the interval. ```elixir theme={null} alias Kameleoon.Client # Queues a flush operation for the given visitor_code. # Data will be sent according to the configured tracking interval. :ok = Client.flush(client, visitor_code) # Immediately sends all pending tracking data for the given visitor_code. :ok = Client.flush_instant(client, visitor_code) ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | ------------ | --------------------------------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------ | | `:ok \| {:error, Error.t()}` | Indicates whether the operation was successfully scheduled or executed, or if an error occurred. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_remote\_data() The `get_remote_data()` method lets you retrieve remote data stored on Kameleoon servers for the specified `key`. In most setups, this data is written through the Kameleoon Data API and can later be fetched by your Elixir service whenever you need additional app context. This method is useful when you want to keep structured information on Kameleoon's remote infrastructure and reuse it from your back-end without maintaining a separate retrieval mechanism. ```elixir theme={null} alias Kameleoon.Client {:ok, data} = Client.get_remote_data(client, "test-key") ``` ##### Parameters | Name | Type | Description | | ----------------------------- | ------------ | --------------------------------------------------------- | | `key` required | `String.t()` | Key associated with the remote data you want to retrieve. | ##### Return value | Type | Description | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `{:ok, String.t()\} \| {:error, Error.t()}` | Payload associated with the specified `key` on success, otherwise an error. In most cases, the payload is JSON serialized as a string. | ##### Errors | Type | Description | | ---------------- | -------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `Network` | Returned when the remote data request fails or the server responds with a non-success status code. | #### get\_remote\_visitor\_data() `get_remote_visitor_data()` retrieves Kameleoon visit data for the provided `visitor_code`. The method adds the data to local visitor storage so other SDK methods can use it for targeting decisions. Data obtained using this method is especially useful when you want to: * use data collected from other devices. * access a visitor's history, such as previously viewed pages from past visits. * use data that's only available on the client side, such as datalayer variables and front-end goal conversions. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `get_remote_visitor_data()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor without the need to call [`add_data()`](#add_data). This is especially useful for [synchronizing custom data across multiple devices](/developer-docs/cross-device-experimentation/). ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Types.RemoteVisitorDataFilter # Fetch remote visitor data without any filter. # This will return all available data for the given visitor. :ok = Client.get_remote_visitor_data(client, visitor_code) # Create a filter to limit the returned data. filter = %RemoteVisitorDataFilter{ # Include data from the last 5 visits previous_visit_amount: 5, # Include conversion events (e.g., goals, transactions) conversions: true, # Include page view history page_views: true } # Fetch remote visitor data using the specified filter. # This will return only the data matching the filter criteria. :ok = Client.get_remote_visitor_data(client, visitor_code, filter: filter) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------------------------------ | ---------------------------------------------------------------- | ---------------------------- | | `visitor_code` required | `String.t()` | Visitor code whose data should be fetched. | | | `filter` optional | `RemoteVisitorDataFilter.t() \| nil` | Filter describing which remote visitor data should be retrieved. | `%RemoteVisitorDataFilter{}` | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------ | | `:ok \| {:error, Error.t()}` | Returns successfully when the data is fetched and added locally, otherwise an error. | ##### Errors | Type | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `Network` | Returned when the remote visitor data request fails, can't be parsed, or the server responds with a non-success status code. | ##### Using parameters in get\_remote\_visitor\_data() The `get_remote_visitor_data()` method lets you control which data is retrieved for a visitor. The same filtering approach works across goals, experiments, variations, and other visitor data. For example, if you want to target users who converted on a goal during their last five visits, you can set `previous_visit_amount` to `5` and `conversions` to `true`. The flexibility shown in this example isn't limited to goal data. You can use the filter to retrieve many different visitor behaviors and make them available to targeting and reporting logic in your Elixir app. ##### `RemoteVisitorDataFilter` fields | Name | Type | Description | Default | | ----------------------- | ------------------- | ------------------------------------------------------------- | ------- | | `previous_visit_amount` | `non_neg_integer()` | Number of previous visits to retrieve data from. | `1` | | `current_visit` | `boolean()` | If `true`, current visit data will be retrieved. | `true` | | `custom_data` | `boolean()` | If `true`, custom data will be retrieved. | `true` | | `visitor_code` | `boolean()` | If `true`, the most recent visitor code will be reused. | `true` | | `page_views` | `boolean()` | If `true`, page view data will be retrieved. | `false` | | `geolocation` | `boolean()` | If `true`, geolocation data will be retrieved. | `false` | | `device` | `boolean()` | If `true`, device data will be retrieved. | `false` | | `browser` | `boolean()` | If `true`, browser data will be retrieved. | `false` | | `operating_system` | `boolean()` | If `true`, operating system data will be retrieved. | `false` | | `conversions` | `boolean()` | If `true`, conversion data will be retrieved. | `false` | | `experiments` | `boolean()` | If `true`, experiment data will be retrieved. | `false` | | `kcs` | `boolean()` | If `true`, Kameleoon Conversion Score data will be retrieved. | `false` | | `personalizations` | `boolean()` | If `true`, personalization data will be retrieved. | `false` | | `cbs` | `boolean()` | If true, Contextual Bandit score data will be retrieved. | `false` | #### get\_visitor\_warehouse\_audience() This method retrieves audience data associated with a visitor in your warehouse integration by using the specified `visitor_code` and, optionally, a `warehouse_key`. The `warehouse_key` is typically your internal user ID. The `custom_data_index` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. When the call succeeds, the SDK converts the returned audience list into [`CustomData`](#customdata), adds it to the visitor locally, and makes it available for targeting purposes. For more background, see the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting). ```elixir theme={null} alias Kameleoon.Client # Fetch audience data for a visitor using only the visitor_code. :ok = Client.get_visitor_warehouse_audience(client, visitor_code, 98) # Fetch audience data for a visitor using both visitor_code and warehouse_key. # Useful when your warehouse uses a different identifier than visitor_code. :ok = Client.get_visitor_warehouse_audience(client, visitor_code, 98, warehouse_key: "internal-user-id") ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | ------------------- | --------------------------------------------------------------------------- | -------------- | | `visitor_code` required | `String.t()` | Visitor whose warehouse audiences should be retrieved. | | | `custom_data_index` required | `non_neg_integer()` | Custom data index configured in Kameleoon for warehouse audience targeting. | | | `warehouse_key` optional | `String.t() \| nil` | External warehouse key, usually your internal user ID. | `visitor_code` | ##### Return value | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Success when the warehouse audience data is retrieved and stored locally as `CustomData`. | ##### Errors | Type | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `Network` | Returned when the warehouse audience request fails, can't be parsed, or the server responds with a non-success status code. | #### set\_legal\_consent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting `legal_consent` to `false` limits the types of data that can be included in tracking requests. This helps you adhere to legal and regulatory requirements while responsibly managing visitor data. For more information, see the [consent management policy](/user-manual/project-management/consent-management-policy). The Elixir SDK updates visitor cookies through the required `Kameleoon.CookieAccessor` adapter and returns the updated adapter state. ```elixir theme={null} alias Kameleoon.Client cookies = {PlugConnCookies, conn} # Set consent and update cookies. {:ok, conn} = Client.set_legal_consent(client, visitor_code, true, cookies) # Set consent without updating cookies {:ok, _} = Client.set_legal_consent(client, visitor_code, true, nil) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | The user's unique identifier. | | | `consent` required | `boolean()` | `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. | | | `cookies` optional | `{module(), term()}` | Cookie accessor module and framework-specific state used to update cookies. | `nil` | ##### Return value | Type | Description | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `{:ok, term()} \| {:error, Error.t()}` | Returns the updated cookie state when the visitor consent state was updated successfully, otherwise an error. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ##### Consent revocation behavior When you call `set_legal_consent` with `legal_consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### track\_conversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitor_code` and `goal_id`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitor_code` is usually identical to the one that was used when triggering the experiment. This method is non-blocking as the server call is made asynchronously. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.CustomData # Track a goal. :ok = Client.track_conversion(client, visitor_code, goal_id) # Track a goal with revenue. :ok = Client.track_conversion(client, visitor_code, goal_id, revenue: 100.0) # Track a goal with negative revenue. :ok = Client.track_conversion(client, visitor_code, goal_id, revenue: 100.0, negative: true) # Track a goal with custom metadata. :ok = Client.track_conversion( client, visitor_code, goal_id, metadata: [CustomData.new!(4, ["true"])] ) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | | | `goal_id` required | `non_neg_integer()` | ID of the goal. | | | `revenue` optional | `number() \| nil` | Revenue of the conversion. | `0` | | `negative` optional | `boolean()` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `[CustomData.t()]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `[]` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`Client.add_data`](#add_data) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `track_conversion` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```elixir theme={null} :ok = Client.add_data(client, visitor_code, [ CustomData.new!(5, ["Credit Card"]), CustomData.new!(9, ["Express Delivery"]) ]) :ok = Client.track_conversion( client, visitor_code, goal_id, metadata: [CustomData.new!(5, ["Amex Credit Card"])] ) ``` ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether the conversion was successfully queued for asynchronous tracking, otherwise an error. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_engine\_tracking\_code() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `get_engine_tracking_code` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```elixir theme={null} alias Kameleoon.Client {:ok, tracking_code} = Client.get_engine_tracking_code(client, visitor_code) ``` * To use this feature, implement both the Elixir SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | -------------------------------------- | ------------ | --------------------------------- | | `visitor_code` required | `String.t()` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ------------------------------------------ | ------------------------------------------------------------------------ | | `{:ok, String.t()} \| {:error, Error.t()}` | JavaScript code to insert into the page on success; otherwise, an error. | ##### Errors | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `Initialization` | Indicates that the SDK isn't yet fully initialized. | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ### Events #### set\_event\_handler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `nil` as the `handler` removes the current handler for the selected event type. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Events.DataFileUpdateEvent :ok = Client.set_event_handler(client, :datafile_update, fn %DataFileUpdateEvent{} = event -> source = event.source # :polling or :streaming date_modified = event.date_modified # Data file modification date in milliseconds # React to the data file update. IO.puts("Kameleoon data file updated (#{source}, #{date_modified})") end) # Clear the handler. :ok = Client.set_event_handler(client, :datafile_update, nil) ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `source` | `DataFileUpdateEvent.source()` | The update source. [`:polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`:streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `date_modified` | `non_neg_integer()` | The modification date of the updated data file, in milliseconds. | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Events.{HttpRequestFailed, HttpRequestSucceeded} :ok = Client.set_event_handler(client, :http_request, fn %HttpRequestSucceeded{request_type: request_type, http_status: http_status} -> # The SDK request completed successfully. IO.puts("Kameleoon #{request_type} request succeeded with status #{http_status}") %HttpRequestFailed{request_type: request_type, failure: failure} -> # The SDK request failed. IO.puts("Kameleoon #{request_type} request failed: #{failure.reason}") end) # Clear the handler. :ok = Client.set_event_handler(client, :http_request, nil) ``` The SDK reports completed SDK HTTP requests once per each actual HTTP request attempt, including retries. The handler receives one of the following structs. Pattern match on the struct to tell a successful request from a failed one. | Type | Description | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `HttpRequestSucceeded` | Describes an SDK HTTP request that completed successfully. Always carries `http_status`. | | `HttpRequestFailed` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation. Always carries `failure`. | ##### Event data fields | Name | Type | Description | | -------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `request_type` | `RequestType.t()` | The SDK request type. Possible values are `:datafile`, `:tracking`, `:remote_visitor_data`, `:remote_data`, and `:access_token`. | | `http_status` | `non_neg_integer()` | The HTTP status code returned by the request. Present only in `HttpRequestSucceeded`. | | `failure` | `HttpRequestFailure.t()` | Details about why the request failed. Present only in `HttpRequestFailed`. | | `duration_ms` | `non_neg_integer()` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailure.reason()` | The failure reason. Possible values are `:http_status`, `:error`, and `:cancelled`. | | `http_status` | `non_neg_integer() \| nil` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `nil` for exception and cancellation failures. | | `cause` | `String.t() \| nil` | The exception (error) that caused the request to fail. This value is `nil` when the request failed because of an HTTP status or cancellation. | Handlers run in their own task, so a crashing handler is logged and never affects the SDK, and a slow handler doesn't delay the SDK's own work. ##### Parameters | Name | Type | Description | | ------------------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event_type` required | `Events.event_type()` | The SDK event type to handle. Use `:datafile_update` to react to SDK data file updates, or `:http_request` to react to completed SDK HTTP requests. | | `handler` required | `(term() -> any()) \| nil` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEvent.t()` for `:datafile_update`, or `HttpRequestSucceeded.t() \| HttpRequestFailed.t()` for `:http_request`. Pass `nil` to remove the current handler. | ##### Return value | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------- | | `:ok \| {:error, Error.t()}` | Indicates whether the handler was successfully registered or removed, or if an error occurred. | ### Data types This section lists the Elixir data types available under `Kameleoon.Data`. #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | --------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------- | | `version` required | `String.t()` | The app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.ApplicationVersion # major :ok = Client.add_data(client, visitor_code, ApplicationVersion.new!("10")) # major.minor :ok = Client.add_data(client, visitor_code, ApplicationVersion.new!("10.20")) # major.minor.patch :ok = Client.add_data(client, visitor_code, ApplicationVersion.new!("10.20.30")) ``` #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | --------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | `type` required | `browser_type()` | List of browsers: `:chrome`, `:internet_explorer`, `:firefox`, `:safari`, `:opera`, `:other`. | | `version` optional | `number() \| nil` | Version of the browser, floating point number represents major and minor version of the browser | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Browser # Browser data with a version :ok = Client.add_data(client, visitor_code, Browser.new!(:safari, version: 26.4)) # Browser data without a version :ok = Client.add_data(client, visitor_code, Browser.new!(:chrome)) ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goal_id` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | ------------------- | ----------------------------------------------- | ------- | | `goal_id` required | `non_neg_integer()` | ID of the goal. | | | `revenue` optional | `number() \| nil` | Revenue of the conversion | `0` | | `negative` optional | `boolean()` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `[CustomData.t()]` | Metadata of the conversion. | `[]` | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Conversion alias Kameleoon.Data.CustomData # Add a simple conversion with ID 32 :ok = Client.add_data(client, visitor_code, Conversion.new!(32)) # Add conversion with ID 33 including revenue and marked as negative :ok = Client.add_data(client, visitor_code, Conversion.new!(33, revenue: 10.0, negative: true)) # Add conversion with ID 34 including revenue, negative flag, and custom metadata :ok = Client.add_data( client, visitor_code, Conversion.new!(34, revenue: 10.0, negative: true, metadata: [ CustomData.new!(3, ["metadata1", "md2"]), CustomData.new!(5, ["md3"]) ] ) ) ``` #### Cookie `Cookie` contains information about the cookies stored on the visitor's device. | Name | Type | Description | | --------- | ----------------------------- | ----------------------------------------------- | | `cookies` | `%{String.t() => String.t()}` | A string map containing cookie keys and values. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Cookie :ok = Client.add_data(client, visitor_code, Cookie.new!(%{"segment" => "vip"})) ``` #### CustomData `CustomData` enables the association of any type of data with each visitor, making it an effective tool for targeting conditions in [segments](/user-manual/assets/segments/create-a-segment/). Additionally, it can be used as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data). Define custom data types in the Kameleoon app or the Data API and use them from the SDK. | Name | Type | Description | Default | | -------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `index`/`name` required | `non_neg_integer()`/`String.t()` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | `values` required | `[String.t()]` | Values of the custom data to be stored. | | | `overwrite` optional | `boolean()` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor is allowed only one `CustomData` for each unique `index`(`name`). Adding another `CustomData` with the same `index`(`name`) will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.CustomData :ok = Client.add_data(client, visitor_code, CustomData.new!(1, ["value"])) # With several values :ok = Client.add_data(client, visitor_code, CustomData.new!(1, ["value1", "value2"])) # To set the `overwrite` flag to false :ok = Client.add_data(client, visitor_code, CustomData.new!(1, ["value"], overwrite: false)) # To use a name instead of the index :ok = Client.add_data(client, visitor_code, CustomData.new_with_name!("my-custom-data", ["value"])) # To use a name instead of the index and set the `overwrite` flag to false :ok = Client.add_data( client, visitor_code, CustomData.new!("my-custom-data", ["value"], overwrite: false) ) ``` #### Device You can use device data to filter experiment and personalization reports by any associated value. | Name | Type | Description | | ------ | --------------- | --------------------------------------------------------------------- | | `type` | `device_type()` | Device type. Possible values are `:phone`, `:tablet`, and `:desktop`. | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Device :ok = Client.add_data(client, visitor_code, Device.new!(:desktop)) ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String.t()` | The country of the visitor. | | `region` optional | `String.t() \| nil` | The region of the visitor. | | `city` optional | `String.t() \| nil` | The city of the visitor. | | `postal_code` optional | `String.t() \| nil` | The postal code of the visitor. | | `latitude` optional | `number() \| nil` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `number() \| nil` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.Geolocation geolocation = Geolocation.new!("France", region: "Ile-de-France", city: "Paris", postal_code: "75009", latitude: 48.8720171, longitude: 2.3338352 ) :ok = Client.add_data(client, visitor_code, geolocation) ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. | Name | Type | Description | | ------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `type` | `operating_system_type()` | Operating system family. Possible values are `:windows`, `:mac`, `:ios`, `:linux`, `:android`, and `:windows_phone`. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.OperatingSystem :ok = Client.add_data(client, visitor_code, OperatingSystem.new!(:windows)) ``` #### PageView Store page view events. | Name | Type | Description | Default | | ----------- | ------------------- | -------------------------------------------- | ------- | | `url` | `String.t()` | URL of the page viewed. | | | `title` | `String.t() \| nil` | Title of the page viewed. | `nil` | | `referrers` | `[integer()]` | Referrer indices of previously viewed pages. | `[]` | The referrer index is available in the Kameleoon app on the [acquisition channel configuration](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) page. Be careful: the index starts at `0`, so the first acquisition channel you create has the ID `0`, not `1`. ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.PageView # Full constructor with url, optional title, and referrers. :ok = Client.add_data(client, visitor_code, PageView.new!("https://example.com", title: "Homepage", referrers: [3])) # Minimal constructor, only requires a URL. :ok = Client.add_data(client, visitor_code, PageView.new!("https://example.com")) ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitor_code` is used as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation/). When you add `UniqueIdentifier.new!(true)`, the SDK links flushed data with the visitor associated with the specified identifier. This can be useful in situations where you can't access the anonymous `visitor_code` originally assigned to a visitor, but you do have access to an internal identifier connected to that visitor through session merging. | Name | Type | Description | | ------- | ----------- | ---------------------------------------------------------------------------- | | `value` | `boolean()` | Whether the current `visitor_code` should be treated as a unique identifier. | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.UniqueIdentifier :ok = Client.add_data(client, visitor_code, UniqueIdentifier.new!(true)) ``` #### UserAgent Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to recognize known bots and spiders, and it also uses the `UserAgent` field to filter out other unwanted traffic that might distort your conversion metrics. For more information, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, send the user-agent value `curl/8.0` to exclude them from analytics. | Name | Type | Description | | ----- | ------------ | --------------------------------------------- | | value | `String.t()` | User-Agent value sent with tracking requests. | ```elixir theme={null} alias Kameleoon.Client alias Kameleoon.Data.UserAgent :ok = Client.add_data(client, visitor_code, UserAgent.new!("Mozilla/5.0")) ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------- | ---------------------------------- | --------------------------------------------------------------------------------- | | `feature_flags` | `%{String.t() => FeatureFlag.t()}` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `date_modified` | `non_neg_integer() \| nil` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```elixir theme={null} # Retrieves the map of feature flags from the DataFile. # The map is keyed by feature flag identifiers, with each value being a FeatureFlag struct. feature_flags = datafile.feature_flags # Retrieves the last modification timestamp of the DataFile. # The value is an integer representing milliseconds since the Unix epoch. date_modified = datafile.date_modified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ----------------------- | -------------------------------- | -------------------------------------------------------------------------- | | `environment_enabled` | `boolean()` | Indicating whether the feature flag is enabled in the current environment. | | `default_variation_key` | `String.t()` | The key of the default variation associated with the feature flag. | | `variations` | `%{String.t() => Variation.t()}` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `[Rule.t()]` | A list of `Rule` objects | ```elixir theme={null} alias Kameleoon.Types.FeatureFlag # Check whether the feature flag is enabled in the current environment. environment_enabled = feature_flag.environment_enabled # Retrieve the key of the default variation. default_variation_key = feature_flag.default_variation_key # Retrieve the default variation struct. default_variation = FeatureFlag.default_variation(feature_flag) # Retrieve all variations of the feature flag as a map (key = variation key, value = Variation struct). variations = feature_flag.variations # Retrieve all targeting rules associated with the feature flag. rules = feature_flag.rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | -------------------------------- | ------------------------------------------------------ | | `variations` | `%{String.t() => Variation.t()}` | A map of `Variation` objects, keyed by variation keys. | ```elixir theme={null} # Retrieve all variations of the rule as a map (key = variation key, value = Variation struct). variations = rule.variations ``` #### Variation `Variation` contains information about the visitor's assigned variation, or the default variation when no specific assignment exists. | Name | Type | Description | | --------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------- | | `name` | `String.t()` | The name of the variation. | | `key` | `String.t()` | The unique key identifying the variation. | | `id` | `non_neg_integer() \| nil` | The ID of the assigned variation, or `nil` for a default variation. | | `experiment_id` | `non_neg_integer() \| nil` | The ID of the experiment associated with the variation, or `nil` for a default variation. | | `variables` | `[Kameleoon.Types.Variable.t()]` | Variables associated with the variation. This collection can be empty when no variables are attached. | * `Variation` describes the assigned or default variation, while [`Variable`](#variable) contains the details of each individual variable. * `id` and `experiment_id` can be `nil`, which indicates a default variation that's not tied to a specific experiment assignment. Additional helper methods: | Method | Return type | Description | | ------------------------ | --------------------- | ------------------------------------------------- | | `Variation.active?` | `boolean()` | Returns `false` for the `off` variation. | | `Variation.get_variable` | `Variable.t() \| nil` | Returns a variable by key, or `nil` if not found. | ```elixir theme={null} alias Kameleoon.Types.Variation # Retrieving the variation name. variation_name = variation.name # Retrieving the variation key. variation_key = variation.key # Retrieving the variation id. variation_id = variation.id # Retrieving the experiment id. experiment_id = variation.experiment_id # Retrieving the variables list. variables = variation.variables # Checking if the variation is active. active? = Variation.active?(variation) # Retrieving a variable by its key, returning nil if not found. variable = Variation.get_variable(variation, "title") ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `key` | `String.t()` | The unique key identifying the variable. | | `kind` | `String.t()` | The variable type. Possible values include `BOOLEAN`, `NUMBER`, `STRING`, `JSON`, `JS`, and `CSS`. | | `value` | `boolean() \| number() \| String.t()` | The value of the variable. Depending on `kind`, it can hold a boolean, number, string, JSON string, JavaScript snippet, or CSS snippet. | ```elixir theme={null} # Retrieve the list of variables associated with the variation. variables = variation.variables # Access the variable type (kind) for conditional handling. kind = variable.kind # Extract the value as a number. number = variable.value # Extract the value as a boolean. apply_discount? = variable.value # Extract the value as a string. title = variable.value ``` ### Deprecated methods This method is deprecated and will be removed in SDK version `1.0.0`. #### on\_datafile\_update() Use [`set_event_handler()`](#set_event_handler) instead, which also reports the update source and the data file modification date. The `on_datafile_update` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```elixir theme={null} alias Kameleoon.Client # Register a callback that is invoked whenever the configuration # is updated through a polling or streaming datafile update event. :ok = Client.on_datafile_update(client, fn -> # Custom logic to execute after the datafile has been updated. IO.puts("Kameleoon datafile updated") end) # Unregister the datafile update callback. # No callback will be invoked for future datafile updates. :ok = Client.on_datafile_update(client, nil) ``` ##### Parameters | Name | Type | Description | | --------- | ---------------------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `(() -> any()) \| nil` | The handler that will be called when the configuration is updated using a real-time configuration event. | # Go SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/go-sdk Integrate the Kameleoon Go SDK to run experiments and activate feature flags in Go web applications and services. With the Go SDK, you can run experiments and activate feature flags. Integrating the SDK into your web app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Go SDK: 3.22.1 [Changelog](https://github.com/Kameleoon/client-go/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the Go SDK, see the [reference](#reference) section. ## Developer guide Follow this section to install and configure the SDK, and learn about advanced features. ### Getting started #### Installing the Go client To install the Kameleoon Go SDK, use the `go get` command and install the package directly from the Kameleoon GitHub repository. Run the command below: ```shell theme={null} go get github.com/Kameleoon/client-go/v3 ``` #### Additional configuration To provide additional settings for the Go SDK, you can use a configuration file, which lets you customize the SDK's behavior. You can download a sample configuration file [here](/assets/developer-docs/sdks/web-sdks/client-configs/client-go.yaml). Install this file to the default path `/etc/kameleoon/client-go.yaml`, which will be read automatically. If you need to customize this path, you can provide an additional argument to the `NewClient()` method. Either specify a string that indicates an alternative path to the configuration file, or add a JavaScript object (map) containing the configuration. The current version of the Go SDK has the following keys available in the configuration file: | Key | Description | Default value | | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `ClientID` / `client_id` required | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `ClientSecret` / `client_secret` required | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `SessionDuration` / `session_duration` optional | Designates the predefined time interval that Kameleoon stores the visitor and their associated data in memory (RAM). Note that increasing the session duration increases the amount of RAM that needs to be allocated to store visitor data. | `30` minutes | | `RefreshInterval` / `refresh_interval` optional | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` minutes | | `DefaultTimeout` / `default_timeout` optional | Specifies the timeout, in milliseconds, for network requests from the SDK. Set the value to 30 seconds or more if you don't have a stable connection. Some methods have an additional parameter that you can use to override the default timeout for that particular method. If you don't specify the timeout for a method explicitly, the SDK uses this default value. | `10000` milliseconds | | `TrackingInterval` / `tracking_interval` optional | Specifies the interval for tracking requests in milliseconds. All visitors who Kameleoon evaluated for any feature flag or had data flushed are included in this tracking request, which the SDK performs once per interval. The minimum value is `1000` ms, which is also the default, and the maximum value is `5000` ms. | `1000` milliseconds | | `Environment` / `environment` optional | Environment from which the feature flag’s configuration is to be used. The value can be `production`, `staging`, `development`. See the [managing environments](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) article for details. | `production` | | `TopLevelDomain` / `top_level_domain` *(required in hybrid mode)* | The current top-level domain for your website. Use the format: `example.com`. Don't include `https://`, `www`, or other subdomains. Kameleoon uses this information to set the corresponding cookie on the top-level domain. | `""` | | `ProxyUrl` / `proxy_url` optional | Sets the proxy host for all outgoing server calls made by the SDK. | `""` | | `NetworkDomain` / `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `""` | | `VerboseMode` / `verbose_mode` *(deprecated)* | Boolean value (`true` or `false`) that turns on additional logging, including network requests and debug information. This field is deprecated and will be removed in SDK version `4.0.0`. Use [`logging.SetLogLevel`](#log-levels) instead. | `false` | To learn more about `client_id` and `client_secret`, and instructions on how to obtain them, refer to this [article](/user-manual/account-and-team-management/users-and-teams/api-credentials). The Go SDK utilizes the Automation API and follows the OAuth 2.0 client credentials flow. #### Initializing the Kameleoon client Once you have installed the SDK in your app, you must initialize Kameleoon. All interactions with the SDK, such as triggering an experiment, are accomplished via the object (the Kameleoon client) created using the `NewClient()` method. You can customize the behavior of the SDK (for example, the environment or the credentials) by providing a [configuration object](#additional-configuration). ```go theme={null} import ( kameleoon "github.com/Kameleoon/client-go/v3" ) // First option config := &kameleoon.KameleoonClientConfig{ Network: kameleoon.NetworkConfig{ // Optional ProxyURL: "http://proxy-pass:1234/", // Optional DoTimeout: 10 * time.Second, // Optional ReadTimeout: 5 * time.Second, // Optional WriteTimeout: 5 * time.Second, // Optional MaxConnsPerHost: 10000, // Optional }, ClientID: "your-client-id", // This field is required. Please enter your client_id here. ClientSecret: "your-client-secret", // This field is required. Please enter your client_secret here. TopLevelDomain: "example.com", // This field is strictly recommended, otherwise you may have problems when using subdomains. RefreshInterval: time.Hour, // Optional (60 minutes by default) TrackingInterval: time.Second, // Optional (1000 ms by default) Environment: "staging", // Optional SessionDuration: 30 * time.Minute, // Optional (30 minutes by default) NetworkDomain: "example.com", // Optional } client, err := KameleoonClientFactory.Create("your-project-sitecode", config) // Second option config, err := LoadConfig("/etc/kameleoon/client-go.yaml") client, err := KameleoonClientFactory.Create("your-project-sitecode", config) // Notice: In the example above, the configuration is loaded every time. To load it once, use `CreateFromFile`. // Third option client, err := KameleoonClientFactory.CreateFromFile("your-project-sitecode", "/etc/kameleoon/client-go.yaml") ``` #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`GetVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `GetVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`GetVariation()`](#getvariation) or [`IsFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. The `GetVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `IsFeatureActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `GetVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `GetVariationOptParams.Track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `GetVariation()` method allows you to control whether tracking is done. If `GetVariationOptParams.Track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `GetVariationOptParams.Track=false` is helpful when using the `GetVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`AddData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`GetRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `GetRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`Flush*()`](#flushall--flushvisitor--flushvisitorinstantly) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking flag exposition and goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`TrackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval`](#additional-configuration)). If you prefer to send the request immediately, use the [`FlushVisitorInstantly()`](#flushall--flushvisitor--flushvisitorinstantly) method. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`GetEngineTrackingCode()`](#getenginetrackingcode) method. The `GetEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `GetRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `GetRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`GetRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```go title="Device A" theme={null} // In this example, Custom data with index `90` was set to "Visitor" scope in Kameleoon. const VisitorScopeCustomDataIndex = 90 kameleoonClient.AddData(visitorCode, types.NewCustomData(VisitorScopeCustomDataIndex, "your data")) err := kameleoonClient.FlushVisitor(visitorCode) ``` ```go title="Device B" theme={null} // Before working with the data, call the `GetRemoteVisitorData` method. _, err := kameleoonClient.GetRemoteVisitorData(visitorCode, true) // After calling the method, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available for targeting and tracking the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`GetRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `GetRemoteVisitorData()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`TrackConversion()`](#trackconversion) or [`Flush*()`](#flushall--flushvisitor--flushvisitorinstantly) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`GetRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```go theme={null} // In this example, `91` represents the Custom Data's index // configured as a unique identifier in Kameleoon. const MappingIndex = 91 const FeatureKey = "ff123" // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. // Assume `anonymousVisitorCode` is the randomly generated ID for that visitor. anonymousVariation, err := kameleoonClient.GetVariation(anonymousVisitorCode, FeatureKey) // 2. After the visitor is authenticated // Assume `userId` is the authenticated visitor's visitor code. kameleoonClient.AddData(anonymousVisitorCode, types.NewCustomData(MappingIndex, userId)) err := kameleoonClient.FlushVisitorInstantly(anonymousVisitorCode) // Indicate that `userId` is a unique identifier. kameleoonClient.AddData(userId, types.NewUniqueIdentifier(true)) // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. userVariation, err := kameleoonClient.GetVariation(userId, FeatureKey) isSameVariation := userVariation.Key == anonymousVariation.Key // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. err := kameleoonClient.TrackConversionRevenue(userId, 123, 10.0) // Additionally, the linked visitors will share all fetched remote visitor data. _, err := kameleoonClient.GetRemoteVisitorData(userId, true) ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`GetVisitorCode()`](#getvisitorcode) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```go theme={null} client.AddData(visitorCode, types.NewCustomData(index, "newVisitorCode")) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`AddData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `AddData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```go theme={null} import ( "development.kameleoon.net/sdk/go-sdk/v3/logging" ) // The `NONE` log level does not allow logging. logging.SetLogLevel(logging.NONE) // The `ERROR` log level only allows logging issues that may affect the SDK's primary behavior. logging.SetLogLevel(logging.ERROR) // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. logging.SetLogLevel(logging.WARNING) //The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. logging.SetLogLevel(logging.INFO) // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. logging.SetLogLevel(logging.DEBUG) ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```go theme={null} import ( "development.kameleoon.net/sdk/go-sdk/v3/logging" "github.com/sirupsen/logrus" ) type CustomLogger struct { } func NewCustomLogger() logging.LoggerWithLevel { return &CustomLogger{} } func (dl CustomLogger) Log(level logging.LogLevel, message string) { switch level { case logging.NONE: case logging.ERROR: logrus.Error(message) case logging.WARNING: logrus.Warn(message) case logging.INFO: logrus.Info(message) case logging.DEBUG: logrus.Debug(message) } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. logging.SetLogLevel(logging.DEBUG) // Optional; defaults to `logging.WARNING`. logging.SetLogger(NewCustomLogger()) ``` ## Reference This is a full reference documentation of the Go SDK. ### Initialization #### Create() Call this method before any others to initialize the SDK. This method is in `KameleoonClientFactory`. This creates an instance of `KameleoonClient` to manage all interactions between the SDK and your app. ```go theme={null} const siteCode = "sitecode" config := &kameleoon.KameleoonClientConfig{ // ... } client, err := KameleoonClientFactory.Create(siteCode, config) ``` ##### Parameters | Name | Type | Description | | -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | siteCode required | string | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. | | cfg required | \*KameleoonClientConfig | Represents either the path to the SDK configuration file or the configuration object. If you provide the configuration object, it must contain the correct configuration keys. | ##### Return value | Type | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------- | | KameleoonClient | An instance of the **KameleoonClient** that will be used to manage your experiments and feature flags. | | error | An error occurred in the `Create` call. The error can be `errs.SiteCodeIsEmpty` or `errs.ConfigCredentialsInvalid`. | #### CreateFromFile() Call this method before any others to initialize the SDK. This method is in `KameleoonClientFactory`. This creates an instance of `KameleoonClient` to manage all interactions between the SDK and your app. ```go theme={null} const siteCode = "sitecode" client, err := KameleoonClientFactory.CreateFromFile(siteCode, "/etc/kameleoon/client-go.yaml") ``` ###### Parameters | Name | Type | Description | | -------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | siteCode required | string | A Kameleoon **siteCode**. | | cfgPath required | string | A path to the config file. The file is loaded if only the `KameleoonClientFactory` doesn't store a `KameleoonClient` instance with the specified **siteCode**. | ###### Return value | Type | Description | | --------------- | -------------------------------------------------------------------------------------------------------------- | | KameleoonClient | An instance of the **KameleoonClient** that will be used to manage your experiments and feature flags. | | error | An error occurred within `Create`. The error can be `errs.SiteCodeIsEmpty` or `errs.ConfigCredentialsInvalid`. | #### Forget() The `Forget` method removes a `KameleoonClient` instance from the `KameleoonClientFactory` with the specified **siteCode** and frees resources used by the `KameleoonClient` instance. The `KameleoonClient` instance must not be used after calling the `Forget` method. ```go theme={null} const siteCode = "sitecode" KameleoonClientFactory.Forget(siteCode) ``` ###### Parameters | Name | Type | Description | | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | | siteCode | string | The **siteCode** of the `KameleoonClient` instance to be removed from the `KameleoonClientFactory`. This field is mandatory. | #### WaitInit() Use `WaitInit()` when your application should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: it completes successfully when the client is initialized, and fails if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return a successful result. If no initialization result is available before the timeout expires, the call fails. If you don't provide a `timeout` value, the SDK uses the default timeout from [`KameleoonClientConfig.DefaultTimeout`](#additional-configuration). Call `WaitInit()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`IsReady()`](#isready) as a non-blocking guard before evaluating feature flags. Even if `WaitInit()` fails, the SDK keeps retrying the configuration fetch in the background, so `IsReady()` starts returning `true` as soon as a retry succeeds. ```go theme={null} // Waits up to the default timeout of the client configuration err := client.WaitInit() // Waits no longer than the specified timeout err = client.WaitInit(1 * time.Second) if err != nil { // Client wasn't initialized properly fmt.Println(err) } else { // The SDK has been initialized; you can fetch a feature flag / experiment configuration here. } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------- | --------------- | ------------------------------------------------------ | -------------------------------------- | | `timeout` optional | `time.Duration` | The maximum time to wait for an initialization result. | `KameleoonClientConfig.DefaultTimeout` | ##### Return value | Type | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `error` | `nil` once the SDK is ready, or an `errs.Initialization` error if the configuration could not be loaded or no initialization result was available within the timeout. In the timeout case the error wraps `context.DeadlineExceeded`. The failure that prevented the SDK from loading its configuration is reported in the error message and available as the wrapped error via `errors.Unwrap`. | #### IsReady() `IsReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`WaitInit()`](#waitinit), this method returns immediately without blocking or throwing. * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. * Call `WaitInit()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`IsReady()`](#isready) as a non-blocking guard before evaluating feature flags. Even if `WaitInit()` fails, the SDK keeps retrying the configuration fetch in the background, so `IsReady()` starts returning `true` as soon as a retry succeeds. ```go theme={null} ready := client.IsReady() ``` ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `bool` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### IsFeatureActive() / IsFeatureActiveWithTracking() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Use this method if you want to retrieve the configuration of a simple feature flag, that has only a turn ON / OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has variations and variables, you should use the [`GetVariation`](#getvariation) method. It takes a **visitorCode** and **featureKey** as mandatory arguments to check if the feature flag is active for a given user. If the user hasn't been associated with your feature flag before, the SDK returns a random boolean value (**true** if the user should have this feature or **false** if not). However, if the user has already been registered with this feature flag, the SDK detects the previous feature flag value. It's important to set up proper error handling in your code to catch any potential exceptions that may occur, as shown in the code example. If you specify a `visitorCode`, the `IsFeatureActive` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `IsFeatureActive()`, `GetVariation()` or `GetVariations()`. Use the default `true` value for the `GetVariationOptParams.Track` parameter when you expose visitors to a variation and need to count them. Set the `GetVariationOptParams.Track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `GetVariations()` to retrieve all variations before you expose visitors, set the `GetVariationsOptParams.Track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```go theme={null} const featureKey = "new_checkout" // Check if a Feature Flag is active (ON / OFF) hasNewCheckout, err := client.IsFeatureActive(visitorCode, featureKey) // disabling tracking hasNewCheckout, err := client.IsFeatureActiveWithTracking(visitorCode, featureKey, false) if err != nil { switch err.(type) { case *errs.VisitorCodeInvalid: // The provided visitor code is not valid. Trigger the old checkout for this visitor. hasNewCheckout = false case *errs.FeatureConfigNotFound: // The Feature Key is not yet in the configuration file that has been fetched by the SDK. Trigger the old checkout for this visitor. hasNewCheckout = false default: // Handle unexpected errors panic(err) } } if hasNewCheckout { // Implement new checkout code here } ``` The `IsFeatureActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The user's unique identifier. This field is mandatory. | | featureKey | string | The key of the feature you want to expose to a user. This field is mandatory. | | isUniqueIdentifier (Deprecated) | bool | A parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | | track | bool | A parameter of the `IsFeatureActiveWithTracking` method to enable or turn off tracking of the feature evaluation. `IsFeatureActive(visitorCode, featureKey)` is equivalent to `IsFeatureActiveWithTracking(visitorCode, featureKey, true)`. | ##### Return value | Type | Description | | ---- | ------------------------------------------------------------------------ | | bool | Value of the feature flag that's registered for a given **visitorCode**. | ##### Exceptions thrown | Type | Description | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | errs.FeatureConfigNotFound | This error indicates that the requested feature key couldn't be found in the internal configuration of the SDK. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | #### GetVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `GetVariationOptParams.Track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `GetVariationOptParams.Track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```go theme={null} const featureKey = "new_checkout" variation, err := client.GetVariation(visitorCode, featureKey) // disabling tracking variation, err := client.GetVariation(visitorCode, featureKey, NewGetVariationOptParams().Track(false)) if err != nil { // handle error } // Fetch a variable value for the assigned variation title := variation.Variables["title"].Value switch (variation.Key) { case "on": // Main variation key is selected for visitorCode case "alternative_variation": // Alternative variation key default: // Default variation key } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `GetVariationOptParams.Track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag on success, otherwise an error. | ##### Exceptions thrown | Type | Description | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `errs.FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `errs.FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### GetVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `GetVariationsOptParams.Track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `GetVariationsOptParams.OnlyActive` and `GetVariationsOptParams.Track` are optional. * If `GetVariationsOptParams.OnlyActive` is set to `true`, the method `GetVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `GetVariationsOptParams.Track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```go theme={null} variations, err := client.GetVariations(visitorCode) // all active variations variations, err := client.GetVariations(visitorCode, NewGetVariationsOptParams().OnlyActive(true)) // disable tracking variations, err := client.GetVariations(visitorCode, NewGetVariationsOptParams().Track(false)) if err != nil { // handle error } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `GetVariationsOptParams.OnlyActive` optional | `bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `GetVariationsOptParams.Track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `map[string]Variation` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features on success, otherwise an error. | ##### Exceptions thrown | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ##### Parameters | Name | Type | Description | | ----------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is required. | | OnlyActive | bool | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags (Defaults to `false`). | | Track | bool | An optional parameter to enable or turn off tracking of the feature evaluation (Defaults to `true`). | ##### Return value | Type | Description | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `map[string]Variation` | Map that contains the assigned [`Variations`](#variation) of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### SetForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `SetForcedVariationOptParams.ForceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```go theme={null} const experimentId = 9516 // Forcing the variation "on" in the experiment 9516 for the visitor. err := client.SetForcedVariation(visitorCode, experimentId, "on") // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment. err := client.SetForcedVariation( visitorCode, experimentId, "on", NewSetForcedVariationOptParams().ForceTargeting(false), ) // Resetting the forced variation in the experiment 9516 for the visitor. err := client.SetForcedVariation(visitorCode, experimentId, "") if err != nil { // Handling the error } ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `experimentId` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `string` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `""`, the forced variation will be reset. | | | `SetForcedVariationOptParams.ForceTargeting` optional | `bool` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Exceptions thrown | Type | Description | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `errs.FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `errs.FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | #### EvaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `EvaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```go theme={null} if err := client.EvaluateAudiences(visitorCode); err != nil { // Handling the error } ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### GetDataFile() To evaluate all feature flags, use [`GetVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`GetVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```go theme={null} dataFile := client.GetDataFile() ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### GetVisitorCode() This method was previously called `ObtainVisitorCode`, which was removed in SDK version `3.0.0`. To ensure user identification consistency, especially when using Kameleoon in [hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/), you should call the [`GetVisitorCode()`](#getvisitorcode) method to obtain the Kameleoon `visitorCode` for the current visitor. Here's how it works: 1. Kameleoon checks if there is a **kameleoonVisitorCode** cookie associated with the current HTTP request. If found, Kameleoon this code as the visitor identifier. 2. If no cookie is found, the method will either randomly generate a new identifier, or use the **defaultVisitorCode** argument if it's passed. Using your identifiers as visitor codes allows you to match Kameleoon visitors with your own users without additional look-ups. 3. The server-side **kameleoonVisitorCode** cookie is then set with the identifier value via HTTP header and the method returns the identifier value. For more information, refer to this [article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation). If you decide to provide your own `User ID` instead of using the Kameleoon generated visitorCode, it's your responsibility to ensure that the User ID is unique. The SDK doesn't check for uniqueness. It's important to note that the User ID you provide must not exceed 255 characters, as any excess characters will result in an exception being thrown. The `GetVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```go theme={null} visitorCode, err := client.GetVisitorCode(req, resp) visitorCode, err := client.GetVisitorCode(req, resp, "defaultVisitorCode") ``` ##### Parameters | Name | Type | Description | | ------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | request | \*fasthttp.Request | The current fasthttp.Request object should be passed as the first parameter. This field is mandatory. | | response | \*fasthttp.Response | The current fasthttp.Response object should be passed as the second parameter. This field is mandatory. | | defaultVisitorCode | string | This parameter will be used as the **visitorCode** if no existing **kameleoonVisitorCode** cookie is found on the request. This field is optional, and by default a random **visitorCode** will be generated. | ##### Return value | Type | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | (string, error) | A pair consisting of a **visitorCode** that will be associated with this particular user and an error. It should be used with most methods of the SDK. | ##### Exceptions thrown | Error Message | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | #### AddData() The `AddData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `AddData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`Flush*()`](#flushall--flushvisitor--flushvisitorinstantly) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `Flush*()`. The [`TrackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `Flush*()`. The same holds true for [`GetVariation()`](#getvariation) and [`GetVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```go theme={null} import ( "github.com/Kameleoon/client-go/v3/types" ) // Add a single data item (tracked by default) client.AddData(visitorCode, types.NewBrowser(types.BrowserTypeChrome)) // Add multiple data items (tracked by default) client.AddData(visitorCode, types.NewPageViewWithTitle("https://url.com", "title", 3), types.UserAgent("UserAgent"), ) // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) client.AddDataWithOptParams( visitorCode, NewAddDataOptParams().Track(false), types.NewPageViewWithTitle("https://url.com", "title", 3), types.UserAgent("UserAgent") ) ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `track` optional | `bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `allData` required | `...types.Data` | Collection of Kameleoon data types. | | ##### Exceptions | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### FlushAll() / FlushVisitor() / FlushVisitorInstantly() * 📨 *Sends Tracking Data to Kameleoon* The `FlushAll()/FlushVisitor()/FlushVisitorInstantly()` methods collects the Kameleoon data linked to the visitor. It then sends a tracking request, along with all data added using the `AddData` method, which hasn't yet been sent using one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). `Flush*()` is non-blocking as the server call is made asynchronously. `Flush*()` lets you control when the data associated with a given `visitorCode` is sent to the server. For instance, if you call `AddData()` a dozen times, it would be inefficient to send data to the server after each time `AddData()` is invoked, so all you have to do is call `Flush()` once at the end. The `FlushVisitor()/FlushVisitorInstantly()` method uses `visitorCode` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```go theme={null} import ( "github.com/Kameleoon/client-go/v3/types" ) visitorCode, err := client.GetVisitorCode(req, resp) client.AddData(visitorCode, types.NewBrowser(types.BrowserTypeChrome)) client.AddData(visitorCode, types.NewConversionWithRevenue(32, 10, false)) client.FlushVisitor(visitorCode) // Interval tracking (most performant tracking method) client.FlushAll() // Interval tracking for all visitors' unsent data client.FlushVisitorInstantly(visitorCode) // Instant tracking client.FlushAll(true) // Instant tracking for all visitors' unsent data // if you operate with a unique ID client.AddData(types.NewUniqueIdentifier(true)) client.FlushVisitor(visitorCode) ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | visitorCode | string | The user's unique identifier. This field is mandatory for `FlushVisitor()/FlushVisitorInstantly()`. | | isUniqueIdentifier (Deprecated) | bool | A parameter of the `FlushVisitor` method for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Exceptions thrown | Type | Description | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | errs.VisitorCodeInvalid | This exception is raised when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | #### GetRemoteData() The `GetRemoteData()` method retrieves external data stored on Kameleoon's remote server for the specified **siteCode** (specified in `KameleoonClient` constructor) according to a **key** passed as an argument. This key is typically the Kameleoon Visitor Code or your User ID. You can use this method to retrieve user preferences, historical data, or any other data relevant to your app's logic. By storing this data on Kameleoon's highly scalable servers using the Data API, you can efficiently manage massive amounts of data and retrieve it for all of your visitors or users. The return value of the method is a JSON object that can be decoded using the `json.Unmarshal()` function. You can use this data to build advanced targeting segments for feature flags and experiments, or filter experiment and personalization reports based on any value stored in the retrieved data. ```go theme={null} type Test1 struct { Value string `json:"some field to insert or update"` } remoteData, err := s.client.GetRemoteData("USER_ID") // uses default timeout var test1 Test1 err = json.Unmarshal(remoteData, &test1) remoteData, err := s.client.GetRemoteData("USER_ID", 1000) ``` Note that, since a server call is required, this mechanism is asynchronous. Kameleoon offers built-in integrations with Mixpanel, Segment, and GA4 to fetch external cohorts and utilize them in feature experiments. The key utilized in these integrations is either the Visitor code or your User ID. You can refer to the sample code provided below to retrieve and utilize Mixpanel cohorts: ```go theme={null} //Retrieve and use Mixpanel Cohorts type Cohort struct { Id string `json:"mixpanel_cohort_id"` Name string `json:"mixpanel_cohort_name"` ProjectId string `json:"mixpanel_cohort_project_id"` } type MixPanelCohorts struct { Cohorts []Cohort `json:"mixpanel_cohorts"` } remoteData, err := s.client.GetRemoteData("USER_ID") var mixPanel MixPanelCohorts if err = json.Unmarshal(remoteData, &mixPanel); err == nil { cohorts := make([]string, len(mixPanel.Cohorts)) for _, cohort := range mixPanel.Cohorts { cohorts = append(cohorts, cohort.Id) } client.AddData(visitorCode, types.NewCustomData(customDataIndex, cohorts...)) } ``` ##### Parameters | Name | Type | Description | | ------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | string | The key with which the data you are trying to retrieve is associated. This field is mandatory. This key is typically the Kameleoon Visitor Code or your own User ID. | | timeout | int | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when [initializing the SDK](#initialization). | ##### Return value | Type | Description | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | \[]byte | This returns the information associated with retrieving data for a specific **key**. The result needs to be decoded using the `json.Unmarshal`() function. | ##### Exceptions thrown | Type | Description | | ----- | -------------------------------------------- | | error | Error indicating that the request timed out. | #### GetRemoteVisitorData() `GetRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `VisitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `GetRemoteVisitorData()` automatically retrieves the latest stored custom data with `Scope=Visitor` and attaches them to the visitor without the need to call the `AddData()` method. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The parameter `IsUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```go theme={null} visitorCode := "visitorCode" var visitorData []types.Data var err error // Visitor data will be fetched and automatically added for `visitorCode` visitorData, err = client.GetRemoteVisitorData(visitorCode, true) // default timeout will be used visitorData, err = client.GetRemoteVisitorData(visitorCode, true, time.Second) // 1000 milliseconds timeout // If you only want to fetch data and add it yourself manually, set `addData` to `false`. visitorData, err = client.GetRemoteVisitorData(visitorCode, false) // default timeout will be used visitorData, err = client.GetRemoteVisitorData(visitorCode, false, time.Second) // 1000 milliseconds timeout // If you operate with a unique ID client.AddData(types.NewUniqueIdentifier(true)) visitorData, err = client.GetRemoteVisitorData(visitorCode, true) // If you want to fetch a custom list of data types var visitorData = client.GetRemoteVisitorDataWithFilter( visitorCode, true, types.RemoteVisitorDataFilter{PreviousVisitAmount: 10, CustomData: true, Conversion: true, Experiments: true}, // default timeout will be used ) // or var visitorData = client.GetRemoteVisitorDataWithFilter( visitorCode, true, types.RemoteVisitorDataFilter{PreviousVisitAmount: 10, CustomData: true, Conversion: true, Experiments: true}, time.Second, // 1000 milliseconds timeout ) ``` ##### Parameters of GetRemoteVisitorData | Name | Type | Description | | ----------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | addData | bool | A boolean indicating whether the method should automatically add retrieved data for a visitor. This field is mandatory. | | timeout | time.Duration | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when [initializing the SDK](#initialization). | ##### Parameters of GetRemoteVisitorDataWithFilter | Name | Type | Description | | ----------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | addData | bool | A boolean indicating whether the method should automatically add retrieved data for a visitor. This field is mandatory. | | filter | types.RemoteVisitorDataFilter | Filter for specifying what data should be retrieved from visits. This field is mandatory. | | timeout | time.Duration | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when [initializing the SDK](#initialization). | ##### Parameters of GetRemoteVisitorDataWithOptParams The `GetRemoteVisitorDataWithOptParams` method is deprecated. Use [`GetRemoteVisitorDataWithFilter`](/developer-docs/sdks/web-sdks/go-sdk#arguments-of-getremotevisitordatawithfilter) and [`UniqueIdentifier`](#uniqueidentifier). instead. | Name | Type | Description | | ----------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | addData | bool | A boolean indicating whether the method should automatically add retrieved data for a visitor. This field is mandatory. | | filter | types.RemoteVisitorDataFilter | Filter for specifying what data should be retrieved from visits. This field is mandatory. | | params | kameleoon.RemoteVisitorDataOptParams | Optional parameters. | Here is the list of `kameleoon.RemoteVisitorDataOptParams` fields: | Name | Type | Description | | ------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | IsUniqueIdentifier optional (Deprecated) | `bool` | A parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. | | Timeout optional | `time.Duration` | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when [initializing the SDK](#initialization). | The default value of `kameleoon.RemoteVisitorDataOptParams` which is `types.RemoteVisitorDataFilter{PreviousVisitAmount: 1, CurrentVisit: true, CustomData: true}`, can be gotten with `types.DefaultRemoteVisitorDataFilter()` function. ##### Return value | Type | Description | | ------------- | ---------------------------------------------- | | \[]types.Data | A slice of data assigned to the given visitor. | | error | An occurred error. | ##### Using parameters in GetRemoteVisitorData() The `GetRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `GetRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `PreviousVisitAmount` parameter to 5 and `Conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `GetRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `types.RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | PreviousVisitAmount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | CurrentVisit optional | `bool` | If true, current visit data will be retrieved | `true` | | CustomData optional | `bool` | If true, custom data will be retrieved. | `true` | | PageViews optional | `bool` | If true, page data will be retrieved. | `false` | | Geolocation optional | `bool` | If true, geolocation data will be retrieved. | `false` | | Device optional | `bool` | If true, device data will be retrieved. | `false` | | Browser optional | `bool` | If true, browser data will be retrieved. | `false` | | OperatingSystem optional | `bool` | If true, operating system data will be retrieved. | `false` | | Conversions optional | `bool` | If true, conversion data will be retrieved. | `false` | | Experiments optional | `bool` | If true, experiment data will be retrieved. | `false` | | Kcs optional | `bool` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | VisitorCode optional | `bool` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | Personalization optional | `bool` | If true, personalization data will be retrieved. This is required for the personalization condition. | `false` | | cbs optional | `bool` | If true, Contextual Bandit score data will be retrieved. | `false` | #### GetVisitorWarehouseAudience() Retrieves all audience data associated with the visitor in your data warehouse using the specified `VisitorCode` and `WarehouseKey`. The `WarehouseKey` is typically your internal user ID. The `CustomDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method returns a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. ```go theme={null} customData, err = client.GetVisitorWarehouseAudience(VisitorWarehouseAudienceParams{ VisitorCode: "visitorCode", CustomDataIndex: 10, WarehouseKey: "warehouseKey", // optional Timeout: 5 * time.Second, // optional }) customData, err = c.GetVisitorWarehouseAudienceWithOptParams( "visitorCode", 10, VisitorWarehouseAudienceOptParams{WarehouseKey: "warehouseKey", Timeout: 5 * time.Second}) ``` ##### Parameters of GetVisitorWarehouseAudience | Name | Type | Description | | --------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | VisitorCode | string | A unique visitor identification string, can't exceed 255 characters length. | | CustomDataIndex | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | WarehouseKey | string | A unique key to identify the warehouse data (usually, your internal user ID). This field is optional. | | Timeout | time.Duration | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when [initializing the SDK](#initialization). This field is optional. | ##### Parameters of GetVisitorWarehouseAudienceWithOptParams | Name | Type | Description | | --------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | visitorCode | string | A unique visitor identification string, can't exceed 255 characters length. | | customDataIndex | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | params | `kameleoon.VisitorWarehouseAudienceOptParams` | Optional parameters. | Here is the list of `kameleoon.VisitorWarehouseAudienceOptParams` fields: | Name | Type | Description | | ------------ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | WarehouseKey | `string` | A unique key to identify the warehouse data (usually, your internal user ID). This field is optional. | | Timeout | `time.Duration` | The timeout parameter specifies the maximum amount of time the method can block to wait for a result, in milliseconds. This field is optional; if not provided, the method will use the default timeout value provided when \[initializing the SDK]. This field is optional. | For `GetVisitorWarehouseAudience` method parameters are passed into the function as `params` of struct `VisitorWarehouseAudienceParams` to make some of them optional (`WarehouseKey` and `Timeout`). For `GetVisitorWarehouseAudienceWithOptParams` method only optional parameters are passed into the function as `params` of struct `VisitorWarehouseAudienceOptParams`. ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------- | | \*types.CustomData | A `CustomData` instance confirming that the data has been added to the visitor. | | error | An occurred error. | #### SetLegalConsent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```go theme={null} visitorCode, err := kameleoonClient.GetVisitorCode(req, resp) err := kameleoonClient.SetLegalConsent(visitorCode, true, resp) ``` ##### Parameters | Name | Type | Description | | ----------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The user's unique identifier. This field is required. | | consent | bool | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | | response | \*fasthttp.Response | The HTTP response where values in the cookies will be adjusted based on the legal consent status. This field is optional. | ##### Exceptions thrown | Type | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | ##### Consent revocation behavior When you call `SetLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### TrackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `TrackConversionOptParams.Revenue`, `TrackConversionOptParams.Negative` and `TrackConversionOptParams.Metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `TrackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```go theme={null} import ( "github.com/Kameleoon/client-go/v3/types" ) const goalID = 83023 client.TrackConversion(visitorCode, goalID) client.TrackConversionRevenue(visitorCode, goalID, 10.0) // Add metadata client.TrackConversionWithOptParams(visitorCode, goalID, TrackConversionOptParams{ Metadata: []*types.CustomData{ types.NewCustomData(3, "metadata1", "md2"), types.NewCustomData(5, "md3") }, }) ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `int` | ID of the goal. | | | `TrackConversionOptParams.Revenue` optional | `float64` | Revenue of the conversion. | `0` | | `TrackConversionOptParams.Negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `TrackConversionOptParams.Metadata` optional | `[]*CustomData` | Lets you set specific values for custom data which have been defined as metadata for the goal in the Kameleoon App. Example: `[CustomData{id: 5, value: "Payment Type"}, CustomData{id: 6, value: "Delivery Method"}]`. In this example, `5` and `6` are the indexes of the custom data (5 = “Payment Type”, 6 = “Delivery Method”). | `nil` | | `isUniqueIdentifier` *(deprecated)* | `bool` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | TrackConversionOptParams.Metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `TrackConversionOptParams.Metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`AddData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `TrackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```go theme={null} kameleoonClient.AddData(visitorCode, types.NewCustomData(5, "Credit Card"), types.NewCustomData(9, "Express Delivery")); kameleoonClient.TrackConversionWithOptParams(visitorCode, 10, TrackConversionOptParams{ Metadata: []*types.CustomData{ types.NewCustomData(9, "Amex Credit Card"), }, }) ``` ##### Exceptions | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `errs.VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### GetEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `GetEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```go theme={null} engineTrackingCode := kameleoonClient.GetEngineTrackingCode(visitorCode) ``` * To use this feature, implement both the Go SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ### Events #### SetEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `nil` as the `handler` removes the current handler for the selected event type. ```go theme={null} import ( "github.com/Kameleoon/client-go/v3/events" ) type DataFileUpdateHandler struct{} func (h DataFileUpdateHandler) OnUpdate(event events.DataFileUpdateEvent) { source := event.Source // events.DataFileUpdateSourcePolling or events.DataFileUpdateSourceStreaming dateModified := event.DateModified // Data file modification date in milliseconds. // React to the data file update. } kameleoonClient.SetEventHandler(events.EventTypeDataFileUpdate, DataFileUpdateHandler{}) // Clear the handler. kameleoonClient.SetEventHandler(events.EventTypeDataFileUpdate, nil) ``` `events.DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Source` | `events.DataFileUpdateSource` | The update source. [`events.DataFileUpdateSourcePolling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`events.DataFileUpdateSourceStreaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `DateModified` | `int64` | The modification date of the updated data file, in milliseconds. | ```go theme={null} import ( "time" "github.com/Kameleoon/client-go/v3/events" ) type HttpRequestHandler struct{} func (h HttpRequestHandler) OnRequestSucceeded( requestType events.RequestType, httpStatus int, duration time.Duration, ) { // The SDK request completed successfully. } func (h HttpRequestHandler) OnRequestFailed( requestType events.RequestType, failure *events.HttpRequestFailure, duration time.Duration, ) { reason := failure.Reason() // One of the events.FailureReason* constants. httpStatus := failure.HttpStatus() cause := failure.Cause() // The SDK request failed. } kameleoonClient.SetEventHandler(events.EventTypeHttpRequest, HttpRequestHandler{}) // Clear the handler. kameleoonClient.SetEventHandler(events.EventTypeHttpRequest, nil) ``` `events.HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `OnRequestSucceeded()` for successful responses and `OnRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `OnRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `OnRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `requestType` | `events.RequestType` | The SDK request type. Possible values are `events.RequestTypeDataFile`, `events.RequestTypeTracking`, `events.RequestTypeRemoteVisitorData`, `events.RequestTypeRemoteData`, and `events.RequestTypeAccessToken`. | | `httpStatus` | `int` | The HTTP status code returned by the request. Passed only to `OnRequestSucceeded()`. | | `failure` | `*events.HttpRequestFailure` | Details about why the request failed. Passed only to `OnRequestFailed()`. | | `duration` | `time.Duration` | The request duration. | ##### events.HttpRequestFailure `events.HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | -------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `Reason()` | `events.HttpRequestFailureReason` | The failure reason. Possible values are `events.FailureReasonHttpStatus`, `events.FailureReasonError`, and `events.FailureReasonCancelled`. | | `HttpStatus()` | `*int` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `nil` for exception and cancellation failures. | | `Cause()` | `error` | The exception (error) that caused the request to fail. This value is `nil` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `events.EventType` | The SDK event type to handle. Use `events.EventTypeDataFileUpdate` to react to SDK data file updates, or `events.EventTypeHttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `events.EventHandler` | The handler to register for the selected event type. Use a `events.DataFileUpdateHandler` for `events.EventTypeDataFileUpdate`, or an `events.HttpRequestHandler` for `events.EventTypeHttpRequest`. Pass `nil` to remove the current handler. | ### Data types #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | ------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `browserType` required | `BrowserType` | List of browsers: `BrowserTypeChrome`, `BrowserTypeIE`, `BrowserTypeFirefox`, `BrowserTypeSafari`, `BrowserTypeOpera`, `BrowserTypeOther`. | | `version` optional | `float32` | Version of the browser, floating point number represents major and minor version of the browser | ```go theme={null} client.AddData(visitorCode, types.NewBrowser(types.BrowserTypeChrome)) client.AddData(visitorCode, types.NewBrowser(types.BrowserTypeSafari, 16.0)) ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ------------------------------------------------------ | --------------------- | ----------------------------------------------- | ------- | | `goalId` required | `int` | ID of the goal. | | | `ConversionOptParams.Revenue` optional | `float64` | Revenue of the conversion | `0` | | `ConversionOptParams.Negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `ConversionOptParams.Metadata` optional | `[]*types.CustomData` | Metadata of the conversion. | `nil` | ```go theme={null} client.AddData(visitorCode, types.NewConversion(32, true)) client.AddData(visitorCode, types.NewConversionWithRevenue(33, 10.0, false)) client.AddData( visitorCode, types.NewConversionWithOptParams(34, types.ConversionOptParams{ Revenue: 5.0, Metadata: []*types.CustomData{ types.NewCustomData(3, "metadata1", "md2"), types.NewCustomData(5, "md3"), }, }), ) ``` ```go theme={null} client.AddData(visitorCode, types.NewConversion(32, false)) client.AddData(visitorCode, types.NewConversionWithRevenue(32, 10, false)) ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. | Name | Type | Description | | ------- | ------------------- | --------------------------------------------------------------------------------- | | cookies | `map[string]string` | A string object map consisting of cookie keys and values. This field is required. | Each visitor can only have one `Cookie`. Adding second `Cookie` overwrites the first one. ```go theme={null} cookie := types.NewCookie(map[string]string{ "k1": "v1", "k2": "v2", }) client.AddData(visitorCode, cookie) ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `string` | The country of the visitor. | | `region` optional | `string` | The region of the visitor. | | `city` optional | `string` | The city of the visitor. | | `postalCode` optional | `string` | The postal code of the visitor. | | `latitude` optional | `float64` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `float64` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```go theme={null} client.AddData(visitorCode, types.NewGeolocation("France", "Île-de-France", "Paris")) client.AddData(visitorCode, types.NewGeolocationWithCoords(48.856667, 2.352222, "France", "Île-de-France", "Paris")) ``` #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `int`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `...string` | The values of the custom data to be stored. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | ```go theme={null} client.AddData(visitorCode, types.NewCustomData(1, "value")) // With several values client.AddData(visitorCode, types.NewCustomData(1, "value1", "value2")) // To set the 'overwrite' flag to false client.AddData( visitorCode, types.NewCustomDataWithOptParams(1, NewCustomDataOptParams().Overwrite(false), "value"), ) // To use a name instead of the index client.AddData(visitorCode, types.NewNamedCustomData("my-custom-data", "value")) // To use a name instead of the index // and set the 'overwrite' flag to false client.AddData( visitorCode, types.NewNamedCustomDataWithOptParams("my-custom-data", NewCustomDataOptParams().Overwrite(false), "value"), ) ``` * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. #### Device You can use device data to filter experiment or personalization reports by any associated value. ###### NewDevice | Name | Type | Description | | ---------- | ---------- | ----------------------------------------------------------------------------- | | deviceType | DeviceType | List of devices: **Phone**, **Device**, **Desktop**. This field is mandatory. | ```go theme={null} client.AddData(visitorCode, types.NewDevice(types.DeviceTypeDesktop)) ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. ##### NewOperatingSystem | Name | Type | Description | | ---- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | type | `types.OperatingSystemType` | List of operating systems: **Windows**, **Mac**, **iOS**, **Linux**, **Android** and **WindowsPhone**. This field is required. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```go theme={null} client.AddData(visitorCode, types.NewOperatingSystem(types.OperatingSystemTypeWindows)) ``` #### PageView You can use pageview data to filter experiment or personalization reports by any associated value. The index or ID of the [referrer](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) can be found in your Kameleoon account. It's important to note that this index starts at 0. This means the first acquisition channel you create for a given site will be assigned 0 as its ID, not 1. ##### NewPageView | Name | Type | Description | | --------- | ------ | ---------------------------------------------------------- | | url | string | The URL of the page viewed. This field is mandatory. | | referrers | ...int | The referrers of the viewed pages. This field is optional. | ##### NewPageViewWithTitle | Name | Type | Description | | --------- | ------ | ---------------------------------------------------------- | | url | string | The URL of the page viewed. This field is mandatory. | | title | string | The title of the page viewed. This field is mandatory. | | referrers | ...int | The referrers of the viewed pages. This field is optional. | ```go theme={null} client.AddData(visitorCode, types.NewPageView("https://url.com", 3)) client.AddData(visitorCode, types.NewPageViewWithTitle("https://url.com", "title", 3)) ``` #### UserAgent Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to tackle this issue and recognize known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that might distort your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. ##### NewUserAgent | Name | Type | Description | | ----- | ------ | --------------------------------------------------------------------------------------- | | value | string | The User-Agent value that will be sent with tracking requests. This field is mandatory. | ```go theme={null} client.AddData(visitorCode, types.NewUserAgent("visitor_user_agent")) ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data with the visitor associated with the specified identifier. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ##### NewUniqueIdentifier | Name | Type | Description | | ----- | ---- | ---------------------------------------------------------------------------------------------- | | value | bool | Parameter for specifying if the visitor\_code is a unique identifier. This field is mandatory. | ```go theme={null} client.AddData(visitorCode, types.NewUniqueIdentifier(true)) ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. ##### NewApplicationVersion | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```go theme={null} client.AddData(visitorCode, types.NewApplicationVersion("10")) // major client.AddData(visitorCode, types.NewApplicationVersion("10.20")) // major.minor client.AddData(visitorCode, types.NewApplicationVersion("10.20.30")) // major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | ------------------------ | --------------------------------------------------------------------------------- | | `FeatureFlags` | `map[string]FeatureFlag` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `DateModified` | `int64` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```go theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. featureFlags := dataFile.FeatureFlags // Retrieves the last modification timestamp of the DataFile. // The value is an int64 representing milliseconds since the Unix epoch. dateModified := dataFile.DateModified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ---------------------- | ---------------------- | -------------------------------------------------------------------------- | | `IsEnvironmentEnabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. | | `DefaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `Variations` | `map[string]Variation` | A map of `Variation` objects, keyed by variation keys. | | `Rules` | `[]Rule` | A list of `Rule` objects | ```go theme={null} // Check whether the feature flag is enabled in the current environment isEnvironmentEnabled := featureFlag.IsEnvironmentEnabled // Retrieve the key of the default variation defaultVariationKey = featureFlag.DefaultVariationKey // Retrieve the default variation object defaultVariation := featureFlag.DefaultVariation() // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) variations := featureFlag.Variations // Retrieve all targeting rules associated with the feature flag rules := featureFlag.Rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ---------------------- | ------------------------------------------------------ | | `Variations` | `map[string]Variation` | A map of `Variation` objects, keyed by variation keys. | ```go theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) variations := rule.Variations ``` #### Variation `Variation` contains information about the visitor's assigned variation (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | `string` | The name of the variation. | | Key | `string` | The unique key identifying the variation. | | VariationID | `*int` | The ID of the assigned variation (or `nil` if it's the default variation). | | ExperimentID | `*int` | The ID of the experiment associated with the variation (or `nil` if default). | | Variables | `map[string]Variable` | A map containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `VariationID` or `ExperimentID` may be `nil`, indicating a default variation. * The `Variables` map might be empty if no variables are associated with the variation. ```go theme={null} // Retrieving the variation name var variationName string = variation.Name // Retrieving the variation key var variationKey string = variation.Key // Retrieving the variation id var variationID *int = variation.VariationID // Retrieving the experiment id var experimentID *int = variation.ExperimentID // Retrieving the variables map var variables map[string]Variable = variation.Variables ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Key | `string` | The unique key identifying the variable. | | Type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | Value | `interface{}` | The value of the variable, which can be of the following types: **bool**, **int**, **float**, **string**, **map**, **array**. | ```go theme={null} // Retrieving the variables map var variables map[string]Variable = variation.Variables // Variable type can be retrieved for further processing var variableType string = variables["isDiscount"].Type // Retrieving the variable value by key var isDiscount bool = variables["isDiscount"].Value.(bool) // Variable value can be of different types var title string = variables["title"].Value.(string) ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `4.0.0`. #### GetFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`GetVariation()`](#getvariation) instead. This method retrieves the configuration of a [feature experiment](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/rules-and-targeting/create-feature-experiments) with several feature variations. You can use it to get a variation key for a given user by providing the **visitorCode** and **featureKey** as mandatory arguments. If the user has never been associated with the feature flag, the SDK returns a variation key randomly, following the feature flag rules. If the user is already registered with the feature flag, the SDK detects the previous **variation key** value. If the user doesn't match any of the rules, the default value defined in Kameleoon's feature flag delivery rules will be returned. It's important to note that the default value may not be a variation key, but a boolean value or another data type, depending on the feature flag configuration. Don't forget to handle potential exceptions with proper error handling in your code. See the example code for guidance. If you specify a `visitorCode`, the `GetFeatureVariationKey` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```go theme={null} // Feature Experiment with variations const variationKey = "" if variationKey, err := s.client.GetFeatureVariationKey(visitorCode, featureKey); err == nil { switch variationKey { case "variation 1": // The visitor has been bucketed with variation 1 key. case "variation 2": // The visitor has been bucketed with variation 2 key. default: //The visitor has been bucketed with the default variation or is part of the unallocated traffic sample. } } else { // An error occurred; the feature flag key has not been found in the current configuration fetched by the SDK. } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The user's unique identifier. This field is mandatory. | | featureKey | string | The key of the feature you want to expose to a user. This field is mandatory. | | isUniqueIdentifier (Deprecated) | bool | A parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------- | | string | Variation key of the feature flag that's registered for a given **visitorCode**. | ##### Exceptions thrown | Type | Description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | errs.FeatureConfigNotFound | This error indicates that the requested feature key couldn't be found in the internal configuration of the SDK. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | | errs.FeatureEnvironmentDisabled | This error indicates that the feature flag is off for the current environment. | #### GetActiveFeatureListForVisitor() Use [`GetActiveFeatures()`](#getactivefeatures) instead. The `GetActiveFeatureListForVisitor()` method takes a `visitorCode` parameter. When you call this method with a specific `visitorCode`, the method returns a list of feature flag keys that are available for that `visitorCode`. Don't forget to handle potential exceptions with proper error handling in your code. For example, see the following code: ```go theme={null} arrayFeatureFlagKeys, err := client.GetActiveFeatureListForVisitor(visitorCode) ``` ##### Arguments | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------ | | visitorCode | string | The user's unique identifier. This field is mandatory. | ##### Return value | Type | Description | | --------- | ----------------------------------------------------------------------- | | \[]string | List of feature flag keys that are active for a specific `visitorCode`. | ##### Exceptions thrown | Type | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | #### GetActiveFeatures() Use [`GetVariations()`](#getvariations) instead. The `GetActiveFeatures()` method retrieves information about the active feature flags that are available for the specified visitor code. Don't forget to handle potential exceptions with proper error handling in your code. For example, see the following code: ```go theme={null} activeFeatures, err := client.GetActiveFeatures(visitorCode) ``` ##### Arguments | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------ | | visitorCode | string | The user's unique identifier. This field is mandatory. | ##### Return value | Type | Description | | --------------------------- | ------------------------------------------------------------------------------------------------------ | | map\[string]types.Variation | Map that contains the assigned variations of the active features using the active feature IDs as keys. | ##### Exceptions thrown | Type | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | #### GetFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* Use [`GetVariation()`](#getvariation) instead. To get a [feature variable](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/define-feature-variables) of a variation key associated with a user, call the `GetFeatureVariable()` method of the SDK. This method takes a **visitorCode**, **featureKey** and **variableKey** as mandatory arguments to get a variable of the variation key for a given user. If the user has never been associated with the feature flag, the SDK returns a variable value of the variation key randomly, following the feature flag rules. If the user is already registered with the feature flag, the SDK detects the previous **variation key** value and returns the **variable** value. If the user doesn't match any of the rules, the default value will be returned. Don't forget to handle potential exceptions with proper error handling in your code. See the example code for guidance. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```go theme={null} visitorCode, err := client.GetVisitorCode(req, resp) featureKey := "featureKey" variableKey = "variableKey" if variableValue, err := s.client.GetFeatureVariable(visitorCode, featureKey, variableKey); err == nil { // your custom code depending on variableValue } else { // An error occurred; the feature flag has not been found in the current configuration fetched by the SDK. } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The user's unique identifier. This field is mandatory. | | featureKey | string | The key of the feature you want to expose to a user. This field is mandatory. | | variableKey | string | The name of the variable for which you want to get a value. This field is mandatory. | | isUniqueIdentifier (Deprecated) | bool | A parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | interface | The value of a variable associated with a particular feature flag's variation that has been registered for a specific visitorCode. Possible types: bool, float64, string, map\[string]interface | ##### Exceptions thrown | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | errs.FeatureConfigNotFound | This error indicates that the requested feature key couldn't be found in the SDK's internal configuration. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.VisitorCodeInvalid | This error is returned when the visitor code provided is invalid, meaning that it's either empty or its length exceeds 255 characters. | | errs.FeatureVariationNotFound | This error indicates that the requested variation ID couldn't be found in the SDK's internal configuration. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.FeatureVariableNotFound | This error indicates that the requested variable key hasn't been found. Check that the variable's key defined in the Kameleoon Platform matches the one in your code. | | errs.FeatureEnvironmentDisabled | This error indicates that the feature flag is off for the current environment. | #### GetFeatureVariationVariables() Use [`GetVariation()`](#getvariation) instead. To retrieve all variables associated with a feature flag, you must call the `GetFeatureVariationVariables` method. This method requires two mandatory arguments: **featureKey** and **variationKey**. The method returns the data with the object type, as defined in the Kameleoon Platform. Don't forget to handle potential exceptions with proper error handling in your code. Check out the example code for guidance. ```go theme={null} featureKey := "test_feature_variables" variationKey := "on" if allVariables, err := s.client.GetFeatureVariationVariables(featureKey, variationKey); err == nil { // your custom code } else { // An error occurred; the feature flag or variation doesn't exist in the client configuration } ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------ | | featureKey | string | The key of the feature flag you want to obtain. This field is mandatory. | | variationKey | string | The key of the variation you want to obtain. This field is mandatory. | ##### Return value | Type | Description | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | map\[string]interface | Data associated with this feature flag and variation. Possible values: string, bool, float64 or map\[string]interface (depending on the type defined in the Kameleoon Platform). | ##### Exceptions thrown | Type | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | errs.FeatureConfigNotFound | This error indicates that the requested feature key couldn't be found in the SDK's internal configuration. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.FeatureVariationNotFound | This error indicates that the requested variation key couldn't be found in the SDK's internal configuration. This typically occurs when the feature flag hasn't yet been retrieved by the SDK, which can happen if the SDK is in [polling](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) mode. | | errs.FeatureEnvironmentDisabled | This error indicates that the feature flag is off for the current environment. | #### GetFeatureList() Returns a list of feature flag keys currently available for the SDK. ```go theme={null} arrayFeatureKeys := client.GetFeatureList() ``` ##### Return value | Type | Description | | ---------- | ------------------------- | | `[]string` | List of feature flag keys | #### OnUpdateConfiguration() ```go theme={null} kameleoonClient.OnUpdateConfiguration( // configuration was updated ) ``` The `OnUpdateConfiguration` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ##### Parameters | Name | Type | Description | | --------- | -------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `func()` | The handler that will be called when the configuration is updated using a real-time configuration event. | # Java SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/java-sdk Integrate the Kameleoon Java SDK to run experiments and activate feature flags on Java EE or Jakarta EE app servers. With the Kameleoon Java SDK, you can run experiments and activate feature flags on your Java EE / Jakarta EE app server. **Getting started**: For help getting started, see the [developer guide](#developer-guide) **Changelog**: Latest version of the Java SDK: 4.24.2 [Changelog](https://github.com/Kameleoon/client-java/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the Java SDK, see the [reference](#reference) section. ## Developer guide This guide helps you integrate the SDK in a few minutes and start running experiments in your Java applications. ### Getting started #### Starter kit To help with getting started, Kameleoon provides a starter kit and demo app to test the SDK. The starter kit includes a fully configured app with examples demonstrating how SDK methods can be used in an app. The starter kit, demo app, and detailed instructions are available at [Starter kit for Java](https://github.com/Kameleoon/java-examples) #### Install the Java client The installation package is available on the Maven Central repository. You can install the Java SDK by adding a dependency into your project's `pom.xml` file, as shown in the example to the right. If you're using another project management system, see the [integrations](https://search.maven.org/artifact/com.kameleoon/kameleoon-client-java) page for additional examples. ```java title="pom.xml" theme={null} com.kameleoon kameleoon-client-java 4.16.0 ``` ```java title="pom.xml" theme={null} com.kameleoon kameleoon-client-java-jakarta 4.16.0 ``` #### Additional configuration Create a `.properties` configuration file to provide credentials and customize SDK behavior. You can also [download a sample configuration](/assets/developer-docs/sdks/web-sdks/client-configs/client-java.properties.zip) file. Save this file to the default path, `/etc/kameleoon/client-java.conf`, but you can save it anywhere in the classpath as `kameleoon-client-java.properties`. The following table shows the available properties that you can set: | Key | Description | Default value | | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `clientId` / `client_id` required | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `clientSecret` / `client_secret` required | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `sessionDuration` / `session_duration_minute` optional | Designates the predefined time interval that Kameleoon stores the visitor and their associated data in memory (RAM). Note that increasing the session duration increases the amount of RAM that needs to be allocated to store visitor data. | `30` minutes | | `refreshInterval` / `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` minutes | | `defaultTimeout` / `default_timeout_millisecond` optional | Specifies the timeout, in milliseconds, for network requests from the SDK. Set the value to 30 seconds or more if you don't have a stable connection. Some methods have an additional parameter that you can use to override the default timeout for that particular method. If you don't specify the timeout for a method explicitly, the SDK uses this default value. | `10000` milliseconds | | `trackingInterval` / `tracking_interval_millisecond` optional | Specifies the interval for tracking requests in milliseconds. All visitors who Kameleoon evaluated for any feature flag or had data flushed are included in this tracking request, which the SDK performs once per interval. The minimum value is `1000` ms, which is also the default, and the maximum value is `5000` ms. | `1000` milliseconds | | `environment` / `environment` optional | Environment from which the feature flag’s configuration is to be used. The value can be `production`, `staging`, `development`. See the [managing environments](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) article for details. | `production` | | `topLevelDomain` / `top_level_domain` *(required in hybrid mode)* | The current top-level domain for your website. Use the format: `example.com`. Don't include `https://`, `www`, or other subdomains. Kameleoon uses this information to set the corresponding cookie on the top-level domain. | `null` | | `proxyHost` / `proxy_host` optional | Sets the proxy host for all outgoing server calls made by the SDK. | `null` | | `networkDomain` / `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `null` | #### Initialize the Kameleoon client After you've installed the SDK into your app and configured your credentials and SDK behavior (in `/etc/kameleoon/client-java.conf`), the next step is creating the Kameleoon client in your app code. For example: ```java theme={null} import com.kameleoon.KameleoonClientFactory; String siteCode = "a8st4f59bj"; try { KameleoonClient kameleoonClient = KameleoonClientFactory.create(siteCode, "custom/file/path/client-java.properties"); } catch (KameleoonException.SiteCodeIsEmpty e) { // indicates that provided site code is empty } catch (KameleoonException.ConfigCredentialsInvalid exception) { // indicates that provided clientId / clientSecret are not valid } try { KameleoonClientConfig config = new KameleoonClientConfig.Builder() .clientId("") // mandatory .clientSecret("") // mandatory .refreshInterval(60) // in minutes, optional (60 minutes by default) .sessionDuration(30) // in minutes, optional (30 minutes by default) .defaultTimeout(10_000) // in milliseconds, optional (10000 ms by default) .trackingInterval(1000) // in milliseconds, optional (1000 ms by default) .topLevelDomain("example.com") // mandatory if you use hybrid mode (engine or web experiments) .environment("development") // optional .proxyHost(new HttpHost("192.168.0.25", 8080, "http")) // optional .networkDomain("example.com") // optional .build(); KameleoonClientFactory.create(siteCode, config); } catch (KameleoonException.SiteCodeIsEmpty e) { // indicates that provided site code is empty } catch (KameleoonException.ConfigCredentialsInvalid exception) { // indicates that provided clientId / clientSecret are not valid } ``` A KameleoonClient is a singleton object that bridges your app and the Kameleoon platform. It includes all the methods and properties you need to run an experiment. The Java SDK also supports the use of an HTTP proxy (see the [`create()` method reference](#create) for details). It's your responsibility to ensure the proper logic of your app code within the context of A/B testing via Kameleoon. A good practice is to always assume that you can exclude the current visitor from the experiment if you haven't launched the experiment. This exclusion is simple because it corresponds to the implementation of the default and reference variation logic. You're now ready to begin creating and implementing experiments and feature flagging. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```java theme={null} kameleoonClient.addData(visitorCode, new CustomData(index, "newVisitorCode")); ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```java title="Device A" theme={null} // In this example, a Custom data with index `90` was set to "Visitor" scope in Kameleoon. final int VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90; kameleoonClient.addData(visitorCode, new CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")); kameleoonClient.flush(visitorCode); ``` ```java title="Device B" theme={null} // Before working with the data, call the `getRemoteVisitorData` method. kameleoonClient.getRemoteVisitorData(visitorCode).get(); // After calling the method, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available for targeting and tracking the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`getRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `getRemoteVisitorData()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`trackConversion()`](#trackconversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```java theme={null} // In this example, 91 represents the Custom Data's index configured as a unique identifier in Kameleoon. final int MAPPING_INDEX = 91; final String FEATURE_KEY = "ff123"; // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. // Assume anonymousVisitorCode is the randomly generated ID for that visitor. Variation anonymousVariation = kameleoonClient.getVariation(anonymousVisitorCode, FEATURE_KEY); // 2. After the visitor is authenticated // Assume `userId` is the visitor code of the authenticated visitor. kameleoonClient.addData(anonymousVisitorCode, new CustomData(MAPPING_INDEX, userId)); kameleoonClient.flush(true, anonymousVisitorCode); // Indicate that `userId` is a unique identifier. kameleoonClient.addData(userId, new UniqueIdentifier(true)); // 3. After the visitor was authorized // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. Variation userVariation = kameleoonClient.getVariation(userId, FEATURE_KEY); boolean isSameVariation = userVariation.getKey().equals(anonymousVariation.getKey()); // true // `userId` and `anonymousVisitorCode` are now linked and can be tracked as a single visitor. kameleoonClient.trackConversion(userId, 123, 10.0); // Additionally, the linked visitors share all fetched previously tracked remote data. kameleoonClient.getRemoteVisitorData(userId).get(); ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`getVisitorCode()`](#getvisitorcode) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```java theme={null} // The `NONE` log level does not allow logging. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's primary behaviour. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require an attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.WARNING); // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```java theme={null} public class CustomLogger implements com.kameleoon.logging.Logger { private final java.util.logging.Logger inner; public CustomLogger(java.util.logging.Logger inner) { this.inner = inner; } // `log` method accepts logs from the SDK @Override public void log(com.kameleoon.logging.LogLevel level, String message) { // Custom log handling logic here. For example: switch (level) { case ERROR: inner.log(java.util.logging.Level.SEVERE, message); break; case WARNING: inner.log(java.util.logging.Level.WARNING, message); break; case INFO: inner.log(java.util.logging.Level.INFO, message); break; case DEBUG: inner.log(java.util.logging.Level.FINE, message); break; } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. com.kameleoon.logging.KameleoonLogger.setLogLevel(com.kameleoon.logging.LogLevel.DEBUG); // Optional; defaults to `LogLevel.WARNING`. com.kameleoon.logging.KameleoonLogger.setLogger(new CustomLogger()); ``` ## Reference This is the full reference documentation for the Java SDK. ### Initialization #### create() To use the SDK, you must finish initialization. Your app conducts all interactions with the SDK through an object of the `KameleoonClient` class. Create this object using the static method `create()` in `KameleoonClientFactory`. ```java theme={null} import com.kameleoon.KameleoonClientFactory; String siteCode = "a8st4f59bj"; try { KameleoonClient kameleoonClient = KameleoonClientFactory.create(siteCode, "custom/file/path/client-java.properties"); } catch (KameleoonException.SiteCodeIsEmpty e) { // indicates that provided site code is empty } catch (KameleoonException.ConfigCredentialsInvalid exception) { // indicates that provided clientId / clientSecret are not valid } try { KameleoonClientConfig config = new KameleoonClientConfig.Builder() .clientId("") // mandatory .clientSecret("") // mandatory .refreshInterval(60) // in minutes, optional (60 minutes by default) .sessionDuration(30) // in minutes, optional (30 minutes by default) .defaultTimeout(10_000) // in milliseconds, optional (10000 ms by default) .trackingInterval(1000) // in milliseconds, optional (1000 ms by default) .topLevelDomain("example.com") // mandatory if you use hybrid mode (engine or web experiments) .environment("development") // optional .proxyHost(new HttpHost("192.168.0.25", 8080, "http")) // optional .networkDomain("example.com") // optional .build(); KameleoonClientFactory.create(siteCode, config); } catch (KameleoonException.SiteCodeIsEmpty e) { // indicates that provided site code is empty } catch (KameleoonException.ConfigCredentialsInvalid exception) { // indicates that provided clientId / clientSecret are not valid } ``` ##### Parameters | Name | Type | Description | Default value | | ----------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | siteCode required | `String` | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. | | | configurationPath optional | `String` | Path to the SDK configuration file. | `/etc/kameleoon/client-java.conf` | | kameleoonConfig optional | `KameleoonClientConfig` | Configuration SDK object that you can pass instead of using a configuration file. | `null` | ##### Return value | Type | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | `KameleoonClient` | An instance of the `KameleoonClient` class that your app can then use to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.ConfigCredentialsInvalid | Exception indicating that the requested credentials weren't provided (either in the configuration file or as arguments to the method). | | KameleoonException.SiteCodeIsEmpty | Exception indicating that the specified site code is empty string which is invalid value. | #### waitInit() Use `waitInit()` when your application should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: it completes successfully when the client is initialized, and fails if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return a successful result. If no initialization result is available before the timeout expires, the call fails. If you don't provide a `timeoutMillis` value, the SDK uses the default timeout from [`KameleoonClientConfig.defaultTimeout`](#additional-configuration). Call `waitInit()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`isReady()`](#isready) as a non-blocking guard before evaluating feature flags. Even if `waitInit()` fails, the SDK keeps retrying the configuration fetch in the background, so `isReady()` starts returning `true` as soon as a retry succeeds. ```java theme={null} // Synchronized approach, waits up to the default timeout of the client configuration try { kameleoonClient.waitInit().get(); } catch (InterruptedException | ExecutionException exception) { // Indicates that the client could not be initialized due to the thrown exception. } // Asynchronous approach with an explicit timeout kameleoonClient.waitInit(1000).handle((res, ex) -> { if (ex != null) { // Indicates that the client could not be initialized due to the thrown exception. } return res; }); ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | ----- | ----------------------------------------------------------------------- | -------------------------------------- | | `timeoutMillis` optional | `int` | The maximum time to wait for an initialization result, in milliseconds. | `KameleoonClientConfig.defaultTimeout` | ##### Return value | Type | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CompletableFuture` | Completes when the client has been successfully initialized. Fails with `KameleoonException.Initialization` if the initial configuration fetch fails or no initialization result is available before the timeout expires. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | The returned task fails with this exception if the client couldn't be initialized or if no initialization result was available before the timeout expired. The underlying failure is available as the cause of the exception. | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`waitInit()`](#waitinit), this method returns immediately without blocking or throwing. * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. * Call `waitInit()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`isReady()`](#isready) as a non-blocking guard before evaluating feature flags. Even if `waitInit()` fails, the SDK keeps retrying the configuration fetch in the background, so `isReady()` starts returning `true` as soon as a retry succeeds. ```java theme={null} boolean ready = kameleoonClient.isReady(); ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### isFeatureActive() * *📨 Sends Tracking Data to Kameleoon (depending on the `track` parameter)* This method was previously called `activeFeature`, which was removed in SDK version `4.0.0`. Call this method to check whether a feature flag should be active for a specified user. This method takes a `visitorCode` and a `featureKey` as mandatory arguments to check if the feature is active for the user. If the user has never been associated with this feature flag, the SDK returns a random boolean value (either `true` to add the user to this feature or `false` to exclude them from the feature). If a user with the specified `visitorCode` is already registered with this feature flag, the SDK detects the previous `featureFlag`value. Make sure you catch and handle potential exceptions. If you specify a `visitorCode`, the `isFeatureActive()` method uses it as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String featureKey = "new_checkout"; Boolean hasNewCheckout = false; try { hasNewCheckout = kameleoonClient.isFeatureActive(visitorCode, featureKey); // disabling tracking hasNewCheckout = kameleoonClient.isFeatureActive(false, visitorCode, featureKey); } catch (KameleoonException.FeatureNotFound e) { // Feature toggle not yet activated on Kameleoon's side - we consider the feature inactive. hasNewCheckout = false; } catch (Exception e) { // This is a generic exception handler that handles all exceptions. System.out.println("Exception occurred"); } if (hasNewCheckout) { // Implement new checkout code here } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | track | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation (`true` by default). | | visitorCode | `String` | Unique identifier of the user. This field is required. | | featureKey | `String` | Key of the feature that you want to check the status of for the user. This field is required. | | isUniqueIdentifier (Deprecated) | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | --------- | ----------------------------------------------------------------------- | | `boolean` | Value of the feature that's registered for the specified `visitorCode`. | ##### Exceptions thrown | Type | Description | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature ID wasn't found in the SDK's internal configuration. This exception usually means that the feature flag hasn't yet been activated on Kameleoon's side (but code that implements the feature is already deployed in the app). | | KameleoonException.VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String featureKey = "new_checkout"; Variation variation; try { variation = kameleoonClient.getVariation(visitorCode, featureKey); // disabling tracking variation = kameleoonClient.getVariation(visitorCode, featureKey, false); } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; the feature flag isn't found in the current configuration. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (KameleoonException.VisitorCodeInvalid e) { // The visitor code you passed to the method is invalid and can't be accepted by SDK. } catch (KameleoonException ex) { // Handle the common Kameleoon Exception } // Fetch a variable value for the assigned variation String title = (String) variation.getVariables().get("title").getValue(); switch (variation.getKey()) { case 'on': // Main variation key is selected for visitorCode break; case 'alternative_variation': // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `featureKey` required | `String` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```java theme={null} try { Map variations = kameleoonClient.getVariations(visitorCode); // only active variations Map variations = kameleoonClient.getVariations(visitorCode, true); // disable tracking Map variations = kameleoonClient.getVariations(visitorCode, true, false); } catch (VisitorCodeInvalid e) { // Handle exception } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```java theme={null} try { // Forcing the variation "on" for the feature flag "featureKey1" for the visitor final int experimentId = 9516; kameleoonClient.setForcedVariation(visitorCode, experimentId, "on"); // Resetting the forced variation for the feature flag "featureKey1" for the visitor kameleoonClient.setForcedVariation(visitorCode, experimentId, null); } catch (KameleoonException ex) { // Handle the common Kameleoon Exception } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `experimentId` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `String` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Exceptions thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```java theme={null} try { kameleoonClient.evaluateAudiences(visitorCode); } catch (KameleoonException ex) { // Handle the common Kameleoon Exception } ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### getFeatureList() This method was previously called `obtainFeatureList`, which was removed in SDK version `4.0.0`. Returns a list of feature flag keys currently available for the SDK. ```java theme={null} List allFeatureFlagKey = kameleoonClient.getFeatureList(); ``` ##### Return value | Type | Description | | -------------- | ------------------------- | | `List` | List of feature flag keys | #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```java theme={null} try { DataFile dataFile = kameleoonClient.getDataFile(); } catch (Exception e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### getVisitorCode() This method was previously called `obtainVisitorCode`, which was removed in SDK version `4.0.0`. The `getVisitorCode()` method should be called to obtain the Kameleoon `visitorCode` for the current visitor. This method is especially important when using Kameleoon in a mixed front-end and back-end environment, where user identification consistency must be guaranteed. The implementation logic is described here: 1. The SDK checks whether a `kameleoonVisitorCode` cookie or query parameter associated with the current HTTP request can be found. If it can, it uses this `kameleoonVisitorCode` as the visitor identifier. 2. If no cookie / parameter is found in the current request, the SDK either randomly generates a new identifier or uses the `defaultVisitorCode` argument as identifier if it's passed. This process lets you use your own identifiers as visitor codes, should you wish to. This flexibility has the added benefit of matching Kameleoon visitors with your own users without any additional look-ups in a matching table. 3. Regardless, the server-side (via HTTP header) `kameleoonVisitorCode` cookie is set with the relevant value. Then, the method returns this identifier value. For more information, refer to [this article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). If you provide a `visitorCode`, its uniqueness must be guaranteed on your end - the SDK can't check it. Also, note that the length of `visitorCode` is limited to `255` characters. Any excess characters will throw an exception. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```java theme={null} import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse, defaultVisitorCode); ``` ##### Parameters | Name | Type | Description | | ------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | httpServletRequest | `HttpServletRequest` | The current HttpServletRequest object should be passed as the first parameter. This field is mandatory. | | httpServletResponse | `HttpServletResponse` | The current HttpServletResponse object should be passed as the second parameter. This field is mandatory. | | defaultVisitorCode | `String` | This parameter will be used as the `visitorCode` when an existing `kameleoonVisitorCode` cookie isn't found on the request. This field is optional. If not specified, the SDK generates a random `visitorCode` when no existing `kameleoonVisitorCode` cookie. | ##### Return value | Type | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------- | | `String` | A `visitorCode` that will be associated with this particular user and should be used with most of the methods of the SDK. | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`trackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`getVariation()`](#getvariation) and [`getVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```java theme={null} // Add a single data item (tracked by default) kameleoonClient.addData(visitorCode, Browser.chrome()); // Add multiple data items (tracked by default) kameleoonClient.addData(visitorCode, new PageView("https://url.com", "title"), new UserAgent("UserAgent")); // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoonClient.addData(visitorCode, false, new PageView("https://url.com", "title"), new UserAgent("UserAgent")); ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `track` optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `data` required | `Data...` | Collection of Kameleoon data types. | | ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * *📨 Sends Tracking Data to Kameleoon* The `flush()` method collects the Kameleoon data linked to the visitor. It then sends a tracking request, along with all data added using the `addData` method, which hasn't yet been sent using one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). `flush()` is non-blocking as the server call is made asynchronously. `flush` allows you to control when data associated with a given `visitorCode` is sent to the server. For instance, if you call `addData()` a dozen times, it would be inefficient to send data to the server each time `addData()` is invoked. So, all you have to do is call `flush()` once. If you specify a `visitorCode`, the `flush()` method uses this code as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```java theme={null} try { kameleoonClient.flush(visitorCode); // Interval tracking (most performant tracking method) kameleoonClient.flush(true, visitorCode); // Instant tracking } catch (VisitorCodeInvalid e) { // Catch exception } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | instant | `boolean` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the default tracking interval (`false`) set with the SDK parameter [`tracking_interval_millisecond`](#additional-configuration). This field is optional. | | visitorCode | `String` | User's unique identifier. This field is required. | | isUniqueIdentifier (Deprecated) | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. The `visitorCode` should be provided and not `null` to apply `isUniqueIdentifier` for a visitor, otherwise it will be ignored. If not provided, the default value is `false`. The field is optional. | #### getRemoteData() This method was previously called `retrieveDataFromRemoteSource`, which was removed in SDK version `4.0.0`. The `getRemoteData()` method allows you to retrieve data (according to a `key` passed as argument) for the specified `siteCode` stored on the Kameleoon server. Your site code is specified in `KameleoonClientFactory.create()`. Usually, data is stored on remote servers using the Data API. This method, along with the availability of scalable servers, provides a convenient way to store additional data that you can later retrieve for your app. ```java theme={null} CompletableFuture data = kameleoonClient.getRemoteData("key"); try { JsonObject test = kameleoonClient.getRemoteData("key").get(5_000, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { // Catch exception } ``` ##### Parameters | Name | Type | Description | | ---- | -------- | --------------------------------------------------------------------------- | | key | `String` | The key that's associated with the retrieved data. This field is mandatory. | ##### Return value | Type | Description | | ------------------------------- | ------------------------------------------------------------------------- | | `CompletableFuture` | Future `JsonObject` associated with retrieving data for specific **key**. | #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```java theme={null} String visitorCode = "visitorCode"; // Visitor data will be fetched and automatically added for `visitorCode` CompletableFuture> visitorData = kameleoonClient.getRemoteVisitorData(visitorCode); // If you only want to fetch data and add it yourself manually, set addData == `false` CompletableFuture> visitorData = kameleoonClient.getRemoteVisitorData(visitorCode, false); // If you want to fetch custom list of data types RemoteVisitorDataFilter filter = RemoteVisitorDataFilter.builder() .previousVisitAmount(25) .customData(false) .conversions(true) .build(); CompletableFuture> visitorData = kameleoonClient.getRemoteVisitorData(visitorCode, filter, true, false); try { List visitorData = kameleoonClient.getRemoteVisitorData(visitorCode).get(5_000, TimeUnit.MILLISECONDS); // Your custom code } catch (CancellationException | InterruptedException | ExecutionException | TimeoutException e) { // Catch exception } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The visitor code for the data you want to retrieve. This field is mandatory. | | filter | `RemoteVisitorDataFilter` | Filter for specifying what data should be retrieved from visits. By default, only `CustomData` is retrieved from the current and latest previous visit (`RemoteVisitorDataFilter.builder().build()` or `new RemoteVisitorDataFilter()`). Other filters parameters are set to `false`. This field is optional. | | addData | `boolean` | A boolean indicating whether the method should automatically add retrieved data for a visitor. This field is optional. | | isUniqueIdentifier (Deprecated) | `boolean` | An optional parameter for specifying if the `visitorCode` is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ------------------------------- | ---------------------------------------------------- | | `CompletableFuture>` | Future `List` associated with a given visitor. | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `kameleoon.types.RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition. | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | #### getVisitorWarehouseAudience() This method retrieves all audience data associated with the visitor in your data warehouse using the specified `visitorCode` and `warehouseKey`. The `warehouseKey` is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method passes the result to the returned future as a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. ```java theme={null} CompletableFuture warehouseAudienceDataCF = kameleoonClient.getVisitorWarehouseAudience(visitorCode, warehouseKeyValue, customDataIndex); // If you need to specify warehouse key CompletableFuture warehouseAudienceDataCF = kameleoonClient.getVisitorWarehouseAudience(visitorCode, customDataIndex); try { CustomData warehouseAudienceData = warehouseAudienceDataCF.get(5_000, TimeUnit.MILLISECONDS); // Your custom code } catch (CancellationException | InterruptedException | ExecutionException | TimeoutException e) { // Catch exception } ``` ##### Parameters | Name | Type | Description | | --------------- | -------- | ------------------------------------------------------------------------------------------------------- | | visitorCode | `String` | The unique identifier of the visitor for whom you want to retrieve and add the data. | | warehouseKey | `String` | The unique key to identify the warehouse data (usually, your internal user ID). This field is optional. | | customDataIndex | `int` | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | ##### Return value | Type | Description | | ------------------------------- | ------------------------------------------------------------------------------------ | | `CompletableFuture` | Future `CustomData` instance confirming that the data has been added to the visitor. | ##### Exceptions thrown | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid (it's either empty or longer than 255 characters). | #### setLegalConsent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```java theme={null} // if you do not need to set the visitor code in a cookie to respond kameleoonClient.setLegalConsent(visitorCode, true); String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); kameleoonClient.setLegalConsent(visitorCode, true, httpServletResponse); ``` ##### Parameters | Name | Type | Description | | ------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | `String` | The user's unique identifier. This field is required. | | legalConsent | `boolean` | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | | response | HttpServletResponse | The HTTP servlet response where values in the cookies will be adjusted based on the legal consent status. The field is optional. | ##### Exceptions thrown | Type | Description | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | KameleoonException.VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid. It's either empty or longer than 255 characters. | ##### Consent revocation behavior When you call `setLegalConsent()` with `legalConsent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); int goalId = 83023; kameleoonClient.trackConversion(visitorCode, goalId); // Add metadata CustomData cd = new CustomData(1, "metadata"); kameleoonClient.trackConversion(visitorCode, goalId, cd); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData...` | Lets you set specific values for custom data which have been defined as metadata for the goal in the Kameleoon App. Example: `[CustomData{id: 5, value: "Payment Type"}, CustomData{id: 6, value: "Delivery Method"}]`. In this example, `5` and `6` are the indexes of the custom data (5 = “Payment Type”, 6 = “Delivery Method”). | `new CustomData[0]` | | `isUniqueIdentifier` *(deprecated)* | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```java theme={null} kameleoonClient.addData(visitorCode, new CustomData(5, "Credit Card"), new CustomData(9, "Express Delivery")); kameleoonClient.trackConversion(visitorCode, 1000, new CustomData(5, "Amex Credit Card")); ``` ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```java theme={null} String engineTrackingCode = kameleoonClient.getEngineTrackingCode(visitorCode); ``` * To use this feature, implement both the Java SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `String` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `String` | JavaScript code to insert into the page. | ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```java theme={null} import com.kameleoon.events.DataFileUpdateEvent; import com.kameleoon.events.EventType; kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, event -> { DataFileUpdateEvent.Source source = event.getSource(); // POLLING or STREAMING long dateModified = event.getDateModified(); // Data file modification date in milliseconds. // React to the data file update. }); // Clear the handler. kameleoonClient.setEventHandler(EventType.DATAFILE_UPDATE, null); ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateEvent.Source` | The update source. [`POLLING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`STREAMING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `long` | The modification date of the updated data file, in milliseconds. | ```java theme={null} import com.kameleoon.events.EventType; import com.kameleoon.events.HttpRequestFailure; import com.kameleoon.events.HttpRequestHandler; import com.kameleoon.events.HttpRequestHandler.RequestType; kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, new HttpRequestHandler() { @Override public void onRequestSucceeded(RequestType requestType, int httpStatus, long durationMillis) { // The SDK request completed successfully. } @Override public void onRequestFailed(RequestType requestType, HttpRequestFailure failure, long durationMillis) { HttpRequestFailure.Reason reason = failure.getReason(); Integer httpStatus = failure.getHttpStatus(); Throwable cause = failure.getCause(); // The SDK request failed. } }); // Clear the handler. kameleoonClient.setEventHandler(EventType.HTTP_REQUEST, null); ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `onRequestSucceeded()` for successful responses and `onRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `onRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `onRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ---------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `requestType` | `HttpRequestHandler.RequestType` | The SDK request type. Possible values are `DATAFILE`, `TRACKING`, `REMOTE_VISITOR_DATA`, `REMOTE_DATA`, and `ACCESS_TOKEN`. | | `httpStatus` | `int` | The HTTP status code returned by the request. Passed only to `onRequestSucceeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `onRequestFailed()`. | | `durationMillis` | `long` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailure.Reason` | The failure reason. Possible values are `HTTP_STATUS`, `EXCEPTION`, and `CANCELLED`. | | `httpStatus` | `Integer` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `null` for exception and cancellation failures. | | `cause` | `Throwable` | The exception (error) that caused the request to fail. This value is `null` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DATAFILE_UPDATE` to react to SDK data file updates, or `EventType.HTTP_REQUEST` to react to completed SDK HTTP requests. | | `handler` required | `T extends EventHandler` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventType.DATAFILE_UPDATE`, or an `HttpRequestHandler` for `EventType.HTTP_REQUEST`. Pass `null` to remove the current handler. | ### Data types This section lists the data types supported by Kameleoon in `com.kameleoon.Data`. The SDK provides several standard data types as well as the `CustomData` type that allows you to define custom data types. #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | --------------------------------- | -------------- | ----------------------------------------------------------------------------------------------- | | `type` required | `Browser.Type` | List of browsers: `CHROME`, `INTERNET_EXPLORER`, `FIREFOX`, `SAFARI`, `OPERA`, `OTHER`. | | `version` optional | `Float` | Version of the browser, floating point number represents major and minor version of the browser | ```java theme={null} kameleoonClient.addData(visitorCode, Browser.chrome()); kameleoonClient.addData(visitorCode, Browser.safari()); kameleoonClient.addData(visitorCode, new Browser(Browser.Type.CHROME, 10.0)); ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | --------------- | ----------------------------------------------- | ------------------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData...` | Metadata of the conversion. | `new CustomData[0]` | ```java theme={null} kameleoonClient.addData(visitorCode, new Conversion(32, 10f)); kameleoonClient.addData(visitorCode, new Conversion(33, null, true)); kameleoonClient.addData( visitorCode, new Conversion(34, 5f, new CustomData(3, "metadata1", "md2"), new CustomData(5, "md3")) ); ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. | Name | Type | Description | | ------- | --------------------- | --------------------------------------------------------------------------------- | | cookies | `Map` | A string object map consisting of cookie keys and values. This field is required. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```java theme={null} Cookie cookie = new Cookie (new HashMap() {{ put("my_key1", "my_value1"); put("my_key2", "my_value1"); }}); kameleoonClient.addData(visitorCode, cookie); ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String` | The country of the visitor. | | `region` optional | `String` | The region of the visitor. | | `city` optional | `String` | The city of the visitor. | | `postalCode` optional | `String` | The postal code of the visitor. | | `latitude` optional | `float` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `float` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```java theme={null} kameleoonClient.addData(visitorCode, new Geolocation("France", "Île-de-France", "Paris")); ``` #### CustomData `CustomData` enables the association of any type of data with each visitor, making it an effective tool for targeting conditions in [segments](/user-manual/assets/segments/create-a-segment/). Additionally, it can be used as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data). Define custom data types in the Kameleoon app or the Data API and use them from the SDK. | Name | Type | Description | Default | | ---------------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `int`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data.. | | | values required | `String...`/`List` | Values of the custom data to be stored. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor is allowed only one `CustomData` for each unique `index`(`name`). Adding another `CustomData` with the same `index`(`name`) will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```java theme={null} kameleoonClient.addData(visitorCode, new CustomData(1, "value")); // With several values kameleoonClient.addData(visitorCode, new CustomData(1, "value1", "value2")); // To set the 'overwrite' flag to false kameleoonClient.addData(visitorCode, new CustomData(1, false, "value")); // To use a name instead of the index kameleoonClient.addData(visitorCode, new CustomData("my-custom-data", "value")); ``` #### Device | Name | Type | Description | | ------ | -------- | ---------------------------------------------------------------------- | | device | `Device` | List of devices: `PHONE`, `TABLET`, `DESKTOP`. This field is required. | ```java theme={null} kameleoonClient.addData(visitorCode, Device.desktop()); ``` #### PageView Store page view events. | Name | Type | Description | | --------- | --------------- | -------------------------------------------------- | | url | `String` | URL of the page viewed. This field is required. | | title | `String` | Title of the page viewed. This field is required. | | referrers | `List` | Referrers of viewed pages. This field is optional. | The referrer's index (ID) is available in the Kameleoon app in the [acquisition channel configuration](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) page. Be careful: this index starts at 0, so the first acquisition channel you create for the specified site would have the ID 0, not 1. ```java theme={null} kameleoonClient.addData( visitorCode, new PageView("https://url.com", "title", Array.asList(3)) ); ``` #### UserAgent Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to tackle this issue and recognize known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that might distort your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. | Name | Type | Description | | ----- | -------- | -------------------------------------------------------------------------------------- | | value | `String` | The User-Agent value that will be sent with tracking requests. This field is required. | ```java theme={null} kameleoonClient.addData(visitorCode, new UserAgent("Your User Agent")); ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data with the visitor associated with the specified identifier. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. | Name | Type | Description | | ----- | --------- | --------------------------------------------------------------------------------------- | | value | `boolean` | Parameter specifying if the visitorCode is a unique identifier. This field is required. | ```java theme={null} kameleoonClient.addData(visitorCode, new UniqueIdentifier(true)); ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. | Name | Type | Description | | ---- | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | | type | `OperatingSystem.Type` | List of operating systems: `WINDOWS_PHONE`, `WINDOWS`, `ANDROID`, `LINUX`, `MAC`, and `IOS`. This field is required. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```java theme={null} kameleoonClient.addData(visitorCode, new OperatingSystem(OperatingSystem.Type.WINDOWS)); kameleoonClient.addData(visitorCode, OperatingSystem.mac()); ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `String` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```java theme={null} kameleoonClient.addData(visitorCode, new ApplicationVersion("10")); // major kameleoonClient.addData(visitorCode, new ApplicationVersion("10.20")); // major.minor kameleoonClient.addData(visitorCode, new ApplicationVersion("10.20.30")); // major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `long` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```java theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. Map featureFlags = dataFile.getFeatureFlags(); // Retrieves the last modification timestamp of the DataFile. // The value is a long representing milliseconds since the Unix epoch. long dateModified = dataFile.getDateModified(); ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `String` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `List` | A list of `Rule` objects | ```java theme={null} // Check whether the feature flag is enabled in the current environment boolean isEnvironmentEnabled = featureFlag.isEnvironmentEnabled(); // Retrieve the key of the default variation String defaultVariationKey = featureFlag.getDefaultVariationKey(); // Retrieve the default variation object Variation defaultVariation = featureFlag.getDefaultVariation(); // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) Map variations = featureFlag.getVariations(); // Retrieve all targeting rules associated with the feature flag List rules = featureFlag.getRules(); ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```java theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) Map variations = rule.getVariations(); ``` #### Variation `Variation` contains information about the visitor's assigned variation (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | name | `String` | The name of the variation. | | key | `String` | The unique key identifying the variation. | | id | `Integer` | The ID of the assigned variation (or `null` if it's the default variation). | | experimentId | `Integer` | The ID of the experiment associated with the variation (or `null` if default). | | variables | `Map` | A map containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```java theme={null} // Retrieving the variation name String variationName = variation.getName(); // Retrieving the variation key String variationKey = variation.getKey(); // Retrieving the variation id Integer variationId = variation.getId(); // Retrieving the experiment id Integer experimentId = variation.getExperimentId(); // Retrieving the variables map Map variables = variation.getVariables(); ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | `String` | The unique key identifying the variable. | | type | `String` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | value | `Object` | The value of the variable, which can be of the following types: **Boolean**, **Integer**, **Long**, **Double**, **String**, **JsonObject**, **JsonArray**. | ```java theme={null} // Retrieving the variables map Map variables = variation.getVariables(); // Variable type can be retrieved for further processing String type = variables.get("isDiscount").getType(); // Retrieving the variable value by key Boolean isDiscount = (Boolean) variables.get("isDiscount").getValue(); // Variable value can be of different types String title = (String) variables.get("title").getValue(); ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `5.0.0`. #### getFeatureVariationKey() * *📨 Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Call this method to get the feature variation key for a specified user and feature. This method takes a `visitorCode` and `featureKey` as mandatory arguments to get the variation key for the user and feature. If the user has never been associated with this feature flag, the SDK returns a randomly assigned variation key (according to the feature flag rules). If a user with the specified `visitorCode` is already registered with this feature flag, the SDK detects the previous variation key value. If the user doesn't match any of the rules, the default value is returned, which you can customize in the Kameleoon app. Make sure you catch and handle potential exceptions. If you specify a `visitorCode`, the `flush()` method uses it as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String featureKey = "new_checkout"; String variationKey = "" try { variationKey = kameleoonClient.GetFeatureVariationKey(visitorCode, featureKey); } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; the feature flag isn't found in the current configuration } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment } catch (KameleoonException.VisitorCodeInvalid e) { // The visitor code you passed to the method is invalid and can't be accepted by SDK. } switch (variationKey) { case 'on': // Main variation key is selected for visitorCode break; case 'alternative_variation': // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | `String` | Unique identifier of the user. This field is required. | | featureKey | `String` | Key of the feature you want to expose to a user. This field is required. | | isUniqueIdentifier (Deprecated) | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | -------- | ------------------------------------------------------------------------------------ | | `String` | Variation key of the feature flag that's registered for the specified `visitorCode`. | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature key wasn't found in the SDK's internal configuration. This exception usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed on your app). | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's empty or longer than 255 characters. | #### getActiveFeatures() Use [`getVariations()`](#getvariations) instead. This method takes a single **visitorCode** parameter. Result contains only active features for a given visitor. ```java theme={null} try { Map activeFeatures = kameleoonClient.getActiveFeatures(visitorCode); } catch (VisitorCodeInvalid e) { // Handle exception } ``` ##### Parameters | Name | Type | Description | | ----------- | ------ | ------------------------------------------------- | | visitorCode | String | User's unique identifier. This field is required. | ##### Return value | Type | Description | | ------------------------ | --------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned variations of the active features using the corresponding active features' keys. | ##### Exceptions thrown | Type | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### getActiveFeatureListForVisitorCode() * Use [`getVariations()`](#getvariations) instead. * This method was previously called `obtainFeatureListForVisitorCode`, which was removed in SDK version `4.0.0`. This method takes a single `visitorCode` parameter. Return only the active feature flags for the specified visitor. ```java theme={null} List listActiveFeatureFlags = kameleoonClient.getActiveFeatureListForVisitorCode(visitorCode); ``` ##### Parameters | Name | Type | Description | | ----------- | -------- | ------------------------------------------------- | | visitorCode | `String` | User's unique identifier. This field is required. | ##### Return value | Type | Description | | -------------- | --------------------------------------------------------------------- | | `List` | List of active feature flag keys available for specific `visitorCode` | #### getFeatureVariable() * *📨 Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. Call this method to get a user's associated feature variation value. This method takes a `visitorCode`, `featureKey` and `variableKey` as required arguments to get the variation key's variable for the specified user. If a user has never been associated with this feature flag, the SDK returns a randomly assigned variable value of the variation key according to the feature flag rules. If a user with the specified `visitorCode` is already registered with this feature flag, the SDK returns the variable value for previously associated variation. If the user doesn't match any of the rules, the default variable is returned. Make sure you catch and handle potential exceptions. If you specify a `visitorCode`, the `getFeatureVariable()` method uses the code as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String featureKey = "feature_key"; String variableKey = "var" try { var variableValue = kameleoonClient.getFeatureVariable(visitorCode, featureKey, variableKey); // Your custom code, depending on variableValue. } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; the feature flag isn't found in current configuration } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (KameleoonException.FeatureVariableNotFound e) { // Requested variable not defined on Kameleoon's side } catch (KameleoonException.VisitorCodeInvalid e) { // The visitor code passed to the method is invalid and can't be accepted by SDK. } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | `String` | Unique identifier of the user. This field is required. | | featureKey | `String` | Key of the feature you want to expose to a user. This field is required. | | variableKey | `String` | Name of the variable you want to get a value for. This field is required. | | isUniqueIdentifier (Deprecated) | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | object | Value of variable of variation that's registered for the specified `visitorCode` for this feature flag. Possible types: `bool`, `int`, `double`, `string`, `JObject`, `JArray` | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature key wasn't found in the internal SDK configuration. This usually means that the feature flag hasn't yet been activated in the Kameleoon app (but code implementing the feature is already deployed in your app). | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.FeatureVariableNotFound | Exception indicating that the requested variable wasn't found. Check that the variable's key in the Kameleoon app matches your code. | | KameleoonException.VisitorCodeInvalid | Exception indicating that the specified visitor code isn't valid. (It's either empty or longer than 255 characters). | #### getFeatureVariables() * *📨 Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. This method retrieves a map containing variable keys and their values assigned according the variation that the visitor is assigned in the specified feature flag. Feature variables can be modified in the Kameleoon app. If a user has never been associated with this feature flag, the SDK returns a randomly assigned set of variable values in the variation according to the feature flag rules. If a user with the specified `visitorCode` is already registered with this feature flag, the SDK returns the variable values for the variation used previously. If the user doesn't match any of the rules, the default variables are returned. Make sure you catch and handle potential exceptions. ```java theme={null} String visitorCode = kameleoonClient.getVisitorCode(httpServletRequest, httpServletResponse); String featureKey = "feature_key"; String variableKey = "var" try { var variableValue = kameleoonClient.getFeatureVariables(visitorCode, featureKey); // Your custom code, depending on variable values. } catch (KameleoonException.FeatureNotFound e) { // The error has occurred; the feature flag isn't found in current configuration. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (KameleoonException.FeatureVariableNotFound e) { // Requested variable not defined on Kameleoon's side } catch (KameleoonException.VisitorCodeInvalid e) { // The visitor code passed to the method is invalid and can't be accepted by SDK. } ``` ##### Parameters | Name | Type | Description | | ------------ | -------- | ---------------------------------------------------------------- | | featureKey | `String` | Key of the feature you want to obtain. This field is required. | | variationKey | `String` | Key of the variation you want to obtain. This field is required. | ##### Return value | Type | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Data associated with this feature flag. The values can be `Boolean`, `Integer`, `Double`, `String`, `JsonObject`, or `JsonArray` (the type is defined in the Kameleoon app). | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature key wasn't found in the internal SDK configuration. This exception usually means that the feature flag hasn't been activated in the Kameleoon app (but code implementing the feature is already deployed in your app). | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.FeatureVariationNotFound | Exception indicating that the requested variation key wasn't found in the internal configuration of the SDK. This usually means that the variation's corresponding experiment isn't activated in the Kameleoon app. | | KameleoonException.VisitorCodeInvalid | Exception indicating that the specified visitor code isn't valid. (It's either empty or longer than 255 characters). | #### getFeatureVariationVariables() * Use [`getVariation()`](#getvariation) instead. * This method was previously called `getFeatureAllVariables`, which was removed in SDK version `4.0.0`. Call this method to retrieve all feature variables for a feature. You can modify feature variables in the Kameleoon app. This method takes two input parameters: `featureKey` and `variationKey`. It returns the data with the `Map` type as defined in the Kameleoon app. It throws an exception (`KameleoonException.FeatureNotFound`) if the feature you request isn't found in the SDK's internal configuration. ```java theme={null} String featureKey = "featureKey"; String variationKey = "variationKey"; try { Map allVariables = kameleoonClient.getFeatureVariationVariables(featureKey, variationKey); } catch (KameleoonException.FeatureNotFound e) { // The feature is not activated in the Kameleoon app. } catch (KameleoonException.FeatureEnvironmentDisabled e) { // The feature flag is disabled for the environment. } catch (KameleoonException.FeatureVariationNotFound e) { // The variation is not activated in the Kameleoon app (most likely, the associated experiment is not active) } catch (Exception e) { // This is a generic Exception handler which will handle all exceptions. System.out.println("Exception occurred"); } ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ---------------------------------------------------------------- | | featureKey | String | Key of the feature you want to obtain. This field is required. | | variationKey | String | Key of the variation you want to obtain. This field is required. | ##### Return value | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Data associated with this feature flag. The values can be `Boolean`, `Integer`, `Double`, `String`, `JsonObject`, `JsonArray` (depending on the type defined in the Kameleoon app). | ##### Exceptions thrown | Type | Description | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | KameleoonException.FeatureNotFound | Exception indicating that the requested feature wasn't found in the internal configuration of the SDK. This exception usually means that the feature flag isn't activated in the Kameleoon app. | | KameleoonException.FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | KameleoonException.FeatureVariationNotFound | Exception indicating that the requested variation key wasn't found in the internal configuration of the SDK. This usually means that the variation's corresponding experiment isn't activated in the Kameleoon app. | #### updateConfigurationHandler() The `updateConfigurationHandler()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```java theme={null} kameleoonClient.updateConfigurationHandler(() -> { // Configuration was updated }); ``` ##### Parameters | Name | Type | Description | | --------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `KameleoonUpdateConfigurationHandler` | The handler that will be called when the configuration is updated using a real-time configuration event. | # JavaScript / TypeScript SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/js-sdk Integrate the Kameleoon JavaScript/TypeScript SDK to run experiments and activate feature flags in client-side web applications. With the Kameleoon JavaScript SDK, you can run experiments and activate feature flags. Integrating the SDK into your web app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Details on the latest version of JavaScript / TypeScript SDK can be found in the [changelog](https://github.com/Kameleoon/client-js/blob/main/CHANGELOG.md). **SDK methods**: For the full reference documentation of the JavaScript SDK, see the [reference](#reference) section. ## Developer guide This section will help you get started as well as introduce you to some of the more advanced concepts. ### Getting started #### Installation The Kameleoon SDK Installation tool is the best method to install the SDK quickly. The **SDK Installer** helps you install the SDK of your choice, generate a basic code sample, and configure [external dependencies](#external-dependencies) if needed. To use the SDK Installation tool, install and run it globally: ```bash theme={null} npm install --global @kameleoon/sdk-installer kameleoon-sdk ``` Or run it directly with `npx`: ```bash theme={null} npx @kameleoon/sdk-installer ``` You can also inject the JavaScript SDK into your app as a single file using the `

Hello, World!

``` ```js title="app.js" theme={null} const { KameleoonClient, CustomData } = KameleoonSDK; ``` To always use the latest version of a major release, use the following script, where `4` is the [current major version](/developer-docs/sdks/versions): ```html theme={null} https://static.kameleoon.com/kameleoonSDK-4-latest.js ``` To always stay on a specific version, specify the full version number instead. For example, for version `4.24.0`, which is the earliest version available as a static script, use the following: ```html theme={null} https://static.kameleoon.com/kameleoonSDK-4.24.0.js ``` Versions can be referenced on the [release page](https://github.com/Kameleoon/client-js/releases). By default, the static script uses the optimized SDK bundle, where [`INFO`](#logging) and `DEBUG` log messages are removed to reduce bundle size. If you use `KameleoonLogger.setLogLevel(LogLevel.DEBUG)` or `KameleoonLogger.setLogLevel(LogLevel.INFO)`, use the `full` static script instead. ```html theme={null} https://static.kameleoon.com/kameleoonSDK-4-latest-full.js https://static.kameleoon.com/kameleoonSDK-4.24.0-full.js ```
#### Initialize the Kameleoon client Here is a step-by-step guide for configuring the JavaScript SDK for your application. ```ts theme={null} import { Environment, KameleoonClient, SDKConfigurationType, } from '@kameleoon/javascript-sdk'; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 20, environment: Environment.Production, cookieDomain: '.example.com', }; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration }); // -- Waiting for the client initialization using `async/await` async function init(): Promise { await client.initialize(); } init(); // -- Waiting for the client initialization using `Promise.then()` client .initialize() .then(() => {}) .catch((error) => {}); ``` ```js theme={null} import { Environment, KameleoonClient } from '@kameleoon/javascript-sdk'; // -- Optional configuration const configuration = { dataFileRefreshInterval: 20, environment: Environment.Production, cookieDomain: '.example.com', }; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration }); // -- Waiting for the client initialization using `async/await` async function init() { await client.initialize(); } init(); // -- Waiting for the client initialization using `Promise.then()` client .initialize() .then(() => {}) .catch((error) => {}); ``` To start, developers need to create an entry point for the JavaScript SDK by creating a new instance of Kameleoon Client. Use `KameleoonClient` to run feature experiments and retrieve the status of feature flags and their variations. `KameleoonClient` initialization is performed asynchronously to ensure that the Kameleoon API call was successful. For initialization, use the method [`initialize()`](#initialize). Use `async/await`, `Promise.then()` or any other method to handle asynchronous client initialization. ##### Parameters | Name | Type | Description | | ------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | siteCode required | `string` | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | configuration optional | `Partial` | Client's configuration | | externals optional | `ExternalsType` | External implementation of SDK dependencies ([External dependencies](#external-dependencies)) | | stubMode optional | `boolean` | When set to true, the client will operate in stub mode and perform no operations. In this mode, all method calls execute no actions, ensuring that no external actions or side effects occur. | ##### Configuration parameters | Name | Type | Description | Default Value | | ---------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | updateInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | | environment optional | `Environment` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data, minimum value is 1 minute | `undefined` (no cleanup will be performed) | | domain optional | `string` | [domain](#domain-information) to which the cookie belongs. Deprecated, use `cookieDomain` instead | `undefined` | | cookieDomain optional | `string` | [domain](#domain-information) to which the cookie belongs. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests. Commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value. | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail. | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests in milliseconds. All visitors who were evaluated for any feature flag or had associated data are included in this tracking request, which is performed once per interval. The minimum value is `100` ms and the maximum value is `1_000` ms | `1_000` (1 second) | The `domain` parameter is deprecated and will be removed in a future release. Use `cookieDomain` instead. | Name | Type | Description | Default Value | | ---------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | dataFileRefreshInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes; the minimum value is 1 minute. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. This parameter replaces the deprecated `updateInterval`; if both are specified, `dataFileRefreshInterval` takes precedence. | `60` | | dataFileCacheTtl optional | `number` | Defines, in minutes, how long the data file cached in the browser storage remains valid. If the cached data file is older than `dataFileRefreshInterval` but within `dataFileCacheTtl`, `initialize()` completes immediately using the cached data file and fetches the latest version in the background. If the age of the cached data file exceeds `dataFileCacheTtl`, `initialize()` waits for the latest data file before completing, so that visitors returning after a long period of inactivity are evaluated against the current configuration; if the request fails, the cached data file is used as a fallback. A value of `0` disables the background refresh: once a refresh is due, `initialize()` always waits for the latest data file. Negative values are treated as `0`.

**Note:** a lower `dataFileCacheTtl` increases the number of blocking data file requests during SDK initialization, which adds the request time to the page load for the affected visitors. Set `dataFileCacheTtl` higher than `dataFileRefreshInterval` (for example, `dataFileRefreshInterval: 5` and `dataFileCacheTtl: 10`), unless every configuration update must be applied before initialization completes. | `90` | | environment optional | `Environment \| string` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data, minimum value is 1 minute | `undefined` (no cleanup will be performed) | | cookieDomain optional | `string` | [domain](#domain-information) to which the cookie belongs. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests. Commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value. | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests in milliseconds. All visitors who were evaluated for any feature flag or had associated data are included in this tracking request, which is performed once per interval. The minimum value is `1_000` ms and the maximum value is `5_000` ms | `1_000` (1 second) | | defaultDataFile optional | `string` | The `defaultDataFile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `defaultDataFile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `undefined` | | updateInterval deprecated | `number` | **Deprecated.** Use `dataFileRefreshInterval` instead; this parameter will be removed in the next major version. If both parameters are specified, `dataFileRefreshInterval` takes precedence. Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | **Option 1 (Recommended):** Use `JSON.stringify()` ```js theme={null} const dataFileJson = {"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\"make\":\"Porsche\",\"model\":\"911\"}"}}, "dateModified":1752209266000}; const dataFileString = JSON.stringify(dataFileJson); const configuration = { dataFileRefreshInterval: 20, defaultDataFile: dataFileString }; ``` **Option 2:** Raw JSON string (escape special characters) ```js theme={null} const configuration = { dataFileRefreshInterval: 20, defaultDataFile: `{"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\\"make\\":\\"Porsche\\",\\"model\\":\\"911\\"}"}, "dateModified":1752209266000}` }; ```
Lowering `dataFileRefreshInterval` increases how often the SDK checks for a newer configuration in the background, but a visitor's cached data file still stays usable until it ages past `dataFileCacheTtl` (90 minutes by default). Lower `dataFileCacheTtl` instead to shorten how long a visitor can keep evaluating against a stale cached configuration. Otherwise, traffic into a newly launched experiment or feature flag ramps up gradually rather than reaching full volume immediately. See [Impact on newly launched experiments and feature flags](/developer-docs/feature-experimentation/technical-reference/technical-considerations#impact-on-newly-launched-experiments-and-feature-flags) for details. Do not use several client instances in one application, as it is not fully supported yet. Several client instances may lead to local storage configuration being overwritten and cause bugs. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureFlagActive()`](#isfeatureflagactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureFlagActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#configuration-parameters). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#configuration-parameters)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```js theme={null} client.addData(visitorCode, new CustomData(index, 'newVisitorCode')); ``` [More information in addData()](#adddata) * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions supported by this SDK, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/feature-experimentation/targeting-and-segmentation\use-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use directly KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's primary behavior. client.setLogLevel(LogLevel.ERROR); // Or use directly KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/javascript-sdk/full’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/javascript-sdk’; const client = new KameleoonClient({ siteCode: ‘my_site_code’, configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK’s primary behavior. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/javascript-sdk’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```ts theme={null} import { KameleoonClient, KameleoonLogger, IExternalLogger, LogLevel } from '@kameleoon/javascript-sdk'; export class CustomLogger implements IExternalLogger { // `log` method accepts logs from the SDK public log(level: LogLevel, message: string): void { // Custom log handling logic here. For example: switch (level) { case LogLevel.DEBUG: console.debug(message); break; case LogLevel.INFO: console.info(message); break; case LogLevel.WARNING: console.warn(message); break; case LogLevel.ERROR: console.error(message); break; } } } const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/javascript-sdk'; export class CustomLogger { // `log` method accepts logs from the SDK log(level, message) { // Custom log handling logic here. For example: switch (level) { case 'DEBUG': console.debug(message); break; case 'INFO': console.info(message); break; case 'WARNING': console.warn(message); break; case 'ERROR': console.error(message); break; } } } const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ### Domain information You provide a domain as the `domain` in the `KameleoonClient` configuration, which is used for storing Kameleoon visitor code in cookies. Domains are important when working with the [`getVisitorCode`](#getvisitorcode) and [`setLegalConsent`](#setlegalconsent) methods. The domain you provide is stored in the cookie as the `Domain=` key. ### Setting the domain The domain you provide indicates if the URL address can use the cookie. For example, if your domain is `www.example.com`. the cookie is only available from a `www.example.com` URL. This means pages with the `app.example.com` domain can't use the cookie. For more fllexibility with subdomains, you can specify the domain with a period (`.`). For example, the domain `.example.com` allows the cookie to function on both `app.example.com` and `login.example.com`. You can't use regular expressions, special symbols, protocol, or port numbers in the `domain`. Additionally, a [specific list of subdomains](https://publicsuffix.org/list/public_suffix_list.dat) can't be used with the prefix `.`. Here's a small domain cheat sheet: | Domain | Allowed URLs | Disallowed URLs | | ------------------------------ | --------------------- | -------------------- | | `www.example.com` | ✅`www.example.com` | ❌ `app.example.com` | | | ✅ `example.com` | ❌ `.com` | | | | | | `.example.com` = `example.com` | ✅ `example.com` | ❌ `otherexample.com` | | | ✅ `www.example.com` | | | | ✅ `app.example.com` | | | | ✅ `login.example.com` | | | `https://www.example.com` | ⛔ bad domain | ⛔ bad domain | | `www.example.com:4408` | ⛔ bad domain | ⛔ bad domain | | `.localhost.com` = `localhost` | ⛔ bad domain | ⛔ bad domain | #### Developing on localhost `localhost` is always considered a bad domain, making testing the domain when developing on `localhost` difficult. There are two ways to avoid this issue: * Don't specify the `domain` field in the SDK client while testing. * Create a local domain for `localhost`. For example: * Navigate to `/etc/hosts` on *Linux* or to `c:\Windows\System32\Drivers\etc\hosts` on *Windows*. * Open `hosts` with file super user or administrator rights. * Add a domain to the `localhost` port, for example: `127.0.0.1 app.com` * Now you can run your app locally on `app.com:{my_port}` and specify `.app.com` as your domain ### External dependencies SDK external dependencies use the *dependency injection* pattern to give you the ability to provide your own implementations for certain parts of an SDK. In the JavaScript SDK, all external dependencies have default implementations, which use a native browser API so there's no need to provide them unless another API is required for specific use cases. Here's the list of available external dependencies: | Dependency | Interface | Required/Optional | API Used | Description | | -------------------- | ----------------------------- | ----------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage` | `IExternalStorage` | *Optional* | Browser `localStorage` | Used for storing all the existing and collected SDK data. | | `requester` | `IExternalRequester` | *Optional* | Browser `fetch` | Used for performing all network requests. | | `eventSource` | `IExternalEventSource` | *Optional* | Browser `EventSource` | Used for receiving Server Sent Events for [Real Time Update](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) capabilities. | | `visitorCodeManager` | `IExternalVisitorCodeManager` | *Optional* | Browser cookie | Used for storing and synchronizing visitor codes. | | `logger` | `ILogger` | *Optional* | Custom implementation | Used for custom handling of logs from the SDK. Lets you define how logs are processed and their output. | The following example implements external dependencies. To import an interface from an SDK, create a class that implements the interface and pass the instantiated class to the SDK. #### Storage ```ts theme={null} import { IExternalStorage, KameleoonClient } from '@kameleoon/javascript-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage implements IExternalStorage { public read(key: string): T | null { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } public write(key: string, data: T): void { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage { read(key) { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } write(key, data) { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` #### EventSource ```ts theme={null} import { IExternalEventSource, KameleoonClient, EventSourceOpenParametersType, } from '@kameleoon/javascript-sdk'; // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource implements IExternalEventSource { private eventSource?: EventSource; public open({ eventType, onEvent, url, }: EventSourceOpenParametersType): void { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } public close(): void { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback: (error: Event) => void): void { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource { eventSource; open({ eventType, onEvent, url }) { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } close() { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback) { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` #### VisitorCodeManager ```ts theme={null} import { IExternalVisitorCodeManager, SetDataParametersType, KameleoonClient, KameleoonUtils, } from '@kameleoon/javascript-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager implements IExternalVisitorCodeManager { public getData(key: string): string | null { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using the provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } public setData({ visitorCode, domain, maxAge, key, path, }: SetDataParametersType): void { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonClient, KameleoonUtils } from '@kameleoon/javascript-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager { getData(key) { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } setData({ visitorCode, domain, maxAge, key, path }) { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` #### Requester ```ts theme={null} import { RequestType, IExternalRequester, KameleoonResponseType, SendRequestParametersType, KameleoonClient, } from '@kameleoon/javascript-sdk'; // --- External Requester Implementation export class MyRequester implements IExternalRequester { public async sendRequest({ url, parameters, }: SendRequestParametersType): Promise { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; // --- External Requester Implementation export class MyRequester { async sendRequest({ url, parameters }) { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` [Return mocked result](#simulatesuccessrequest) ### Error handling Almost every `KameleoonClient` method may throw an error occassionaly. These errors are deliberately predefined `KameleoonError`s that extend the native JavaScript `Error` class, providing useful messages and special `type` fields with a type `KameleoonException`. `KameleoonException` is an enum containing all possible error types. To know exactly what type of `KameleoonException` the method may throw, check the `Throws` section in the method description on this page, or hover over the method in your IDE to see the jsdocs description. Handling errors makes your application more stable and avoids technical issues. ```ts theme={null} import { KameleoonError, KameleoonClient, KameleoonException, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { try { await client.initialize(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); } catch (error) { // -- Type guard for inferring error type, as native JavaScript `catch` // only infers `unknown` if (error instanceof KameleoonError) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } } init(); ``` ```js theme={null} import { KameleoonClient, KameleoonException } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { try { await client.initialize(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); } catch (error) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } init(); ``` ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```ts title="Device One" theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); client.addData('my_visitor', customData); client.flush(); } init(); ``` ```ts title="Device Two" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Before working with data, make `getRemoteVisitorData` call await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available for targeting and tracking "my_visitor". } init(); ``` ```ts title="Device One" theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); client.addData('my_visitor', customData); client.flush(); } init(); ``` ```ts title="Device Two" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Before working with data, make `getRemoteVisitorData` call. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available for targeting and tracking "my_visitor" } init(); ``` #### Using custom data for session merging Cross-device experimentation allows you to combine a visitor's history across each of their devices (history reconciliation). History reconciliation lets you merge different visitors sessions into a single session. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data in Kameleoon. When your custom data is set up, you can use it in your code to merge a visitor's sessions. Sessions with the same identifier will always see the same experiment variation, and are displayed as a single visitor in the `Visitor` view of your experiment's result page. The SDK configuration ensures that associated sessions always see the same variation of the experiment. Afterwards, you can use the SDK normally. The following methods may be helpful with session merging: * [`getRemoteVisitorData`](#getremotevisitordata) with `isUniqueIdentifier=true` - to retrieve data for all linked visitors * [`trackConversion`](#trackconversion) or [`flush`](#flush) with `isUniqueIdentifier=true` - to track data for a specific visitor that is associated with another visitor. As the custom data you use as the identifier must be set to the `Visitor` scope, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. In this example, we have an application with a login page. Since we don't know the user ID at the moment of login, we use an anonymous visitor identifier generated by the[`getVisitorCode`](#getvisitorcode) method. After the user logs in, we can associate the anonymous visitor with the user ID and use it as the visitor's unique identifier. ```ts title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `window` to re-use it later window.anonymousVisitor = anonymousVisitor; // -- Getting a variation—assume it's variation `A` const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```ts title="Application Page" theme={null} import { CustomData } from '@kameleoon/javascript-sdk'; async function init(): Promise { // -- At this point, the anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier. // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `window` object client.addData(window.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier isUniqueIdentifier: true, }); } init(); ``` ```js title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `window` to re-use it later. window.anonymousVisitor = anonymousVisitor; // -- Getting a variation—assume it's variation `A` const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```js title="Application Page" theme={null} import { CustomData } from '@kameleoon/javascript-sdk'; async function init() { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `window` object client.addData(window.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); } init(); ``` [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`getRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `getRemoteVisitorData()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`trackConversion()`](#trackconversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```ts title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `window` to re-use it later. window.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A` const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```ts title="Application Page" theme={null} import { CustomData, UniqueIdentifier } from '@kameleoon/javascript-sdk'; async function init(): Promise { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `window` object client.addData(window.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` client.flush(window.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier client.addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', }); } init(); ``` ```js title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `window` to re-use it later. window.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A` const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```js title="Application Page" theme={null} import { CustomData, UniqueIdentifier } from '@kameleoon/javascript-sdk'; async function init() { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `window` object client.addData(window.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` client.flush(window.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier. client.addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', }); } init(); ``` ### Utilities The SDK has a set of utility methods that you can use to simplify your development process. All methods are represented as static members of `KameleoonUtils` class. #### simulateSuccessRequest Use the `simulateSuccessRequest` method to simulate a successful request to the Kameleoon server. It can be useful for custom [Requester](#requester) implementations, when a developer needs to simulate a successful request (for example, disabling tracking). ```ts theme={null} import { KameleoonUtils, IExternalRequester, SendRequestParametersType, RequestType, KameleoonResponseType, } from '@kameleoon/javascript-sdk'; // - Example of `Requester` with disabled tracking class Requester implements IExternalRequester { public async sendRequest({ url, parameters, requestType, }: SendRequestParametersType): Promise { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest( requestType, null, ); } return await fetch(url, parameters); } } ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/javascript-sdk'; // - Example of `Requester` with disabled tracking class Requester { async sendRequest({ url, parameters, requestType }) { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest(requestType, null); } return await fetch(url, parameters); } } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------- | | requestType required | `RequestType` | A type of request | | data required | `SimulateRequestDataType[RequestType]` | A type of request data, which is different depending on `RequestType` | Data type `SimulateRequestDataType` is defined as follows: * `RequestType.Tracking` - `null` * `RequestType.ClientConfiguration` - `ClientConfigurationDataType` * `RequestType.RemoteData` - `JSONType` ##### Return value | Type | Description | | -------------------------------- | -------------------------------------------------- | | `Promise` | returns a promise with the response of the request | #### getCookieValue Use the `getCookieValue` method to parse a common cookie string (`key_1=value_1; key_2=value_2; ...`) and get the value of a specific cookie key. This method is useful when working with a custom implementation of [`VisitorCodeManager`](#visitorcodemanager). ```ts theme={null} import { KameleoonUtils } from '@kameleoon/javascript-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/javascript-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ##### Parameters | Name | Type | Description | | ------------------------------ | -------- | ------------------------------------------------------ | | cookie required | `string` | Cookie string in a form `key_1=value_1; key_2=value_2` | | key required | `string` | String representation of a key to find a value by | ##### Return value | Type | Description | | | -------- | ----------- | ----------------------------------------------------------------------- | | \`string | null\` | returns a string with a cookie value or `null` if the key was not found | ## Reference This is the full reference documentation for the Kameleoon JavaScript SDK. ### Initialization #### initialize() `initialize()` is an asynchronous method for `KameleoonClient` initialization. The method fetches Kameleoon SDK data from our servers or retrieves data from a local source if data is up-to-date or the update interval has not been reached. * If the SDK configuration could not be retrieved but there is an older configuration available in the SDK storage, the SDK uses the older configuration as a fallback and `initialize` does not throw an error. * Client initialization has an optional *offline mode*. It is activated by setting the optional `useCache` parameter to `true`. In *offline mode*, if tracking requests for any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request when internet connection has been reestablished: * [flush](/developer-docs/sdks/web-sdks/js-sdk#flush) * [trackConversion](/developer-docs/sdks/web-sdks/js-sdk#trackconversion) * [getFeatureFlagVariationKey](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariationkey) * [getFeatureFlagVariable](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariable) * [isFeatureFlagActive](/developer-docs/sdks/web-sdks/js-sdk#isfeatureflagactive) ```ts theme={null} import { KameleoonClient, KameleoonError, KameleoonException, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { try { await client.initialize(); } catch (err) { if (err instanceof KameleoonError) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } } init(); ``` ```js theme={null} import { KameleoonClient, KameleoonException } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { try { await client.initialize(); } catch (err) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } init(); ``` ##### Parameters | Name | Type | Description | Default Value | | -------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | useCache optional | `boolean` | parameter for activating SDK offline mode. If `true`, failed polls will not return error and will use cached data if such data is available. | `false` | ##### Return value | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | A promise that resolves to a boolean indicating whether the SDK was successfully initialized. Usually, if an unresolvable issue occurs, the `initialize` method will throw an error instead of resolving the promise. Therefore, the `boolean` value is almost always `true` and typically does not provide much additional information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | An asynchronous method for `KameleoonClient` initialization by fetching Kameleoon SDK related data from server or by retrieving data from local source if data is up-to-date or update interval has not been reached. * If the SDK configuration could not be retrieved but there is an older configuration available in SDK storage, the SDK uses the older configuration as a fallback and the `initialize` does not throw an error. * SDK supports an *offline mode*. In *offline mode* if tracking requests from any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request as soon as it detects that the internet connection has been re-established: * [flush](/developer-docs/sdks/web-sdks/js-sdk#flush) * [trackConversion](/developer-docs/sdks/web-sdks/js-sdk#trackconversion) * [getFeatureFlagVariationKey](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariationkey) * [getFeatureFlagVariable](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariable) * [isFeatureFlagActive](/developer-docs/sdks/web-sdks/js-sdk#isfeatureflagactive) ```ts theme={null} import { KameleoonClient, KameleoonError, KameleoonException, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { try { await client.initialize(); } catch (err) { if (err instanceof KameleoonError) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } } init(); ``` ```js theme={null} import { KameleoonClient, KameleoonException } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { try { await client.initialize(); } catch (err) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } init(); ``` ##### Return value | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | A promise that resolves to a boolean indicating whether the SDK was successfully initialized. Usually, if an unresolvable issue occurs, the `initialize` method will throw an error instead of resolving the promise. Therefore, the `boolean` value is almost always `true` and typically does not provide much additional information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); // -- Check whether the SDK is ready const ready: boolean = client.isReady(); if (ready) { // -- The SDK is ready; you can evaluate feature flags here } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); // -- Check whether the SDK is ready const ready = client.isReady(); if (ready) { // -- The SDK is ready; you can evaluate feature flags here } } init(); ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes `featureKey` as a mandatory argument and `track` as an optional argument. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get variation with tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get variation with tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } } init(); ``` ##### Parameters An object of type `GetVariationParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.FeatureFlagConfigurationNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get all feature flag variations with tracking const variations = client.getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = client.getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = client.getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get all feature flag variations with tracking const variations = client.getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = client.getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = client.getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } } init(); ``` ##### Parameters An object of type `GetVariationParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | #### isFeatureFlagActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) The method `isFeatureFlagActive()` returns a boolean value indicating whether the visitor identified by `visitorCode` has the specified `featureKey` active. This method checks for targeting, determines the variation for the visitor, and saves this information to storage. Additionally, it sends a tracking request. There is also an overload of this method that allows you to pass a `track` parameter, which you can use to disable tracking of the feature evaluation. Only visitors with an active feature flag must be targetted. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureFlagActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Check if the feature flag is active for visitor const isActive = client.isFeatureFlagActive(visitorCode, 'my_feature_key'); // -- Check if the feature flag is active for visitor without tracking const isActive = client.isFeatureFlagActive({ visitorCode, featureKey: 'my_feature_key', track: false}); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Check if the feature flag is active for visitor const isActive = client.isFeatureFlagActive(visitorCode, 'my_feature_key'); // -- Check if the feature flag is active for visitor without tracking const isActive = client.isFeatureFlagActive({ visitorCode, featureKey: 'my_feature_key', track: false}); } init(); ``` The `isFeatureFlagActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters There are two overloads available for this method: 1. Two parameters overload: This overload is deprecated and will be removed in the next major update. Use the new overload with an object parameter. | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | | featureKey required | `string` | a unique key for feature flag | 2. Object parameter overload of type `IsFeatureFlagActiveParamsType`: | Name | Type | Description | Default | | ----------------------------------- | --------- | --------------------------------------------------------------------------- | ------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | - | | featureKey required | `string` | a unique key for feature flag | - | | track optional | `boolean` | a boolean indicator of whether to track the feature evaluation | `true` | ##### Return value | Type | Description | | --------- | ------------------------------------------------------------------------------------------------------------ | | `boolean` | a boolean indicating whether the feature flag with `featureKey` is active for the visitor with `visitorCode` | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for provided `featureKey`. | | `KameleoonException.DataInconsistency` | Allocated variation was found, but there is no feature flag with according `featureKey` | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); } init(); ``` ##### Parameters An object of type `SetForcedVariationParametersType` with the following properties: | Name | Type | Description | Default | | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | | `experimentId` required | `number` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | | `variationKey` required | \`string | null\` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | ##### Exceptions thrown | Type | Description | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.Initialization` | Indicates that the SDK isn't yet fully initialized. | | `KameleoonException.FeatureFlagExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.FeatureFlagVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.StorageRead` | Couldn't read storage data. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); client.evaluateAudiences(visitorCode); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); client.evaluateAudiences(visitorCode); } init(); ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); const dataFile = client.getDataFile(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); const dataFile = client.getDataFile(); ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### getVisitorCode() The `getVisitorCode()` method obtains a visitor code from the browser cookie. If the visitor code doesn't exist, the method generates a random visitor code (or uses the `defaultVisitorCode` value if you provided one) and sets the new visitor code in a cookie. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get visitor code with default value const visitorCode = client.getVisitorCode('my_default_visitor_code'); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get visitor code with default value const visitorCode = client.getVisitorCode('my_default_visitor_code'); } init(); ``` ##### Parameters | Name | Type | Description | | -------------------------------------------- | -------- | -------------------------------------------------------- | | `defaultVisitorCode` optional | `string` | visitor code used if there is no visitor code in cookies | If you don't provide a `defaultVisitorCode` and there is no visitor code stored in a cookie, the visitor code will be randomly generated. ##### Return value | Type | Description | | -------- | ------------------- | | `string` | result visitor code | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------ | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length was exceeded | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [flush](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call. Note that the [trackConversion](#trackconversion) method also sends out any previously associated data, just like the flush method. The same is true for the [getFeatureFlagVariationKey](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariationkey) and [getFeatureFlagVariable](/developer-docs/sdks/web-sdks/js-sdk#getfeatureflagvariable) methods, if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, `CustomData` is an exception. Visitors can have one instance of associated `CustomData` per `customDataIndex`. * `userAgent` data will not be stored in storage like other data, and it will be sent with every tracking request for bot filtration. * For the data types you can use for targeting, see the [supported targeting conditions](#targeting-conditions). ```ts theme={null} import { KameleoonClient, BrowserType, CustomData, Browser, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Create Kameleoon Data Types const browserData = new Browser(BrowserType.Chrome); const customData = new CustomData(0, 'my_data'); // -- Add a single data item (tracked by default) client.addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) client.addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; client.addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) client.addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData, Browser, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Create Kameleoon Data Types const browserData = new Browser(BrowserType.Chrome); const customData = new CustomData(0, 'my_data'); // -- Add a single data item (tracked by default) client.addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) client.addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; client.addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) client.addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); } init(); ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | | | track optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | kameleoonData optional | `KameleoonDataType[]` | number of instances of any type of `KameleoonData`, can be added solely in array or as sequential arguments | | * `kameleoonData` is a variadic argument. It can be passed as one or several arguments (see the example). * The index or ID of the [custom data](/user-manual/assets/custom-data/create-custom-data) can be found in your Kameleoon account. Note that this index starts at `0`, which means that the first custom data you create for a given site will be assigned `0` as its ID, not `1`. ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length of 255 characters. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | Check the [Data Types](#data-types) reference for more details on how to manage different data types. #### flush() The `flush()` method collects the Kameleoon data linked to the visitor. It then sends a tracking request, along with all data added using the `addData` method, which has not yet been sent using one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally in [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you cannot access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; client.flush(internalUserId, true); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; client.flush(internalUserId, true); } init(); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | | isUniqueIdentifier optional | `boolean` | an optional parameter for specifying if the `visitorCode` is a unique identifier. | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | `flush()` takes the Kameleoon data associated with a visitor and schedules the data to be sent in the next tracking request. The time of the next tracking request is defined by the SDK Configuration [`trackingInterval`](#configuration-parameters) parameter. Visitor data can be added using the [addData](#adddata) and [getRemoteVisitorData](#getremotevisitordata) methods. If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally in [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. If you need to send tracking requests immediately, use `flushInstant()` — the asynchronous version of `flush` that returns `Promise`. You can `await` it when you need delivery guarantees (for example, before page navigation/unload), or call it without `await` as a fire-and-forget request: * `await client.flushInstant(visitorCode)` sends tracking requests immediately for a specific visitor and waits for completion * `await client.flushInstant()` sends tracking requests immediately for all visitors and waits for completion ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Instantly flush added custom data for visitor (fire-and-forget) client.flushInstant(visitorCode); // -- Instantly flush added custom data for visitor and wait for completion await client.flushInstant(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Instantly flush data for all the visitors (fire-and-forget) client.flushInstant(); // -- Instantly flush data for all the visitors and wait for completion await client.flushInstant(); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Instantly flush added custom data for visitor client.flush({ visitorCode, instant: true }); // -- Flush data for all the visitors client.flush(); // -- Instantly flush data for all the visitors client.flush({ instant: true }); } init(); ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | Or an object with the type FlushParamsType, containing: | Name | Type | Description | Default | | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | | instant optional | `boolean` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). | - | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call. | #### getRemoteData() The `getRemoteData()` method returns data that is stored for a specified site code in a remote Kameleoon server. You can use this method to retrieve user preferences, historical data, or any other data relevant to your application's logic. By storing this data on our highly scalable servers using our Data API, you can efficiently manage massive amounts of data and retrieve it for each of your visitors or users. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); } init(); ``` ##### Parameters | Name | Type | Description | | --------------------------- | -------- | ----------------------------------------- | | key required | `string` | unique key with which data is associated. | ##### Return value | Type | Description | | ---------- | ---------------------------------------------- | | `JSONType` | promise with data retrieved for a specific key | ##### Exceptions thrown | Type | Description | | ------------------------------- | ------------------------------------------------- | | `KameleoonException.RemoteData` | Couldn't retrieve data from the Kameleoon server. | #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method used to retrieve Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. This method stores data for making targeting decisions. Data obtained using this method is important when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that is only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It is particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The `isUniqueIdentifier` can be helpful in unique situations; for example, if you cannot access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```ts theme={null} import { KameleoonClient, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Get remote visitor data and add it to storage const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Get remote visitor data and add it to storage const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be added to storage automatically (without calling the `addData` method afterwards). | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default, only `customData` is retrieved from the current and latest previous visit. | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | | isUniqueIdentifier optional | `boolean` | optional parameter that, when `true`, specifies that the `visitorCode` is a unique identifier. | `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, if you want to retrieve data on visitors who completed a goal "Order transaction", you can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example is not limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds data to storage for other methods to use when making targeting decisions. Data obtained using this is important when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that is only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It is particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). ```ts theme={null} import { KameleoonClient, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Get remote visitor data and add it to storage const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Get remote visitor data and add it to storage const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be added to storage automatically (without calling the `addData` method afterwards) | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility, allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, if you want to retrieve data on visitors who completed a goal "Order transaction", you can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example is not limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | #### getVisitorWarehouseAudience() `getVisitorWarehouseAudience` is an asynchronous method that retrieves all audience data associated with the visitor in your data warehouse using the specified `visitorCode` and `warehouseKey`. The `warehouseKey` is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. Refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. ```ts theme={null} import { KameleoonClient, KameleoonDataType, CustomData, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | customDataIndex required | `number` | number representing the index of the custom data you want to use to target your Warehouse Audiences | | warehouseKey optional | `string` | unique key to identify the warehouse data (usually, your internal user ID) | ##### Return value | Type | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | `Promise` | promise containing CustomData with the associated warehouse data or `null` if there was no data | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | #### setLegalConsent() Consent information is synchronized between the Kameleoon Engine (application file `engine.js`) and the JS SDK. This synchronization means that once consent is set on either the Engine or the SDK, it's automatically set for both. This feature eliminates the need for manual consent handling and ensures that SDKs operate in compliance with user preferences. If you use Kameleoon in Hybrid mode, we recommend reading the consent section in our [Hybrid experimentation article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/#managing-consent-in-hybrid-mode) When handling legal consent, it's important to use the [`getVisitorCode`](#getvisitorcode) method from `KameleoonClient`, not the deprecated method from `KameleoonUtils`. Additionally, this method does not accept `domain` as an argument. Instead, pass it to the `KameleoonClient` constructor. Refer to the above example. The `setLegalConsent` method specifies whether the visitor has given legal consent to use personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); const visitorCode = client.getVisitorCode(); client.setLegalConsent(visitorCode, true); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); const visitorCode = client.getVisitorCode(); client.setLegalConsent(visitorCode, true); } init(); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates that the visitor has given legal consent. `false` indicates that the visitor has never provided or has withdrawn legal consent. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | ##### Consent revocation behavior When you call `setLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue` argument. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. If you specify a `visitorCode` and set `isUniqueIdentifier` to `true`, the `trackConversion()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](#cross-device-experimentation) because the SDK links the flushed data with the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { const experimentId = 123; await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; client.trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { const experimentId = 123; await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; client.trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | -------------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `isUniqueIdentifier` optional | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { const experimentId = 123; await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { const experimentId = 123; await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `undefined` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```ts theme={null} kameleoonClient.addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); kameleoonClient.trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ```js theme={null} kameleoonClient.addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); kameleoonClient.trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | *** #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200`, and implicit experiment id is `100`. client.getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code const engineCode = client.getEngineTrackingCode('visitor_code'); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200`, and implicit experiment id is `100` client.getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code const engineCode = client.getEngineTrackingCode('visitor_code'); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); } init(); ``` * To use this feature, implement both the JavaScript SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | ### Events #### Sending exposure events to external tools Kameleoon offers built-in integrations with various analytics and CDP solutions, such as [Mixpanel, Google Analytics 4, Segment...](/user-manual/integrations/integrations-overview). To ensure that you can track and analyze your server-side experiments, Kameleoon provides a method, `getEngineTrackingCode()`, that returns the JavasScript code to be inserted in your page. The code automatically sends the exposure events to your analytics solution. The SDK builds a tracking code for your active analytics solution based on the experiments that the visitor has triggered in the last five seconds. For more information about hybrid experimentation, refer to this [article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation). The `getEngineTrackingCode()` method returns the Kameleoon tracking code for the current visitor. The tracking code is based on the experiments that were triggered during the last five seconds. To benefit from this feature, you will need to implement both the JavaScript SDK and our Kameleoon JavaScript tag. We recommend you implement the Kameleoon asynchronous tag, which you can install before closing the `` tag in your HTML page, as it will only be used for tracking purposes. #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```ts theme={null} import { KameleoonClient, EventType, DataFileUpdateEventDataType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); client.setEventHandler(EventType.DataFileUpdate, (eventData: DataFileUpdateEventDataType) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); // -- Clear the handler client.setEventHandler(EventType.DataFileUpdate, null); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); client.setEventHandler(EventType.DataFileUpdate, (eventData) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); // -- Clear the handler client.setEventHandler(EventType.DataFileUpdate, null); } init(); ``` `DataFileUpdateEventDataType` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource.Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource.Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `number` | The modification date of the updated data file, in milliseconds. | ```ts theme={null} import { KameleoonClient, EventType, HttpRequestEventDataType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); client.setEventHandler(EventType.HttpRequest, (eventData: HttpRequestEventDataType) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); // -- Clear the handler client.setEventHandler(EventType.HttpRequest, null); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); client.setEventHandler(EventType.HttpRequest, (eventData) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); // -- Clear the handler client.setEventHandler(EventType.HttpRequest, null); } init(); ``` `HttpRequestEventDataType` contains information about a completed SDK HTTP request. The SDK reports the event once per each actual HTTP request attempt, including retries. The type is a union of the following types. Check the `success` field to narrow the type. | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `HttpRequestSucceededEventDataType` | Describes an SDK HTTP request that completed successfully (`success: true`). Always carries `httpStatus`. | | `HttpRequestFailedEventDataType` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation (`success: false`). Always carries `failure`. | ##### Event data fields | Name | Type | Description | | ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `success` | `boolean` | `true` if the SDK HTTP request completed successfully, `false` if it failed because of an HTTP status, error, or cancellation. | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `HttpRequestType.DataFile`, `HttpRequestType.Tracking`, `HttpRequestType.RemoteVisitorData`, and `HttpRequestType.RemoteData`. | | `httpStatus` | `number` | The HTTP status code returned by the request. Present only in `HttpRequestSucceededEventDataType`. | | `failure` | `HttpRequestFailureType` | Details about why the request failed. Present only in `HttpRequestFailedEventDataType`. | | `duration` | `number` | The request duration in milliseconds. | ##### HttpRequestFailureType `HttpRequestFailureType` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason.HttpStatus`, `HttpRequestFailureReason.Error`, and `HttpRequestFailureReason.Cancelled`. | | `httpStatus` | `number` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `undefined` for exception and cancellation failures. | | `cause` | `unknown` | The exception (error) that caused the request to fail. This value is `undefined` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DataFileUpdate` to react to SDK data file updates, or `EventType.HttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `EventCallbackType` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEventDataType` for `EventType.DataFileUpdate`, or `HttpRequestEventDataType` for `EventType.HttpRequest`. Pass `null` to remove the current handler. | *** ##### Sending exposure events to external tools Kameleoon offers built-in integrations with various analytics and CDP solutions, such as [Mixpanel, Google Analytics 4, Segment...](/user-manual/integrations/integrations-overview). To ensure that you can track and analyze your server-side experiments, Kameleoon provides a method, `getEngineTrackingCode()`, that returns the JavasScript code to be inserted in your page. The code automatically sends the exposure events to your analytics solution. The SDK builds a tracking code for your active analytics solution based on the experiments that the visitor has triggered in the last five seconds. For more information about hybrid experimentation, refer to this [article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation). The `getEngineTrackingCode()` method returns the Kameleoon tracking code for the current visitor. The tracking code is based on the experiments that were triggered during the last five seconds. To benefit from this feature, you will need to implement both the JavaScript SDK and our Kameleoon JavaScript tag. We recommend you implement the Kameleoon asynchronous tag, which you can install before closing the `` tag in your HTML page, as it will only be used for tracking purposes. *** ### Data types Kameleoon Data types are helper classes used to store data in storage in predefined forms. During the [flush](#flush) execution, the SDK collects all data and sends it with the tracking request. Data available in the SDK is not available for targeting and reporting in the Kameleoon app until you add the data (for example, by using the `addData()` methodt). See [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for more information. If you are using hybrid mode, call `getRemoteVisitorData()` to automatically fill all data that Kameleoon has previously collected. #### Browser Since JavaScript SDK `4.10.0`, `Browser` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. `Browser` contains browser information. Each visitor can only have one `Browser`. Adding second a `Browser` overwrites the first one. | Name | Type | Description | | ------------------------------- | ------------- | ------------------------------------------------------------------------------------------------ | | browser required | `BrowserType` | predefined browser type (`Chrome`, `InternetExplorer`, `Firefox`, `Safari`, `Opera`, `Other`). | | version optional | `number` | version of the browser, floating point number represents major and minor version of the browser. | ```ts theme={null} import { KameleoonClient, BrowserType, Browser, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); client.addData('my_visitor_code', browser); } init(); ``` ```js theme={null} import { KameleoonClient, BrowserType, Browser, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); client.addData('my_visitor_code', browser); } init(); ``` *** #### UniqueIdentifier `UniqueIdentifier` data is used as marker for unique visitor identification. If you add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). Associating a `UniqueIdentifier` with a visitor notifies the SDK that the visitor is linked to another visitor. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you cannot access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. Each visitor can only have one `UniqueIdentifier`. Adding another `UniqueIdentifier` overwrites the first one. | Name | Type | Description | | ----------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | value required | `boolean` | value that specifies if the visitor is associated with another visitor, `false` implies that the visitor is not associated with any other visitor. | ```ts theme={null} import { KameleoonClient, UniqueIdentifier } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add unique identifier to a visitor client.addData('my_visitor_code', new UniqueIdentifier(true)); } init(); ``` ```js theme={null} import { KameleoonClient, UniqueIdentifier } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add unique identifier to a visitor client.addData('my_visitor_code', new UniqueIdentifier(true)); } init(); ``` *** #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. `ConversionParametersType` conversionParameters - an object with conversion parameters described below | Name | Type | Description | Default | | ---------------------------------- | -------------- | ----------------------------------------------- | ----------- | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. | `undefined` | ```ts theme={null} import { KameleoonClient, ConversionParametersType, Conversion, CustomData, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Defined conversion parameters const conversionParameters: ConversionParametersType = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); client.addData('my_visitor_code', conversion); } init(); ``` ```js theme={null} import { KameleoonClient, Conversion, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Defined conversion parameters const conversionParameters = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); client.addData('my_visitor_code', conversion); } init(); ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. * Generally, the JavaScript SDK will attempt to use a `localStorage` cookie for the conditions. If `localStorage` is not possible, the SDK can use `Cookie` data as an alternative. * Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. | Name | Type | Description | | ------------------------------ | -------------- | -------------------------------------------------------------------- | | cookie required | `CookieType[]` | A list of `CookieType` objects consisting of cookie keys and values. | ```ts theme={null} import { KameleoonClient, CookieType, Cookie } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add new cookie data to client const cookieData: CookieType[] = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); client.addData('my_visitor_code', cookie); } init(); ``` ```js theme={null} import { KameleoonClient, Cookie } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add new cookie data to client const cookieData = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); client.addData('my_visitor_code', cookie); } init(); ``` ##### Methods `Cookie` data has a static utility method, `fromString`, that you can use to create a cookie by parsing a string that contains valid cookie data. The method accepts `string` as a parameter and returns an initialized `Cookie` instance. ```ts theme={null} import { Cookie } from '@kameleoon/javascript-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie: Cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` ```js theme={null} import { Cookie } from '@kameleoon/javascript-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` #### GeolocationData `GeolocationData` contains the visitor's geolocation details. Each visitor can only have one `GeolocationData`. Adding a second `GeolocationData` overwrites the first one. An object parameter with the type `GeolocationInfoType` contains the following fields: | Name | Type | Description | | ----------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | | country required | `string` | The country of the visitor. | | region optional | `string` | The region of the visitor. | | city optional | `string` | The city of the visitor. | | postalCode optional | `string` | The postal code of the visitor. | | coordinates optional | `[number, number]` | Coordinates array tuple of two location values (latitude and longitude). Coordinate number represents decimal degrees. | ```ts theme={null} import { KameleoonClient, GeolocationData, GeolocationInfoType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add geolocation data const geolocationInfo: GeolocationInfoType = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); client.addData('my_visitor_code', geolocationData); } init(); ``` ```js theme={null} import { KameleoonClient, GeolocationData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add geolocation data const geolocationInfo = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); client.addData('my_visitor_code', geolocationData); } init(); ``` *** #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `number`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | value required | `string[]` | The custom data value. It must be stringified to match the `string` type. *Note:* value is variadic. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance is not initialized or the name is not registered, will result in the data being ignored. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData(customDataIndex, dataItemOne, dataItemTwo); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add custom data client.addData('my_visitor_code', customData); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData(customDataIndex, dataItemOne, dataItemTwo); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add custom data client.addData('my_visitor_code', customData); } init(); ``` *** #### Device Since JavaScript SDK `4.10.0`, `Device` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. Device contains information about your device. Each visitor can have only one `Device`. Adding a second `Device` overwrites the first one. | Name | Type | Description | | ---------------------------------- | ------------ | ------------------------------------------------------------- | | deviceType required | `DeviceType` | possible types for device type (`PHONE`, `TABLET`, `DESKTOP`) | ```ts theme={null} import { KameleoonClient, DeviceType, Device } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add device data const device = new Device(DeviceType.Desktop); client.addData('my_visitor_code', device); } init(); ``` ```js theme={null} import { KameleoonClient, DeviceType, Device } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add device data const device = new Device(DeviceType.Desktop); client.addData('my_visitor_code', device); } init(); ``` *** #### OperatingSystem Since JavaScript SDK `4.10.0`, `OperatingSystem` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. `OperatingSystem` contains information about the operating system on the visitor's device. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. | Name | Type | Description | | --------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------ | | operatingSystem required | `OperatingSystemType` | possible types for device type: `WINDOWS_PHONE`, `WINDOWS`, `ANDROID`, `LINUX`, `MAC`, and `IOS` | ```ts theme={null} import { KameleoonClient, OperatingSystem, OperatingSystemType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); client.addData('my_visitor_code', operatingSystem); } init(); ``` ```js theme={null} import { KameleoonClient, OperatingSystem, OperatingSystemType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); client.addData('my_visitor_code', operatingSystem); } init(); ``` #### PageView Since JavaScript SDK `4.10.0`, `PageView` is automatically detected based on the `window.location?.href` and `document.title`. However, you can still manually override it if needed. `PageView` contains information about your web page. Each visitor can have one `PageView` per unique URL. Adding a second `PageView` with the same URL notifies the SDK that the visitor re-visited the page. `PageViewParametersType` pageViewParameters - an object with page view parameters described below | Name | Type | Description | | ---------------------------------- | ---------- | ---------------------------------------------------------------------------------- | | urlAddress required | `string` | url address of the page to track. | | title required | `string` | title of the web page. | | referrer optional | `number[]` | an optional parameter containing a list of referrer indices, has no default value. | You can find the index or [referrer](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) ID in your Kameleoon account. Note that this index starts at 0, meaning the first acquisition channel you create for a given site will be assigned 0 as its ID, not 1. ```ts theme={null} import { KameleoonClient, PageViewParametersType, PageView, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Define page view parameters const pageViewParameters: PageViewParametersType = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add page view data const pageView = new PageView(pageViewParameters); client.addData('my_visitor_code', pageView); } init(); ``` ```js theme={null} import { KameleoonClient, PageView } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Define page view parameters const pageViewParameters = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add page view data const pageView = new PageView(pageViewParameters); client.addData('my_visitor_code', pageView); } init(); ``` *** #### UserAgent `UserAgent` lets you store information on the visitor's user-agent. Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to tackle this issue and recognize known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that might distort your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. `Visitor` can only have one `UserAgent`. Adding a second `UserAgent` overwrites the first one. | Name | Type | Description | | ----------------------------- | -------- | ------------------------- | | value required | `string` | value used for comparison | If you run Kameleoon in an hybrid mode, your feature experiments are automatically protected against bot traffic. This protection occurs because Kameleoon collects the user-agent automatically on the front-end. Therefore, you don't need to pass the user-agent or any other parameter to filter bots and spiders. If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. ```ts theme={null} import { KameleoonClient, UserAgent } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add user agent data const userAgent = new UserAgent('my_unique_value'); client.addData('my_visitor_code', userAgent); } init(); ``` ```js theme={null} import { KameleoonClient, UserAgent } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add user agent data const userAgent = new UserAgent('my_unique_value'); client.addData('my_visitor_code', userAgent); } init(); ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```ts theme={null} import { KameleoonClient, ApplicationVersion } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Add application version const applicationVersion = new ApplicationVersion('1.2'); client.addData('my_visitor_code', applicationVersion); } init(); ``` ```js theme={null} import { KameleoonClient, ApplicationVersion } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Add application version const applicationVersion = new ApplicationVersion('1.2'); client.addData('my_visitor_code', applicationVersion); } init(); ``` *** ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `number` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```ts theme={null} import { FeatureFlag } from '@kameleoon/javascript-sdk'; // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags: Map = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified: number = dataFile.dateModified; ``` ```js theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified = dataFile.dateModified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Rule[]` | A list of `Rule` objects | ```ts theme={null} import { Variation, Rule } from '@kameleoon/javascript-sdk'; // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled: boolean = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey: string = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations: Map = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules: Rule[] = featureFlag.rules; ``` ```js theme={null} // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules = featureFlag.rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```ts theme={null} import { Variation } from '@kameleoon/javascript-sdk'; // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations: Map = rule.variations; ``` ```js theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations = rule.variations; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | --------------------------------------------------------------------------------------------------- | | name | `string` | name of the variation. | | key | `string` | key of the variation. | | id | `number` or `null` | id of the variation or `null` if the visitor landed on the default variation. | | experimentId | `number` or `null` | id of the experiment or `null` if the visitor landed on the default variation. | | variables | `Map` | map of variables for the variation, where key is the variable key and value is the variable object. | * Ensure that your code handles the case where `id` or `experimentId` is `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```ts theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` ```js theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | key | `string` | The unique key identifying the variable. | | type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | value | `any` | The value of the variable, which can be of the following types: **boolean**, **number**, **String**, **Record\**, **any\[]**. | ```ts theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ```js theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ### Deprecated methods These methods are deprecated and will be removed in the next major update. #### getFeatureFlagVariationKey() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Use the [`getVariation`](#getvariation) method instead The `getFeatureFlagVariationKey()` method retrieves the variation key for a visitor identified by a `visitorCode`. This method includes a targeting check that identifies the appropriate variation exposed to the visitor, saves it to storage, and sends a tracking request. When a user is not associated with a feature flag, the SDK randomly returns a variation key according to the feature flag rules. If the user has already been registered with the feature flag, the SDK will detect this association and return the user's previous variation key value. However, if the user does not meet any of the defined rules, the SDK will return the default value specified in Kameleoon's feature flag delivery rules. It's important to note that the default value can be a variation key, a boolean value, or another data type, depending on the feature flag's configuration. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(new CustomData(0, 'visitor_id')); // -- Get visitor feature flag variation key const variationKey = client.getFeatureFlagVariationKey( visitorCode, 'my_feature_key', ); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(new CustomData(0, 'visitor_id')); // -- Get visitor feature flag variation key const variationKey = client.getFeatureFlagVariationKey( visitorCode, 'my_feature_key', ); } init(); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | ##### Return value | Type | Description | | -------- | -------------------------------------------------------------------------------------------------- | | `string` | a string containing variable key for the allocated feature flag variation for the provided visitor | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ---------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was performed for `kameleoonClient`. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for provided `featureKey`. | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is disabled for the current environment. | #### getVisitorFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) Use the [`getVariations`](#getvariations) method instead. The `getVisitorFeatureFlags()` method returns a list of feature flags that target a visitor identified by their `visitorCode` and the feature flags that are active for the specified visitor. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = client.getVisitorFeatureFlags(visitorCode); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = client.getVisitorFeatureFlags(visitorCode); } init(); ``` This method only collects the visitor's *active* feature flags, meaning the result excludes all feature flags for which the visitor is assigned the `off` (default or control) variation. When you need all of the visitor's feature flags, use `getFeatureFlags` instead. For example: ```ts theme={null} // -- `getVisitorFeatureFlags` doesn't trigger feature experiments; // it only returns feature flags where visitor didn't get the `off` variation client.getVisitorFeatureFlags('my_visitor').forEach(({ key }) => { // -- `getFeatureFlagVariationKey` triggers feature experiments, // as `off` is already filtered out - you won't see a // visitor taking part in experiment where the `off` variation was allocated. client.getFeatureFlagVariationKey('my_visitor', key); }); ``` For cases where you need all of the visitor's feature flags, use [`getFeatureFlags`](#getfeatureflags) instead: ```ts theme={null} // -- Both `off` and other variations are processed as expected client.getFeatureFlags('my_visitor').forEach(({ key }) => { client.getFeatureFlagVariationKey('my_visitor', key); }); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | ##### Return value | Type | Description | | ------------------- | --------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags. Each feature flag item contains `id` and `key` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageRead` | Error while reading storage data. | *** #### getActiveFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) Use the [`getVariations`](#getvariations) method instead. The `getActiveFeatureFlags()` method returns a `Map`, where key is featurekey and value is detailed information about the visitor's variation and it's variables ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = client.getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = client.getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } } init(); ``` This method only collects the visitor's *active* feature flags. This means the result excludes all the feature flags for which the visitor is assigned to the `off` (default or control) variation. When you need all of the visitor's feature flags to iterate, use `getFeatureFlags` instead. See the [getVisitorFeatureFlags](#getvisitorfeatureflags) *CAUTION* section method for more details. ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | ##### Return value | Type | Description | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `Map` | a map of feature flags, where `key` is `featureKey` and `value` is detailed information about the visitor's variation and its variables. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageRead` | Error while reading storage data. | | `KameleoonException.NumberParse` | Couldn't parse Number value. | | `KameleoonException.JSONParse` | Couldn't parse JSON value. | #### getFeatureFlagVariable() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Use the [`getVariation`](#getvariation) method. The `getFeatureFlagVariable()` method returns a variable for a visitor identified by a `visitorCode`. This method includes a targeting check that identifies the appropriate variation exposed to the visitor, saves it to storage, and sends a tracking request. ```ts theme={null} import { KameleoonClient, VariableType, JSONType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get feature variable const result = client.getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key' variableKey: 'my_variable_key' }); // -- Infer the type of variable by its `type` switch (result.type) { case VariableType.BOOLEAN: const myBool: boolean = result.value; break; case VariableType.NUMBER: const myNum: number = result.value; break; case VariableType.JSON: const myJson: JSONType = result.value; break; case VariableType.STRING: case VariableType.JS: case VariableType.CSS: const myStr: string = result.value; break; default: break; } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get feature variable const variableResult = client.getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key' variableKey: 'my_variable_key' }); const { type, value } = variableResult; } init(); ``` ##### Parameters Parameters object of type `GetFeatureFlagVariableParamsType` containing the following fields: | Name | Type | Description | | ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length. | | featureKey required | `string` | a unique key for feature flag. | | variableKey required | `string` | key of the variable to be found for a feature flag with provided `featureKey`. Can be found in Kameleoon platform | ##### Return value | Type | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureFlagVariableType` | a variable object containing `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN`, then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for provided `featureKey`. | | `KameleoonException.FeatureFlagVariableNotFound` | No feature variable was found for provided `visitorCode` and `variableKey`. | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is disabled for the current environment. | | `KameleoonException.JSONParse` | Couldn't parse JSON value. | | `KameleoonException.NumberParse` | Couldn't parse Number value. | #### getFeatureFlagVariables() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) Use the [`getVariation`](#getvariation) method. The `getFeatureFlagVariables()` method returns a variable for a visitor identified by a `visitorCode`. This method includes a targeting check that identifies the appropriate variation exposed to the visitor, saves it to storage, and sends a tracking request. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = client.getFeatureFlagVariables( visitorCode, 'my_feature_key', ); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration: { cookieDomain: '.example.com' }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = client.getFeatureFlagVariables( visitorCode, 'my_feature_key', ); } init(); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | | featureKey required | `string` | a unique key for feature flag. | ##### Return value | Type | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureVariableResultType[]` | a list of variable objects containing `key`, `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN`, then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for provided `featureKey`. | | `KameleoonException.FeatureFlagVariationNotFound` | No feature variation was found for provided `visitorCode` and `variationKey`. | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is disabled for the current environment. | | `KameleoonException.JSONParse` | Couldn't parse JSON value. | | `KameleoonException.NumberParse` | Couldn't parse Number value. | #### onConfigurationUpdate() Use the `onEvent` method with `EventType.ConfigurationUpdate` instead. The `onConfigurationUpdate()` method fires a callback on client configuration update. This method is applicable only for server-sent events used in real-time updates. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Define logic to be executed on client configuration update client.onConfigurationUpdate(() => { // -- My Logic }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Define logic to be executed on client configuration update client.onConfigurationUpdate(() => { // -- My Logic }); } init(); ``` ##### Parameters | Name | Type | Description | | -------------------------------- | ------------ | ----------------------------------------------------------------------------------- | | callback required | `() => void` | callback function with no parameters that will be called upon configuration update. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | *** #### getFeatureFlags() Use the [`getDataFile()`](#getdatafile) method instead. 🚫 *Doesn't send Tracking Data to Kameleoon* The `getFeatureFlags()` method returns a list of feature flags stored in the client configuration. ```ts theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Get all feature flags const featureFlags = client.getFeatureFlags(); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Get all feature flags const featureFlags = client.getFeatureFlags(); } init(); ``` ##### Return value | Type | Description | | ------------------- | ------------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags. Each feature flag item contains an `id` and `key`. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call. | #### onEvent() Use [`setEventHandler()`](#seteventhandler) instead. Method `onEvent()` fires a callback when a specific event is triggered. The callback function can access the data associated with the event. The SDK methods in this documentation note which event types they trigger, if any. You can only assign one callback to each `EventType`. ```ts theme={null} import { KameleoonClient, EventType, EvaluationEventDataType, } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init(): Promise { await client.initialize(); // -- Define logic to be executed on SDK event client.onEvent(EventType.Evaluation, (eventData: EvaluationEventDataType) => { // -- My Logic }); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/javascript-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code' }); async function init() { await client.initialize(); // -- Define logic to be executed on SDK event client.onEvent(EventType.Evaluation, (eventData) => { // -- My Logic }); } init(); ``` ##### Events Events are defined in the `EventType` enum. Depending on the event type, the `eventData` parameter will have a different type. | Type | `eventData` type | Description | | ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `EventType.Evaluation` | `EvaluationEventDataType` | Triggered when the SDK evaluates any variation for a feature flag. It is triggered regardless of the result variation. | | `EventType.ConfigurationUpdate` | `ConfigurationUpdateEventDataType` | Triggered when the SDK receives a configuration update from the server (when using real-time streaming). | ##### Parameters | Name | Type | Description | | -------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | event required | `EventType` | a type of the event to associate with the callback. | | callback required | `(eventData: EventDataType) => void` | a callback function with the `eventData` parameter that is called when a configuration update occurs. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed the `initialize` call. | # NodeJS SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/nodejs-sdk Integrate the Kameleoon Node.js SDK to run experiments and activate feature flags in Node.js server applications. You can run experiments and activate feature flags with the NodeJS SDK. Integrating the SDK into your server is easy, and its footprint (memory and network usage) is low. The Node SDK supports Node version 16+ with the option to downgrade it to version 14 and 12 using [compatibility mode](#compatibility-mode). **Getting started**: For help getting started, see the [developer guide](#developer-guide). **SDK methods**: For the full reference documentation of the NodeJS SDK, see the [reference](#reference) section. **Changelog**: Latest version of the NodeJS SDK: 5.26.1 [Changelog](https://github.com/Kameleoon/client-nodejs/blob/main/CHANGELOG.md). **Next.js example**: For working examples of the NodeJS SDK integrated with Next.js, including App Router, Pages Router, middleware, and server actions, see the [Next.js starter kits repository](https://github.com/Kameleoon/nextjs-starter-kits). Before you begin installing the NodeJS SDK, read the [technical considerations article](/developer-docs/feature-experimentation/technical-reference/technical-considerations) to understand the technological concepts behind the Kameleoon SDKs. This article helps ensure a successful integration. ## Developer guide Follow this section to install and configure the SDK and to learn about advanced features. ### Get started #### Installation Use the Kameleoon SDK Installation tool to install the SDK. The **SDK Installer** helps you install the SDK of your choice, generate a basic code sample, and configure [external dependencies](#external-dependencies) if needed. To use the SDK Installation tool, install and run it globally: ```bash theme={null} npm install --global @kameleoon/sdk-installer kameleoon-sdk ``` Or run it directly with `npx`: ```bash theme={null} npx @kameleoon/sdk-installer ``` When using **Deno**, provide dependencies manually in `deno.json`: ```json title=deno.json theme={null} { "imports": { "@kameleoon/nodejs-sdk": "npm:@kameleoon/nodejs-sdk@^4.0", // -- Optional dependencies, can be implemented manually "@kameleoon/nodejs-requester": "npm:@kameleoon/nodejs-requester@^1.0", "@kameleoon/nodejs-event-source": "npm:@kameleoon/nodejs-event-source@^1.0", "@kameleoon/deno-visitor-code-manager": "npm:@kameleoon/deno-visitor-code-manager@^1.0", } } ``` #### Initializing the Kameleoon client Developers must create an entry point for the NodeJS SDK by instantiating a new `KameleoonClient`. It's recommended to treat `KameleoonClient` as a **singleton**, creating a single shared instance and reusing it throughout the server instance to ensure consistency and avoid unnecessary reinitialization. Use `KameleoonClient` to run feature experiments and retrieve the status of feature flags and their variations. `KameleoonClient` initializes asynchronously to ensure that communication with the Kameleoon API is successful, using the [`initialize()`](#initialize) method. You can use `async/await`, `Promise.then()`, or any other asynchronous pattern to handle client initialization. To add NodeJS SDK to an Edge environment, refer to [this section](#integration-with-edge-providers). ```ts theme={null} import { Environment, KameleoonClient, SDKConfigurationType, CredentialsType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; // -- Mandatory credentials const credentials: CredentialsType = { clientId: 'my_client_id', clientSecret: 'my_client_secret', }; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 20, environment: Environment.Production, cookieDomain: '.example.com', }; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials, configuration, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); // -- Waiting for the client initialization using `async/await` async function init(): Promise { await client.initialize(); } init(); // -- Waiting for the client initialization using `Promise.then()` client .initialize() .then(() => {}) .catch((error) => {}); ``` ```js theme={null} import { Environment, KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; // -- Mandatory credentials const credentials = { clientId: 'my_client_id', clientSecret: 'my_client_secret', }; // -- Optional configuration const configuration = { dataFileRefreshInterval: 20, environment: Environment.Production, cookieDomain: '.example.com', }; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials, configuration, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); // -- Waiting for the client initialization using `async/await` async function init() { await client.initialize(); } init(); // -- Waiting for the client initialization using `Promise.then()` client .initialize() .then(() => {}) .catch((error) => {}); ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | siteCode required | `string` | This is the [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | credentials required | `CredentialsType` | client [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials), see [credentials flow](/developer-docs/apis/automation-api-rest/get-started/get-started#client-credentials-flow) for more information | | externals required | `ExternalsType` | external implementation of SDK dependencies ([External dependencies](#external-dependencies)) | | configuration optional | `Partial` | client's configuration | | compatibility optional | `Compatibility` | SDK's compatibility mode, see [Compatibility Mode](#compatibility-mode) | | integrations optional | `IntegrationType` | compute edge integrations, see [Integration with Edge providers](#integration-with-edge-providers) | ##### Exceptions thrown | Type | Description | | -------------------------------- | ------------------------------------------------ | | `KameleoonException.Credentials` | Client credentials weren't provided or are empty | ##### Configuration parameters | Name | Type | Description | Default Value | | ---------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | updateInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | | environment optional | `Environment` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data, minimum value is 1 minute | `30` | | domain optional | `string` | [domain](#domain-information) to which the cookie belongs. Deprecated, use `cookieDomain` instead | `undefined` | | cookieDomain optional | `string` | [domain](#domain-information) to which the cookie belongs. | `undefined` | | networkDomain optional | `string` | custom domain the SDK uses in all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). The SDK uses the default Kameleoon value if the format is invalid. | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request. The tracking request is performed once per interval. The minimum value is `100` ms and the maximum value is `1_000` ms | `1_000` (1 second) | The `domain` parameter is deprecated and will be removed in the future. Use `cookieDomain` instead. | Name | Type | Description | Default Value | | ---------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | dataFileRefreshInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes; the minimum value is 1 minute. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. This parameter replaces the deprecated `updateInterval`; if both are specified, `dataFileRefreshInterval` takes precedence. | `60` | | environment optional | `Environment \| string` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data, minimum value is 1 minute | `30` | | cookieDomain optional | `string` | [domain](#domain-information) that the cookie belongs to. | `undefined` | | networkDomain optional | `string` | The custom domain the SDK uses in all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). The SDK uses the default Kameleoon value if the format is invalid. | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request. The tracking request is performed once per interval. The minimum value is `1_000` ms and the maximum value is `5_000` ms | `1_000` (1 second) | | defaultDataFile optional | `string` | The `defaultDataFile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `defaultDataFile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `undefined` | | updateInterval deprecated | `number` | **Deprecated.** Use `dataFileRefreshInterval` instead; this parameter will be removed in the next major version. If both parameters are specified, `dataFileRefreshInterval` takes precedence. Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | **Option 1 (Recommended):** Use `JSON.stringify()` ```js theme={null} const dataFileJson = {"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\"make\":\"Porsche\",\"model\":\"911\"}"}}, "dateModified":1752209266000}; const dataFileString = JSON.stringify(dataFileJson); const configuration = { dataFileRefreshInterval: 20, defaultDataFile: dataFileString }; ``` **Option 2:** Raw JSON string (escape special characters) ```js theme={null} const configuration = { dataFileRefreshInterval: 20, defaultDataFile: `{"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\\"make\\":\\"Porsche\\",\\"model\\":\\"911\\"}"}, "dateModified":1752209266000}` }; ``` ##### Compatibility mode Use the SDK parameter `compatibility` to turn off some of the SDK's features to improve compatibility with older NodeJS versions. `Compatibility` is an enum representing all possible compatibility modes: * `Compatibility.Node16` - default mode, all features are enabled. This mode will be used if no compatibility mode is provided. Supports Node version 16 and higher. * `Compatibility.Node14` - compatibility with this version will make the `requestTimeout` parameter in `SDKConfigurationType` unavailable and prevent the SDK from using `AbortController` for request cancellation, even within default `10_000` ms timeout. Supports Node version 14 and higher. * `Compatibility.Node12` - compatibility with this version implies the same limitations as the `Node14` compatibility mode. Additionally, you can't provide "@kameleoon/nodejs-requester" as a requester implementation in this compatibility mode, as it uses the "node-fetch" library, which doesn't support the Node.js 12.x.x version. A developer must provide a custom requester implementation of their choice, such as an older "node-fetch" version or other HTTP based implementation. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureFlagActive()`](#isfeatureflagactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureFlagActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#configuration-parameters). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#configuration-parameters)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Error handling Almost every `KameleoonClient` method may throw an error at some point. These errors are deliberately predefined `KameleoonError`s that extend the native JavaScript `Error` class, providing useful messages and a special `type` field with a type `KameleoonException`. `KameleoonException` is an enum containing all possible error types. To know exactly what type of `KameleoonException` the method may throw, check the method description's `Throws` section, or hover over the method in your IDE to see the jsdocs description. Handling errors is considered a good practice to make your app more stable and avoid technical issues. ```ts theme={null} import { KameleoonError, KameleoonClient, KameleoonException, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { try { await client.initialize(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); } catch (error) { // -- Type guard for inferring error type, as native JavaScript `catch` // only infers `unknown` if (error instanceof KameleoonError) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } } init(); ``` ```js theme={null} import { KameleoonClient, KameleoonException } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { try { await client.initialize(); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); } catch (error) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } init(); ``` ### Integration with edge providers Kameleoon provides the following starter packs to automate your integration with specific edge providers: | Provider | Starter pack | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Fastly Compute\@Edge | [https://github.com/Kameleoon/fastly-compute-starter-kit](https://github.com/Kameleoon/fastly-compute-starter-kit) | | Cloudfare Workers | [https://github.com/Kameleoon/cloudflare-worker-starter-kit](https://github.com/Kameleoon/cloudflare-worker-starter-kit) | | AWS Lambda\@Edge Function | [https://github.com/Kameleoon/aws-lambda-edge-starter-kit](https://github.com/Kameleoon/aws-lambda-edge-starter-kit) | For the other edge providers, you can initialize the Kameleoon Client yourself using `externalClientConfiguration`. Passing `externalClientConfiguration` causes the SDK to rely solely on the provided configuration data instead of making a call to the Kameleoon servers. `externalClientConfiguration` gives you greater control and flexibility over the configuration data used in your app. For example: ```ts theme={null} import rp from 'request-promise'; import { KameleoonClient, KameleoonUtils } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; async function init() { // -- Get Kameleoon Client Configuration URL const uri = KameleoonUtils.getClientConfigurationUrl('my_site_code'); const clientConfiguration = await rp({ uri, json: true, }); const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, integrations: { externalClientConfiguration: clientConfiguration, }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); } init(); ``` Kameleoon provides the following starter packs to automate your integration with specific edge providers: | Provider | Starter pack | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Fastly Compute\@Edge | [https://github.com/Kameleoon/fastly-compute-starter-kit](https://github.com/Kameleoon/fastly-compute-starter-kit) | | Cloudfare Workers | [https://github.com/Kameleoon/cloudflare-worker-starter-kit](https://github.com/Kameleoon/cloudflare-worker-starter-kit) | | AWS Lambda\@Edge Function | [https://github.com/Kameleoon/aws-lambda-edge-starter-kit](https://github.com/Kameleoon/aws-lambda-edge-starter-kit) | For other edge providers, use [External Dependencies](#external-dependencies) for greater control over the SDK. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```ts title="Device One" theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); client.addData('my_visitor', customData); client.flush(); } init(); ``` ```ts title="Device Two" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is available to target and track "my_visitor". } init(); ``` ```ts title="Device One" theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); client.addData('my_visitor', customData); client.flush(); } init(); ``` ```ts title="Device Two" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available to target and track "my_visitor". } init(); ``` #### Using custom data for session merging Cross-device experimentation lets you combine a visitor's history across each of their devices (history reconciliation). You can merge multiple visitor sessions into one with history reconciliation. Use [`CustomData`](#customdata) and provide a unique identifier for the visitor to reconcile visit history. Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data in Kameleoon. You can use custom data in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation, and will be displayed as a single visitor in the `Visitor` view of your experiment's result page. The SDK configuration ensures that associated sessions always see the same variation of the experiment. The following methods might be helpful in the context of session merging: * Use [`getRemoteVisitorData`](#getremotevisitordata) with `isUniqueIdentifier=true` to retrieve data for all linked visitors. * Use [`trackConversion`](#trackconversion) or [`flush`](#flush) with `isUniqueIdentifier=true` to track data for specific visitor that's associated with another visitor. Since the custom data you use as the identifier must be set to the `Visitor` scope, you must use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. The following example shows an app with a login page. Since the user ID isn't known at the time of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```ts title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `global` to re-use it later global.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A` const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```ts title="Application Page" theme={null} import { CustomData } from '@kameleoon/nodejs-sdk'; async function init(): Promise { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `global` object client.addData(global.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); } init(); ``` ```js title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `global` to re-use it later. global.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A`. const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```js title="Application Page" theme={null} import { CustomData } from '@kameleoon/nodejs-sdk'; async function init() { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `global` object client.addData(global.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); } init(); ``` Cross-device experimentation lets you combine a visitor's history across each of their devices (history reconciliation). You can merge multiple visitor sessions into one with history reconciliation. Use [`CustomData`](#customdata) and provide a unique identifier for the visitor to reconcile visit history. Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data in Kameleoon. You can use custom data in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation, and will be displayed as a single visitor in the `Visitor` view of your experiment's result page. The SDK configuration ensures that associated sessions always see the same variation of the experiment. Before using other methods, inform the SDK that the visitor is a unique identifier by adding [`UniqueIdentifier`](#uniqueidentifier) data to a visitor. Since the custom data you use as the identifier must be set to the `Visitor` scope, you must use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. The following example shows an app with a login page. Since the user ID isn't known at the time of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```ts title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `global` to re-use it later. global.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A`. const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```ts title="Application Page" theme={null} import { CustomData, UniqueIdentifier } from '@kameleoon/nodejs-sdk'; async function init(): Promise { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `global` object client.addData(global.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` client.flush(global.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier. client.addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', }); } init(); ``` ```js title="Login Page" theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in `global` to re-use it later. global.anonymousVisitor = anonymousVisitor; // -- Getting a variation, assume it's variation `A`. const variation = client.getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); } init(); ``` ```js title="Application Page" theme={null} import { CustomData, UniqueIdentifier } from '@kameleoon/nodejs-sdk'; async function init() { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Taking `visitorCode` from `global` object client.addData(global.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` client.flush(global.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier. client.addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. client.trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await client.getRemoteVisitorData({ visitorCode: 'my_user_id', }); } init(); ``` ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```js theme={null} client.addData(visitorCode, new CustomData(index, 'newVisitorCode')); ``` [More information in addData()](#adddata) * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions supported by this SDK, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/feature-experimentation/targeting-and-segmentation\use-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use directly KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use directly KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```ts theme={null} import { KameleoonClient, KameleoonLogger, IExternalLogger, LogLevel } from '@kameleoon/nodejs-sdk'; export class CustomLogger implements IExternalLogger { // `log` method accepts logs from the SDK public log(level: LogLevel, message: string): void { // Custom log handling logic here. For example: switch (level) { case LogLevel.DEBUG: console.debug(message); break; case LogLevel.INFO: console.info(message); break; case LogLevel.WARNING: console.warn(message); break; case LogLevel.ERROR: console.error(message); break; } } } const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/nodejs-sdk'; export class CustomLogger { // `log` method accepts logs from the SDK log(level, message) { // Custom log handling logic here. For example: switch (level) { case 'DEBUG': console.debug(message); break; case 'INFO': console.info(message); break; case 'WARNING': console.warn(message); break; case 'ERROR': console.error(message); break; } } } const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ### Domain information You provide a domain as the `domain` in `KameleoonClient` [configuration](#initializing-the-kameleoon-client), which is used for storing Kameleoon visitor code in cookies. Providing a domain is important when working with the [`getVisitorCode`](#getvisitorcode) and [`setLegalConsent`](#setlegalconsent) methods. The domain you provide is stored in the cookie as the `Domain=` key. #### Setting the domain The domain you provide lets the URL address use the cookie. For example, if your domain is `www.example.com`, the cookie is only available from a `www.example.com` URL. Pages with the `app.example.com` domain can't use the cookie. For more flexibility with subdomains, you can specify a domain starting with a `.`. For instance, domain `.example.com` allows the cookie to function on both `app.example.com` and `login.example.com`. You can't use regular expressions, special symbols, protocol, or port numbers in the `domain`. Additionally, a [specific list of subdomains](https://publicsuffix.org/list/public_suffix_list.dat) can't be used with the prefix `.`. Here's a small domain cheat sheet: | Domain | Allowed URLs | Disallowed URLs | | ------------------------------ | --------------------- | -------------------- | | `www.example.com` | ✅`www.example.com` | ❌ `app.example.com` | | | ✅ `example.com` | ❌ `.com` | | | | | | `.example.com` = `example.com` | ✅ `example.com` | ❌ `otherexample.com` | | | ✅ `www.example.com` | | | | ✅ `app.example.com` | | | | ✅ `login.example.com` | | | `https://www.example.com` | ⛔ bad domain | ⛔ bad domain | | `www.example.com:4408` | ⛔ bad domain | ⛔ bad domain | | `.localhost.com` = `localhost` | ⛔ bad domain | ⛔ bad domain | #### Developing on localhost `localhost` is always considered a bad domain, making it hard to test the domain when developing on `localhost`. There are two ways to avoid this issue: * Don't specify the `domain` field in the SDK client while testing. This prevents `localhost` issues (the cookie will be set on any domain). * Create a local domain for `localhost`. For example: * Navigate to `/etc/hosts` on *Linux* or to `c:\Windows\System32\Drivers\etc\hosts` on *Windows* * Open `hosts` with file super user or administrator rights * Add a domain to the `localhost` port, for example: `127.0.0.1 app.com` * Now, you can run your app locally on `app.com:{my_port}` and specify `.app.com` as your domain *** ### External dependencies The SDK's external dependencies use the *dependency injection* pattern, letting you provide your own implementations for certain parts of an SDK. In the NodeJS SDK, some external dependencies have default implementations, while others must be provided by the user, whether using dedicated Kameleoon implementations or custom implementations. Here's the list of available external dependencies: | Dependency | Interface | Required/Optional | API Used | Description | | -------------------- | ----------------------------- | ----------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage` | `IExternalStorage` | *Optional* | Server memory | Used for storing all of the existing and collected SDK data | | `eventSource` | `IExternalEventSource` | *Required* | - | Used for receiving Server Sent Events for [Real Time Updates](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) capabilities | | `requester` | `IExternalRequester` | *Required* | - | Used for performing all network requests | | `visitorCodeManager` | `IExternalVisitorCodeManager` | *Required* | - | Used for storing and synchronizing visitor code | | `logger` | `ILogger` | *Optional* | Custom implementation | Used for custom handling of logs from the SDK. Lets users define how logs are processed and where they output. | You can also implement `visitorCodeManager` using the `IExternalNextJSVisitorCodeManager`, `IExternalDenoVisitorCodeManager`, or `IExternalCustomVisitorCodeManager` interfaces for NextJS, Deno, or custom visitor code manager implementations, respectively. External dependencies provide developers flexibility to adapt and use the NodeJS SDK in any environment. There are a number of npm packages Kameleoon provides for frequently used environments. You can install the packages manually, or by using the [SDK installation tool](#installation) (recommended). These are the Kameleoon-provided external dependencies for NodeJS SDK: * `@kameleoon/nodejs-event-source` - based on `eventsource` library (can be used for NodeJS/Deno/NextJS SSR) * `@kameleoon/nodejs-requester` - based on `node-fetch` library (can be used for NodeJS/Deno/NextJS SSR) * `@kameleoon/nodejs-visitor-code-manager` - implemented with server memory storage * `@kameleoon/deno-visitor-code-manager` - implemented using Deno request/response cookies * `@kameleoon/nextjs-visitor-code-manager` - implemented using NextJS SSR `headers` cookie or NextJS SSR request/response You can optionally implement external dependencies on your own. The following example implements external dependencies. To import an interface from an SDK, create a class that implements it and pass the instantiated class to the SDK. #### Storage ```ts theme={null} import { IExternalStorage, KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage implements IExternalStorage { public read(key: string): T | null { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } public write(key: string, data: T): void { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage { read(key) { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } write(key, data) { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` #### EventSource ```ts theme={null} import { IExternalEventSource, KameleoonClient, EventSourceOpenParametersType, } from '@kameleoon/nodejs-sdk'; // --- External EventSource implementation --- // - Example uses dummy `EventSource` class class MyEventSource implements IExternalEventSource { private eventSource?: EventSource; public open({ eventType, onEvent, url, }: EventSourceOpenParametersType): void { // - Initialize `EventSource` (use any event source of your choice here) const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } public close(): void { // - Clean up open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback: (error: Event) => void): void { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External EventSource implementation --- // - Example uses dummy `EventSource` class class MyEventSource { eventSource; open({ eventType, onEvent, url }) { // - Initialize `EventSource` (use any event source of your choice here) const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } close() { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback) { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` #### VisitorCodeManager `visitorCodeManager` implementation for `NodeJS`/`NextJS SSR`: ```ts theme={null} import { IExternalVisitorCodeManager, SetDataParametersType, GetDataParametersType, KameleoonClient, KameleoonUtils, } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `request` and `response` class MyVisitorCodeManager implements IExternalVisitorCodeManager { public getData({ request, key }: GetDataParametersType): string | null { // - Get cookie from server request const cookieString = request.headers.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using the provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } public setData({ visitorCode, response, domain, maxAge, key, path, }: SetDataParametersType): void { // - Set cookie to request using provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } response.setHeader('Set-Cookie', resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonClient, KameleoonUtils } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `request` and `response` class MyVisitorCodeManager { getData({ request, key }) { // - Get cookie from server request const cookieString = request.headers.cookie; if (!cookieString) { return null; } // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using the provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } setData({ visitorCode, response, domain, maxAge, key, path }) { // - Set cookie to request using provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } response.setHeader('Set-Cookie', resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` `visitorCodeManager` implementation for `Deno`: ```ts theme={null} import { IExternalDenoVisitorCodeManager, SetDenoDataParametersType, GetDenoDataParametersType, KameleoonClient, KameleoonUtils, } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `request` and `response` class MyVisitorCodeManager implements IExternalDenoVisitorCodeManager { public getData({ request, key }: GetDenoDataParametersType): string | null { // - Get cookie from server request const cookieString = request.headers.get('cookie'); // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using the provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } public setData({ visitorCode, response, domain, maxAge, key, path, }: SetDenoDataParametersType): void { // - Set cookie to request using provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } response.headers.set('Set-Cookie', resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonClient, KameleoonUtils } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `request` and `response` class MyVisitorCodeManager { getData({ request, key }) { // - Get cookie from server request const cookieString = request.headers.get('cookie'); // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie finding it by provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } setData({ visitorCode, response, domain, maxAge, key, path }) { // - Set cookie to request using provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } response.headers.set('Set-Cookie', resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` `visitorCodeManager` implementation for `NextJS` Server Actions: ```ts theme={null} import { IExternalNextJSVisitorCodeManager, SetNextJSDataParametersType, GetNextJSDataParametersType, KameleoonClient, } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `cookies` object imported from "next/headers" class MyVisitorCodeManager implements IExternalNextJSVisitorCodeManager { public getData({ cookies, key }: GetNextJSDataParametersType): string | null { // - Get cookie from server request by provided `key` const cookie = cookies().get(key); if (cookie) { return cookie.value; } // - Return `null` if no cookie was found return null; } public setData({ visitorCode, cookie, domain, maxAge, key, path, }: SetNextJSDataParametersType): void { // - Set cookie to request using provided parameters cookies().set(key, visitorCode, { path, domain, maxAge, }); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses server `cookies` object imported from "next/headers" class MyVisitorCodeManager { public getData({ cookies, key }) { // - Get cookie from server request by provided `key` const cookie = cookies().get(key); if (cookie) { return cookie.value; } // - Return `null` if no cookie was found return null; } public setData({ visitorCode, cookie, domain, maxAge, key, path, }){ // - Set cookie to request using provided parameters cookies().set(key, visitorCode, { path, domain, maxAge, }); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` Custom `visitorCodeManager` implementation with arbitrary parameters: ```ts theme={null} import { IExternalCustomVisitorCodeManager, SetDataCustomParametersType, GetDataCustomParametersType, KameleoonClient, } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses custom arbitrary `input` and `output` objects class MyVisitorCodeManager implements IExternalCustomVisitorCodeManager { public getData({ input, key }: GetDataCustomParametersType): string | null { // - Get visitor code from `input` object // `input` is of type `unknown`, so you can provide any structure. // In Example, we assume `input` is a `Map` object. const visitorCode = input.get(key); if (visitorCode) { return visitorCode; } // - Return `null` if no visitor code was found return null; } public setData({ visitorCode, output, domain, maxAge, key, path, }: SetDataCustomParametersType): void { // - Set visitor code as a cookie to `output` object using provided parameters. let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } // - `output` is of type `unknown`, so you can provide any structure. // In Example, we assume `output` is a `Map` object. output.set(key, resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses custom arbitrary `input` and `output` objects. class MyVisitorCodeManager { public getData({ input, key }) { // - Get visitor code from `input` object // `input` is of type `unknown`, so you can provide any structure // In Example, we assume `input` is a `Map` object. const visitorCode = input.get(key); if (visitorCode) { return visitorCode; } // - Return `null` if no visitor code was found return null; } public setData({ visitorCode, output, domain, maxAge, key, path, }) { // - Set visitor code as a cookie to `output` object using provided parameters. let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } // - `output` is of type `unknown`, so you can provide any structure. // In Example, we assume `output` is a `Map` object. output.set(key, resultCookie); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` #### Requester ```ts theme={null} import { RequestType, IExternalRequester, KameleoonResponseType, SendRequestParametersType, KameleoonClient, } from '@kameleoon/nodejs-sdk'; // --- External Requester Implementation export class MyRequester implements IExternalRequester { public async sendRequest({ url, parameters, }: SendRequestParametersType): Promise { // - Using native NodeJS `fetch` (for v18+) return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; // --- External Requester Implementation export class MyRequester { async sendRequest({ url, parameters }) { // - Using native NodeJS `fetch` (for v18+) return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` [Return mocked result](#simulatesuccessrequest) ### Utilities The SDK has a set of utility methods that you can use to simplify development. All methods are represented as static members of the `KameleoonUtils` class. #### getClientConfigurationUrl Use the `getClientConfigurationUrl` method to get the URL for fetching the client configuration. This method is useful for handling edge cases when working with [Edge computing integrations](#integration-with-edge-providers). This method will soon be deprecated and replaced by custom [`Requester`](#requester) implementation. ```ts theme={null} import { KameleoonUtils, Environment } from '@kameleoon/nodejs-sdk'; const url = KameleoonUtils.getClientConfigurationUrl( 'my_site_code', Environment.Production, 'example.com', ); ``` ```js theme={null} import { KameleoonUtils, Environment } from '@kameleoon/nodejs-sdk'; const url = KameleoonUtils.getClientConfigurationUrl( 'my_site_code', Environment.Production, 'example.com', ); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------ | | siteCode required | `string` | site code | - | | environment optional | `Environment` | an optional parameter specifying the SDK environment | `Environment.Production` | | networkDomain optional | `string` | an optional parameter specifying the domain for instances using custom domains. Must be in form `example.com` | `kameleoon.com` | ##### Return value | Type | Description | | -------- | ----------------------------------------------------- | | `string` | returns the URL for fetching the client configuration | #### simulateSuccessRequest Use the `simulateSuccessRequest` method to simulate a successful request to the Kameleoon server. This method can be useful for custom [Requester](#requester) implementations when a developer needs to simulate a successful request. ```ts theme={null} import { KameleoonUtils, IExternalRequester, SendRequestParametersType, RequestType, KameleoonResponseType, } from '@kameleoon/nodejs-sdk'; // - Example of `Requester` with disabled tracking class Requester implements IExternalRequester { public async sendRequest({ url, parameters, requestType, }: SendRequestParametersType): Promise { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest( requestType, null, ); } return await fetch(url, parameters); } } ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/nodejs-sdk'; // - Example of `Requester` with disabled tracking class Requester { async sendRequest({ url, parameters, requestType }) { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest(requestType, null); } return await fetch(url, parameters); } } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------- | | requestType required | `RequestType` | A type of request | | data required | `SimulateRequestDataType[RequestType]` | A type of request data, which is different depending on `RequestType` | The `SimulateRequestDataType` data type is defined as follows: * `RequestType.Tracking` - `null` * `RequestType.ClientConfiguration` - `ClientConfigurationDataType` * `RequestType.RemoteData` - `JSONType` ##### Return value | Type | Description | | -------------------------------- | --------------------------------------------- | | `Promise` | returns a promise with the request's response | #### getCookieValue Use the `getCookieValue` method to parse a common cookie string (`key_1=value_1; key_2=value_2; ...`), and get the value of a specific cookie key. This method is useful when working with a custom implementation of [`VisitorCodeManager`](#visitorcodemanager). ```ts theme={null} import { KameleoonUtils } from '@kameleoon/nodejs-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/nodejs-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ##### Parameters | Name | Type | Description | | ------------------------------ | -------- | ---------------------------------------------------- | | cookie required | `string` | Cookie string in form `key_1=value_1; key_2=value_2` | | key required | `string` | String representation of a key to find a value by | ##### Return value | Type | Description | | | -------- | ----------- | ------------------------------------------------------------------------ | | \`string | null\` | returns a string with a cookie value, or `null` if the key was not found | ## Reference This is the full reference documentation for the Kameleoon JavaScript SDK. ### Initialization #### initialize() An asynchronous method for initializing `KameleoonClient` that retrieves Kameleoon SDK data either from the server or a local source if the data is still up-to-date or the update interval hasn't yet elapsed. ```ts theme={null} import { KameleoonError, KameleoonClient, KameleoonException, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { try { await client.initialize(); } catch (err) { if (err instanceof KameleoonError) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } } init(); ``` ```js theme={null} import { KameleoonClient, KameleoonException } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { try { await client.initialize(); } catch (err) { switch (err.type) { case KameleoonException.StorageWrite: // -- Handle error case case KameleoonException.ClientConfiguration: // -- Handle error case default: break; } } } init(); ``` ##### Return value | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | A promise resolved to a boolean indicating the SDK's successful initialization. Generally, initialize() will throw an error if an unhandled issue occurs, so the `boolean` value will almost always be `true` and may not provide much useful information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Check whether the SDK is ready const ready: boolean = client.isReady(); if (ready) { // -- The SDK is ready; you can evaluate feature flags here } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Check whether the SDK is ready const ready = client.isReady(); if (ready) { // -- The SDK is ready; you can evaluate feature flags here } } init(); ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | *** ### Feature flags and variations #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes `featureKey` as a mandatory argument and `track` as an optional argument. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get variation with tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get variation with tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = client.getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } } init(); ``` ##### Parameters An object of type `GetVariationParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.FeatureFlagConfigurationNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get all feature flag variations with tracking const variations = client.getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = client.getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = client.getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get all feature flag variations with tracking const variations = client.getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = client.getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = client.getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } } init(); ``` ##### Parameters An object of type `GetVariationsParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | #### isFeatureFlagActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) The `isFeatureFlagActive()` method returns a boolean indicating whether the visitor with `visitorCode` has an active `featureKey`. This method checks for targeting, finds the variation for the visitor, and saves it to storage. The method also sends a tracking request. This method has an additional overload that lets you pass a `track` parameter, which disables the tracking of feature evaluation. A visitor must be targeted for feature flags to activate. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureFlagActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Check if the feature flag is active for visitor const isActive = client.isFeatureFlagActive(visitorCode, 'my_feature_key'); // -- Check if the feature flag is active for visitor without tracking const isActive = client.isFeatureFlagActive({ visitorCode, featureKey: 'my_feature_key', track: false}); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Check if the feature flag is active for visitor const isActive = client.isFeatureFlagActive(visitorCode, 'my_feature_key'); // -- Check if the feature flag is active for visitor without tracking const isActive = client.isFeatureFlagActive({ visitorCode, featureKey: 'my_feature_key', track: false}); } init(); ``` The `isFeatureFlagActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters There are two overloads available for this method: 1. Two parameters overload: This overload is deprecated and will be removed in the next major version. Use the new overload with an object parameter. | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | 2. Object parameter overload of type `IsFeatureFlagActiveParamsType`: | Name | Type | Description | Default | | ----------------------------------- | --------- | --------------------------------------------------------------------------- | ------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | - | | featureKey required | `string` | a unique key for a feature flag | - | | track optional | `boolean` | a boolean indicator of whether to track the feature evaluation | `true` | ##### Return value | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------- | | `boolean` | a boolean indicator of whether the feature flag with `featureKey` is active for the visitor with `visitorCode` | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for provided `featureKey` | | `KameleoonException.FeatureFlagVariableNotFound` | No feature variable were found for provided `visitorCode` and `variableKey` | | `KameleoonException.DataInconsistency` | Allocated variation was found, but there is no feature flag with the according `featureKey`. | *** #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor. client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor. client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor. client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor. client.setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); } init(); ``` ##### Parameters An object of type `SetForcedVariationParametersType` with the following properties: | Name | Type | Description | Default | | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | | `experimentId` required | `number` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | | `variationKey` required | \`string | null\` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | ##### Exceptions thrown | Type | Description | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.Initialization` | Indicates that the SDK isn't yet fully initialized. | | `KameleoonException.FeatureFlagExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.FeatureFlagVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.StorageRead` | Couldn't read storage data. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); client.evaluateAudiences(visitorCode); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); client.evaluateAudiences(visitorCode); } init(); ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); const dataFile = client.getDataFile(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); const dataFile = client.getDataFile(); ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### getVisitorCode() The `getVisitorCode` method retrieves a visitor code from the request's cookie in the headers. If the visitor code doesn't exist, the method generates a new random visitor code, or uses a provided `defaultVisitorCode` value. It then sets the new visitor code in a cookie in the response headers. This method utilizes Node.js's native types for `request` and `response`, specifically `IncomingMessage` and `ServerResponse`, imported from the `http` module. However, if you're using the Express framework, Deno, or Next.js super server-rendering methods, like `getServerProps`, the types for `request` and `response` will differ. You can resolve this issue using type casting, which will yield identical results. When using `getVisitorCode()` with Deno, `Next.js SSR`, `Node`, or `Express`, ensure that you've implemented the correct external dependencies. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using native `NodeJS/NextJS/Deno` `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get visitor code using `Express`/`Deno`/`NextJS` SSR methods' `request`, `response`, and optionally providing // default visitor code const visitorCode = client.getVisitorCode({ request: req, response: res, defaultVisitorCode: 'my_default_visitor_code', }); // -- Get visitor code using `NextJS` server side actions // (`cookies` imported from "next/headers") const visitorCode = client.getVisitorCode({ cookies, }); // -- Get visitor code using custom `VisitorCodeManager` implementation // `myInput` and `myOutput` are custom input and output parameters with arbitrary types. // According types should be defined in `VisitorCodeManager` implementation. const visitorCode = client.getVisitorCode({ input: myInput, output: myOutput, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using native `NodeJS` `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get visitor code using `Express`/`Deno`/`NextJS SSR methods`, and optionally providing // default visitor code const visitorCode = client.getVisitorCode({ request: req, response: res, defaultVisitorCode: 'my_default_visitor_code', }); // -- Get visitor code using `NextJS` server side actions // (`cookies` imported from "next/headers") const visitorCode = client.getVisitorCode({ cookies, }); // -- Get visitor code using custom `VisitorCodeManager` implementation // `myInput` and `myOutput` are custom input and output parameters with arbitrary types. // According types should be defined in `VisitorCodeManager` implementation. const visitorCode = client.getVisitorCode({ input: myInput, output: myOutput, }); } init(); ``` ##### Parameters The parameters object is overloaded with two types: * Type `GetVisitorCodeParametersType` (for `NodeJS`/`Express`/`NextJS SSR methods`), containing the following fields: | Name | Type | Description | | ------------------------------------------ | ----------------- | ------------------------------------------------------------ | | request required | `IncomingMessage` | server request | | response required | `ServerResponse` | server response | | defaultVisitorCode optional | `string` | visitor code to be used if is no visitor code in the cookies | * Type `GetNextJSVisitorCodeParametersType` (for `NextJS SSR server actions`), containing the following fields: | Name | Type | Description | | ------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------- | | cookies required | `typeof 'next/headers' cookies` | NextJS server actions headers cookie | | defaultVisitorCode optional | `string` | visitor code to be used if is no visitor code available in the cookies | * Type `GetDenoVisitorCodeParametersType` (for `Deno`), containing the following fields: | Name | Type | Description | | ------------------------------------------ | ------------- | ----------------------------------------------------------------------------------- | | request required | `DenoMessage` | server request | | response required | `DenoMessage` | server response | | defaultVisitorCode optional | `string` | default visitor code that the SDK uses when there is no visitor code in the cookies | * Type `GetCustomVisitorCodeParametersType` (for custom `VisitorCodeManager` implementation), containing the following fields: | Name | Type | Description | | ------------------------------------------ | --------- | ----------------------------------------------------------------------- | | input required | `unknown` | arbitrary input object from which you want to read the visitor code | | output required | `unknown` | arbitrary output object to which you want to write the visitor code | | defaultVisitorCode optional | `string` | visitor code to be used in case there is no visitor code in the cookies | If you don't provide a `defaultVisitorCode` and there is no visitor code stored in a cookie, the visitor code will be randomly generated. ##### Return value | Type | Description | | -------- | ------------------- | | `string` | result visitor code | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The maximum visitor code length was exceeded (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | *** #### addData() Use the `addData()` method to add targeting data to storage so other methods can utilize this information to determine whether to target the current visitor. The `addData()` method doesn't return any value, and doesn't directly interact with the Kameleoon back-end servers. Instead, all data the method collects is saved for future transmission using the [`flush()`](#flush) method. This approach minimizes the number of server calls, as data is generally grouped into a single server call that's activated by the `flush()` method. Additionally, the [`trackConversion()`](#trackconversion) method transmits any previously associated data. The [`getFeatureFlagVariationKey()`](#getfeatureflagvariationkey) and [`getFeatureFlagVariable()`](#getfeatureflagvariable) methods transmit data when an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types; however, `CustomData` is an exception, as visitors can have one instance of associated `CustomData` for each `customDataIndex`. Check the [list of supported conditions](#targeting-conditions) to see the data types you can use for targeting. ```ts theme={null} import { KameleoonClient, BrowserType, CustomData, Browser, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Create Kameleoon Data Types const browserData = new Browser(BrowserType.Chrome); const customData = new CustomData(0, 'my_data'); // -- Add a single data item (tracked by default) client.addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) client.addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; client.addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) client.addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); } init(); ``` ```js theme={null} import { KameleoonClient, BrowserType, CustomData, Browser, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Create Kameleoon Data Types const browserData = new Browser(BrowserType.Chrome); const customData = new CustomData(0, 'my_data'); // -- Add a single data item (tracked by default) client.addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) client.addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; client.addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) client.addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); } init(); ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | | | track optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it's not sent to the Kameleoon Data API. | `true` | | kameleoonData optional | `KameleoonDataType[]` | number of instances of any type of `KameleoonData`, can be added solely in array or as sequential arguments | | * `kameleoonData` is a variadic argument: it can be passed as one or several arguments (see the example). * The [custom data's](/user-manual/assets/custom-data/create-custom-data) index or ID can be found in your Kameleoon account. Note that this index starts at `0`, meaning the first custom data you create for a given site will be assigned `0` as its ID, rather than `1`. ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | Check the [data types](#data-types) reference for more details on how to manage different data types. *** #### flush() The `flush()` method takes the Kameleoon data associated with a visitor and sends a data tracking request along with all previously added data using the [`addData()`](#adddata) method. The SDK will send all of its stored data to the remote Kameleoon servers if you don't specify a `visitorCode`. Additionally, if there were any tracking requests that previously failed and were stored locally in [offline mode](#initialize), the SDK will attempt to send those stored requests before processing the latest request. The `isUniqueIdentifier` parameter can be beneficial in certain edge cases. For example, if you can't access the anonymous `visitorCode` initially assigned to a visitor but have an internal ID linked through session merging, this parameter is useful. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; client.flush(internalUserId, true); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; client.flush(internalUserId, true); } init(); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters in length, if not passed, all data will be flushed (sent to the remote Kameleoon servers) | - | | isUniqueIdentifier optional | `boolean` | an optional parameter for specifying if the `visitorCode` is a unique identifier | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | `flush()` takes the Kameleoon data associated with the visitor and schedules the data to be sent with the next tracking request. The time of the next tracking request is defined in the SDK Configuration's [`trackingInterval`](#configuration-parameters) parameter. You can add visitor data using the [`addData()`](#adddata) and [`getRemoteVisitorData()`](#getremotevisitordata) methods. The SDK will send all of its stored data to the remote Kameleoon servers if you don't specify a `visitorCode`. Additionally, if there were any tracking requests that previously failed and were stored locally in [offline mode](#initialize), the SDK will attempt to send those stored requests before processing the latest request. If you need to send tracking requests immediately, use `flushInstant()` (the asynchronous version of `flush` that returns `Promise`). You can `await` it when you need delivery guarantees (for example, before ending a request/response cycle), or call it without `await` as a fire-and-forget request: * `await client.flushInstant(visitorCode)` sends tracking requests immediately for a specific visitor and waits for completion * `await client.flushInstant()` sends tracking requests immediately for all visitors and waits for completion ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Instantly flush added custom data for visitor client.flush({ visitorCode, instant: true }); // -- Flush data for all the visitors client.flush(); // -- Instantly flush data for all the visitors client.flush({ instant: true }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); const customData = new CustomData(0, 'my_data'); client.addData(visitorCode, customData); // -- Flush added custom data for visitor client.flush(visitorCode); // -- Instantly flush added custom data for visitor (fire-and-forget) client.flushInstant(visitorCode); // -- Instantly flush added custom data for visitor and wait for completion await client.flushInstant(visitorCode); // -- Flush data for all the visitors client.flush(); // -- Instantly flush data for all the visitors (fire-and-forget) client.flushInstant(); // -- Instantly flush data for all the visitors and wait for completion await client.flushInstant(); } init(); ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | Or an object with the type FlushParamsType, containing: | Name | Type | Description | Default | | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | | instant optional | `boolean` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). | - | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | *** #### getRemoteData() The `getRemoteData()` method retrieves data that's stored on a remote Kameleoon server for a specified site code. For instance, you can use this method to access user preferences, historical data, or any other information pertinent to your app's logic. By storing this data on Kameleoon's highly scalable servers using the [Data API](/developer-docs/apis/data-api-rest/overview), you can efficiently manage large volumes of data and retrieve it for each of your visitors or users. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); } init(); ``` ##### Parameters | Name | Type | Description | | --------------------------- | -------- | ---------------------------------------------------------- | | key required | `string` | the unique key with which the retrieved data is associated | ##### Return value | Type | Description | | ---------- | -------------------------------------------- | | `JSONType` | promise with data retrieved for specific key | ##### Exceptions thrown | Type | Description | | ------------------------------- | -------------------------------------------- | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method that retrieves Kameleoon Visits Data for a specific `visitorCode` from the Kameleoon Data API. This method stores the data for use by other methods when making targeting decisions. The data obtained using this method is essential for certain scenarios, including: * Utilizing data collected from multiple devices. * Accessing a user's history, including visited pages during prior visits. * Using client-side data, such as datalayer variables and goals that only convert on the front end. For a clearer understanding of the potential use cases, read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). By default, `getRemoteVisitorData()` retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor, eliminating the need to call the method `addData()`. This feature is especially useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The `isUniqueIdentifier` parameter can be beneficial in certain edge cases. For example, if you can't access the anonymous `visitorCode` initially assigned to a visitor but have an internal ID linked through session merging, this parameter is useful. ```ts theme={null} import { KameleoonClient, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved data should be added to storage automatically (without calling `addData()` afterwards). | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default, only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | | isUniqueIdentifier optional | `boolean` | optional parameter that, when `true`, specifies the `visitorCode` as a unique identifier | `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method provides flexibility by letting you define various parameters when retrieving visitor data. This method can target data based on goals, experiments, or variations, and the same approach applies to all data types. For example, if you want to retrieve data on visitors who completed the goal "Order transaction," you can specify parameters in the `getRemoteVisitorData()` method to refine your targeting. If you're interested in users who converted on the goal during their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits from which to retrieve data. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert). | `false` | The `getRemoteVisitorData()` method is an asynchronous function that retrieves Kameleoon Visits Data for a specific `visitorCode` from the Kameleoon Data API. This method stores the data so that it can be accessed when making targeting decisions. The data obtained through this method is crucial when you want to: * Access data collected from multiple devices. * Review a user's history, including pages visited during previous sessions. * Utilize client-side data, such as data layer variables and goals that are only applicable on the front end. For a better understanding of potential use cases, read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). By default, `getRemoteVisitorData()` retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor without the need to call the method `addData()`. This feature is particularly useful for [synchronizing custom data across multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). ```ts theme={null} import { KameleoonClient, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); } init(); ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType`, containing: | Name | Type | Description | Default Value | | ------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be added to storage automatically (without calling `addData` afterwards) | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default, only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method provides flexibility by letting you define various parameters when retrieving visitor data. This method can target data based on goals, experiments, or variations, and the same approach applies to all data types. For example, if you want to retrieve data on visitors who completed the goal "Order transaction," you can specify parameters in the `getRemoteVisitorData()` method to refine your targeting. If you're interested in users who converted on the goal during their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits from which to retrieve data. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert). | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This retrieval is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | *** #### getVisitorWarehouseAudience() The `getVisitorWarehouseAudience` method is asynchronous and retrieves all audience data related to a visitor from your data warehouse. To use this method, you’ll need to provide a `visitorCode` and a `warehouseKey`, which typically correspond to your internal user ID. The `customDataIndex` parameter refers to the custom data Kameleoon uses to target your visitors. For more details, refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting). ```ts theme={null} import { KameleoonClient, KameleoonDataType, CustomData, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | | --------------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | | customDataIndex required | `number` | number representing the custom data's index you want to use to target your Warehouse Audiences | | warehouseKey optional | `string` | unique key identifying the warehouse data (usually your internal user ID) | ##### Return value | Type | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------ | | `Promise` | promise containing CustomData with the associated warehouse data, or `null` if there was no data | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### setLegalConsent() When handling legal consent, it’s important you use the [`getVisitorCode`](#getvisitorcode) method from the `KameleoonClient` class, rather than the deprecated method from `KameleoonUtils`. Note that this method doesn't require the `domain` as an argument. Instead, you should pass the `domain` to the `KameleoonClient` constructor. Refer to the example preceding for clarification. The method `setLegalConsent` determines whether a visitor has provided legal consent for their personal data's use. If you set the `legalConsent` parameter to `false`, it restricts the types of data you can include in tracking requests. This measure ensures that you comply with legal and regulatory requirements while responsibly managing visitor data. For more information on personal data, refer to the [consent management policy](/user-manual/project-management/consent-management-policy). ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init(): Promise { await client.initialize(); const visitorCode = client.getVisitorCode(); client.setLegalConsent({ visitorCode, consent: true, response, // or cookies or output, depending on the environment }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; const client = new KameleoonClient({ siteCode: 'my_site_code', }); async function init() { await client.initialize(); const visitorCode = client.getVisitorCode(); client.setLegalConsent({ visitorCode, consent: true, response, // or cookies or output, depending on the environment }); } init(); ``` ##### Parameters The parameters object is overloaded with the following types: * Type `SetLegalConsentParametersType` (for `NodeJS`/`Express`/`NextJS SSR methods`), containing the following fields: | Name | Type | Description | | ----------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string. Can't exceed 255 characters in length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided or withdrawn legal consent | | response required | `ServerResponse` | server response | * Type `SetNextJSLegalConsentParametersType` (for `NextJS SSR server actions`), containing the following fields: | Name | Type | Description | | ----------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string. Can't exceed 255 characters in length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided or withdrawn legal consent | | cookies required | `typeof 'next/headers' cookies` | NextJS server actions headers cookie | * Type `SetDenoLegalConsentParametersType` (for `Deno`), containing the following fields: | Name | Type | Description | | ----------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string. Can't exceed 255 characters in length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided or withdrawn legal consent | | response required | `DenoMessage` | server response | * Type `SetCustomLegalConsentParametersType` (for custom `VisitorCodeManager` implementation), containing the following fields: | Name | Type | Description | | ----------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string. Can't exceed 255 characters in length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided or withdrawn legal consent | | output required | `unknown` | arbitrary output object to which you want to write the visitor code | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | ##### Consent revocation behavior When you call `setLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. *** ### Goals and third-party analytics #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue` argument. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. If you specify a `visitorCode` and set `isUniqueIdentifier` to `true`, the `trackConversion()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](#cross-device-experimentation) because the SDK links the flushed data with the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; client.trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; client.trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | -------------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `isUniqueIdentifier` optional | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Track conversion client.trackConversion({ visitorCode, revenue: 20000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); } init(); ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `undefined` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```ts theme={null} kameleoonClient.addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); kameleoonClient.trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ```js theme={null} kameleoonClient.addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); kameleoonClient.trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | *** #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200`, and implicit experiment id is `100` client.getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code const engineCode = client.getEngineTrackingCode('visitor_code'); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200`, and implicit experiment id is `100` client.getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code const engineCode = client.getEngineTrackingCode('visitor_code'); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` } init(); ``` * To use this feature, implement both the NodeJS SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```ts theme={null} import { KameleoonClient, EventType, DataFileUpdateEventDataType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); client.setEventHandler(EventType.DataFileUpdate, (eventData: DataFileUpdateEventDataType) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); // -- Clear the handler client.setEventHandler(EventType.DataFileUpdate, null); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); client.setEventHandler(EventType.DataFileUpdate, (eventData) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); // -- Clear the handler client.setEventHandler(EventType.DataFileUpdate, null); } init(); ``` `DataFileUpdateEventDataType` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource.Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource.Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `number` | The modification date of the updated data file, in milliseconds. | ```ts theme={null} import { KameleoonClient, EventType, HttpRequestEventDataType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); client.setEventHandler(EventType.HttpRequest, (eventData: HttpRequestEventDataType) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); // -- Clear the handler client.setEventHandler(EventType.HttpRequest, null); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); client.setEventHandler(EventType.HttpRequest, (eventData) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); // -- Clear the handler client.setEventHandler(EventType.HttpRequest, null); } init(); ``` `HttpRequestEventDataType` contains information about a completed SDK HTTP request. The SDK reports the event once per each actual HTTP request attempt, including retries. The type is a union of the following types. Check the `success` field to narrow the type. | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `HttpRequestSucceededEventDataType` | Describes an SDK HTTP request that completed successfully (`success: true`). Always carries `httpStatus`. | | `HttpRequestFailedEventDataType` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation (`success: false`). Always carries `failure`. | ##### Event data fields | Name | Type | Description | | ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `success` | `boolean` | `true` if the SDK HTTP request completed successfully, `false` if it failed because of an HTTP status, error, or cancellation. | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `HttpRequestType.DataFile`, `HttpRequestType.Tracking`, `HttpRequestType.RemoteVisitorData`, `HttpRequestType.RemoteData`, and `HttpRequestType.AccessToken`. | | `httpStatus` | `number` | The HTTP status code returned by the request. Present only in `HttpRequestSucceededEventDataType`. | | `failure` | `HttpRequestFailureType` | Details about why the request failed. Present only in `HttpRequestFailedEventDataType`. | | `duration` | `number` | The request duration in milliseconds. | ##### HttpRequestFailureType `HttpRequestFailureType` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason.HttpStatus`, `HttpRequestFailureReason.Error`, and `HttpRequestFailureReason.Cancelled`. | | `httpStatus` | `number` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `undefined` for exception and cancellation failures. | | `cause` | `unknown` | The exception (error) that caused the request to fail. This value is `undefined` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DataFileUpdate` to react to SDK data file updates, or `EventType.HttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `EventCallbackType` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEventDataType` for `EventType.DataFileUpdate`, or `HttpRequestEventDataType` for `EventType.HttpRequest`. Pass `null` to remove the current handler. | *** ### Data types Kameleoon Data types are helper classes used for storing data in predefined forms. During the [`flush()`](#flush) execution, the SDK collects all data and sends it along with the tracking request. Data available in the SDK isn't available for targeting and reporting in the Kameleoon app until you add the data (for example, by using the `addData()` method). See [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for more information. If you are using Kameleoon in hybrid mode, you can call `getRemoteVisitorData()` to automatically fill all data that Kameleoon previously collected. #### Browser Browser contains browser information. Each visitor can only have one `Browser`. Adding a second `Browser` overwrites the first one. | Name | Type | Description | | ------------------------------- | ------------- | ----------------------------------------------------------------------------------------------- | | browser required | `BrowserType` | predefined browser type (`Chrome`, `InternetExplorer`, `Firefox`, `Safari`, `Opera`, `Other`) | | version optional | `number` | version of the browser, floating point number represents major and minor version of the browser | ```ts theme={null} import { KameleoonClient, BrowserType, Browser } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); client.addData('my_visitor_code', browser); } init(); ``` ```js theme={null} import { KameleoonClient, BrowserType, Browser } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); client.addData('my_visitor_code', browser); } init(); ``` *** #### UniqueIdentifier `UniqueIdentifier` data is used for unique visitor identification. If you add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). Linking a `UniqueIdentifier` to a visitor informs the SDK that this visitor is associated with another visitor. The `UniqueIdentifier` parameter can be beneficial in certain edge cases. For example, if you can't access the anonymous `visitorCode` initially assigned to a visitor but have an internal ID linked through session merging, this parameter is useful. Each visitor can only have one `UniqueIdentifier`. Adding another `UniqueIdentifier` overwrites the first one. | Name | Type | Description | | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | value required | `boolean` | value that specifies if the visitor is associated with another visitor, provided `false` will imply that the visitor isn't associated with any other visitor | ```ts theme={null} import { KameleoonClient, UniqueIdentifier } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add a unique identifier to a visitor client.addData('my_visitor_code', new UniqueIdentifier(true)); } init(); ``` ```js theme={null} import { KameleoonClient, UniqueIdentifier } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add a unique identifier to a visitor client.addData('my_visitor_code', new UniqueIdentifier(true)); } init(); ``` *** #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. `ConversionParametersType` conversionParameters - an object with conversion parameters described below | Name | Type | Description | Default | | ---------------------------------- | -------------- | ----------------------------------------------- | ----------- | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. | `undefined` | ```ts theme={null} import { KameleoonClient, ConversionParametersType, Conversion, CustomData, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Defined conversion parameters const conversionParameters: ConversionParametersType = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); client.addData('my_visitor_code', conversion); } init(); ``` ```js theme={null} import { KameleoonClient, Conversion, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Defined conversion parameters const conversionParameters = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); client.addData('my_visitor_code', conversion); } init(); ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. The NodeJS SDK doesn't require a `request` or `response` to extract the cookie. Instead, add the cookie manually using `Cookie` data. Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. | Name | Type | Description | | ------------------------------ | -------------- | ------------------------------------------------------------------- | | cookie required | `CookieType[]` | A list of `CookieType` objects consisting of cookie keys and values | ```ts theme={null} import { KameleoonClient, CookieType, Cookie } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add new cookie data to client const cookieData: CookieType[] = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); client.addData('my_visitor_code', cookie); } init(); ``` ```js theme={null} import { KameleoonClient, Cookie } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add new cookie data to client const cookieData = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); client.addData('my_visitor_code', cookie); } init(); ``` ##### Methods `Cookie` data has a static utility method, `fromString`, that can help you create a cookie by parsing a string that contains valid cookie data. The method accepts `string` as a parameter, and returns an initialized `Cookie` instance. ```ts theme={null} import { Cookie } from '@kameleoon/nodejs-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie: Cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` ```js theme={null} import { Cookie } from '@kameleoon/nodejs-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` *** #### GeolocationData `GeolocationData` contains the visitor's geolocation details. Each visitor can only have one `GeolocationData`. Adding a second `GeolocationData` overwrites the first one. An object parameter with the type `GeolocationInfoType` contains the following fields: | Name | Type | Description | | ----------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------- | | country required | `string` | The visitor's country | | region optional | `string` | The visitor's region | | city optional | `string` | The visitor's city | | postalCode optional | `string` | The visitor's postal code | | coordinates optional | `[number, number]` | Coordinates array tuple of two location values (longitude and latitude). Coordinate number represents decimal degrees | ```ts theme={null} import { KameleoonClient, GeolocationData, GeolocationInfoType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add geolocation data const geolocationInfo: GeolocationInfoType = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); client.addData('my_visitor_code', geolocationData); } init(); ``` ```js theme={null} import { KameleoonClient, GeolocationData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add geolocation data const geolocationInfo = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); client.addData('my_visitor_code', geolocationData); } init(); ``` *** #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). To maintain the custom data in future visits, the SDK sends `CustomData` with the `Visitor` scope with the next tracking request. You can set the scope in the [custom data dashboard](https://app.kameleoon.com/customData/dashboard). | Name | Type | Description | Default | | ---------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `number`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | value required | `string[]` | The custom data value. It must be stringified to match the `string` type. *Note:* value is variadic. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData(customDataIndex, dataItemOne, dataItemTwo); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add custom data client.addData('my_visitor_code', customData); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData(customDataIndex, dataItemOne, dataItemTwo); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add custom data client.addData('my_visitor_code', customData); } init(); ``` *** #### Device Device contains information about your device. Each visitor can only have one `Device`. Adding a second `Device` overwrites the first one. | Name | Type | Description | | ---------------------------------- | ------------ | ------------------------------------------------------------- | | deviceType required | `DeviceType` | possible types for device type (`PHONE`, `TABLET`, `DESKTOP`) | ```ts theme={null} import { KameleoonClient, DeviceType, Device } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add device data const device = new Device(DeviceType.Desktop); client.addData('my_visitor_code', device); } init(); ``` ```js theme={null} import { KameleoonClient, DeviceType, Device } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add device data const device = new Device(DeviceType.Desktop); client.addData('my_visitor_code', device); } init(); ``` *** #### OperatingSystem `OperatingSystem` contains information about the visitor's operating system. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. | Name | Type | Description | | --------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- | | operatingSystem required | `OperatingSystemType` | possible types for device type: `WINDOWS_PHONE`, `WINDOWS`, `ANDROID`, `LINUX`, `MAC`, `IOS` | ```ts theme={null} import { KameleoonClient, OperatingSystem, OperatingSystemType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); client.addData('my_visitor_code', operatingSystem); } init(); ``` ```js theme={null} import { KameleoonClient, OperatingSystem, OperatingSystemType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); client.addData('my_visitor_code', operatingSystem); } init(); ``` *** #### PageView PageView contains information about your web page. Each visitor can have one `PageView` per unique URL. Adding a `PageView` with the same URL notifies the SDK that the visitor revisited the page. `PageViewParametersType` pageViewParameters - an object with page view parameters described below | Name | Type | Description | | ---------------------------------- | ---------- | ---------------------------------------------------------------------------------- | | urlAddress required | `string` | url address of the page to track | | title required | `string` | title of the web page | | referrer optional | `number[]` | an optional parameter containing a list of referrers indices, has no default value | You can find the [referrer's](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) index or ID in your Kameleoon account. Note that this index starts at 0, meaning the first acquisition channel you create for a given site will be assigned 0 as its ID, not 1. ```ts theme={null} import { KameleoonClient, PageViewParametersType, PageView, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Define page view parameters const pageViewParameters: PageViewParametersType = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add page view data const pageView = new PageView(pageViewParameters); client.addData('my_visitor_code', pageView); } init(); ``` ```js theme={null} import { KameleoonClient, PageView } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Define page view parameters const pageViewParameters = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add page view data const pageView = new PageView(pageViewParameters); client.addData('my_visitor_code', pageView); } init(); ``` *** #### UserAgent `UserAgent` stores information on the visitor's user-agent. Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that could otherwise skew your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. A visitor can only have one `UserAgent`. Adding a second `UserAgent` overwrites the first one. | Name | Type | Description | | ----------------------------- | -------- | ------------------------- | | value required | `string` | value used for comparison | Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Pass the user agent to be filtered by Kameleoon when running server-side experiments for each visitor browsing your website, to avoid counting bots in your analytics. If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. ```ts theme={null} import { KameleoonClient, UserAgent } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add user agent data const userAgent = new UserAgent('my_unique_value'); client.addData('my_visitor_code', userAgent); } init(); ``` ```js theme={null} import { KameleoonClient, UserAgent } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add user agent data const userAgent = new UserAgent('my_unique_value'); client.addData('my_visitor_code', userAgent); } init(); ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```ts theme={null} import { KameleoonClient, ApplicationVersion } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Add application version const applicationVersion = new ApplicationVersion('1.2'); client.addData('my_visitor_code', applicationVersion); } init(); ``` ```js theme={null} import { KameleoonClient, ApplicationVersion } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Add application version const applicationVersion = new ApplicationVersion('1.2'); client.addData('my_visitor_code', applicationVersion); } init(); ``` *** ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `number` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```ts theme={null} import { FeatureFlag } from '@kameleoon/nodejs-sdk'; // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags: Map = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified: number = dataFile.dateModified; ``` ```js theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified = dataFile.dateModified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Rule[]` | A list of `Rule` objects | ```ts theme={null} import { Variation, Rule } from '@kameleoon/nodejs-sdk'; // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled: boolean = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey: string = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations: Map = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules: Rule[] = featureFlag.rules; ``` ```js theme={null} // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules = featureFlag.rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```ts theme={null} import { Variation } from '@kameleoon/nodejs-sdk'; // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations: Map = rule.variations; ``` ```js theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations = rule.variations; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | --------------------------------------------------------------------------------------------------- | | name | `string` | name of the variation. | | key | `string` | key of the variation. | | id | `number` or `null` | id of the variation or `null` if the visitor landed on the default variation. | | experimentId | `number` or `null` | id of the experiment or `null` if the visitor landed on the default variation. | | variables | `Map` | map of variables for the variation, where key is the variable key and value is the variable object. | * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```ts theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` ```js theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | key | `string` | The unique key identifying the variable. | | type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | value | `any` | The value of the variable, which can be of the following types: **boolean**, **number**, **String**, **Record\**, **any\[]**. | ```ts theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ```js theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ### Edge helpers These helper methods are primarily intended for short-lived or edge-style runtimes where the SDK may need explicit revalidation between requests. #### refreshDataFileIfStale() The `refreshDataFileIfStale()` method triggers a data file revalidation only when the current configuration is stale. If the data file is still valid and the last update happened less than the configured [`dataFileRefreshInterval`](#configuration-parameters) ago, the method returns `false` and no update request is made. If the data file is stale, the method waits for the revalidation request to finish and returns `true` when the request was performed. Returning `true` means that the check was executed, but the configuration itself may still remain unchanged, for example when the server reports that the current data file is already up to date. In a typical long-lived Node.js runtime, using this method is generally not recommended, because the SDK already keeps the data file fresh automatically during initialization and normal runtime execution. It can still be helpful in edge-style environments such as Cloudflare Workers, where runtime behavior is more short-lived and revalidation may need to be triggered explicitly. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Revalidate the data file only if the current configuration is stale const dataFileRevalidated = await client.refreshDataFileIfStale(); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Revalidate the data file only if the current configuration is stale const dataFileRevalidated = await client.refreshDataFileIfStale(); } init(); ``` ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | `Promise` | `true` if an update request was performed, `false` if the current data file is still valid and no request was made. | ### Deprecated methods These methods are deprecated and will be removed in the next major update. #### getFeatureFlagVariationKey() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Use the [`getVariation`](#getvariation) method instead. The `getFeatureFlagVariationKey()` method retrieves the variation key for the specified `visitorCode` in the corresponding feature flag. This method includes a targeting check, finding the appropriate variation exposed to the visitor, saving it to storage, and sending a tracking request. If a user hasn't been previously assigned a variation key for the feature flag, the SDK will randomly determine a variation based on the feature flag's rules. If the user is already linked to the feature flag, the SDK will return their previously assigned variation key. If the user doesn't meet any of the specified rules, the default value defined in Kameleoon's feature flag delivery rules will be returned. This default value isn't always a variation key—it can also be a boolean or another data type, depending on the feature flag's configuration. ```ts theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(new CustomData(0, 'visitor_id')); // -- Get visitor feature flag variation key const variationKey = client.getFeatureFlagVariationKey( visitorCode, 'my_feature_key', ); } init(); ``` ```js theme={null} import { KameleoonClient, CustomData } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = KameleoonUtils.getVisitorCode({ request: req, response: res, }); // -- Add CustomData with index `0` containing visitor id to check the targeting client.addData(new CustomData(0, 'visitor_id')); // -- Get visitor feature flag variation key const variationKey = client.getFeatureFlagVariationKey( visitorCode, 'my_feature_key', ); } init(); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | | featureKey required | `string` | a unique key for a feature flag | ##### Return value | Type | Description | | -------- | ------------------------------------------------------------------------------ | | `string` | a string containing the variable key for the visitor's allocated feature flag. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was completed for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | *** #### getVisitorFeatureFlags() Use the [`getVariations`](#getvariations) method instead. The `getVisitorFeatureFlags()` method returns a list of feature flags that are active for the visitor with the specified `visitorCode`, ensuring that the visitor is allocated one of the variations. * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get active feature flags for visitor const featureFlags = client.getVisitorFeatureFlags(visitorCode); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get active feature flags for visitor const featureFlags = client.getVisitorFeatureFlags(visitorCode); } init(); ``` This method only collects the visitor's *active* feature flags, meaning the result excludes all feature flags for which the visitor is assigned the `off` (default or control) variation. For example: ```ts theme={null} // -- `getVisitorFeatureFlags` doesn't trigger feature experiments; // it only returns feature flags where visitors didn't get the `off` variation. client.getVisitorFeatureFlags('my_visitor').forEach(({ key }) => { // -- `getFeatureFlagVariationKey` triggers a feature experiment, // as `off` is already filtered out - visitors will never take part // in an experiment where the `off` variation was allocated. client.getFeatureFlagVariationKey('my_visitor', key); }); ``` Use [`getFeatureFlags`](#getfeatureflags) when you need all of the visitor's feature flags: ```ts theme={null} // -- Both `off` and other variations are processed as expected. client.getFeatureFlags('my_visitor').forEach(({ key }) => { client.getFeatureFlagVariationKey('my_visitor', key); }); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | ##### Return value | Type | Description | | ------------------- | --------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | *** #### getActiveFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) Use the [`getVariations`](#getvariations) method instead. The `getActiveFeatureFlags()` method returns a `Map`, where the key represents the feature key, and the value contains detailed information about the visitor’s variation and its variables. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = client.getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code const visitorCode = client.getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = client.getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } } init(); ``` This method only collects the visitor's *active* feature flags, meaning the result excludes all feature flags for which the visitor is assigned the `off` (default or control) variation. See the [getVisitorFeatureFlags](#getvisitorfeatureflags) method's *CAUTION* section for more details. ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | ##### Return value | Type | Description | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `Map` | a map of feature flags, where key is feature key and value is detailed information about the visitor's variation and its variables | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | | `KameleoonException.NumberParse` | Couldn't parse Number value | | `KameleoonException.JSONParse` | Couldn't parse JSON value | *** #### getFeatureFlagVariable() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Use the [`getVariation`](#getvariation) method instead. The `getFeatureFlagVariable()` method retrieves a variable for the visitor based on the `visitorCode` within the identified feature flag. This method includes a targeting check, determines the appropriate variation for the visitor, saves it to storage, and sends a tracking request. ```ts theme={null} import { KameleoonClient, VariableType, JSONType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get feature variable const result = client.getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key' variableKey: 'my_variable_key' }); // -- Infer the type of a variable by its `type` switch (result.type) { case VariableType.BOOLEAN: const myBool: boolean = result.value; break; case VariableType.NUMBER: const myNum: number = result.value; break; case VariableType.JSON: const myJson: JSONType = result.value; break; case VariableType.STRING: case VariableType.JS: case VariableType.CSS: const myStr: string = result.value; break; default: break; } } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get feature variable const variableResult = client.getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key' variableKey: 'my_variable_key' }); const { type, value } = variableResult; } init(); ``` ##### Parameters Parameters object of type `GetFeatureFlagVariableParamsType` containing the following fields: | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | | featureKey required | `string` | a unique key for a feature flag | | variableKey required | `string` | variable's key for a feature flag with provided `featureKey`, can be found in Kameleoon | ##### Return value | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureFlagVariableType` | is a variable object containing `type` and `value` fields. You can check the `type` field against the `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN`, the `value` is a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was completed for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | *** #### getFeatureFlagVariables() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) (for each feature flag) Use the [`getVariation`](#getvariation) method instead. The `getFeatureFlagVariables()` method retrieves a list of variable values for a specified visitor and feature flag. This method checks if the user is targeted, identifies the visitor’s assigned variation, stores it, and sends a tracking request. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = client.getFeatureFlagVariables( visitorCode, 'my_feature_key', ); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get visitor code using server `request` and `response` const visitorCode = client.getVisitorCode({ request: req, response: res, }); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag. const variables = client.getFeatureFlagVariables( visitorCode, 'my_feature_key', ); } init(); ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters in length | | featureKey required | `string` | a unique key for the feature flag | ##### Return value | Type | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureVariableResultType[]` | a list of variable objects containing `key`, `type` and `value` fields. You can check the `type` field against the `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN` then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagVariationNotFound` | No feature variation was found for the specified `visitorCode` and `variationKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | *** #### onConfigurationUpdate() Use the `onEvent` method with `EventType.ConfigurationUpdate` instead. The `onConfigurationUpdate()` method fires a callback upon client configuration update. This method is only applicable to server-sent events for real-time updates. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Define logic that will execute on client configuration update client.onConfigurationUpdate(() => { // -- My Logic }); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Define logic that will execute on client configuration update client.onConfigurationUpdate(() => { // -- My Logic }); } init(); ``` ##### Parameters | Name | Type | Description | | -------------------------------- | ------------ | ---------------------------------------------------------------------------------- | | callback required | `() => void` | callback function with no parameters that will be called upon configuration update | ##### Exceptions thrown | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | *** #### getFeatureFlags() Use the [`getDataFile()`](#getdatafile) method instead. 🚫 *Doesn't send Tracking Data to Kameleoon* The `getFeatureFlags()` method retrieves a list of feature flags that are stored in the client configuration. ```ts theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Get all feature flags const featureFlags = client.getFeatureFlags(); } init(); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Get all feature flags const featureFlags = client.getFeatureFlags(); } init(); ``` ##### Return value | Type | Description | | ------------------- | --------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key` | ##### Exceptions thrown | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | #### onEvent() Use [`setEventHandler()`](#seteventhandler) instead. The `onEvent()` method fires a callback when a specific event is triggered. The callback function accesses the data associated with the event. The SDK methods in this documentation note which event types they trigger, if any. You can only assign one callback to each `EventType`. ```ts theme={null} import { KameleoonClient, EventType, EvaluationEventDataType, } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init(): Promise { await client.initialize(); // -- Define logic that will execute on SDK event client.onEvent(EventType.Evaluation, (eventData: EventDataType) => { // -- My Logic }); } init(); ``` ```js theme={null} import { KameleoonClient, EventType } from '@kameleoon/nodejs-sdk'; import { KameleoonVisitorCodeManager } from '@kameleoon/nodejs-visitor-code-manager'; import { KameleoonEventSource } from '@kameleoon/nodejs-event-source'; import { KameleoonRequester } from '@kameleoon/nodejs-requester'; const client = new KameleoonClient({ siteCode: 'my_site_code', credentials: { clientId: 'my_client_id', clientSecret: 'my_client_secret' }, externals: { visitorCodeManager: new KameleoonVisitorCodeManager(), eventSource: new KameleoonEventSource(), requester: new KameleoonRequester(), }, }); async function init() { await client.initialize(); // -- Define logic that will execute on SDK event client.onEvent(EventType.Evaluation, (eventData) => { // -- My Logic }); } init(); ``` ##### Events Events are defined in the `EventType` enum. The `eventData` parameter will have a different type based on the event type. | Type | `eventData` type | Description | | ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `EventType.Evaluation` | `EvaluationEventDataType` | Triggered when the SDK evaluates any variation for a feature flag. The event is triggered regardless of the result variation | | `EventType.ConfigurationUpdate` | `ConfigurationUpdateEventDataType` | Triggered when the SDK receives a configuration update from the server (when using real-time streaming) | ##### Parameters | Name | Type | Description | | -------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------- | | event required | `EventType` | a type of the event to associate the callback action with | | callback required | `(eventData: EventDataType) => void` | a callback function with the `eventData` parameter that's called when a configuration update occurs | ##### Exceptions thrown | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | # PHP SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/php-sdk Integrate the Kameleoon PHP SDK to run experiments and activate feature flags on PHP back-end servers, with optional cron job tracking. With the PHP SDK, you can run experiments and activate feature flags on your back-end PHP server. Integrating the SDK into your web-app is easy, and its footprint ( memory and network usage) is low. **Getting started**: For help getting started, see the [Developer guide](#developer-guide). **Changelog**: Latest version of the PHP SDK: 4.24.1 [Changelog](https://github.com/Kameleoon/client-php/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the PHP SDK, see the [reference](#reference) section. ## Developer guide This guide helps you integrate the SDK into your app code. ### Getting started You should first install the SDK. Once uncompressed, you will see two directories: **kameleoon/** and **job/**. #### Installing the PHP client (Composer package) Carefully read the sections on [installing the cron job](#installing-the-cron-job) and [using the PHP SDK without a cron job](#using-the-php-sdk-without-a-cron-job). The installation package is available on [Packagist](https://packagist.org/packages/kameleoon/kameleoon-client-php). You can install the PHP SDK by adding it as a dependency using Composer: ```json title="composer.json" theme={null} { "require": { "kameleoon/kameleoon-client-php": "^4.18.0" } } ``` Finally, execute the following command to regenerate the autoloader: ```bash theme={null} composer install ``` #### With a cron job (Recommended) Setting up the cron allows the tracking of data added by the PHP sdk with the [addData()](#adddata) method. However, if you are unable to install it, front end tracking can still be implemented following [this guide](#without-a-cron-job). The **job/** directory corresponds to a job that must be executed via a standard job scheduler (like cron). Install the script at `/usr/local/opt/kameleoon/kameleoon-client-php-process-queries.sh` and use the default supplied crontab entry. However, you can install it in another location and modify the crontab entry accordingly. ##### Without a cron job If you can't install the cron job, you can use Kameleoon in [hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) to benefit from the Kameleoon App Engine's, `engine.js` (previously named, `kameleoon.js`) tracking capabilities. The SDK provides the [`getEngineTrackingCode()`](#getenginetrackingcode) method, which sends exposure events to Kameleoon or any other analytics solution you use on your website. With this approach **you won't be able to track data** added with the [addData()](#adddata) method of the PHP SDK. In other words, the **only** way for the PHP SDK to collect and process experiment data server-side is through the **cron job**. The hybrid mode is useful for tracking purposes, but **doesn't enable backend data collection**. #### Additional configuration You can customize the behavior of the PHP SDK via a configuration file. Kameleoon provides a sample configuration file named `client-php.json.sample` in the SDK archive. You can also [download a sample configuration](/assets/developer-docs/sdks/web-sdks/client-configs/client-php.json) file. Install this file to the default path of `/tmp/kameleoon/client-php.json`. The following table shows the available properties that you can set: | Key | Description | Default value | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `clientId` / `client_id` required | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `clientSecret` / `client_secret` required | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `kameleoonWorkDir` / `kameleoon_work_dir` optional | Specifies a working directory for the PHP client (which will create files in this directory). The directory needs to be writable by the PHP user. | `/tmp/kameleoon/client-php/` | | `refreshIntervalMinute` / `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. | `60` minutes | | `defaultTimeoutMillisecond` / `default_timeout_millisecond` optional | Specifies the timeout, in milliseconds, for network requests from the SDK. Set the value to `30` seconds or more if you don't have a stable connection. Some methods have an additional parameter that you can use to override the default timeout for that particular method. If you don't specify the timeout for a method explicitly, the SDK uses this default value. | `10000` milliseconds | | `cookieOptions->topLevelDomain` / `cookie_options.domain` *(required in hybrid mode)* | The current top-level domain for your website. Use the format: `example.com`. Don't include `https://`, `www`, or other subdomains. Kameleoon uses this information to set the corresponding cookie on the top-level domain. | `null` | | `cookieOptions->secure` / `cookie_options.secure` optional | Controls the **Secure** cookie attribute. | `false` | | `cookieOptions->httpOnly` / `cookie_options.http_only` optional | Controls the **HttpOnly** cookie attribute. | `false` | | `cookieOptions->sameSite` / `cookie_options.samesite` optional | Controls the **SameSite** cookie attribute. | `Lax` | | `environment` / `environment` optional | Environment from which the feature flag’s configuration is to be used. The value can be `production`, `staging`, `development`. See the [managing environments](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) article for details. | `production` | | `networkDomain` / `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `null` | | `requestBodySizeLimitBytes` / `request_body_size_limit_bytes` *(optional)* | Limits the size of tracking packages (in bytes), which store data collected between cron job runs. In some cases, reducing the package size may be necessary due to server or network restrictions. The maximum allowed value is 2.5 MB (`2621440` bytes). | `2621440` | | `debugMode` / `debug_mode` *(deprecated)* | The parameter sends additional information to the tracking servers to help analyze issues. It should usually be off (**false**), but activating it (**true**) has no impact on SDK performance. This field is deprecated and will be removed in SDK version `5.0.0`. Use [`KameleoonLogger::setLogLevel`](#log-levels) instead. | `false` | If you don't use the default path (`/tmp/kameleoon/client-php.json`) for the configuration file, you will need to: * pass your configuration file's path as a third argument to the `KameleoonClientFactory::create()` method; * modify your crontab entry to add the --conf argument to the job script (so, for instance, it would be `bash /usr/local/opt/bin/kameleoon-client-php-process-queries.sh --conf /my/path/kameleoon.json`). To learn more about `client_id` and `client_secret`, and how to obtain them, refer to the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) article. Note that the Kameleoon PHP SDK uses the Automation API and follows the OAuth 2.0 client credentials flow. #### Initializing the Kameleoon client After installing the SDK into your app and configuring the correct credentials (in `/tmp/kameleoon/client-php.json`), the next step is to create the Kameleoon client in your app code. For example: ```php theme={null} require "vendor/autoload.php"; use Kameleoon\KameleoonClientConfig; use Kameleoon\KameleoonClientFactory; use Kameleoon\Exception\ConfigCredentialsInvalid; use Kameleoon\Exception\KameleoonException; use Kameleoon\Exception\SiteCodeIsEmpty; $siteCode = "a8st4f59bj"; try { // Read from default configuration path: "/tmp/kameleoon/php-client/" $kameleoonClient = KameleoonClientFactory::create($siteCode); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } try { $kameleoonClient = KameleoonClientFactory::create($siteCode, "custom/file/path/client-php.json"); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } try { $cookieOptions = KameleoonClientConfig::createCookieOptions( "example.com", // domain: optional, but strictly recommended false, // secure: optional (false by default) false, // httponly: optional (false by default) "Lax" // samesite: optional (Lax by default) ); $config = new KameleoonClientConfig( "", // clientId: mandatory "", // clientSecret: mandatory "/tmp/kameleoon/php-client/", // kameleoonWorkDir: optional / ("/tmp/kameleoon/php-client/" by default) 60, // refreshIntervalMinute: in minutes, optional (60 minutes by default) 10_000, // defaultTimeoutMillisecond: in milliseconds, optional (10_000 ms by default) false, // debugMode: optional (false by default) $cookieOptions, // cookieOptions: optional "development", // environment: optional ("production" by default) "example.com", // networkDomain: optional (null by default) 1024*1024, // requestBodySizeLimitBytes: optional (2560 * 1024 by default) ); $kameleoonClient = KameleoonClientFactory::createWithConfig($siteCode, $config); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } ``` A KameleoonClient is a singleton object that acts as a bridge between your app and the Kameleoon platform. It includes all the methods and properties you need to run an experiment. Note that the SDK takes its settings from a [configuration file.](#additional-configuration) By default, the path `/tmp/kameleoon/client-php.json` will be used, but you can use a different path for the configuration file by providing an optional third argument to the `KameleoonClientFactory::create()` method. It's your responsibility as the app developer to use correct logic in your app code within the context of A/B testing via Kameleoon. A good practice is to always assume that the current visitor can be left out of the experiment because the experiment hasn't yet been launched. Leaving out the current visitor is simple, as it corresponds to the implementation of the default / reference variation logic. The code samples in the next paragraph show examples of such an approach. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureActive()`](#isfeatureactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on one of the next tracking request, which is automatically performed by the cron job. By default, its interval is 1 minute. The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined in the interval tracking crontab). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```php title="Device A" theme={null} // In this example, Custom data with index `90` was set to "Visitor" scope on Kameleoon. const VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90; $kameleoonClient->addData($visitorCode, new CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")); $kameleoonClient->flush($visitorCode); ``` ```php title="Device B" theme={null} // Call the `getRemoteVisitorData` method before working with the data. $kameleoonClient->getRemoteVisitorData($visitorCode); // After the call, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available to target and track the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`getRemoteVisitorData()`](#getremotevisitordata) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `getRemoteVisitorData()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`trackConversion()`](#trackconversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`getRemoteVisitorData()`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. ```php theme={null} // In this example, `91` represents the Custom Data's index // configured as a unique identifier in Kameleoon. const MAPPING_INDEX = 91; const FEATURE_KEY = "ff123"; // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. // Assume `anonymousVisitorCode` is the randomly generated ID for that visitor. $anonymousVariation = $kameleoonClient->getVariation($anonymousVisitorCode, FEATURE_KEY); // 2. After the visitor is authenticated // Assume `userId` is the authenticates visitor's visitor code. $kameleoonClient->addData($anonymousVisitorCode, new CustomData(MAPPING_INDEX, $userId)); $kameleoonClient->flush($anonymousVisitorCode, null, null, true); // Indicate that `userId` is a unique identifier. $kameleoonClient->addData($userId, new UniqueIdentifier(true)); // 3. After the visitor has been authenticated // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. $userVariation = $kameleoonClient->getVariation($userId, FEATURE_KEY); $isSameVariation = $userVariation->key == $anonymousVariation->key; // true // The `userId` and `anonymousVisitorCode` are now linked and tracked as a single visitor. $kameleoonClient->trackConversion($userId, 123, 10.0); // Additionally, the linked visitors will share all fetched remote visitor data. $kameleoonClient->getRemoteVisitorData($userId); ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`getVisitorCode()`](#getvisitorcode) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\CustomData(1, "newVisitorCode")); ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```php theme={null} use Kameleoon\logging\KameleoonLogger; use Kameleoon\logging\LogLevel; // The `NONE` log level does not allow logging. KameleoonLogger::setLogger(LogLevel::NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's primary behavior. KameleoonLogger::setLogger(LogLevel::ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. KameleoonLogger::setLogger(LogLevel::WARNING); // The `INFO` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. KameleoonLogger::setLogger(LogLevel::INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. KameleoonLogger::setLogger(LogLevel::DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```php theme={null} use Kameleoon\logging\KameleoonLogger; use Kameleoon\logging\Logger; use Kameleoon\logging\LogLevel; use Monolog\Logger as MonologLogger; public class CustomLogger implements Logger { // Monolog logger private MonologLogger $inner; public function __construct(MonologLogger $inner) { $this->inner = $inner; } // `log` method accepts logs from the SDK public function log($level, string $message): void { // Custom log handling logic here. For example: switch ($level) { case LogLevel::ERROR: $this->inner->error($message); break; case LogLevel::WARNING: $this->inner->warning($message); break; case LogLevel::INFO: $this->inner->info($message); break; case LogLevel::DEBUG: $this->inner->debug($message); break; } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. KameleoonLogger::setLogLevel(LogLevel::DEBUG); // Optional; defaults to `LogLevel::WARNING`. KameleoonLogger::setLogger(new CustomLogger($inner)); ``` ## Reference This is a full reference documentation of the PHP SDK. ### Initialization #### create() This method in `Kameleoon\KameleoonClientFactory` creates a `KameleoonClient` instance by providing your SDK configuration in a configuration file. You need to initialize the SDK by creating this instance of `KameleoonClient` before you can use other SDK methods. All interactions with the SDK use this `KameleoonClient` instance. To provide the configuration as a `KameleoonClientConfig` object instead, see the [`createWithConfig`](#createWithConfig) method. ```php theme={null} require "vendor/autoload.php"; use Kameleoon\KameleoonClientFactory; use Kameleoon\Exception\ConfigCredentialsInvalid; use Kameleoon\Exception\KameleoonException; use Kameleoon\Exception\SiteCodeIsEmpty; $siteCode = "a8st4f59bj"; try { // Read from default configuration path: "/tmp/kameleoon/php-client/" $kameleoonClient = KameleoonClientFactory::create($siteCode); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } try { $kameleoonClient = KameleoonClientFactory::create($siteCode, "custom/file/path/client-php.json"); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } ``` ##### Parameters | Name | Type | Description | | --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | siteCode | String | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | configurationFilePath | String | Path to the SDK configuration file. This field is optional and set to `/tmp/kameleoon/client-php.json` by default. | ##### Return value | Type | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------ | | Kameleoon\KameleoonClient | An instance of the **KameleoonClient** class that will be used to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | SiteCodeIsEmpty | Exception indicating that the requested credentials weren't provided (either using the configuration file or the config parameter in the method). | | ConfigCredentialsInvalid | Exception indicating that the requested credentials weren't provided (either using the configuration file or the config parameter in the method). | | KameleoonException | Exception that may indicate that the SDK is unable to access the **Kameleoon working directory**. | #### createWithConfig() This method in `Kameleoon\KameleoonClientFactory` creates a `KameleoonClient` instance and allows you to pass your SDK configuration in a `KameleoonClientConfig` object. You need to initialize the SDK by creating this `KameleoonClient` instance before you can use other SDK methods. All interactions with the SDK use this `KameleoonClient` instance. To provide your SDK configuration in a file instead, use the [`create`](#create) method. ```php theme={null} require "vendor/autoload.php"; use Kameleoon\KameleoonClientConfig; use Kameleoon\KameleoonClientFactory; use Kameleoon\Exception\ConfigCredentialsInvalid; use Kameleoon\Exception\KameleoonException; use Kameleoon\Exception\SiteCodeIsEmpty; $siteCode = "a8st4f59bj"; try { $cookieOptions = KameleoonClientConfig::createCookieOptions( "example.com", // domain: optional, but strictly recommended false, // secure: optional (false by default) false, // httponly: optional (false by default) "Lax" // samesite: optional (Lax by default) ); $config = new KameleoonClientConfig( "", // clientId: mandatory "", // clientSecret: mandatory "/tmp/kameleoon/php-client/", // kameleoonWorkDir: optional / ("/tmp/kameleoon/php-client/" by default) 60, // refreshIntervalMinute: in minutes, optional (60 minutes by default) 10_000, // defaultTimeoutMillisecond: in milliseconds, optional (10_000 ms by default) false, // debugMode: optional (false by default) $cookieOptions, // cookieOptions: optional "development", // environment: optional ("production" by default) "example.com", // networkDomain: optional (null by default) 1024*1024, // requestBodySizeLimitBytes: optional (2560 * 1024 by default) ); $kameleoonClient = KameleoonClientFactory::create($siteCode, $config); } catch (SiteCodeIsEmpty $ex) { // indicates that provided site code is empty } catch (ConfigCredentialsInvalid $ex) { // indicates that provided clientId / clientSecret are not valid } catch (KameleoonException $ex) { // probably indicates that the SDK is unable to access the **Kameleoon working directory** } ``` ##### Parameters | Name | Type | Description | | --------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | siteCode | String | Code of the website you want to run experiments on. This unique code ID can be found in the Kameleoon app. This field is mandatory. | | kameleoonConfig | KameleoonClientConfig | Configuration SDK object that you pass. This field is optional. | ##### Return value | Type | Description | | --------------- | ------------------------------------------------------------------------------------------------------- | | KameleoonClient | An instance of the **KameleoonClient** class that you use to manage your experiments and feature flags. | ##### Exceptions thrown | Type | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | SiteCodeIsEmpty | Exception indicating that the requested credentials weren't provided (either using the configuration file or the config parameter in the method). | | ConfigCredentialsInvalid | Exception indicating that the requested credentials weren't provided (either using the configuration file or the config parameter in the method). | | KameleoonException | Exception that may indicate that the SDK is unable to access the **Kameleoon working directory**. | #### waitInit() `waitInit()` ensures the SDK is ready for use: its configuration is downloaded, valid, and up to date. The method returns immediately if the configuration has already been downloaded and is not outdated. Otherwise, it synchronously downloads the configuration, blocking until the download completes, fails, or the timeout expires. If you don't provide a `timeout` value, the SDK uses the default timeout from [`defaultTimeoutMillisecond`](#additional-configuration). Calling `waitInit()` before using the SDK is not required: every other SDK method loads the configuration in the same way. Calling it explicitly lets you handle initialization errors in a single place. `waitInit()` fails only if the SDK could not be initialized at all: the configuration could not be downloaded and no valid local copy is available. If a local copy exists but the update request fails, the method returns successfully and the SDK keeps using the existing configuration. ```php theme={null} use Kameleoon\Exception\Initialization; try { // Waits up to the default timeout of the client configuration $kameleoonClient->waitInit(); // Waits no longer than the specified timeout $kameleoonClient->waitInit(1000); } catch (Initialization $ex) { // The SDK could not be initialized: the configuration could not be downloaded // and no valid local copy is available. Use $ex->getPrevious() to get the cause. } ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------- | ----- | ----------------------------------------------------------------------- | --------------------------- | | `timeout` optional | `int` | The maximum time to wait for an initialization result, in milliseconds. | `defaultTimeoutMillisecond` | ##### Exceptions thrown | Type | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Initialization` | The SDK has no configuration: it couldn't be downloaded and no valid local copy is available. The underlying failure is available as the cause of the exception. | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`waitInit()`](#waitinit), this method returns immediately without blocking or throwing. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```php theme={null} $ready = $kameleoonClient->isReady(); ``` ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `bool` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### isFeatureActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* This method was previously called `activateFeature`, which was removed in SDK version `4.0.0`. This method takes a **visitorCode** and **featureKey** as mandatory arguments to check if the specified feature will be active for a given user. If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly (**true** if the user should have this feature or **false** if not). If a user with a given **visitorCode** is already registered with this feature flag, it will detect the previous **FeatureFlag** value. You have to make sure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitorCode`, the `isFeatureActive()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```php theme={null} $visitorCode = $kameleoonClient->getVisitorCode(); $featureKey = "new_checkout"; $hasNewCheckout = false; try { $hasNewCheckout = $kameleoonClient->isFeatureActive($visitorCode, $featureKey, $timeout); // disabling tracking $hasNewCheckout = $kameleoonClient->isFeatureActive($visitorCode, $featureKey, $timeout, null, false); } catch (Kameleoon\Exception\FeatureNotFound $e) { // Feature toggle not yet activated on Kameleoon's side - we consider the feature inactive. $hasNewCheckout = false; } catch (Kameleoon\Exception\VisitorCodeInvalid $e) { // VisitorCode, which you passed to a method, is invalid and can't be accepted. } catch (Kameleoon\Exception\DataFileInvalid $e) { // It appears that the configuration has not been loaded and // there is no previously saved version of the configuration available. } catch (Exception $e) { // This is a generic Exception handler which will handle all exceptions. echo "Exception: ", $e->getMessage(), "\n"; } if ($hasNewCheckout) { // Implement new checkout code here. } ``` The `isFeatureActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | featureKey | string | Key of the feature you want to expose to a user. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | | isUniqueIdentifier (Deprecated) | ?bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `null`. The field is optional. | | track | bool | An optional parameter to enable or turn off tracking of the feature evaluation (`true` by default). | ##### Return value | Type | Description | | ---- | ------------------------------------------------------------------------ | | bool | Value of the feature flag that's registered for a given **visitorCode**. | ##### Exceptions thrown | Type | Description | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitorCode` and `featureKey` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```php theme={null} $featureKey = "new_checkout"; try { $variation = $kameleoonClient->getVariation($visitorCode, $featureKey); // disabling tracking $variation = $kameleoonClient->getVariation($visitorCode, $featureKey, false); } catch (Kameleoon\Exception\FeatureNotFound $e) { // An error has occurred; the feature flag isn't found in the current configuration. } catch (Kameleoon\Exception\FeatureEnvironmentDisabled $e) { // The feature flag is disabled for the environment. } catch (Kameleoon\Exception\VisitorCodeInvalid $e) { // The visitor code you passed to the method is invalid and can't be accepted by the SDK. } // Fetch a variable value for the assigned variation $title = $variation->variables["title"]->value; switch ($variation->key) { case "on": // Main variation key is selected for visitorCode break; case "alternative_variation": // Alternative variation key break; default: // Default variation key break; } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```php theme={null} try { $variations = $kameleoonClient->getVariations($visitorCode); // only active variations $variations = $kameleoonClient->getVariations($visitorCode, true); // disable tracking $variations = $kameleoonClient->getVariations($visitorCode, $onlyActive, false); } catch (Kameleoon\Exception\VisitorCodeInvalid $e) { // Handle exception } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `onlyActive` optional | `bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `array` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```php theme={null} $experimentId = 9516; try { // Forcing the variation "on" for the experiment 9516 for the visitor $kameleoonClient->setForcedVariation($visitorCode, $experimentId, "on"); // Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment $kameleoonClient->setForcedVariation($visitorCode, $experimentId, "on", false); // Resetting the forced variation for the experiment 9516 for the visitor $kameleoonClient->setForcedVariation($visitorCode, $experimentId, null); } catch (Kameleoon\Exception\KameleoonException $e) { // Handling the error } ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `experimentId` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variationKey` required | `?string` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `bool` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | `timeout` optional | `?int` | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | `null` | ##### Exceptions thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```php theme={null} try { $kameleoonClient->evaluateAudiences($visitorCode); } catch (Kameleoon\Exception\KameleoonException $e) { // Handling the exception } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `timeout` optional | `?int` | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | `null` | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```php theme={null} try { $dataFile = $kameleoonClient->getDataFile(); } catch (Kameleoon\Exception\KameleoonException $e) { // Recommended (but optional) safeguard for unexpected exceptions from third-party libraries } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | timeout optional | `?int` | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### getVisitorCode() This method was previously called `obtainVisitorCode`, which was removed in SDK version `4.0.0`. Call this method to obtain the Kameleoon **visitorCode** for the current visitor. Calling is especially important when using Kameleoon in a mixed front-end and back-end environment, where user identification consistency must be guaranteed. The implementation logic is described here: 1. The SDK first checks whether there is a **kameleoonVisitorCode** cookie or query parameter associated with the current HTTP request. If so, it uses this as the visitor identifier. 2. If no cookie or parameter is present in the current request, the SDK has two options for generating an identifier. One option is to create a new identifier randomly, and the other is to use the **defaultVisitorCode** argument if it has been provided. This feature lets you input your own identifiers as visitor codes, which can be beneficial; it seamlessly matches Kameleoon visitors with your own users, eliminating the need for extra look-ups in a matching table. 3. In any case, the server-side (via HTTP header) **kameleoonVisitorCode** cookie is set with the value. The method returns this visitor value. For more information, refer to [this article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). If you provide your own `visitorCode`, its uniqueness must be guaranteed on your end - the SDK can't check it. Also note that the length of `visitorCode` is limited to **255** characters. Any excess characters will throw an exception. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```php theme={null} require "vendor/autoload.php"; // The cookie's domain must be provided in the configuration file if no argument is given. $visitorCode = $kameleoonClient->getVisitorCode(); // default visitor code provided $visitorCode = $kameleoonClient->getVisitorCode($defaultVisitorCode); ``` ##### Parameters | Name | Type | Description | | ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | defaultVisitorCode | String | This parameter will be used as the **visitorCode** if no existing **kameleoonVisitorCode** cookie is found on the request. This field is optional, and, by default, a random **visitorCode** will be generated. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------- | | String | A **visitorCode** that will be associated with this particular user and should be used with most of the methods of the SDK. | ##### Exceptions thrown | Type | Description | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | InvalidArgumentException | Exception indicating that the cookie's domain value wasn't provided (either via the configuration file, or via the **topLevelDomain** parameter on the method). | #### addData() The `addData()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `addData()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`trackConversion()`](#trackconversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`getVariation()`](#getvariation) and [`getVariations()`](#getvariations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```php theme={null} // Add a single data item (tracked by default) $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Browser(Kameleoon\Data\Browser::CHROME)); // Add multiple data items (tracked by default) $kameleoonClient->addData( $visitorCode, new Kameleoon\Data\PageView("https://url.com", "title", [3]), new Kameleoon\Data\UserAgent("UserAgent") ); // Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) $kameleoonClient->addData( $visitorCode, false, new Kameleoon\Data\PageView("https://url.com", "title", [3]), new Kameleoon\Data\UserAgent("UserAgent") ); ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `track` optional | `bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | | `data` required | `...Data` | Collection of Kameleoon data types. | | ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * 📨 *Sends Tracking Data to Kameleoon* The `flush()` method is responsible for sending the Kameleoon data linked to a specific visitor. It triggers a tracking request that includes all the data previously added with the `addData` method, which hasn't yet been sent during an earlier call to [one of the methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics). `flush()` is non-blocking, as the server call is made asynchronously, unless the `instant` parameter is set to `true`. The `flush()` function lets you decide when the data linked to a specific `visitorCode` is sent to the server. For example, if you call `addData()` multiple times—say a dozen times—it would be inefficient to send data to the server every time you perform a call. Instead, you can gather all your data first and then call `flush()` once at the end to send everything at once. If you specify a `visitorCode`, the `flush()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```php theme={null} $visitorCode = $kameleoonClient->getVisitorCode(); $kameleoonClient->addData( $visitorCode, new Kameleoon\Data\Browser(Kameleoon\Data\Browser::CHROME). new Kameleoon\Data\PageView("https://url.com", "title", array(3)), new Kameleoon\Data\Conversion(32, 10, false) ); $kameleoonClient->flush($visitorCode); // Interval tracking, non-blocking operation $kameleoonClient->flush($visitorCode, null, null, true); // Instant tracking, blocking operation // if you operate with unique ID $kameleoonClient->addData($visitorCode, new Kameleoon\Data\UniqueIdentifier(true)); $kameleoonClient->flush($visitorCode); ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | | isUniqueIdentifier (Deprecated) | ?bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `null`. The field is optional. | | instant | bool | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). If not provided, the default value is `false`. This field is optional. | #### getRemoteData() This method was previously called `retrieveDataFromRemoteSource`, which was removed in SDK version `4.0.0`. The `getRemoteData()` method lets you retrieve data based on a **key** you provide for a specific **siteCode** (set in `KameleoonClientFactory.create()`). A Kameleoon server stores this data. The Data API is typically used to save this data on remote servers. This method, combined with Kameleoon's scalable servers, makes storing large amounts of data easy. You can then access this data later for each visitor or user. ```php theme={null} $test_value = $kameleoonClient->getRemoteData("test"); // default timeout will be used $test_value = $kameleoonClient->getRemoteData("test", 1000); // 1000 milliseconds timeout try { $test_value = $kameleoonClient->getRemoteData("test"); } catch (Exception $e) { // Timeout or Json Decoding Exception } ``` ##### Parameters | Name | Type | Description | | ------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | string | The key that the data you try to get is associated with. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ------ | ------------------------------------------------------------ | | Object | Object associated with retrieving data for specific **key**. | ##### Exceptions thrown | Type | Description | | --------- | ------------------------------------------------------------------------------------------------------------- | | Exception | Exception indicating that the request timed out or retrieved data can't be decoded with `json_decode` method. | #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. ```php theme={null} $visitorCode = "visitorCode"; // Visitor data will be fetched and automatically added for `visitorCode` $dataArray = $kameleoonClient->getRemoteVisitorData($visitorCode, null); // default timeout will be used $dataArray = $kameleoonClient->getRemoteVisitorData($visitorCode, 1000); // 1000 milliseconds timeout // If you only want to fetch data and add it yourself manually, set shouldAddData == `false` $dataArray = $kameleoonClient->getRemoteVisitorData($visitorCode, null, false); // default timeout will be used $dataArray = $kameleoonClient->getRemoteVisitorData($visitorCode, 1000, false); // 1000 milliseconds timeout ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | `string` | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | timeout | `?int` | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | | addData | `bool` | A boolean indicating whether the method should automatically add retrieved data for a visitor. If not specified, the default value is **true**. This field is optional. | | filter | `Kameleoon\Types\RemoteVisitorDataFilter` | Filter for specifying what data should be retrieved from visits, by default only `CustomData` is retrieved from the current and latest previous visit (`new RemoteVisitorDataFilter(1, true, true)` or `new RemoteVisitorDataFilter()`). Other filters parameters are set to `false`. This field is optional. | | isUniqueIdentifier (Deprecated) | `?bool` | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `null`. The field is optional. | ##### Return value | Type | Description | | ----------------------- | --------------------------------------------- | | `array` | A list of data assigned to the given visitor. | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `Kameleoon\Types\RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `bool` | If true, current visit data will be retrieved | `true` | | customData optional | `bool` | If true, custom data will be retrieved. | `true` | | pageViews optional | `bool` | If true, page data will be retrieved. | `false` | | geolocation optional | `bool` | If true, geolocation data will be retrieved. | `false` | | device optional | `bool` | If true, device data will be retrieved. | `false` | | browser optional | `bool` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `bool` | If true, operating system data will be retrieved. | `false` | | conversions optional | `bool` | If true, conversion data will be retrieved. | `false` | | experiments optional | `bool` | If true, experiment data will be retrieved. | `false` | | kcs optional | `bool` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `bool` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | cbs optional | `bool` | If true, Contextual Bandit score data will be retrieved. | `false` | | personalization optional | `bool` | If true, personalization data will be retrieved. This is required for the personalization condition. | `false` | #### getVisitorWarehouseAudience() This method retrieves all audience data associated with the visitor in your data warehouse using the specified `visitorCode` and `warehouseKey`. The `warehouseKey` is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method passes the result to the returned future as a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. ```php theme={null} $warehouseAudienceCustomData = $kameleoonClient->getVisitorWarehouseAudience($visitorCode, $customDataIndex); // If you need to specify warehouse key $warehouseAudienceCustomData = $kameleoonClient->getVisitorWarehouseAudience( $visitorCode, $customDataIndex, $warehouseKeyValue ); // If you need to specify warehouse key & timeout $warehouseAudienceCustomData = $kameleoonClient->getVisitorWarehouseAudience( $visitorCode, $customDataIndex, $warehouseKeyValue, 2000 ); ``` ##### Parameters | Name | Type | Description | | --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The unique identifier of the visitor for whom you want to retrieve and add the data. | | customDataIndex | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | warehouseKey | string | The unique key to identify the warehouse data (usually, your internal user ID). This field is optional. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `?Customdata` | `CustomData` instance confirming that the data has been added to the visitor. If value is `null`, the request is failed and `CustomData` wasn't added to the visitor. | ##### Exceptions thrown | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid (it's either empty or longer than 255 characters). | #### setLegalConsent() You must use this method to specify whether the visitor has given legal consent to use thier personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```php theme={null} $visitorCode = $kameleoonClient->getVisitorCode(); $kameleoonClient->setLegalConsent($visitorCode, true); ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | The user's unique identifier. This field is required. | | legalConsent | bool | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. This field is required. | ##### Exceptions thrown | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | ##### Consent revocation behavior When you call `setLegalConsent()` with `legalConsent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third party analytics #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```php theme={null} $engineTrackingCode = $kameleoonClient->getEngineTrackingCode($visitorCode); ``` * To use this feature, implement both the PHP SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```php theme={null} require "vendor/autoload.php"; $kameleoonClient = Kameleoon\KameleoonClientFactory::create("a8st4f59bj", "/tmp/kameleoon/client-php.json"); $visitorCode = $kameleoonClient->getVisitorCode(); $goalID = 83023; $kameleoonClient->trackConversion($visitorCode, $goalID); // if you operate with unique ID $kameleoonClient->addData($visitorCode, new Kameleoon\Data\UniqueIdentifier(true)); $kameleoonClient->trackConversion($visitorCode, $goalID, 0.0); // Add metadata $cd = new Kameleoon\Data\CustomData(1, "metadata"); $kameleoonClient->trackConversion($visitorCode, $goalID, 0.0, null, null, false, [$cd]); ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion. | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `?array` | Lets you set specific values for custom data which have been defined as metadata for the goal in the Kameleoon App. Example: `[CustomData{id: 5, value: "Payment Type"}, CustomData{id: 6, value: "Delivery Method"}]`. In this example, `5` and `6` are the indexes of the custom data (5 = “Payment Type”, 6 = “Delivery Method”). | `null` | | `isUniqueIdentifier` *(deprecated)* | `bool` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```php theme={null} $kameleoonClient->addData( $visitorCode, new CustomData(5, "Credit Card"), new CustomData(9, "Express Delivery") ); $kameleoonClient->trackConversion($visitorCode, 10, 0.0, null, null, false, [new CustomData(5, "Amex Credit Card")]); ``` ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ### Events #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. ```php theme={null} use Kameleoon\Events\DataFileUpdateEvent; use Kameleoon\Events\DataFileUpdateHandler; use Kameleoon\Events\EventType; class CustomDataFileUpdateHandler implements DataFileUpdateHandler { public function onUpdate(DataFileUpdateEvent $event): void { $source = $event->getSource(); // DataFileUpdateEvent::SOURCE_POLLING $dateModified = $event->getDateModified(); // Data file modification date in milliseconds. // React to the data file update. } } $kameleoonClient->setEventHandler(EventType::DATAFILE_UPDATE, new CustomDataFileUpdateHandler()); // Clear the handler. $kameleoonClient->setEventHandler(EventType::DATAFILE_UPDATE, null); ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `source` | `string` | The update source. The only possible value is [`DataFileUpdateEvent::SOURCE_POLLING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default), which indicates a scheduled data file refresh. The PHP SDK doesn't support the [streaming](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) mode. | | `dateModified` | `int` | The modification date of the updated data file, in milliseconds. | ```php theme={null} use Kameleoon\Events\EventType; use Kameleoon\Events\HttpRequestFailure; use Kameleoon\Events\HttpRequestHandler; class CustomHttpRequestHandler implements HttpRequestHandler { public function onRequestSucceeded(string $requestType, int $httpStatus, int $durationMillis): void { // The SDK request completed successfully. } public function onRequestFailed(string $requestType, HttpRequestFailure $failure, int $durationMillis): void { $reason = $failure->getReason(); // One of the HttpRequestFailure::REASON_* constants. $httpStatus = $failure->getHttpStatus(); $cause = $failure->getCause(); // The SDK request failed. } } $kameleoonClient->setEventHandler(EventType::HTTP_REQUEST, new CustomHttpRequestHandler()); // Clear the handler. $kameleoonClient->setEventHandler(EventType::HTTP_REQUEST, null); ``` The SDK reports `RequestType::TRACKING` events only for tracking requests sent with the [`flush(instant: true)`](#flush) method. `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `onRequestSucceeded()` for successful responses and `onRequestFailed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | | `onRequestSucceeded()` | Called when an SDK HTTP request completes successfully. | | `onRequestFailed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ---------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `requestType` | `string` | The SDK request type. Possible values are `RequestType::DATAFILE`, `RequestType::TRACKING`, `RequestType::REMOTE_VISITOR_DATA`, `RequestType::REMOTE_DATA`, and `RequestType::ACCESS_TOKEN`. | | `httpStatus` | `int` | The HTTP status code returned by the request. Passed only to `onRequestSucceeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `onRequestFailed()`. | | `durationMillis` | `int` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `string` | The failure reason. Possible values are `HttpRequestFailure::REASON_HTTP_STATUS`, `HttpRequestFailure::REASON_EXCEPTION`, and `HttpRequestFailure::REASON_CANCELLED`. | | `httpStatus` | `?int` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `null` for exception and cancellation failures. | | `cause` | `mixed` | The exception (error) that caused the request to fail. This value is `null` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` required | `string` | The SDK event type to handle. Use `EventType::DATAFILE_UPDATE` to react to SDK data file updates, or `EventType::HTTP_REQUEST` to react to completed SDK HTTP requests. | | `handler` required | `?EventHandler` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventType::DATAFILE_UPDATE`, or an `HttpRequestHandler` for `EventType::HTTP_REQUEST`. Pass `null` to remove the current handler. | ### Data types You can use the following pre-defined data types from `Kameleoon\Data`. #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | ------------------------------------- | ------- | ----------------------------------------------------------------------------------------------- | | `browserType` required | `int` | List of browsers: `CHROME`, `INTERNET_EXPLORER`, `FIREFOX`, `SAFARI`, `OPERA`, `OTHER`. | | `version` optional | `float` | Version of the browser, floating point number represents major and minor version of the browser | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Browser(Kameleoon\Data\Browser::CHROME)); $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Browser(Kameleoon\Data\Browser::CHROME, 10.0)); ``` #### PageView | Name | Type | Description | Default | | --------------------------------- | ------------- | -------------------------------------------------- | ------- | | url required | `string` | URL of the page viewed. This field is mandatory. | | | title required | `?string` | Title of the page viewed. This field is mandatory. | `null` | | referrers optional | `?array` | Referrers of viewed pages. This field is optional. | `null` | The index (ID) of the referrer is available in the Back-Office in the Acquisition channel configuration page. Be careful: this index starts at 0, so the first [acquisition channel](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) you create for a given site would have the ID 0, not 1. ```php theme={null} $kameleoonClient->addData( $visitorCode, new Kameleoon\Data\PageView("https://url.com", "title", [3]) ); ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | -------------------- | ----------------------------------------------- | ------- | | `goalId` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `?array` | Metadata of the conversion. | `null` | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Conversion(32, 10, false)); $cd = new Kameleoon\Data\CustomData(1, "metadata"); $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Conversion(32, 0.0, false, [$cd])); ``` #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ----------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | indexOrName required | `int`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `string...` | Values of the custom data to store. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\CustomData(1, "value")); // With several values $kameleoonClient->addData($visitorCode, new Kameleoon\Data\CustomData(1, "value1", "value2")); // To set the 'overwrite' flag to false $kameleoonClient->addData($visitorCode, Kameleoon\Data\CustomData::newWithOverwrite(1, false, "value")); // To use a name instead of the index $kameleoonClient->addData($visitorCode, new Kameleoon\Data\CustomData("my-custom-data", "value")); // To use a name instead of the index // and set the 'overwrite' flag to false $kameleoonClient->addData($visitorCode, Kameleoon\Data\CustomData::newWithOverwrite("my-custom-data", false, "value")); ``` #### Device | Name | Type | Description | | ---- | ---- | ----------------------------------------------------------------- | | type | int | List of devices: PHONE, Device, DESKTOP. This field is mandatory. | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Device(Kameleoon\Data\Device::PHONE)); ``` #### UserAgent Keep track of visitors' user-agent information. Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to tackle this issue and recognize known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that might distort your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. | Name | Type | Description | | ----- | ------ | --------------------------------------------------------------------------------------- | | value | string | The User-Agent value that will be sent with tracking requests. This field is mandatory. | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\UserAgent("TestUserAgent")); ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data with the visitor associated with the specified identifier. The `isUniqueIdentifier` can be helpful in unique situations; for example, if you can't access the anonymous `visitorCode` given to a visitor, but you can use an internal ID linked to that visitor through session merging. | Name | Type | Description | | ----- | ---- | --------------------------------------------------------------------------------------------- | | value | bool | Parameter for specifying if the `visitorCode` is a unique identifier. This field is required. | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\UniqueIdentifier(true)); ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. | Name | Type | Description | | ---- | ----- | ------------------------------------------------------------------------------------------------------------------- | | type | `int` | List of operating systems: `WINDOWS`, `MAC`, `IOS`, `LINUX`, `ANDROID` and `WINDOWS_PHONE`. This field is required. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\OperatingSystem(Kameleoon\Data\OperatingSystem::WINDOWS)); ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. | Name | Type | Description | | ------- | ------- | --------------------------------------------------------------------------------- | | cookies | `array` | A string object map consisting of cookie keys and values. This field is required. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```php theme={null} $cookie = new Kameleoon\Data\Cookie([ "k1" => "v1", "k2" => "v2", ]); $kameleoonClient->addData($visitorCode, $cookie); ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `string` | The country of the visitor. | | `region` optional | `?string` | The region of the visitor. | | `city` optional | `?string` | The city of the visitor. | | `postalCode` optional | `?string` | The postal code of the visitor. | | `latitude` optional | `float` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `float` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\Geolocation("France", "Île-de-France", "Paris")); ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```php theme={null} $kameleoonClient->addData($visitorCode, new Kameleoon\Data\ApplicationVersion("10")); // major $kameleoonClient->addData($visitorCode, new Kameleoon\Data\ApplicationVersion("10.20")); // major.minor $kameleoonClient->addData($visitorCode, new Kameleoon\Data\ApplicationVersion("10.20.30")); // major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | ---------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `array` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `int` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```php theme={null} // Retrieves the array of feature flags from the DataFile. // The array is keyed by feature flag identifiers, with each value being a FeatureFlag object. $featureFlags = $dataFile->featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is an int representing milliseconds since the Unix epoch. $dateModified = $dataFile->getDateModified(); ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ---------------------- | -------------------------- | -------------------------------------------------------------------------- | | `isEnvironmentEnabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `variations` | `array` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `array` | A list of `Rule` objects | ```php theme={null} // Check whether the feature flag is enabled in the current environment $isEnvironmentEnabled = $featureFlag->isEnvironmentEnabled; // Retrieve the key of the default variation $defaultVariationKey = $featureFlag->defaultVariationKey; // Retrieve the default variation object $defaultVariation = $featureFlag->getDefaultVariation(); // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) $variations = $featureFlag->variations; // Retrieve all targeting rules associated with the feature flag $rules = $featureFlag->rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | -------------------------- | ------------------------------------------------------ | | `variations` | `array` | A map of `Variation` objects, keyed by variation keys. | ```php theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) $variations = $rule->variations; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation if no specific assignment exists). | Name | Type | Description | | ------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | `string` | The name of the variation. | | key | `string` | The unique key identifying the variation. | | id | `?int` | The ID of the assigned variation (or `null` if it's the default variation). | | experimentId | `?int` | The ID of the experiment associated with the variation (or `null` if default). | | variables | `array` | An array containing the variables of the assigned variation, keyed by variable names. This could be an empty collection if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` array might be empty if no variables are associated with the variation. ```php theme={null} // Retrieving the variation name $variationName = $variation->name; // Retrieving the variation key $variationKey = $variation->key; // Retrieving the variation id $variationId = $variation->id; // Retrieving the experiment id $experimentId = $variation->experimentId; // Retrieving the variables map $variables = $variation->variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | key | `string` | The unique key identifying the variable. | | type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS** | | value | `?mixed` | The value of the variable, which can be of the following types: **bool**, **int**, **float**, **string**, **stdClass**, **array**, **null**. | ```php theme={null} // Retrieving the variables map $variables = $variation->variables; // Variable type can be retrieved for further processing $type = $variables["isDiscount"]->type; // Retrieving the variable value by key $isDiscount = (bool) $variables["isDiscount"]->value; // Variable value can be of different types $title = (string) $variables["title"]->value; ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `5.0.0`. #### getFeatureVariationKey() * 📨 *Sends Tracking Data to Kameleoon* Use [`getVariation()`](#getvariation) instead. To get feature variation key, call the `getFeatureVariationKey()` method of the SDK. This method takes a **visitorCode** and **featureKey** as mandatory arguments to get the variation key for a given user. If a user has never been associated with this feature flag, the SDK returns a variation key randomly (according to the feature flag rules). If a user with a given **visitorCode** is already registered with this feature flag, it will detect the previous **variation key** value. If the user doesn't match any of the rules, the default value will be returned, which you can define in your account. You have to make sure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitorCode`, the `getFeatureVariationKey()` method uses it as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```php theme={null} $visitorCode = $kameleoonClient->getVisitorCode(); $featureKey = "featureKey"; $variationKey = ""; try { $variationKey = $kameleoonClient->getFeatureVariationKey($visitorCode, $featureKey); switch ($variationKey) { case "on": // Main variation key is selected for visitorCode break; case "alternativeVariation": // Alternative variation key break; default: // Default variation key break; } } catch (Kameleoon\Exception\FeatureNotFound $e) { // Feature toggle not yet activated on Kameleoon's side - we consider the feature inactive. } catch (Kameleoon\Exception\DataFileInvalid $e) { // It appears that the configuration has not been loaded // and there is no previously saved version of the configuration available. } catch (Kameleoon\Exception\VisitorCodeInvalid $e) { // VisitorCode, which you passed to a method, is invalid and can't be accepted. } catch (Kameleoon\Exception\FeatureEnvironmentDisabled){ // The feature flag is disabled for the environment. } catch (Exception $e) { // This is a generic Exception handler which will handle all exceptions. echo "Exception: ", $e->getMessage(), "\n"; } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | featureKey | string | Key of the feature you want to expose to a user. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | | isUniqueIdentifier (Deprecated) | ?bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `null`. The field is optional. | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------- | | string | Variation key of the feature flag that's registered for a given **visitorCode**. | ##### Exceptions thrown | Type | Description | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getActiveFeatureListForVisitor() Use [`getActiveFeatures()`](#getActiveFeatures) instead. This method takes only input parameters: **visitorCode**. Result contains only active feature flags for a given visitor. ```php theme={null} $visitorCode = "visitor"; $arrayFeatureFlagKeys = $kameleoonClient->getActiveFeatureListForVisitor($visitorCode); ``` ##### Arguments | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ---- | ---------------------------------------------------------------------- | | any | List of feature flag keys which are active for a given **visitorCode** | ##### Exceptions thrown | Type | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getActiveFeatures() Use [`getVariations()`](#getvariations) instead. `getActiveFeatures` method retrieves information about the active feature flags that are available for the specified visitor code. ```php theme={null} $visitorCode = "visitor"; $arrayActiveFeatures = $kameleoonClient->getActiveFeatures($visitorCode); ``` ##### Arguments | Name | Type | Description | | ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ----- | ----------------------------------------------------------------------------------------------------------- | | array | An array that contains the assigned variations of the active features using the active feature IDs as keys. | ##### Exceptions thrown | Type | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getFeatureVariable() * 📨 *Sends Tracking Data to Kameleoon* - Use [`getVariation()`](#getvariation) instead. - This method was previously called `obtainFeatureVariable`, which has been deprecated since SDK version `3.0.0` and will be removed in a future release. To get the variable of a variation key associated with a user, call the `getFeatureVariable()` method. This method takes a **visitorCode**, **featureKey**, and **variableName** as mandatory arguments to get a variable of the variation key for a given user. If the user has never been associated with this feature flag, the SDK returns a variable value of the variation key randomly (according to the feature flag rules). If a user with a given **visitorCode** is already registered with this feature flag, the method will detect the **variable** value for the associated **variation**. If the user doesn't match any of the rules, the default variable will be returned. Ensure proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitorCode`, the `getFeatureVariable()` method uses it as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitorCode` and set the `isUniqueIdentifier` parameter to `true`, the SDK links the flushed data with the visitor associated with the specified identifier. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` is useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```php theme={null} $visitorCode = $kameleoonClient->getVisitorCode(); $featureKey = "featureKey"; $variableName = "variableName"; try { $variationValue = $kameleoonClient->getFeatureVariable($visitorCode, $featureKey, $variableName); // Your custom code depending on variableValue } catch (Kameleoon\Exception\FeatureNotFound $e) { // Feature toggle not yet activated on Kameleoon's side - we consider the feature inactive. } catch (Kameleoon\Exception\FeatureEnvironmentDisabled){ // The feature flag is disabled for the environment. } catch (Kameleoon\Exception\VisitorCodeInvalid $e) { // VisitorCode, which you passed to a method, is invalid and can't be accepted. } catch (Kameleoon\Exception\FeatureVariableNotFound $e) { // Requested variable not defined on Kameleoon's side. } catch (Kameleoon\Exception\DataFileInvalid $e) { // It appears that the configuration has not been loaded // and there is no previously saved version of the configuration available. } catch (Exception $e) { // This is a generic Exception handler which will handle all exceptions. echo "Exception: " . $e->getMessage() . "\n"; } ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode | string | Unique identifier of the user. This field is mandatory. | | featureKey | string | Key of the feature you want to expose to a user. This field is mandatory. | | variableName | string | Name of the variable you want to get a value. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | | isUniqueIdentifier (Deprecated) | ?bool | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `null`. The field is optional. | ##### Return value | Type | Description | | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Any | Value of variable of variation that's registered for a given **visitorCode** for this feature flag. Possible types: bool, int, float, string, object, array | ##### Exceptions thrown | Type | Description | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the internal configuration of the SDK. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | FeatureVariableNotFound | Exception indicating that the requested variable hasn't been found. Check that the variable's ID (or key) matches your code. | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getFeatureVariationVariables() * Use [`getVariation()`](#getvariation) instead. * This method was previously called `getFeatureAllVariables`, which was removed in SDK version `4.0.0`. To retrieve the all feature variables, call the `getFeatureVariationVariables()` method. A feature variable can be changed easily via the Kameleoon web app. This method takes **featureKey** and **variationKey** as mandatory arguments. It will return the data with the object type, as defined on the web interface. The method throws an error (`FeatureNotFound`) if the requested feature flag hasn't been found in the SDK's client configuration. If the variation key isn't found, the method throws the `FeatureVariationNotFound` error. ```php theme={null} $featureKey = "test_feature_variables"; $variationKey = "on"; try { $variables = $kameleoonClient->getFeatureVariationVariables($featureKey, $variationKey); $firstName = $variables["firstName"]; } catch (Kameleoon\Exception\FeatureNotFound $e) { // The feature is not yet activated on Kameleoon's side. } catch (Kameleoon\Exception\FeatureEnvironmentDisabled){ // The feature flag is disabled for the environment. } catch (Kameleoon\Exception\FeatureVariationNotFound $e) { // The variation is not yet activated on Kameleoon's side, i.e., the associated experiment is not online. } catch (Kameleoon\Exception\DataFileInvalid $e) { // It appears that the configuration has not been loaded // and there is no previously saved version of the configuration available. } catch (Exception $e) { // This is a generic Exception handler which will handle all exceptions. echo "Exception: " . $e->getMessage() . "\n"; } ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | featureKey | string | Key of the feature flag you want to obtain. This field is mandatory. | | variationKey | string | Key of the variation you want to obtain. This field is mandatory. | | timeout | ?int | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Any | Value of the variation variable that's registered for a given **visitorCode** for this feature flag. Possible types: bool, int, float, string, object, array | ##### Exceptions thrown | Type | Description | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | FeatureVariationNotFound | Exception indicating that the requested variation ID hasn't been found in the SDK's internal configuration. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | | DataFileInvalid | Exception indicating that the configuration hasn't been loaded and there is no previously saved version of the configuration available. | #### getFeatureList() Returns a list of feature flag keys currently available for the SDK. ```php theme={null} $arrayFeatureKeys = $kameleoonClient->getFeatureList(); ``` ##### Parameters | Name | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | timeout optional | `?int` | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. If a timeout value isn't provided, the SDK uses the [`default_timeout`](#additional-configuration) specified in your configuration. | ##### Return value | Type | Description | | --------------- | ------------------------- | | `array` | List of feature flag keys | # Python SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/python-sdk Integrate the Kameleoon Python SDK to run experiments and activate feature flags on Python back-end servers, including Django environments. With the Python SDK, you can run experiments and activate feature flags on your back-end Python server. Integrating the SDK into your web app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: The Python SDK is at version 3.22.1. See the [changelog](https://github.com/Kameleoon/client-python/blob/main/CHANGELOG.md) for release history. **SDK methods**: For the full reference documentation of the Python SDK, see the [reference](#reference) section. ## Developer guide This guide helps you integrate the SDK in a few minutes and start running experiments in your Python applications. This tutorial will explain the setup of a simple A/B test to change the number of recommended products based on different variations. ### Getting started #### Installing the Python client You can install the SDK using a Python pip package. The package is hosted on the official pip repository, so you just have to run the following command: ```python theme={null} pip install kameleoon-client-python ``` #### Additional configuration You should provide credentials for the Python SDK via a configuration file, which you can also use to customize the SDK's behavior. A sample configuration file [can be obtained here](/assets/developer-docs/sdks/web-sdks/client-configs/client-python.yaml). Install this file to the default path `/etc/kameleoon/client-python.yaml`, but you can put it in another location and pass the path as an argument to the `KameleoonClient()` constructor method. With the current version of the Python SDK, these are the available keys: | Key | Description | Default value | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `client_id` required | Required for authentication to the Kameleoon service. To find your **client id**, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `client_secret` required | Required for authentication to the Kameleoon service. To find your **client secret**, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `session_duration_minute` optional | Designates the predefined time interval that Kameleoon stores the visitor and their associated data in memory (RAM). Note that increasing the session duration increases the amount of RAM that needs to be allocated to store visitor data. | `30` minutes | | `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` minutes | | `default_timeout_millisecond` optional | Specifies the timeout, in milliseconds, for network requests from the SDK. Set the value to 30 seconds or more if you don't have a stable connection. Some methods have an additional parameter that you can use to override the default timeout for that particular method. If you don't specify the timeout for a method explicitly, the SDK uses this default value. | `10000` milliseconds | | `tracking_interval_millisecond` optional | Specifies the interval for tracking requests in milliseconds. All visitors who Kameleoon evaluated for any feature flag or had data flushed are included in this tracking request, which the SDK performs once per interval. The minimum value is `1000` ms, which is also the default, and the maximum value is `5000` ms. | `1000` milliseconds | | `environment` optional | Environment from which the feature flag’s configuration is to be used. The value can be `production`, `staging`, `development`. See the [managing environments](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) article for details. | `production` | | `top_level_domain` *(required in hybrid mode)* | The current top-level domain for your website. Use the format: `example.com`. Don't include `https://`, `www`, or other subdomains. Kameleoon uses this information to set the corresponding cookie on the top-level domain. | `""` | | `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `None` | | `logger` *(deprecated)* | Allows overriding the default Python logger. This field is deprecated and will be removed in SDK version `4.0.0`. Use [`KameleoonLogger.set_logger()`](#logging) instead. | `logging.Logger` | | `multi_threading` *(deprecated)* | An option of type `bool` indicating whether threads can be used for network requests. By default everything is executed in one thread to avoid performance issues with GIL if (C)Python interpreter is being used. Possible values: `True`, `False`. | `None` | Alternatively, you can use `configuration_object` of type `KameleoonClientConfig` as a parameter during initialization. It has the same list of arguments as a config file. `configuration_object` takes precedence over the configuration file and overwrites its settings. #### Initializing the Kameleoon client After installing the SDK into your app, configuring the correct credentials (in `/etc/kameleoon/client-python.yaml`), and setting up a server-side experiment in Kameleoon's back-office, the next step is creating the Kameleoon client in your app code. The code on the right gives a clear example. A `KameleoonClient` is a singleton object that acts as a bridge between your app and the Kameleoon platform. It includes all the methods and properties you will need to run an experiment. Developers are responsible for ensuring the correct logic of their app code when implementing A/B testing with Kameleoon. A best practice is to always assume that a visitor may be excluded from the experiment if it hasn't yet been launched. This practice is simple to implement, as it aligns with the default or reference variation logic, which should always be in place. The code samples in the next section demonstrate this approach. ```python theme={null} from kameleoon import KameleoonClient, KameleoonClientConfig, KameleoonClientFactory SITE_CODE = 'a8st4f59bj' # Option 1 kameleoon_client = KameleoonClientFactory.create(SITE_CODE, config_path='/etc/kameleoon/client-python.yaml') # Option 2 configuration_object = KameleoonClientConfig.read_from_yaml('/etc/kameleoon/client-python.yaml') configuration_object.set_top_level_domain("example.com") kameleoon_client = KameleoonClientFactory.create(SITE_CODE, configuration_object) # Option 3 configuration_object = KameleoonClientConfig( "client_id", # required "client_secret", # required refresh_interval_minute=60, # (in minutes) optional, default: 60 minutes session_duration_minute=30, # (in minutes) optional, default: 30 minutes default_timeout_millisecond=10000, # (in milliseconds) optional, default: 10000 milliseconds tracking_interval_millisecond=1000, # (in milliseconds) optional, default: 1000 milliseconds environment="production", # optional, possible values: "production" / "staging" / "development" / "staging", default: None top_level_domain="example.com", multi_threading=False, # optional, default: False logger=my_logger, # optional, default: standard kameleoon logger. This field is deprecated and will be removed in SDK version `4.0.0`. Use `KameleoonLogger.set_logger` instead. network_domain="example.com", # optional ) kameleoon_client = KameleoonClientFactory.create(SITE_CODE, configuration_object) ``` #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`get_visitor_code()`](#get_visitor_code) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `default_visitor_code` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `get_visitor_code()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`get_variation()`](#get_variation) or [`is_feature_active()`](#is_feature_active) method to retrieve the configuration based on the `feature_key`. The `get_variation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `feature_key` and `visitor_code`. The `is_feature_active()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `get_variation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=True`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `get_variation()` method allows you to control whether tracking is done. If `track=False`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=False` is helpful when using the `get_variations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`add_data()`](#add_data) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`get_remote_visitor_data()`](#get_remote_visitor_data) method. This method asynchronously fetches data from the servers. It's important to call `get_remote_visitor_data()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`track_conversion()`](#track_conversion) method and provide the required `visitor_code` and `goal_id` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=True`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`get_engine_tracking_code()`](#get_engine_tracking_code) method. The `get_engine_tracking_code()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Using the Kameleoon Python SDK in a Django environment If you use Django, initialize the Kameleoon client at server start-up, in the `apps.py` file of your Django app. When you use python manage.py runserver, Django starts two processes: one for the actual development server, and the other to reload your app when the code changes. You can also start the server without the reload option, and you will only see one process running. The process is only executed once: `python manage.py runserver --noreload` You can also check the RUN\_MAIN env var in the `ready()` method. ```python theme={null} def ready(self): if os.environ.get('RUN_MAIN', None) == 'true': configuration_path = os.path.join(ROOT_DIR, 'path_to_config', 'config.yml') self.kameleoon_client = KameleoonClientFactory.create(SITE_CODE, config_path=configuration_path) ``` This only applies to local development when you use python manage.py runserver. In a production environment, the code in the `ready()` function will be executed only once when the app is initialized. ```python theme={null} from django.apps import apps my_application = apps.get_app_config('your_app') client = my_application.kameleoon_client ``` You can then access the Kameleoon client in your app. Another advantage of using Django is that the SDK will automaticallyo read and write the **visitor\_code** on the HTTP request/response via a cookie. If you're using another framework in a web environment where you would like to use a cookie mechanism to persist the **visitor\_code**, you must provide implementations of the `read_cookies()` and `write_cookies()` methods. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `get_remote_visitor_data()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `get_remote_visitor_data()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`get_remote_visitor_data()`](#get_remote_visitor_data) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitor_code`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```python title="Device A" theme={null} # In this example, Custom data with index `90` was set to "Visitor" scope in Kameleoon. VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90 kameleoon_client.add_data(visitor_code, CustomData(VISITOR_SCOPE_CUSTOM_DATA_INDEX, "your data")) kameleoon_client.flush(visitor_code) ``` ```python title="Device B" theme={null} # Before working with the data, call `get_remote_visitor_data`. kameleoon_client.get_remote_visitor_data(visitor_code) # After calling, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. # So, "your data" will be available to target and track the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`get_remote_visitor_data()`](#get_remote_visitor_data) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `get_remote_visitor_data()` with added `UniqueIdentifier(True)` - to retrieve data for all linked visitors. * [`track_conversion()`](#track_conversion) or [`flush()`](#flush) with added `UniqueIdentifier(True)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`get_remote_visitor_data()`](#get_remote_visitor_data) method on each device. Here's an example of how to use custom data for session merging. ```python theme={null} # In this example, `91` represents the Custom Data's index configured as a unique identifier in Kameleoon. MAPPING_INDEX = 91 FEATURE_KEY = "ff123" # 1. Before the visitor is authenticated # Retrieve the variation for an unauthenticated visitor. # Assume `anonymous_visitor_code` is the randomly generated ID for that visitor. anonymous_variation = kameleoon_client.get_variation(anonymous_visitor_code, FEATURE_KEY) # 2. After the visitor is authenticated # Assume `user_id` is the visitor code of the authenticated visitor. kameleoon_client.add_data(anonymous_visitor_code, CustomData(MAPPING_INDEX, user_id)) kameleoon_client.flush(anonymous_visitor_code, instant=True) # Indicate that `user_id` is a unique identifier. kameleoon_client.add_data(user_id, UniqueIdentifier(True)) # 3. After the visitor has been authenticated # Retrieve the variation for the `user_id`, which will match the anonymous visitor code's variation. user_variation = kameleoon_client.get_variation(user_id, FEATURE_KEY) is_same_variation = user_variation.key == anonymous_variation.key # True # The `user_id` and `anonymous_visitor_code` are now linked and tracked as a single visitor. kameleoon_client.track_conversion(user_id, 123, 10.0) # Additionally, the linked visitors will share all fetched remote visitor data. kameleoon_client.get_remote_visitor_data(user_id) ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`get_visitor_code()`](#get_visitor_code) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitor_code`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitor_code`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `account_id`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```python theme={null} from kameleoon.data import CustomData kameleoon_client.add_data(visitor_code, CustomData(index, "new_visitor_code")) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`add_data()`](#add_data) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `new_visitor_code` refers to the identifier you wish to use for your bucketing (for example, the new `user_id` or `account_id`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `add_data()` method, all hash calculations for assigning users to variations will use this `new_visitor_code` (your custom key) instead of the default `visitor_code`. Using the `new_visitor_code` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `new_visitor_code` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitor_code`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `str`. * It must be unique for the entity you intend to bucket (for example, if using a `user_id`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```python theme={null} from kameleoon.logging.log_level import LogLevel from kameleoon.logging.kameleoon_logger import KameleoonLogger # The `NONE` log level does not allow logging. KameleoonLogger.set_log_level(LogLevel.NONE) # The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. KameleoonLogger.set_log_level(LogLevel.ERROR) # The `WARNING` log level allows logging issues which may require additional attention. # It extends the `ERROR` log level. # The `WARNING` log level is a default log level. KameleoonLogger.set_log_level(LogLevel.WARNING) # The `INFO` log level allows logging general information on the SDK's internal processes. # It extends the `WARNING` log level. KameleoonLogger.set_log_level(LogLevel.INFO) # The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level # with more granular diagnostic output. # This information is not intended for end-user interpretation but can be sent to our support team # to assist with internal troubleshooting. KameleoonLogger.set_log_level(LogLevel.DEBUG) ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```python theme={null} from loguru import logger from kameleoon.logging.log_level import LogLevel from kameleoon.logging.logger import Logger class CustomLogger(Logger): """Custom logger implementation using loguru.""" def log(self, level: LogLevel, message: str) -> None: """Accepts logs from the SDK""" if level == LogLevel.ERROR: logger.error(message) elif level == LogLevel.WARNING: logger.warning(message) elif level == LogLevel.INFO: logger.info(message) elif level == LogLevel.DEBUG: logger.debug(message) from kameleoon.logging.kameleoon_logger import KameleoonLogger # Log level filtering is applied separately from log handling logic. # The custom logger will only accept logs that meet or exceed the specified log level. # Ensure the log level is set correctly. KameleoonLogger.set_logger(CustomLogger()) KameleoonLogger.set_log_level(LogLevel.DEBUG) # Optional, defaults to `LogLevel.WARNING`. ``` ## Reference This is a full reference documentation of the Python SDK. #### create() To start using the SDK, you must complete the initialization. All interactions with the SDK are completed through an object called `Kameleoon::KameleoonClient`, so the first thing you must do is create this object. ```python theme={null} kameleoon_config = KameleoonClientConfig("client_id", "client_secret") kameleoon_client = KameleoonClientFactory.create("a8st4f59bj", kameleoon_config) kameleoon_client = KameleoonClientFactory.create("a8st4f59bj", config_path="/etc/kameleoon/client-ruby.yaml") ``` ##### Arguments | Name | Type | Description | | ------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | site\_code | str | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | config | KameleoonClientConfig | Configuration SDK object that you can pass instead of using a configuration file. This field is optional. | | config\_path | str | Path to the SDK configuration file. This field is optional. The default value is `/etc/kameleoon/client-ruby.yaml` | ##### Exceptions thrown | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------ | | SiteCodeIsEmpty | Exception indicating that the specified site code is an empty string, which is an invalid value. | | ConfigFileNotFound | Exception indicating that the configuration file wasn't found. | #### wait\_init\_async() Use `wait_init_async()` when your application should wait asynchronously for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: `True` when the client is initialized, and `False` if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return `True`. If no initialization result is available before the timeout expires, the call returns `False`. If you don't provide a `timeout_milliseconds` value, the SDK uses the default timeout from [`default_timeout_millisecond`](#additional-configuration). The failure that prevented the SDK from initializing is reported to the log. ```python theme={null} # Waits up to the default timeout of the client configuration if await kameleoon_client.wait_init_async(): # The SDK has been initialized # Waits no longer than the specified timeout if await kameleoon_client.wait_init_async(1000): # The SDK has been initialized ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------------- | --------------- | ----------------------------------------------------------------------- | ----------------------------- | | `timeout_milliseconds` optional | `Optional[int]` | The maximum time to wait for an initialization result, in milliseconds. | `default_timeout_millisecond` | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------------------------------------- | | `bool` | `True` if the SDK is ready, or `False` if the initial configuration fetch failed or the timeout elapsed first. | #### wait\_init() Use `wait_init()` when your application should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: `True` when the client is initialized, and `False` if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return `True`. If no initialization result is available before the timeout expires, the call returns `False`. If you don't provide a `timeout_milliseconds` value, the SDK uses the default timeout from [`default_timeout_millisecond`](#additional-configuration). The failure that prevented the SDK from initializing is reported to the log. Call `wait_init()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `wait_init()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `True` as soon as a retry succeeds. ```python theme={null} # Waits up to the default timeout of the client configuration if kameleoon_client.wait_init(): # The SDK has been initialized # Waits no longer than the specified timeout if kameleoon_client.wait_init(1000): # The SDK has been initialized ``` ##### Parameters | Name | Type | Description | Default | | ---------------------------------------------- | --------------- | ----------------------------------------------------------------------- | ----------------------------- | | `timeout_milliseconds` optional | `Optional[int]` | The maximum time to wait for an initialization result, in milliseconds. | `default_timeout_millisecond` | ##### Return value | Type | Description | | ------ | -------------------------------------------------------------------------------------------------------------- | | `bool` | `True` if the SDK is ready, or `False` if the initial configuration fetch failed or the timeout elapsed first. | #### is\_ready() `is_ready()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`wait_init()`](#wait_init), this method returns immediately without blocking or throwing. * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. * Call `wait_init()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `wait_init()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `True` as soon as a retry succeeds. ```python theme={null} ready = kameleoon_client.is_ready() ``` ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `bool` | `True` if the SDK has been successfully initialized; `False` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### is\_feature\_active() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Previously called `activate_feature`—deprecated since SDK version `2.1.0` and will be removed in a future releases. To check if feature flag is active for a visitor, call the `is_feature_active()` method. This method takes a **visitor\_code** and **feature\_key** as mandatory arguments to check if the feature will be active for a given user. If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly (**true** if feature will be active for the user, or **false** if not). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the previous **feature flag** value. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `is_feature_active()` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `is_feature_active()`, `get_variation()` or `get_variations()`. Use the default `True` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `False` only if you call these methods before you expose visitors. For example, if you call `get_variations()` to retrieve all variations before you expose visitors, set the `track` parameter to `False`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```python theme={null} visitor_code = kameleoon_client.get_visitor_code(request.COOKIES) feature_key = "new_checkout" has_new_checkout = False try has_new_checkout = kameleoon_client.is_feature_active(visitor_code, feature_key) # disabling tracking has_new_checkout = kameleoon_client.is_feature_active(visitor_code, feature_key, track=False) except FeatureNotFound as ex: # The user will not be counted in the experiment, # but should see the reference variation. has_new_checkout = False except VisitorCodeInvalid as ex: # The visitor code you passed to the method isn't valid and can't be accepted by SDK. has_new_checkout = False if has_new_checkout # Implement new checkout code here ``` The `is_feature_active()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters | Name | Type | Description | | ----------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------- | | visitor\_code | str | Unique identifier of the user. This field is mandatory. | | feature\_key | str | ID or Key of the feature you want to expose to a user. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | When set to `True`, the SDK links the flushed data to the visitor associated with the specified identifier. | | track | bool | An optional parameter to enable or turn off tracking of the feature evaluation (`True` by default). | ##### Return value | Type | Description | | ---- | --------------------------------------------------------------------- | | bool | Value of the feature that's registered for a given **visitor\_code**. | ##### Exceptions thrown | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty or longer than 255 characters). | #### get\_variation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitor_code` and `feature_key` as mandatory arguments. The `track` argument is optional and defaults to `True`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```python theme={null} feature_key = "new_checkout" try: variation = kameleoon_client.get_variation(visitor_code, feature_key) # disabling tracking variation = kameleoon_client.get_variation(visitor_code, feature_key, False) except FeatureNotFound as ex: # The error has occurred; the feature flag isn't found in current configuration. except FeatureEnvironmentDisabled as ex: # The feature flag is disabled for the environment. except VisitoCodeNotValid as ex: # The visitor code you passed to the method is invalid and can't be accepted by SDK. # Fetch a variable value for the assigned variation title = variation.variables["title"].value if variation.key == "on": # Main variation key is selected for visitorCode elif variation.key == "alternative_variation": # Alternative variation key else: # Default variation key ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------ | ------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | | | `feature_key` required | `str` | Key of the feature you want to expose to a visitor. | | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `True` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### get\_variations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitor_code` as a mandatory argument, while `only_active` and `track` are optional. * If `only_active` is set to `True`, the method `get_variations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `True`. If set to `False`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```python theme={null} try: variations = kameleoon_client.get_variations(visitor_code) # only active variations variations = kameleoon_client.get_variations(visitor_code, only_active=True) # disable tracking variations = kameleoon_client.get_variations(visitor_code, track=False) except VisitorCodeInvalid as ex: # Handle exception ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | | | `only_active` optional | `bool` | An optional parameter indicating whether to return variations for active (`True`) or all (`False`) feature flags. | `False` | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `True` | ##### Return value | Type | Description | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Dict[str, Variation]` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_data\_file() To evaluate all feature flags, use [`get_variations()`](#get_variations). This method is more efficient than calling `DataFile` and iterating through flags with [`get_variation()`](#get_variation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```python theme={null} data_file = kameleoon_client.get_data_file() date_modified = data_file.date_modified ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | #### set\_forced\_variation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `force_targeting=False` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#get_visitor_code)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```python theme={null} experiment_id = 9516 try: # Forcing the variation "on" for the experiment 9516 for the visitor kameleoon_client.set_forced_variation(visitor_code, experiment_id, "on") # Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment kameleoon_client.set_forced_variation(visitor_code, experiment_id, "on", False) # Resetting the forced variation for the experiment 9516 for the visitor kameleoon_client.set_forced_variation(visitor_code, experiment_id, None) except KameleoonError as e: # Handling the error ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | | | `experiment_id` required | `int` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variation_key` required | `Optional[str]` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `None`, the forced variation will be reset. | | | `force_targeting` optional | `bool` | Indicates whether targeting for the experiment should be forced and skipped (`True`) or applied as in the standard evaluation process (`False`). | `True` | ##### Exceptions thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonError`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. #### evaluate\_audiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluate_audiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```python theme={null} try: kameleoon_client.evaluate_audiences(visitor_code) except KameleoonError as e: # Handling the error ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | ----- | --------------------------------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | In most cases, only the basic error, `KameleoonError`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Exception`. ### Visitor data #### get\_visitor\_code() This method was previously called `obtain_visitor_code`, which was removed in SDK version `3.0.0`. The `get_visitor_code()` helper method should be called to obtain the current visitor's Kameleoon **visitor\_code**. This method is especially important when using Kameleoon in a mixed front-end and back-end environment, where user identification consistency must be guaranteed. The implementation logic is described here: 1. The SDK first checks whether a **kameleoonVisitorCode** cookie or query parameter associated with the current HTTP request can be found. If so, it uses this as the visitor identifier. 2. If no cookie/parameter is found in the current request, the SDK either randomly generates a new identifier, or uses the **default\_visitor\_code** argument as an identifier if it's passed. This lets you use your own identifiers as visitor codes, should you wish to, which has the added benefit of matching Kameleoon visitors with your own users without any additional look-ups in a matching table. 3. In any case, the server-side (via HTTP header) **kameleoonVisitorCode** cookie is set with the value. Then, this identifier value is finally returned by the method. If you provide your own `visitor_code`, you must guarantee its uniqueness. The SDK doesn't validate the value passed as an argument. Also note that the length of `visitor_code` is limited to **255** characters. A `VisitorCodeInvalid` exception is raised if this limit is exceeded. The `get_visitor_code()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#set_forced_variation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```python theme={null} ### if you use KameleoonWSGIMiddleware service visitor_code = kameleoon_client.get_visitor_code(cookies_readonly=request.COOKIES) kameleoon_client.set_legal_consent(visitor_code, True) ### if you want to manage cookies manually simple_cookies = SimpleCookie() simple_cookies.load(cookie_header) visitor_code = kameleoon_client.get_visitor_code(cookies=simple_cookies, default_visitor_code=default_visitor_code) cookie_header = simple_cookies.output() ``` ##### Parameters | Name | Type | Description | | ---------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cookies\_readonly | Optional\[Dict\[str, str]] | Read-only dictionary, usually `request.COOKIES`. Use this parameter if you also use the `KameleoonWSGIMiddleware` service. This field is optional. | | cookies | Optinal\[Dict\[str, http.cookies.Morsel\[str]]] | Pass cookies on the current HTTP request as a`Dict[str, http.cookies.Morsel[str]]` or `http.cookies.\|SimpleCookie[str]` object if you manage cookies manually without `KameleoonWSGIMiddleware` service. This field is optional. | | default\_visitor\_code | str | This parameter will be used as the **visitor\_code** if no existing **kameleoonVisitorCode** cookie is found in the request. This field is optional, and by default, a random **visitor\_code** will be generated. | ##### Return value | Type | Description | | ---- | --------------------------------------------------------------------------------------------------------------- | | str | A **visitor\_code** that will be associated with this particular user and should be used with most SDK methods. | ##### Exceptions thrown | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid. It's either empty or longer than 255 characters. | #### add\_data() The `add_data()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `add_data()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`track_conversion()`](#track_conversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`get_variation()`](#get_variation) and [`get_variations()`](#get_variations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```python theme={null} require "kameleoon" require "kameleoon/data" # Add a single data item (tracked by default) kameleoon_client.add_data(visitor_code, Browser(BrowserType.CHROME)) # Add multiple data items (tracked by default) kameleoon_client.add_data( visitor_code, PageView("https://url.com", "title", [3]), UserAgent("UserAgent") ) # Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoon_client.add_data( visitor_code, PageView("https://url.com", "title", [3]), UserAgent("UserAgent"), track=False ) ``` ##### Parameters | Name | Type | Description | Default value | | -------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | | | `data` required | `*Data` | Collection of Kameleoon data types. | | | `track` optional | `bool` | Specifies whether the added data is eligible for tracking. When set to `False`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `True` | ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * 📨 *Sends Tracking Data to Kameleoon* `flush()` takes the Kameleoon data associated with the visitor and all of the data that was added previously using the `add_data` method, that hasn't yet been sent when calling one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics), and sends a tracking request. `flush()` is non-blocking, as the server call is made asynchronously. `flush()` lets you control when the data associated with a given `visitor_code` is sent to the server. For instance, if you call `add_data()` a dozen times, it would be inefficient to send data to the server each time `add_data()` is invoked, so you only have to call `flush()` once at the end. If you specify a `visitor_code`, the `flush()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} kameleoon_client.add_data(visitor_code, Browser(BrowserType.CHROME)) kameleoon_client.add_data( visitor_code, PageView("https://url.com", "title", [3]), CustomData(0, "value") ) kameleoon_client.add_data(visitor_code, Conversion(32, 10, false)) kameleoon_client.flush(visitor_code) # Interval tracking (most performant way for tracking) kameleoon_client.flush(visitor_code, instant=True) # Instant tracking # If you operate with unique ID kameleoon_client.add_data(UniqueIdentifier(True)) kameleoon_client.flush(visitor_code) ``` ##### Arguments | Name | Type | Description | | ----------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | visitor\_code | String | Unique identifier of the user. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | When `True`, the SDK links the flushed data to the visitor associated with the specified identifier. | | instant | bool | Boolean flag indicating whether the data should be sent instantly (`True`) or according to the scheduled tracking interval (`False`). If not provided, the default value is `False`. This field is optional. | #### get\_remote\_data() * Previously called `retrieve_data_from_remote_source`, which was removed in SDK version `3.0.0`. * If you want to retrieve data asynchronously, use the `get_remote_data_async` method instead (available since version 2.3.0). The `get_remote_data` method retrieves data synchronously (according to a **key** passed as argument) for a specified **site\_code** (specified with `KameleoonClient.__init__`) stored on a remote Kameleoon server. Data is usually stored on remote servers via the Data API. This method, along with the availability of highly scalable servers for this purpose, provides a convenient method for storing massive amounts of data that can be retrieved for each of your visitors/users. ```python theme={null} kameleoon_client.get_remote_data('key1') # default timeout kameleoon_client.get_remote_data('key2', 1.0) # 1 second timeout ``` ##### Parameters | Name | Type | Description | | ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | key | str | The key the data is associated with. This field is mandatory. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it will use the `default_timeout_millisecond` value from configuration file, or 2 seconds if it's not specified in the file. | ##### Return value | Type | Description | | ----------- | ----------------------------------------------------------------- | | JSON object | JSON object associated with retrieving data for specific **key**. | #### get\_remote\_data\_async() The `get_remote_data_async` method lets you retrieve data asynchronously (according to a **key** passed as argument) for specified **site\_code** (specified with `KameleoonClient.__init__`) stored in a remote Kameleoon server. Data is usually stored on remote servers via the Data API. This method, along with the availability of highly scalable servers for this purpose, provides a convenient method for storing massive amounts of data that can be retrieved for each of your visitors/users. ```python theme={null} await kameleoon_client.get_remote_data_async('key1') # default timeout await kameleoon_client.get_remote_data_async('key2', 1.0) # 1 second timeout ``` ##### Parameters | Name | Type | Description | | ------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | key | str | The key the data is associated with. This field is mandatory. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it will use the `default_timeout_millisecond` value from configuration file or 2 seconds if it's not specified in the file. | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------- | | JSON object | JSON object associated with retrieving data for a specific **key**. | #### get\_remote\_visitor\_data() `get_remote_visitor_data()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitor_code` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `get_remote_visitor_data()` automatically retrieves the latest stored custom data with `scope=visitor` and attaches them to the visitor without the need to call the `add_data()` method. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} visitor_code = 'visitorCode' # Visitor data will be fetched and automatically added for `visitor_code` data_list = kameleoon_client.get_remote_visitor_data(visitor_code) # default timeout data_list = kameleoon_client.get_remote_visitor_data(visitor_code, timeout=1.0) # 1 second timeout # If you only want to fetch data and add it yourself manually, set `add_data` to `False` data_list = kameleoon_client.get_remote_visitor_data(visitor_code, False) # default timeout data_list = kameleoon_client.get_remote_visitor_data(visitor_code, False, 1.0) # 1 second timeout # If you want to fetch custom list of data types data_filter = RemoteVisitorDataFilter(25, customData=False, conversions=True, experiments=True) data_list = kameleoon_client.get_remote_visitor_data(visitor_code, data_filter=data_filter) # If you want the SDK to link the extracted data with the visitor associated with the specified identifier. kameleoon_client.add_data(UniqueIdentifier(True)) data_list = kameleoon_client.get_remote_visitor_data(visitor_code) ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | str | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | add\_data | bool | A boolean indicating whether the method should automatically add retrieved data for a visitor. If not specified, the default value is **True**. This field is optional. | | data\_filter | RemoteVisitorDataFilter | Filter that specifies what data should be retrieved from visits. By default, only `CustomData` is retrieved from the current and latest previous visit (`RemoteVisitorDataFilter(previousVisitAmount=1, currentVisit=True, customData=True)` or `RemoteVisitorDataFilter()`). Other filters parameters are set to `False`. This field is optional. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | An optional parameter for specifying if the `visitorCode` is a unique identifier. If not provided, the default value is `False`. This field is optional. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it will use the `default_timeout_millisecond` value from configuration file or 2 seconds if it's not specified in the file. | ##### Using parameters in get\_remote\_visitor\_data() The `get_remote_visitor_data()` method offers flexibility by letting you define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies to all data types. For example, suppose you want to retrieve data on visitors who completed the goal "Order transaction." You can specify parameters within the `get_remote_visitor_data()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previous_visit_amount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `get_remote_visitor_data()` method to retrieve data on a variety of visitor behaviors. ##### Return value | Type | Description | | ----------- | --------------------------------------------- | | List\[Data] | A list of data assigned to the given visitor. | Here is the list of available `Kameleoon::Configuration::RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ----------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previous\_visit\_amount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | current\_visit optional | `bool` | If True, current visit data will be retrieved | `True` | | custom\_data optional | `bool` | If True, custom data will be retrieved. | `True` | | page\_views optional | `bool` | If True, page data will be retrieved. | `False` | | geolocation optional | `bool` | If True, geolocation data will be retrieved. | `False` | | device optional | `bool` | If True, device data will be retrieved. | `False` | | browser optional | `bool` | If True, browser data will be retrieved. | `False` | | operating\_system optional | `bool` | If True, operating system data will be retrieved. | `False` | | conversions optional | `bool` | If True, conversion data will be retrieved. | `False` | | experiments optional | `bool` | If True, experiment data will be retrieved. | `False` | | kcs optional | `bool` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `False` | | visitor\_code optional | `bool` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [cross-device experimentation](/developer-docs/cross-device-experimentation). | `True` | | cbs optional | `bool` | If true, Contextual Bandit score data will be retrieved. | `False` | | personalization optional | `bool` | If True, personalization data will be retrieved. This is required for the personalization condition. | `False` | #### get\_remote\_visitor\_data\_async() The `get_remote_visitor_data_async` method asynchronously retrieves custom data stored in remote Kameleoon servers for a visitor (specified using the **visitor\_code** argument). If **add\_data** is **True**, this method automatically adds the retrieved data to a visitor without requiring you to make a separate `add_data` call. You must have previously stored data on remote servers, which you can add with any of the following tracking calls in the SDK: * `flush` * `get_feature_variation_key` * `get_feature_variable` * `is_feature_active` Using the `get_remote_visitor_data` method along with the availability of highly scalable servers provides a convenient method for accessing and synchronizing large amounts of data across all of the visitor's devices. If you specify a `visitor_code`, the `get_remote_visitor_data_async` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} visitor_code = 'visitorCode' # Visitor data will be fetched and automatically added for `visitor_code` data_list = await kameleoon_client.get_remote_visitor_data_async(visitor_code) # default timeout data_list = await kameleoon_client.get_remote_visitor_data_async(visitor_code, timeout=1.0) # 1 second timeout # If you only want to fetch data and add it yourself manually, set `add_data` to `False` data_list = await kameleoon_client.get_remote_visitor_data_async(visitor_code, False) # default timeout data_list = await kameleoon_client.get_remote_visitor_data_async(visitor_code, False, 1.0) # 1 second timeout # If you want to fetch custom list of data types data_filter = RemoteVisitorDataFilter(25, customData=False, conversions=True, experiments=True) data_list = kameleoon_client.get_remote_visitor_data(visitor_code, data_filter=data_filter) # If you want the SDK to link the extracted data with the visitor associated with the specified identifier. kameleoon_client.add_data(UniqueIdentifier(True)) data_list = kameleoon_client.get_remote_visitor_data(visitor_code) ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | str | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | add\_data | bool | A boolean indicating whether the method should automatically add retrieved data for a visitor. If not specified, the default value is **True**. This field is optional. | | data\_filter | RemoteVisitorDataFilter | Filter that specifies which data should be retrieved from visits. By default, only `CustomData` is retrieved from the current and latest previous visit (`RemoteVisitorDataFilter(previousVisitAmount=1, currentVisit=True, customData=True)` or `RemoteVisitorDataFilter()`). Other filters parameters are set to `False`. This field is optional. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | An optional parameter for specifying if the `visitorCode` is a unique identifier. If not provided, the default value is `False`. The field is optional. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it will use the `default_timeout_millisecond` value from configuration file or 2 seconds if it's not specified in the file. | ##### Return value | Type | Description | | ----------- | --------------------------------------- | | List\[Data] | A list of data assigned to the visitor. | ##### Using parameters in get\_remote\_visitor\_data\_async() The `get_remote_visitor_data_async()` method offers flexibility by letting you define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed the goal "Order transaction." You can specify parameters within the `get_remote_visitor_data_async()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previous_visit_amount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `get_remote_visitor_data_async()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `Kameleoon::Configuration::RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ----------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previous\_visit\_amount optional | `int` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | current\_visit optional | `bool` | If True, current visit data will be retrieved | `True` | | custom\_data optional | `bool` | If True, custom data will be retrieved. | `True` | | page\_views optional | `bool` | If True, page data will be retrieved. | `False` | | geolocation optional | `bool` | If True, geolocation data will be retrieved. | `False` | | device optional | `bool` | If True, device data will be retrieved. | `False` | | browser optional | `bool` | If True, browser data will be retrieved. | `False` | | operating\_system optional | `bool` | If True, operating system data will be retrieved. | `False` | | conversions optional | `bool` | If True, conversion data will be retrieved. | `False` | | experiments optional | `bool` | If True, experiment data will be retrieved. | `False` | | kcs optional | `bool` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `False` | | visitor\_code optional | `bool` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `True` | | cbs optional | `bool` | If true, Contextual Bandit score data will be retrieved. | `False` | #### get\_visitor\_warehouse\_audience() Synchronously retrieves all audience data associated with the visitor in your data warehouse using the specified **visitor\_code** and **warehouse\_key**. The **warehouse\_key** is typically your internal user ID. The **custom\_data\_index** parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method returns a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. If you want to retrieve the data asynchronously, use the `get_visitor_warehouse_audience_async` method instead. ```python theme={null} try: warehouse_audience_data = kameleoon_client.\ get_visitor_warehouse_audience(visitor_code, custom_data_index) # default timeout warehouse_audience_data = kameleoon_client.\ get_visitor_warehouse_audience(visitor_code, custom_data_index, timeout=1.0) # 1 second timeout warehouse_audience_data = kameleoon_client.\ get_visitor_warehouse_audience(visitor_code, custom_data_index, warehouse_key) # default timeout warehouse_audience_data = kameleoon_client.\ get_visitor_warehouse_audience(visitor_code, custom_data_index, warehouse_key, 1.0) # 1 second timeout # Your custom code except VisitorCodeInvalid as e: # Handle exception ``` ##### Parameters | Name | Type | Description | | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | str | A unique visitor identification string, can't exceed 255 characters length. This field is mandatory. | | custom\_data\_index | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. This field is mandatory. | | warehouse\_key | Optional\[str] | A unique key identifying the warehouse data (usually your internal user ID). This field is optional. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time to wait for a result. This field is optional. If not provided, the default value is 10 seconds. | ##### Return value | Type | Description | | --------------------- | ------------------------------------------------------------------------------- | | Optional\[CustomData] | A `CustomData` instance confirming that the data has been added to the visitor. | ##### Exceptions thrown | Type | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_visitor\_warehouse\_audience\_async() Asynchronously retrieves all audience data associated with the visitor in your data warehouse using the specified **visitor\_code** and **warehouse\_key**. The **warehouse\_key** is typically your internal user ID. The **custom\_data\_index** parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method returns a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. ```python theme={null} try: warehouse_audience_data = await kameleoon_client.\ get_visitor_warehouse_audience_async(visitor_code, custom_data_index) # default timeout warehouse_audience_data = await kameleoon_client.\ get_visitor_warehouse_audience_async(visitor_code, custom_data_index, timeout=1.0) # 1 second timeout warehouse_audience_data = await kameleoon_client.\ get_visitor_warehouse_audience_async(visitor_code, custom_data_index, warehouse_key) # default timeout warehouse_audience_data = await kameleoon_client.\ get_visitor_warehouse_audience_async(visitor_code, custom_data_index, warehouse_key, 1.0) # 1 second timeout # Your custom code except VisitorCodeInvalid as e: # Handle exception ``` ##### Parameters | Name | Type | Description | | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | str | A unique visitor identification string, can't exceed 255 characters length. This field is mandatory. | | custom\_data\_index | int | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. This field is mandatory. | | warehouse\_key | Optional\[str] | A unique key identifying the warehouse data (usually your internal user ID). This field is optional. | | timeout | Optional\[float] | Timeout (in seconds). This parameter specifies the maximum amount of time to wait for a result. This field is optional. If not provided, the default value is 10 seconds. | ##### Return value | Type | Description | | --------------------- | ------------------------------------------------------------------------------- | | Optional\[CustomData] | A `CustomData` instance confirming that the data has been added to the visitor. | ##### Exceptions thrown | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid. It's either empty or longer than 255 characters. | #### set\_legal\_consent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting the `consent` parameter to `False` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```python theme={null} ### if you use KameleoonWSGIMiddleware service visitor_code = kameleoon_client.get_visitor_code(cookies_readonly=request.COOKIES) kameleoon_client.set_legal_consent(visitor_code, True) ### if you want to manage cookies manually cookies = http.cookies.SimpleCookie() cookies.load(cookie_header) visitor_code = kameleoon_client.get_visitor_code(cookies=cookies) kameleoon_client.set_legal_consent(visitor_code, True, cookies) cookie_header = cookies.output() ``` ##### Consent revocation behavior When you call `set_legal_consent()` with `consent=False`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ##### Parameters | Name | Type | Description | | ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | str | The user's unique identifier. This field is required. | | consent | bool | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided or withdrawn legal consent. This field is required. | | cookies | Optional\[Dict\[str, http.cookies.Morsel\[str]]] | The cookies to be adjusted based on the legal consent status as `Dict[str, http.cookies.Morsel[str]]` or `http.cookies.SimpleCookie[str]` object. This field is optional. | ##### Exceptions thrown | Type | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid. It's either empty or longer than 255 characters. | #### forget() The `forget` method removes a `KameleoonClient` instance from the `KameleoonClientFactory` with the specified **site\_code** and frees resources used by the `KameleoonClient` instance. The `KameleoonClient` instance must not be used after calling the `forget` method. If you specify a `visitor_code`, the `track_conversion` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} require "kameleoon" require "kameleoon/data" visitor_code = kameleoon_client.get_visitor_code(request.COOKIES) goal_id = 83023 kameleoon_client.add_data(visitor_code, Browser(BrowserType.CHROME)) kameleoon_client.add_data( visitor_code, PageView("https://url.com", "title", [3]), CustomData(2, "value") ) kameleoon_client.add_data(visitor_code, Conversion(32, 10, false)) kameleoon_client.track_conversion(visitor_code, goal_id) ``` ##### Arguments | Name | Type | Description | | ---------------------- | ------ | ---------------------------------------------------------------------------------------------------- | | visitor\_code | String | Unique identifier of the user. This field is mandatory. | | goal\_id | int | ID of the goal. This field is mandatory. | | revenue | float | Revenue of the conversion. This field is optional. | | is\_unique\_identifier | bool | When `True`, the SDK links the flushed data to the visitor associated with the specified identifier. | ### Goals and third-party analytics #### get\_engine\_tracking\_code() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `get_engine_tracking_code()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```python theme={null} engine_tracking_code = kameleoon_client.get_engine_tracking_code(visitor_code) ``` * To use this feature, implement both the Python SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | -------------------------------------- | ----- | --------------------------------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ----- | ---------------------------------------- | | `str` | JavaScript code to insert into the page. | #### track\_conversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitor_code` and `goal_id`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitor_code` is usually identical to the one that was used when triggering the experiment. The `track_conversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```python theme={null} visitor_code = "visitorCode" goal_id = 83023 kameleoon_client.add_data(visitor_code, Browser(BrowserType.CHROME)) kameleoon_client.add_data( visitor_code, PageView("https://url.com", "title", [3]), CustomData(2, "value") ) kameleoon_client.add_data(visitor_code, Conversion(32, 10, False)) # Add metadata cd = CustomData(1, "metadata") kameleoon_client.track_conversion(visitorCode, goalId, metadata=[cd]) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | | `visitor_code` required | `str` | Unique identifier of the visitor. | | | `goal_id` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion. | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `False` | | `metadata` optional | `Optional[Iterable[CustomData]]` | Lets you set specific values for custom data which have been defined as metadata for the goal in the Kameleoon App. Example: `[CustomData{id: 5, value: "Payment Type"}, CustomData{id: 6, value: "Delivery Method"}]`. In this example, `5` and `6` are the indexes of the custom data (5 = “Payment Type”, 6 = “Delivery Method”). | `None` | | `is_unique_identifier` *(deprecated)* | `bool` | An optional parameter for specifying if the visitor\_code is a unique identifier. | `False` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`add_data()`](#add_data) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `track_conversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```python theme={null} kameleoon_client.add_data(visitor_code, CustomData(5, "Credit Card"), CustomData(9, "Express Delivery")) kameleoon_client.track_conversion(visitor_code, 10, metadata=[CustomData(5, "Amex Credit Card")]) ``` ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ### Events #### set\_event\_handler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `None` as the `handler` removes the current handler for the selected event type. ```python theme={null} from kameleoon.events import DataFileUpdateEvent, EventType class DataFileUpdateHandler: def on_update(self, update_event: DataFileUpdateEvent) -> None: source = update_event.source # POLLING or STREAMING date_modified = update_event.date_modified # Data file modification date in milliseconds. # React to the data file update. kameleoon_client.set_event_handler(EventType.DATAFILE_UPDATE, DataFileUpdateHandler()) # Clear the handler. kameleoon_client.set_event_handler(EventType.DATAFILE_UPDATE, None) ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | --------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateEvent.Source` | The update source. [`POLLING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`STREAMING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `date_modified` | `int` | The modification date of the updated data file, in milliseconds. | ```python theme={null} from kameleoon.events import EventType, HttpRequestFailure, RequestType class HttpRequestHandler: def on_request_succeeded(self, request_type: RequestType, http_status: int, duration_millis: int) -> None: # The SDK request completed successfully. ... def on_request_failed(self, request_type: RequestType, failure: HttpRequestFailure, duration_millis: int) -> None: reason = failure.reason # HTTP_STATUS, EXCEPTION, or CANCELLED http_status = failure.http_status cause = failure.cause # The SDK request failed. kameleoon_client.set_event_handler(EventType.HTTP_REQUEST, HttpRequestHandler()) # Clear the handler. kameleoon_client.set_event_handler(EventType.HTTP_REQUEST, None) ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `on_request_succeeded()` for successful responses and `on_request_failed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ------------------------ | -------------------------------------------------------------------------------------------- | | `on_request_succeeded()` | Called when an SDK HTTP request completes successfully. | | `on_request_failed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ----------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `request_type` | `RequestType` | The SDK request type. Possible values are `DATAFILE`, `TRACKING`, `REMOTE_VISITOR_DATA`, `REMOTE_DATA`, and `ACCESS_TOKEN`. | | `http_status` | `int` | The HTTP status code returned by the request. Passed only to `on_request_succeeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `on_request_failed()`. | | `duration_millis` | `int` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailure.Reason` | The failure reason. Possible values are `HTTP_STATUS`, `EXCEPTION`, and `CANCELLED`. | | `http_status` | `Optional[int]` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `None` for exception and cancellation failures. | | `cause` | `Optional[Exception]` | The exception (error) that caused the request to fail. This value is `None` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event_type` required | `EventType[T]` | The SDK event type to handle. Use `EventType.DATAFILE_UPDATE` to react to SDK data file updates, or `EventType.HTTP_REQUEST` to react to completed SDK HTTP requests. | | `handler` required | `Optional[T]` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventType.DATAFILE_UPDATE`, or an `HttpRequestHandler` for `EventType.HTTP_REQUEST`. Pass `None` to remove the current handler. | ### Data types #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | -------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | `browser_type` required | `BrowserType` | List of browsers: `CHROME`, `INTERNET_EXPLORER`, `FIREFOX`, `SAFARI`, `OPERA`, `OTHER`. | | `version` optional | `Optional[float]` | Version of the browser, floating point number represents major and minor version of the browser | ```python theme={null} kameleoon_client.add_data(visitor_code, Browser(BrowserType.CHROME)) kameleoon_client.add_data(visitor_code, Browser(BrowserType.SAFARI, 10)) ``` #### PageView | Name | Type | Description | | --------- | --------------------- | -------------------------------------------------- | | url | str | URL of the page viewed. This field is mandatory. | | title | Optional\[str] | Title of the page viewed. This field is optional. | | referrers | Optional\[List\[int]] | Referrers of viewed pages. This field is optional. | The index (ID) of the referrer is available in the Acquisition channel configuration page of the Back-Office. Be careful: this index starts at 0, so the first [acquisition channel](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) you create for a given site will have the ID 0, not 1. ```python theme={null} kameleoon_client.add_data(visitor_code, PageView("https://url.com", "title", [3])) ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goal_id` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | -------------------------------- | ----------------------------------------------- | ------- | | `goal_id` required | `int` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `False` | | `metadata` optional | `Optional[Iterable[CustomData]]` | Metadata of the conversion. | `None` | ```python theme={null} kameleoon_client.add_data(visitor_code, Conversion(32, 10)) kameleoon_client.add_data(visitor_code, Conversion(33, negative=True)) kameleoon_client.add_data( visitor_code, Conversion(34, 5, metadata=[ CustomData(3, "metadata1", "md2"), CustomData(5, "md3"), ]) ) ``` #### CustomData `CustomData` allows any type of data to be easily associated with each visitor. It can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | --------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index\_or\_name required | `Union[int, str]` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | args required | `*str` | Values of the custom data to be stored. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `True` | * Each visitor can only have one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing `CustomData`. * The custom data `index` can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the **Use this data only locally for targeting purposes** option when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```python theme={null} from kameleoon.data import CustomData kameleoon_client.add_data(visitor_code, CustomData(1, "value")) # With several values kameleoon_client.add_data(visitor_code, CustomData(1, "value1", "value2")) # To set the 'overwrite' flag to false kameleoon_client.add_data(visitor_code, CustomData(1, "value", overwrite=False)) # To use a name instead of the index kameleoon_client.add_data(visitor_code, CustomData("my-custom-data", "value")) ``` #### Device | Name | Type | Description | | ------ | ---------- | ----------------------------------------------------------------------- | | device | DeviceType | List of devices: `PHONE`, `TABLET`, `DESKTOP`. This field is mandatory. | ```python theme={null} from kameleoon import Device, DeviceType kameleoon_client.add_data(visitor_code, Device(DeviceType.DESKTOP)) ``` #### UserAgent Store information on the visitor's user-agent. Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that could otherwise skew your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the `userAgent` to exclude them from your analytics. | Name | Type | Description | | ----- | ---- | --------------------------------------------------------------------------------------- | | value | str | The User-Agent value that will be sent with tracking requests. This field is mandatory. | ```python theme={null} from kameleoon.data import UserAgent kameleoon_client.add_data(visitor_code, UserAgent('userAgent')) ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitor_code` is used as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data to the visitor associated with the specified identifier. The `UniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. | Name | Type | Description | | ----- | ---- | --------------------------------------------------------------------------------------------- | | value | bool | Parameter for specifying if the visitor\_code is a unique identifier. This field is required. | ```python theme={null} from kameleoon.data import UniqueIdentifier kameleoon_client.add_data(visitor_code, UniqueIdentifier(True)) ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. | Name | Type | Description | | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------ | | os\_type | OperatingSystemType | List of types: **WINDOWS**, **MAC**, **IOS**, **LINUX**, **ANDROID**, **WINDOWS\_PHONE**. This field is mandatory. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```python theme={null} from kameleoon.data import OperatingSystem, OperatingSystemType kameleoon_client.add_data(visitor_code, OperatingSystem(OperatingSystemType.ANDROID)) ``` #### Cookie `Cookie` contains information about the cookies stored on the visitor's device. | Name | Type | Description | | ------- | --------------- | --------------------------------------------------------------------------------------------------- | | cookies | Dict\[str, str] | Dict (`{cookie_name: cookie_value}`) consisting of cookie keys and values. This field is mandatory. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```python theme={null} from kameleoon.data import Cookie cookie = Cookie({"k1": "v1", "k2": "v2"}) kameleoon_client.add_data(visitor_code, cookie) ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `str` | The country of the visitor. | | `region` optional | `Optional[str]` | The region of the visitor. | | `city` optional | `Optional[str]` | The city of the visitor. | | `postal_code` optional | `Optional[str]` | The postal code of the visitor. | | `latitude` optional | `Optional[float]` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `Optional[float]` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```python theme={null} from kameleoon.data import Geolocation kameleoon_client.add_data(visitor_code, Geolocation("France", "Île-de-France", "Paris")) ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `str` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```python theme={null} kameleoon_client_sdk.add_data(visitorCode, ApplicationVersion("10")) # major kameleoon_client_sdk.add_data(visitorCode, ApplicationVersion("10.20")) # major.minor kameleoon_client_sdk.add_data(visitorCode, ApplicationVersion("10.20.30")) # major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------- | ------------------------ | --------------------------------------------------------------------------------- | | `feature_flags` | `Dict[str, FeatureFlag]` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `date_modified` | `int` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```python theme={null} # Retrieves the dict of feature flags from the DataFile. # The dict is keyed by feature flag identifiers, with each value being a FeatureFlag object. feature_flags: Dict[str, FeatureFlag] = data_file.feature_flags # Retrieves the last modification timestamp of the DataFile. # The value is an int representing milliseconds since the Unix epoch. date_modified: int = data_file.date_modified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ----------------------- | ---------------------- | -------------------------------------------------------------------------- | | `environment_enabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. | | `default_variation_key` | `str` | The key of the default variation associated with the feature flag. | | `variations` | `Dict[str, Variation]` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Iterable[Rule]` | A list of `Rule` objects | ```python theme={null} # Check whether the feature flag is enabled in the current environment is_environment_enabled: bool = feature_flag.environment_enabled # Retrieve the key of the default variation default_variation_key: str = feature_flag.default_variation_key # Retrieve the default variation object default_variation: Variation = feature_flag.default_variation # Retrieve all variations of the feature flag as a dict (key = variation key, value = Variation object) variations: Dict[str, Variation] = feature_flag.variations # Retrieve all targeting rules associated with the feature flag rules: Iterable[Rule] = feature_flag.rules ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | key | `str` | The unique key identifying the variation. | | id\_ | `Optional[int]` | The ID of the assigned variation (or `None` if it's the default variation). | | experiment\_id | `Optional[int]` | The ID of the experiment associated with the variation (or `None` if default). | | variables | `Dict[str, Variable]` | A dict containing the variables of the assigned variation, keyed by variable names. This value could be empty if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id_` or `experiment_id` may be `None`, indicating a default variation. * The `variables` hash might be empty if no variables are associated with the variation. ```python theme={null} # Retrieving the variation key variation_key: str = variation.key # Retrieving the variation id variation_id: Optional[int] = variation.id_ # Retrieving the experiment id experiment_id: Optional[int] = variation.experiment_id # Retrieving the variables map variables: Dict[str, Variable] = variation.variables ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | --------------- | -------------------------------------------------------------------------------------------------------------------------- | | key | `str` | The unique key identifying the variable. | | type | `str` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS** | | value | `Optional[Any]` | The value of the variable, which can be of the following types: **bool**, **int**, **float**, **str**, **dict**, **list**. | ```python theme={null} # Retrieving the variable key variables: Dict[str, Variable] = variation.variables # Variable type can be retrieved for further processing variable_type: str = variables["isDiscount"].type # Retrieving the variable value by key is_discount: Optional[bool] = variables["isDiscount"].value # Variable value can be of different types title: Optional[str] = variables["title"].value ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `4.0.0`. #### get\_feature\_variation\_key() * 📨 *Sends Tracking Data to Kameleoon* Use [`get_variation()`](#get_variation) instead. To get a feature variation key, call the `get_feature_variation_key` method. This method takes a **visitor\_code** and **feature\_key** as mandatory arguments to get a variation key for a given user. If such a user has never been associated with this feature flag, the SDK returns a variation key randomly (according to the feature flag rules). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the previous **variation key** value. If the user doesn't match any of the rules, the default value will be returned, which you can define in your account. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `get_feature_variation_key` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} visitor_code = kameleoon_client.get_visitor_code(request.COOKIES) feature_key = "feature_key" variation_key = "" try variation_key = kameleoon_client.get_feature_variation_key(visitor_code, feature_key) if variation_key == 'on': # main variation key is selected for visitorCode elif variation_key == 'alternative_variation': # alternative variation key else: # default variation key except FeatureNotFound as ex: # The user will not be counted in the experiment, but should see the reference variation. except VisitorCodeInvalid as ex: # The visitor code you passed to the method isn't valid and can't be accepted by SDK. except FeatureEnvironmentDisabled as ex: # The feature flag is disabled for certain environments. ``` ##### Parameters | Name | Type | Description | | ------------------------------------ | --------------- | ---------------------------------------------------------------------------------------------------- | | visitor\_code | string | Unique identifier of the user. This field is mandatory. | | feature\_key | string | Key of the feature you want to expose to a user. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | When `True`, the SDK links the flushed data to the visitor associated with the specified identifier. | ##### Return value | Type | Description | | ------ | ---------------------------------------------------------------------------------- | | string | Variation key of the feature flag that's registered for a given **visitor\_code**. | ##### Exceptions thrown | Type | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | FeatureEnvironmentDisabled | Exception indicating that the feature flag is off for certain environments. | #### get\_active\_features() Use [`get_variations()`](#get_variations) instead. This method only takes the input parameter **visitorCode**. The result only contains active features for a given visitor. ```python theme={null} try: active_features = kameleoon_client_sdk.get_active_features(visitor_code) except VisitorCodeInvalid as e: # Handle exception ``` ##### Arguments | Name | Type | Description | | ------------- | ---- | ------------------------------------------------------ | | visitor\_code | str | Unique identifier of the user. This field is optional. | ##### Return value | Type | Description | | --------------------- | --------------------------------------------------------------------------------------- | | Dict\[str, Variation] | List of features with assigned variations that are active for a given **visitor\_code** | ##### Exceptions thrown | Type | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_active\_feature\_list\_for\_visitor() Use [`get_variation()`](#get_variation) instead. This method only takes the input parameter **visitorCode**. The result only contains the active feature flags for a given visitor. ```python theme={null} active_feature_flag_for_visitor = kameleoonClient.get_active_feature_list_for_visitor(visitor_code) ``` ##### Arguments | Name | Type | Description | | ------------- | ---- | ------------------------------------------------------ | | visitor\_code | str | Unique identifier of the user. This field is optional. | ##### Return value | Type | Description | | ---------- | ----------------------------------------------------------------------- | | List\[str] | List of feature flag keys that are active for a given **visitor\_code** | #### get\_feature\_variable() * 📨 *Sends Tracking Data to Kameleoon* Use [`get_variation()`](#get_variation) instead. Previously called `obtain_feature_variable`, which was removed in SDK version `3.0.0`. To get a variable of the variation key associated with a user, call the `get_feature_variable` method. This method takes a **visitor\_code**, **feature\_key**, and **variable\_key** as mandatory arguments. If a user has never been associated with this feature flag, the SDK returns a variable value randomly (according to the feature flag rules). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the **variable** value for the associated **variation**. If the user doesn't match any of the rules, the default variable will be returned. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `get_feature_variable` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```python theme={null} visitor_code = kameleoon_client.get_visitor_code(request.COOKIES) feature_key = "feature_key" variable_key = "variable_key" variation_value = "" try: variable_value = kameleoon_client.get_feature_variable(visitor_code, feature_key, variable_key) except FeatureNotFound as ex: # The user will not be counted in the experiment, but should see the reference variation. except FeatureVariableNotFound as ex: # Requested variable not defined on Kameleoon's side except VisitorCodeInvalid as ex: # The visitor code you passed to the method isn't valid and can't be accepted by SDK. except FeatureEnvironmentDisabled as ex: # The feature flag is disabled for certain environments. # your custom code depending of variable_value, e.g. if variable_value == "value-1": # your custom code if variable == 'value-1' elif variable_value == "value-2": # your custom code if variable == 'value-2' else: # ... ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------- | | visitor\_code | str | Unique identifier of the user. This field is mandatory. | | feature\_key | str | Key of the feature you want to expose to a user. This field is mandatory. | | variable\_key | str | Name of the variable you want to get a value. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Optional\[bool] | When `True`, the SDK links the flushed data to the visitor associated with the specified identifier. | ##### Return value | Type | Description | | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | Union\[bool, str, float, Dict\[str, Any], List\[Any], None] | Value of a variation's variable that's registered for a given **visitor\_code** for this feature flag. Possible types: bool, float, str, List, Dict, None | ##### Exceptions thrown | Type | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | VisitorCodeInvalid | Exception indicating that the provided visitor code isn't valid (empty, or longer than 255 characters). | | FeatureVariableNotFound | Exception indicating that the requested variable hasn't been found. Check that the variable's key matches the one in your code. | | FeatureEnvironmentDisabled | Exception indicating that the feature flag is off in certain environments. | #### get\_feature\_variation\_variables() Use [`get_variation()`](#get_variation) instead. Previously called `get_feature_all_variables`, which was removed in SDK version `3.0.0`. To retrieve the all feature variables, call the `get_feature_variation_variables` method. A feature variable can be changed easily via the Kameleoon web app. This method takes the **feature\_key** input parameter. It will return data with the `Dict[str,Any]` type, as defined on the web interface. It will throw an exception (`FeatureNotFound`) if the requested feature hasn't been found in the SDK's internal configuration. ```python theme={null} feature_key = "myFeature" try data = kameleoon_client.get_feature_variation_variables(feature_key) except FeatureNotFound as ex: # The feature is not activated on Kameleoon's side. except FeatureEnvironmentDisabled as ex: # The feature flag is disabled for certain environments. ``` ##### Parameters | Name | Type | Description | | ------------ | ------ | --------------------------------------------------------------- | | feature\_key | String | Key of the feature you want to obtain. This field is mandatory. | ##### Return value | Type | Description | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | Dict\[str, Any] | Data associated with this feature flag. The value can be int, str, bool or Dict or List (depending on the type defined on the web interface). | ##### Exceptions thrown | Type | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side. | | FeatureEnvironmentDisabled | Exception indicating that the feature flag is off in certain environments. | #### get\_feature\_list() Previously called `obtain_feature_list`, which was removed in SDK version `3.0.0`. Returns a list of feature flag keys currently available for the SDK. ```python theme={null} all_feature_list = kameleoon_client.get_feature_list() ``` ##### Return value | Type | Description | | ----------- | ------------------------- | | `List[str]` | List of feature flag keys | #### on\_update\_configuration() The `on_update_configuration()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```python theme={null} kameleoon_client.on_update_configuration( # configuration was updated ) ``` ##### Parameters | Name | Type | Description | | --------- | -------------------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `Callable[[], None]` | The handler that will be called when the configuration is updated using a real-time configuration event. | # React SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/react-js-sdk Integrate the Kameleoon React SDK to run experiments and activate feature flags in React applications using hooks and components. With the Kameleoon React SDK, you can run feature experiments and activate feature flags on your front-end web and mobile app. Integrating the SDK into your web and mobile app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide) **Changelog**: Details on the latest version of the React SDK can be found in the [changelog](https://github.com/Kameleoon/client-react/blob/main/CHANGELOG.md). **SDK methods**: For the full reference documentation of the React SDK, see the [reference](#reference) section. **Requirements**: React SDK requires `React 16.8.0+` ## Developer guide Follow this section to integrate the SDK into your app and learn more about using the SDK. ### Getting started This section walks you through installing and configurating the SDK for the first time. #### Installation The Kameleoon SDK Installation tool is the preferred way to install the SDK. This **SDK Installer** helps you to install the SDK of your choice, generate a basic code sample, and configure [external dependencies](#external-dependencies) if needed. To start the SDK Installation tool, install and run it globally: ```bash theme={null} npm install --global @kameleoon/sdk-installer kameleoon-sdk ``` Or run it directly with `npx`: ```bash theme={null} npx @kameleoon/sdk-installer ``` #### Create the Kameleoon client To get started, you need to create an entry point for React SDK by creating a Kameleoon Client at the top level of your app using the `createClient()` function imported from `kameleoon` package. ```tsx theme={null} import { createClient, Environment, SDKConfigurationType, } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ```jsx theme={null} import { createClient, Environment } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` #### Wrap the app in the Kameleoon Provider The second step is connecting the previously created Kameleoon Client to `KameleoonProvider` by passing the configured client to `KameleoonProvider`: ```tsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, }); function AppWrapper(): JSX.Element { return ( ); } ``` ```jsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, }); function AppWrapper() { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```tsx theme={null} import { createClient, Environment, KameleoonProviderSSR, } from '@kameleoon/react-sdk'; function AppWrapper(): JSX.Element { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```jsx theme={null} import { createClient, Environment, KameleoonProviderSSR, } from '@kameleoon/react-sdk'; function AppWrapper() { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```tsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; // Checks if the code is running on the server (Node.js) and not in the browser. // This can be replaced with any other mechanism you use to detect server-side execution. const isServer = typeof window === 'undefined'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, externals: { // Add your external dependencies here, e.g. storage, eventSource, visitorCodeManager, etc. }, stubMode: isServer, }); function AppWrapper(): JSX.Element { return ( ); } ``` If you are using **Next.js** for server-side rendering (SSR), you **have to** use `KameleoonProviderSSR` or `KameleoonProvider` with `stubMode=true`. This prevents the SDK client from being initialized on the server and ensures that the React SDK runs exclusively on the client side. ```jsx theme={null} import { createClient, Environment, KameleoonProvider, } from '@kameleoon/react-sdk'; // Checks if the code is running on the server (Node.js) and not in the browser. // This can be replaced with any other mechanism you use to detect server-side execution. const isServer = typeof window === 'undefined'; const client = createClient({ siteCode: 'my_site_code', configuration: { dataFileRefreshInterval: 60, environment: Environment.Production, }, externals: { // Add your external dependencies here, e.g. storage, eventSource, visitorCodeManager, etc. }, stubMode: isServer, }); function AppWrapper() { return ( ); } ``` ##### KameleoonProvider Use this provider on root level by wrapping your app to gain an access to `KameleoonClient`. This ensures your app doesn't flicker due to flag changes at startup time. ###### Props | Name | Type | Description | | -------------------------------- | ----------------- | ------------------------------------------------------ | | children required | `ReactNode` | child elements of the provider | | client required | `KameleoonClient` | `KameleoonClient` instance created by `createClient()` | ##### KameleoonProviderSSR Use this provider on root level by wrapping your app to gain an access to `KameleoonClient`. `KameleoonProviderSSR` differs from `KameleoonProvider` in that it creates a `KameleoonClient` instance inside the context on the first client request. This prevents the risk of creating the client on the server side. It's recommended for use in SSR-based systems, such as Next.js with SSR. ###### Props | Name | Type | Description | | ------------------------------------- | --------------- | ---------------------------------------------------------------------- | | children required | `ReactNode` | child elements of the provider | | sdkParameters required | `SDKParameters` | `SDKParameters` settings for creating an instance of `KameleoonClient` | #### Await for the client initialization `KameleoonClient` initialization is done asynchronously to make sure that Kameleoon API call was successful for that hook `useInitialize` is used. You can use `async/await`, `Promise.then()` or any other method to handle asynchronous client initialization. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); // -- Waiting for the client initialization using `async/await` const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); // -- Waiting for the client initialization using `async/await` const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`getVisitorCode()`](#getvisitorcode) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `defaultVisitorCode` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `getVisitorCode()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`getVariation()`](#getvariation) or [`isFeatureFlagActive()`](#isfeatureflagactive) method to retrieve the configuration based on the `featureKey`. The `getVariation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `featureKey` and `visitorCode`. The `isFeatureFlagActive()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `getVariation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#configuration-parameters). By default, this interval is set to 1000 milliseconds (1 second). The `getVariation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `getVariations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`addData()`](#adddata) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`getRemoteVisitorData()`](#getremotevisitordata) method. This method asynchronously fetches data from the servers. It's important to call `getRemoteVisitorData()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`trackConversion()`](#trackconversion) method and provide the required `visitorCode` and `goalId` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#configuration-parameters)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`getEngineTrackingCode()`](#getenginetrackingcode) method. The `getEngineTrackingCode()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### React Native considerations React Native on `android` platform doesn't support `Real Time Update` feature. While React SDK works the same way in both React Native and React contexts, it's important to note that setup steps differ. Due to the lack of browser API in React Native, React SDK has to have different [external dependency](#external-dependencies) implementations to work correctly. For that, Kameleoon provides several dedicated npm packages that you can install and set up manually or install using [Kameleoon SDK Installation Tool](#installation) (recommended). The packages include: * `@kameleoon/react-native-storage` - built using `react-native-mmkv` library * `@kameleoon/react-native-event-source` - built using `react-native-event-source-ts` library * `@kameleoon/react-native-visitor-code-manager` - built on top of `react-native-mmkv` library * `@kameleoon/react-native-platform-analyzer` - built using `react-native` library * *optional* `@kameleoon/react-native-secure-prng` - built using `react-native-get-random-values` library If you don't want to use the listed packages, you can provide your own implementation following [the external dependencies guide](#external-dependencies). Example React SDK setup for React Native app: ```ts theme={null} import { createClient } from '@kameleoon/react-sdk'; import { KameleoonEventSource } from '@kameleoon/react-native-event-source'; import { KameleoonStorage } from '@kameleoon/react-native-storage'; import { KameleoonVisitorCodeManager } from '@kameleoon/react-native-visitor-code-manager'; import { KameleoonSecurePRNG } from '@kameleoon/react-native-secure-prng'; import { KameleoonPlatformAnalyzer } from '@kameleoon/react-native-platform-analyzer'; // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new KameleoonStorage(), eventSource: new KameleoonEventSource(), visitorCodeManager: new KameleoonVisitorCodeManager(), platformAnalyzer: new KameleoonPlatformAnalyzer(), // -- Optional -- prng: new KameleoonSecurePRNG(), }, }); ``` ```js theme={null} import { createClient } from '@kameleoon/react-sdk'; import { KameleoonEventSource } from '@kameleoon/react-native-event-source'; import { KameleoonStorage } from '@kameleoon/react-native-storage'; import { KameleoonVisitorCodeManager } from '@kameleoon/react-native-visitor-code-manager'; import { KameleoonSecurePRNG } from '@kameleoon/react-native-secure-prng'; import { KameleoonPlatformAnalyzer } from '@kameleoon/react-native-platform-analyzer'; // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new KameleoonStorage(), eventSource: new KameleoonEventSource(), visitorCodeManager: new KameleoonVisitorCodeManager(), platformAnalyzer: new KameleoonPlatformAnalyzer(), // -- Optional -- prng: new KameleoonSecurePRNG(), }, }); ``` ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitorCode`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitorCode`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```jsx theme={null} addData(visitorCode, new CustomData(index, 'newVisitorCode')); ``` [More information in addData()](#adddata) * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`addData()`](#adddata) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `addData()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitorCode`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitorCode`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `string`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions supported by this SDK, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/feature-experimentation/targeting-and-segmentation\use-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/react-sdk'; const client = createClient({ siteCode: 'my_site_code', configuration }); // The `NONE` log level does not allow logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```ts theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk/full’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk’; const client = createClient({ siteCode: ‘my_site_code’, configuration }); // The `NONE` log level allows no logging. client.setLogLevel(LogLevel.NONE); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.NONE); // The `ERROR` log level only allows logging issues that may affect the SDK’s main behaviour. client.setLogLevel(LogLevel.ERROR); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.ERROR); // The `WARNING` log level allows logging issues which may require additional attention. // It extends the `ERROR` log level. // The `WARNING` log level is a default log level. client.setLogLevel(LogLevel.WARNING); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.WARNING); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from ‘@kameleoon/react-sdk/full’; // The `INFO` log level allows logging general information on the SDK’s internal processes. // It extends the `WARNING` log level. client.setLogLevel(LogLevel.INFO); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.INFO); // The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```ts theme={null} import { KameleoonClient, KameleoonLogger, IExternalLogger, LogLevel } from '@kameleoon/react-sdk'; export class CustomLogger implements IExternalLogger { // `log` method accepts logs from the SDK public log(level: LogLevel, message: string): void { // Custom log handling logic here. For example: switch (level) { case LogLevel.DEBUG: console.debug(message); break; case LogLevel.INFO: console.info(message); break; case LogLevel.WARNING: console.warn(message); break; case LogLevel.ERROR: console.error(message); break; } } } const client = createClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ```js theme={null} import { KameleoonClient, KameleoonLogger, LogLevel } from '@kameleoon/react-sdk'; export class CustomLogger { // `log` method accepts logs from the SDK log(level, message) { // Custom log handling logic here. For example: switch (level) { case 'DEBUG': console.debug(message); break; case 'INFO': console.info(message); break; case 'WARNING': console.warn(message); break; case 'ERROR': console.error(message); break; } } } const client = createClient({ siteCode: 'my_site_code', externals: { logger: new CustomLogger(), }, }); // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. client.setLogLevel(LogLevel.DEBUG); // Or use KameleoonLogger KameleoonLogger.setLogLevel(LogLevel.DEBUG); ``` ### Domain information You provide a domain as the `domain` in `KameleoonClient` \[configuration], which is used for storing Kameleoon visitor code in cookies. This is important when working with the [`getVisitorCode`](#getvisitorcode) and [`setLegalConsent`](#setlegalconsent) methods. The domain you provide is stored in the cookie as the `Domain=` key. #### Setting the domain The domain you provide indicates the URL address can use the cookie. For example, if your domain is `www.example.com`. the cookie is only available from a `www.example.com` URL. That means that pages with the `app.example.com` domain can't use the cookie. To be more flexible around subdomains, you can prefix a domain with `.`. For example, the domain `.example.com` allows the cookie to function on both `app.example.com` and `login.example.com`. You can't use regular expressions, special symbols, protocol, or port numbers in the `domain`. Additionally, a [specific list of subdomains](https://publicsuffix.org/list/public_suffix_list.dat) aren't allowed to be used with the prefix `.`. Here's a small domain cheat sheet: | Domain | Allowed URLs | Disallowed URLs | | ------------------------------ | --------------------- | -------------------- | | `www.example.com` | ✅`www.example.com` | ❌ `app.example.com` | | | ✅ `example.com` | ❌ `.com` | | | | | | `.example.com` = `example.com` | ✅ `example.com` | ❌ `otherexample.com` | | | ✅ `www.example.com` | | | | ✅ `app.example.com` | | | | ✅ `login.example.com` | | | `https://www.example.com` | ⛔ bad domain | ⛔ bad domain | | `www.example.com:4408` | ⛔ bad domain | ⛔ bad domain | | `.localhost.com` = `localhost` | ⛔ bad domain | ⛔ bad domain | #### Developing on localhost `localhost` is always considered a bad domain, making it hard to test the domain when developing on localhost. There are two ways to avoid this issue: * Don't specify the `domain` field in the SDK client while testing. This prevents `localhost` issues (the cookie will be set on any domain). * Create a local domain for `localhost`. For example: * Navigate to `/etc/hosts` on *Linux* or to `c:\Windows\System32\Drivers\etc\hosts` on *Windows* * Open `hosts` with file super user or administrator rights * Add a domain to the localhost port, for example: `127.0.0.1 app.com` * Now you can run your app locally on `app.com:{my_port}` and specify `.app.com` as your domain ### External dependencies SDK external dependencies use the *dependency injection* pattern to give you the ability to provide your own implementations for certain parts of an SDK. In the React SDK, all external dependencies have default implementations, which use a native browser API so there's no need to provide them unless another API is required for specific use cases. Here's the list of available external dependencies: | Dependency | Interface | API Used | Description | | -------------------------------------------- | ----------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage` optional | `IExternalStorage` | Browser `localStorage` | Used for storing all the existing and collected SDK data | | `requester` optional | `IExternalRequester` | Browser `fetch` | Used for performing all the network requests | | `eventSource` optional | `IExternalEventSource` | Browser `EventSource` | Used for receiving Server Sent Events for [Real Time Update](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) capabilities | | `visitorCodeManager` optional | `IExternalVisitorCodeManager` | Browser cookie | Used for storing and synchronizing visitor code | | `prng` optional | `IExternalPRNG` | `Math.random` or Browser `crypto.getRandomValues` | Used to generate unique IDs for tracking events | | `logger` optional | `ILogger` | Custom implementation | Used for custom handling of logs from the SDK. Allows to define how logs are processed and where they're output. | | `platformAnalyzer` optional | `IPlatformAnalyzer` | React Native API | Automatically detects the platform and attaches this information to the visitor data. Designed specifically for React Native. | The following example implements external dependencies. To import an interface from an SDK, create a class that implements it and pass the instantiated class to the SDK. #### Storage ```ts theme={null} import { IExternalStorage } from '@kameleoon/react-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage implements IExternalStorage { public read(key: string): T | null { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } public write(key: string, data: T): void { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/react-sdk'; // --- External Storage implementation --- // - JavaScript `Map` is used as an example storage const storage = new Map(); class MyStorage { read(key) { // - Read data using `key` const data = storage.get(key); // - Return `null` if there's no data if (!data) { return null; } // - Return obtained data return data; } write(key, data) { // - Write data using `key` storage.set(key, data); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { storage: new MyStorage(), }, }); ``` #### EventSource ```ts theme={null} import { IExternalEventSource, EventSourceOpenParametersType, } from '@kameleoon/react-sdk'; // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource implements IExternalEventSource { private eventSource?: EventSource; public open({ eventType, onEvent, url, }: EventSourceOpenParametersType): void { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } public close(): void { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback: (error: Event) => void): void { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` ```js theme={null} // --- External EventSource implementation --- // - Example uses native browser `EventSource` class MyEventSource { eventSource; open({ eventType, onEvent, url }) { // - Initialize `EventSource` const eventSource = new EventSource(url); this.eventSource = eventSource; // - Add event listener with provided event type and event callback this.eventSource.addEventListener(eventType, onEvent); } close() { // - Cleanup open event source if (this.eventSource) { this.eventSource.close(); } } public onError(callback) { // - Set error callback if (this.eventSource) { this.eventSource.onerror = callback; } } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { eventSource: new MyEventSource(), }, }); ``` #### VisitorCodeManager ```ts theme={null} import { IExternalVisitorCodeManager, SetDataParametersType, KameleoonUtils, } from '@kameleoon/react-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager implements IExternalVisitorCodeManager { public getData(key: string): string | null { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } public setData({ visitorCode, domain, maxAge, key, path, }: SetDataParametersType): void { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; // --- External Visitor Code Manager implementation --- // - Example uses browser `document.cookie` API class MyVisitorCodeManager { getData(key) { const cookieString = document.cookie; // - Return `null` if no cookie was found if (!cookieString) { return null; } // - Parse cookie using provided `key` return KameleoonUtils.getCookieValue(cookieString, key); } setData({ visitorCode, domain, maxAge, key, path }) { // - Set cookie with provided parameters let resultCookie = `${key}=${visitorCode}; Max-Age=${maxAge}; Path=${path}`; if (domain) { resultCookie += `; Domain=${domain}`; } document.cookie = resultCookie; } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { visitorCodeManager: new MyVisitorCodeManager(), }, }); ``` #### Requester ```ts theme={null} import { RequestType, IExternalRequester, KameleoonResponseType, SendRequestParametersType, } from '@kameleoon/react-sdk'; // --- External Requester Implementation export class MyRequester implements IExternalRequester { public async sendRequest({ url, parameters, }: SendRequestParametersType): Promise { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` ```js theme={null} import { KameleoonClient } from '@kameleoon/react-sdk'; // --- External Requester Implementation export class MyRequester { async sendRequest({ url, parameters }) { // - Using native browser `fetch` return await fetch(url, parameters); } } // --- Create KameleoonClient --- const client = new KameleoonClient({ siteCode: 'my_site_code', externals: { requester: new MyRequester(), }, }); ``` [Return mocked result](#simulatesuccessrequest) #### Pseudo random number generator Pseudo Random Number Generator (PRNG) is a dependency that generates random floating point number between `0` and `1` (similar to `Math.random`). Default Kameleoon implementation relies on Browser's `crypto` or `Math.random` function if `crypto` isn't available. Those API are very secure and reliable, however in some edge cases (especially in some `React Native` engines) you might want to provide your own implementation or use a dedicated Kameleoon package for React Native - `@kameleoon/react-native-secure-prng` ```ts theme={null} import { IExternalPRNG } from '@kameleoon/react-sdk'; // --- External Pseudo Random Number Generator (PRNG) implementation --- class MyPRNG implements IExternalPRNG { public getRandomNumber(): number { // Return a random floating point number between `0` and `1`, like `Math.random()` does. return Math.random(); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { prng: new MyPRNG(), }, }); ``` ```js theme={null} // --- External Pseudo Random Number Generator (PRNG) implementation --- class MyPRNG { getRandomNumber() { // Return a random floating point number between `0` and `1`, like `Math.random()` does. return Math.random(); } } // --- Create KameleoonClient --- const client = createClient({ siteCode: 'my_site_code', externals: { prng: new MyPRNG(), }, }); ``` ### Error handling Almost every React SDK callback which is returned by hooks may throw an error at some point, these errors aren't just caveats but rather deliberately predefined `KameleoonError`s that extend native JavaScript `Error` class providing useful messages and special `type` field with a type `KameleoonException`. `KameleoonException` is an enum containing all possible error types. To know exactly what type of `KameleoonException` the callbacks may throw, you can check `Throws` section of the hooks description on this page or just hover over the callback in your IDE to see jsdocs description. Overall handling the errors considered a good practice to make your app more stable and avoid technical issues. *** ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, KameleoonError, KameleoonException, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async (): Promise => { try { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); } catch (error) { // -- Type guard for inferring error type, as native JavaScript `catch` // only infers `unknown`. if (error instanceof KameleoonError) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } } }, [initialize, addData, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, KameleoonException, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async () => { try { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); } catch (error) { switch (error.type) { case KameleoonException.VisitorCodeMaxLength: // -- Handle an error break; case KameleoonException.StorageWrite: // -- Handle an error break; case KameleoonException.Initialization: // -- Handle an error break; default: break; } } }, [initialize, addData, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `getRemoteVisitorData()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `getRemoteVisitorData()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`getRemoteVisitorData()`](#getremotevisitordata) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitorCode`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```tsx title="Device One" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); addData('my_visitor', customData); flush(); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Device Two" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getRemoteVisitorData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available to target and track "my_visitor". }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Device One" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Custom Data with index `0` was set to `Visitor` scope // in Kameleoon. const customDataIndex = 0; const customData = new CustomData(customDataIndex, 'my_data'); addData('my_visitor', customData); flush(); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Device Two" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getRemoteVisitorData } = useData(); const init = useCallback(async () => { await initialize(); // -- Before working with data, call `getRemoteVisitorData`. await getRemoteVisitorData({ visitorCode: 'my_visitor_code' }); // -- New SDK code will have access to CustomData with `Visitor` scope // defined on Device One. // So, "my_data" is now available to target and track "my_visitor". }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows you to combine a visitor's history across each of their devices (history reconciliation). One of the powerful features that history reconciliation provides is the ability to merge different visitors sessions into one. To reconcile visit history, you can use [`CustomData`](#customdata) to provide a unique identifier for the visitor. Follow the [activating cross-device history reconciliation](/developer-docs/cross-device-experimentation#activating-cross-device-history-reconciliation) guide to set up your custom data on the Kameleoon platform When your custom data is set up, you can use it in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation and will be displayed as a single visitor in the `Visitor` view of your experiment's result pages. The configuration SDK ensures that associated sessions always see the same variation of the experiment. Afterwards, you can use the SDK normally. The following methods might be helpful in the context of session merging: * Use [`getRemoteVisitorData`](#getremotevisitordata) with `isUniqueIdentifier=true` to retrieve data for all linked visitors * Use [`trackConversion`](#trackconversion) or [`flush`](#flush) with `isUniqueIdentifier=true` to track some data for specific visitor that's associated with another visitor As the custom data you use as the identifier must be set to `Visitor` scope, you need to use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. This example uses an app with a login page. Since the user ID isn't known at the moment of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```tsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage(): JSX.Element { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; type Props = { anonymousVisitor: string; }; function ApplicationPage(props: Props): JSX.Element { const { addData, trackConversion, getRemoteVisitorData } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage() { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function ApplicationPage(props) { const { addData, trackConversion, getRemoteVisitorData } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async () => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, // -- Informing the SDK that the visitor is a unique identifier. isUniqueIdentifier: true, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', // -- Informing the SDK know the visitor is a unique identifier. isUniqueIdentifier: true, }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows you to combine a visitor's history across each of their devices (history reconciliation). One of the powerful features that history reconciliation provides is the ability to merge different visitors sessions into one. To reconcile visit history, you can use [`CustomData`](#customdata) to provide a unique identifier for the visitor. Follow the [activating cross-device history reconciliation](/developer-docs/cross-device-experimentation#activating-cross-device-history-reconciliation) guide to set up your custom data on the Kameleoon platform When your custom data is set up, you can use it in your code to merge a visitor's session. Sessions with the same identifier will always see the same experiment variation and will be displayed as a single visitor in the `Visitor` view of your experiment's result pages. The SDK configuration ensures that associated sessions always see the same variation of the experiment. Before using other methods make sure to let SDK know that the visitor is a unique identifier by adding [`UniqueIdentifier`](#uniqueidentifier) data to a visitor As the custom data you use as the identifier must be set to `Visitor` scope, you need to use [cross-device custom data synchronization](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices) to retrieve the identifier with the [`getRemoteVisitorData`](#getremotevisitordata) method on each device. Here's an example of how to use custom data for session merging. This example uses an app with a login page. Since the user ID isn't known at the moment of login, the code uses an anonymous visitor identifier generated by the [`getVisitorCode`](#getvisitorcode) method. After the user logs in, you can associate the anonymous visitor with the user ID and use it as a unique identifier for the visitor. ```tsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage(): JSX.Element { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```tsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, UniqueIdentifier, } from '@kameleoon/react-sdk'; type Props = { anonymousVisitor: string; }; function ApplicationPage(props: Props): JSX.Element { const { addData, trackConversion, getRemoteVisitorData, flush } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` flush(props.anonymousVisitor); // -- Informing the SDK that the visitor is unique identifier. addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Login Page" theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function LoginPage() { const [visitorCode, setVisitorCode] = useState(null); const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); const anonymousVisitor = getVisitorCode(); // -- Saving `visitorCode` in the state to re-use it later. setVisitorCode(anonymousVisitor); // -- Getting a variation, assume it's variation `A` const variation = getVariation({ visitorCode: anonymousVisitor, featureKey: 'my_feature_key', }); }, [initialize, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx title="Application Page" theme={null} import { useEffect, useCallback } from 'react'; import { useData, useFeatureFlag, useVisitorCode, CustomData, UniqueIdentifier, } from '@kameleoon/react-sdk'; function ApplicationPage(props) { const { addData, trackConversion, getRemoteVisitorData, flush } = useData(); const { getVariation } = useFeatureFlag(); const init = useCallback(async () => { // -- At this point anonymous visitor has logged in, // and we have a user ID to use as a visitor identifier // -- Associating both visitors with an identifier Custom Data, // where index `1` is the Custom Data's index, configured // as a unique identifier in Kameleoon. const userIdentifierData = new CustomData(1, 'my_user_id'); // -- Let's assume the anonymous visitor identifier // was passed as a prop. addData(props.anonymousVisitor, userIdentifierData); // -- Flushing data for the anonymous `visitorCode` flush(props.anonymousVisitor); // -- Informing the SDK that the visitor is a unique identifier. addData('my_user_id', new UniqueIdentifier(true)); // -- Retrieving the variation for the user ID ensures // consistency with the anonymous visitor's variation. // Both the anonymous visitor and the user ID will be // assigned variation `A`. const variation = client.getVariation({ visitorCode: 'my_user_id', featureKey: 'my_feature_key', }); // -- `my_user_id` and `anonymousVisitor` are now linked. // They can be tracked as a single visitor. trackConversion({ visitorCode: 'my_user_id', goalId: 123, revenue: 100, }); // -- Additionally, linked visitors share previously // collected remote data. const data = await getRemoteVisitorData({ visitorCode: 'my_user_id', }); }, [ getRemoteVisitorData, trackConversion, addData, getVariation, ]); useEffect(() => { init(); }, [init]); } ``` ### Utilities SDK has a set of utility methods that can be used to simplify the development process. All the methods are represented as static members of `KameleoonUtils` class. #### simulateSuccessRequest Method `simulateSuccessRequest` is used to simulate a successful request to the Kameleoon server. It can be useful for custom [Requester](#requester) implementations when developer needs to simulate a successful request, for example disabling tracking. ```ts theme={null} import { KameleoonUtils, IExternalRequester, SendRequestParametersType, RequestType, KameleoonResponseType, } from '@kameleoon/react-sdk'; // - Example of `Requester` with disabled tracking class Requester implements IExternalRequester { public async sendRequest({ url, parameters, requestType, }: SendRequestParametersType): Promise { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest( requestType, null, ); } return await fetch(url, parameters); } } ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; // - Example of `Requester` with disabled tracking class Requester { async sendRequest({ url, parameters, requestType }) { if (requestType === RequestType.Tracking) { return KameleoonUtils.simulateSuccessRequest(requestType, null); } return await fetch(url, parameters); } } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------------------------------------- | --------------------------------------------------------------------- | | requestType required | `RequestType` | A type of request | | data required | `SimulateRequestDataType[RequestType]` | A type of request data, which is different depending on `RequestType` | Data type `SimulateRequestDataType` is defined as follows: * `RequestType.Tracking` - `null` * `RequestType.ClientConfiguration` - `ClientConfigurationDataType` * `RequestType.RemoteData` - `JSONType` ##### Return value | Type | Description | | -------------------------------- | -------------------------------------------------- | | `Promise` | returns a promise with the response of the request | #### getCookieValue Method `getCookieValue` is used to parse a common cookie string (`key_1=value_1; key_2=value_2; ...`) and get the value of a specific cookie key. It's useful when working with a custom implementation of [`VisitorCodeManager`](#visitorcodemanager). ```ts theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ```js theme={null} import { KameleoonUtils } from '@kameleoon/react-sdk'; const cookies = 'key_1=value_1; key_2=value_2'; const key = 'key_1'; const value = KameleoonUtils.getCookieValue(cookies, key); // = `value_1` ``` ##### Parameters | Name | Type | Description | | ------------------------------ | -------- | ------------------------------------------------------ | | cookie required | `string` | Cookie string in a form `key_1=value_1; key_2=value_2` | | key required | `string` | String representation of a key to find a value by | ##### Return value | Type | Description | | | -------- | ----------- | ----------------------------------------------------------------------- | | \`string | null\` | returns a string with a cookie value or `null` if the key was not found | ## Reference This is the full reference documentation for the React SDK. ### Initialization This section provides the methods you use to create and initialize the Kameleoon Client in your app. #### initialize() An asynchronous `initialize` function, collected with `useInitialize` hook, that's used for KameleoonClient initialization by fetching Kameleoon SDK related data from server or by retrieving data from local source if data is up-to-date or update interval hasn't been reached. * If the SDK configuration couldn't be retrieved but there is an older configuration available in SDK storage, the SDK uses the older configuration as a fallback and the `initialize` doesn't throw an error. * Client initialization has an optional *offline mode*. It's activated by setting optional `useCache` parameter to `true`. In *offline mode* if tracking requests from any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request as soon as it detects that the internet connection has been re-established: * [flush](#flush) * [trackConversion](#trackconversion) * [getFeatureFlagVariationKey](#getfeatureflagvariationkey) * [getFeatureVariable](#getfeatureflagvariable) * [sFeatureFlagActive](#isfeatureflagactive) ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default Value | | -------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | useCache optional | `boolean` or `undefined` | parameter for activating SDK offline mode, if `true` is passed failed polls won't return error and will use cached data if such data is available | `false` | ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | a promise resolved to a boolean indicating a successful sdk initialization. Generally initialize will throw an error if the something that can not be handled will happen, so the `boolean` value will almost always be `true` and won't give as much useful information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | An asynchronous `initialize` function, collected with `useInitialize` hook, that's used for KameleoonClient initialization by fetching Kameleoon SDK related data from server or by retrieving data from local source if data is up-to-date or update interval hasn't been reached. * If the SDK configuration couldn't be retrieved but there is an older configuration available in SDK storage, the SDK uses the older configuration as a fallback and the `initialize` doesn't throw an error. * SDK supports an *offline mode*. In *offline mode* if tracking requests from any of the following methods fail due to internet connectivity issues, the SDK automatically resends the request as soon as it detects that the internet connection has been re-established: * [flush](#flush) * [trackConversion](#trackconversion) * [getFeatureFlagVariationKey](#getfeatureflagvariationkey) * [getFeatureVariable](#getfeatureflagvariable) * [sFeatureFlagActive](#isfeatureflagactive) ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } ``` ##### Return value | Type | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Promise` | a promise resolved to a boolean indicating a successful sdk initialization. Generally initialize will throw an error if the something that can not be handled will happen, so the `boolean` value will almost always be `true` and won't give as much useful information. | ##### Exceptions thrown | Type | Description | | ------------------------------------------ | --------------------------------------------------------- | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.ClientConfiguration` | Couldn't retrieve client configuration from Kameleoon API | | `KameleoonException.MaximumRetriesReached` | Maximum retries reached, request failed | #### isReady() `isReady()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing. Collect the `isReady` function with the `useInitialize` hook. It's especially useful in a deeply nested component tree, because it lets you check SDK readiness without managing a global state or passing the initialization result through component props. Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent(): JSX.Element { const { isReady } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isReady()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent() { const { isReady } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isReady()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | #### isInitialized() `isInitialized` is deprecated and will be removed in the next major release. Use [`isReady()`](#isready) instead. The `isInitialized` function, collected with the `useInitialize` hook, is a small utility method that checks if the SDK initialization has completed. For example, this can be useful when dealing with a deeply nested component tree, because it allows you to quickly check the SDK readiness without having to manage a global state, or pass the initialization result using component props. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent(): JSX.Element { const { isInitialized } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isInitialized()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const init = useCallback(async () => { await initialize(); }, [initialize]); useEffect(() => { init(); }, [init]); } function DeeplyNestedComponent() { const { isInitialized } = useInitialize(); const { getVariation } = useFeatureFlag(); if (isInitialized()) { const variation = getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); } } ``` ##### Return value A `boolean` value. Returns `true` if SDK was successfully initialized, otherwise returns `false`. #### createClient() To get started, you need to create an entry point for React SDK by creating a Kameleoon Client at the top level of your app using the `createClient()` function imported from `kameleoon` package. An instance of `KameleoonClient` is created using `createClient()` function. ```tsx theme={null} import { createClient, Environment, SDKConfigurationType, } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration: Partial = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ```jsx theme={null} import { createClient, Environment } from '@kameleoon/react-sdk'; // -- Optional configuration const configuration = { dataFileRefreshInterval: 60, environment: Environment.Production, cookieDomain: '.example.com', }; const client = createClient({ siteCode: 'my_site_code', configuration }); ``` ##### Parameters An object of type `SDKParameters` containing: | Name | Type | Description | | ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | siteCode required | `string` | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | configuration optional | `Partial` | client's configuration | | externals optional | `ExternalsType` | external implementation of SDK dependencies ([External dependencies](#external-dependencies)) | ##### Configuration parameters | Name | Type | Description | Default Value | | ---------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | updateInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | | environment optional | `Environment` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data; minimum value is 1 minute | `undefined` (no cleanup will be performed) | | domain optional | `string` | [domain](#domain-information) that the cookie belongs to. Deprecated, use `cookieDomain` instead | `undefined` | | cookieDomain optional | `string` | [domain](#domain-information) that the cookie belongs to. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request, which is performed once per interval. The minimum value is `100` ms and the maximum value is `1_000` ms | `1_000` (1 second) | The `domain` parameter is deprecated and will be removed in a future release. Use `cookieDomain` instead. | Name | Type | Description | Default Value | | ---------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | dataFileRefreshInterval optional | `number` | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes; the minimum value is 1 minute. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. This parameter replaces the deprecated `updateInterval`; if both are specified, `dataFileRefreshInterval` takes precedence. | `60` | | dataFileCacheTtl optional | `number` | Defines, in minutes, how long the data file cached in the storage remains valid. If the cached data file is older than `dataFileRefreshInterval` but within `dataFileCacheTtl`, `initialize()` completes immediately using the cached data file and fetches the latest version in the background. If the age of the cached data file exceeds `dataFileCacheTtl`, `initialize()` waits for the latest data file before completing, so that visitors returning after a long period of inactivity are evaluated against the current configuration; if the request fails, the cached data file is used as a fallback. A value of `0` disables the background refresh: once a refresh is due, `initialize()` always waits for the latest data file. Negative values are treated as `0`.

**Note:** a lower `dataFileCacheTtl` increases the number of blocking data file requests during SDK initialization, which adds the request time to the page load for the affected visitors. Set `dataFileCacheTtl` higher than `dataFileRefreshInterval` (for example, `dataFileRefreshInterval: 5` and `dataFileCacheTtl: 10`), unless every configuration update must be applied before initialization completes. | `90` | | environment optional | `Environment \| string` | feature flag environment | `Environment.Production` | | targetingDataCleanupInterval optional | `number` | interval in *minutes* for cleaning up targeting data; minimum value is 1 minute | `undefined` (no cleanup will be performed) | | cookieDomain optional | `string` | [domain](#domain-information) that the cookie belongs to. | `undefined` | | networkDomain optional | `string` | custom domain the SDKs uses for all outgoing network requests, commonly used for proxying. The format is `second_level_domain.top_level_domain` (for example, `example.com`). If an invalid format is specified, the SDK uses the default Kameleoon value | `undefined` | | requestTimeout optional | `number` | timeout in *milliseconds* for all SDK network requests, if timeout is exceeded request will fail immediately | `10_000` (10 seconds) | | trackingInterval optional | `number` | Specifies the interval for tracking requests, in milliseconds. All visitors who were evaluated for any feature flag or had associated data will be included in this tracking request, which is performed once per interval. The minimum value is `1_000` ms and the maximum value is `5_000` ms | `1_000` (1 second) | | stubMode optional | `boolean` | When set to true, the client will operate in stub mode and perform no operations. In this mode, all method calls execute no actions, ensuring that no external actions or side effects occur. | `false` | | defaultDataFile optional | `string` | The `defaultDataFile` feature ensures the Kameleoon SDK is always **READY** by providing a fallback configuration when no cached data file exists. Developers can preload a valid configuration by fetching it from `https://sdk-config.kameleoon.eu/v3/` and passing it as `defaultDataFile` during initialization. When a `dateModified` timestamp (in milliseconds) is provided and is newer than the cached version, the SDK will use the default datafile instead of the cached version. **If `dateModified` is omitted, the default datafile is only applied when no cached version exists**. This ensures the SDK always has a valid configuration, whether default, cached, or updated. | `undefined` | | updateInterval deprecated | `number` | **Deprecated.** Use `dataFileRefreshInterval` instead; this parameter will be removed in the next major version. If both parameters are specified, `dataFileRefreshInterval` takes precedence. Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. If left unspecified, the default interval is set to 60 minutes. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` | **Option 1 (Recommended):** Use `JSON.stringify()` ```js theme={null} const dataFileJson = {"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\"make\":\"Porsche\",\"model\":\"911\"}"}}, "dateModified":1752209266000}; const dataFileString = JSON.stringify(dataFileJson); const configuration = { dataFileRefreshInterval: 20, defaultDataFile: dataFileString }; ``` **Option 2:** Raw JSON string (escape special characters) ```js theme={null} const configuration = { dataFileRefreshInterval: 20, defaultDataFile: `{"configuration":{"consentType":....., {"key":"show_car","type":"JSON","value":"{\\"make\\":\\"Porsche\\",\\"model\\":\\"911\\"}"}, "dateModified":1752209266000}` }; ```
Lowering `dataFileRefreshInterval` increases how often the SDK checks for a newer configuration in the background, but a visitor's cached data file still stays usable until it ages past `dataFileCacheTtl` (90 minutes by default). Lower `dataFileCacheTtl` instead to shorten how long a visitor can keep evaluating against a stale cached configuration. Otherwise, traffic into a newly launched experiment or feature flag ramps up gradually rather than reaching full volume immediately. See [Impact on newly launched experiments and feature flags](/developer-docs/feature-experimentation/technical-reference/technical-considerations#impact-on-newly-launched-experiments-and-feature-flags) for details. ##### Return value | Type | Description | | ----------------- | ------------------------------- | | `KameleoonClient` | an instance of KameleoonClient. | Make sure not to use several client instances in one app as it's not fully supported yet and may overwrite the local storage configuration and cause unintended behavior (bugs). ### Feature flags and variations This section provides the methods you use to retrieve and manage the feature flags and variations assigned to the visitor. #### getVariation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes `featureKey` as a mandatory argument and `track` as an optional argument. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get variation with tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } }, [initialize, visitorCode, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVariation } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get variation with tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', }); // -- Get variation without tracking const variation = getVariation({ visitorCode, featureKey: 'my_feature_key', track: false, }); // -- An Example variation: // { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } }, [initialize, visitorCode, getVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `GetVariationParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `featureKey` required | `string` | Key of the feature you want to expose to a visitor. | | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.FeatureFlagConfigurationNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### getVariations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* [`EventType.Evaluation`](#events-1) Method is obtained using `useFeatureFlag` hook. Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitorCode` as a mandatory argument, while `onlyActive` and `track` are optional. * If `onlyActive` is set to `true`, the method `getVariations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ts theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVariations } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get all feature flag variations with tracking const variations = getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } }, [initialize, visitorCode, getVariations, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVariations } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); // -- Get all feature flag variations with tracking const variations = getVariations({ visitorCode, }); // -- Get active feature flag variations with tracking const variations = getVariations({ visitorCode, onlyActive: true, }); // -- Get active feature flag variations without tracking const variations = getVariations({ visitorCode, onlyActive: true, track: false, }); // -- An Example variations: // Map { // 'feature_key' => { // key: 'variation_key', // id: 123, // experimentId: 456, // variables: Map { // 'variable_key' => { // key: 'variable_key', // type: VariableType.BOOLEAN, // value: true, // } // }, // } // } }, [initialize, visitorCode, getVariations, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `GetVariationsParamsType` with the following properties: | Name | Type | Description | Default | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `onlyActive` optional | `boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | #### isFeatureFlagActive() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* * 🎯 *Events:* `EventType.Evaluation` The method `isFeatureFlagActive()`, used with the `useFeatureFlag` hook, determines whether a visitor identified by `visitorCode` has the specified `featureKey` active. This method checks the targeting conditions, identifies the variation for the visitor, and saves this information to storage. Additionally, the hook sends a tracking request. There is also an overload for this method that includes a `track` parameter, allowing you to turn off the tracking of the feature evaluation. Visitor must be targeted to has feature flag active Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `isFeatureFlagActive()`, `getVariation()` or `getVariations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `getVariations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const { isFeatureFlagActive } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Add CustomData with index `0` containing visitor id to check the targeting addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Get the status of feature flag const isActive = isFeatureFlagActive(visitorCode, featureKey); // -- Check if the feature flag is active for visitor without tracking const isActive = isFeatureFlagActive({ visitorCode, featureKey: 'my_feature', track: false}); }, [initialize, visitorCode, isFeatureFlagActive, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, useFeatureFlag, useVisitorCode, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const { isFeatureFlagActive } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function. const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Add CustomData with index `0` containing visitor id to check targeting. addData(visitorCode, new CustomData(0, 'visitor_id')); // -- Get the feature flag's status. const isActive = isFeatureFlagActive(visitorCode, featureKey); // -- Check if the feature flag is active for visitors without tracking. const isActive = isFeatureFlagActive({ visitorCode, featureKey: 'my_feature', track: false}); }, [initialize, visitorCode, isFeatureFlagActive, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` The `isFeatureFlagActive()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters There are two overloads available for this method: 1. Two parameters overload: This overload is deprecated and will be removed in the next major version. Use the new overload with an object parameter. | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | 2. Object parameter overload of type `IsFeatureFlagActiveParamsType`: | Name | Type | Description | Default | | ----------------------------------- | --------- | ------------------------------------------------------------------------ | ------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | featureKey required | `string` | a unique key for feature flag | - | | track optional | `boolean` | a boolean indicator of whether to track the feature evaluation | `true` | ##### Return value | Type | Description | | --------- | ------------------------------------------------------------------------------------------------- | | `boolean` | indicator of whether the feature flag with `featureKey` is active for visitor with `visitorCode`. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | -------------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.DataInconsistency` | Allocated variation was found but there is no feature flag with according `featureKey` | *** #### setForcedVariation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `forceTargeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#getvisitorcode)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { setForcedVariation } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Forcing the variation "on" in the feature flag "featureKey1" for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); }, [initialize, visitorCode, setForcedVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { setForcedVariation } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Forcing the variation "on" for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: 'on', forceTargeting: false, }); // -- Resetting the forced variation for the "featureKey1" feature flag for the visitor setForcedVariation({ visitorCode: visitorCode, experimentId: 9516, variationKey: null, }); }, [initialize, visitorCode, setForcedVariation, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters An object of type `SetForcedVariationParametersType` with the following properties: | Name | Type | Description | Default | | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | | `experimentId` required | `number` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | | `variationKey` required | \`string | null\` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `null`, the forced variation will be reset. | | | `forceTargeting` optional | `boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | ##### Exceptions thrown | Type | Description | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.Initialization` | Indicates that the SDK isn't yet fully initialized. | | `KameleoonException.FeatureFlagExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.FeatureFlagVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `KameleoonException.StorageRead` | Couldn't read storage data. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### evaluateAudiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluateAudiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { evaluateAudiences } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); evaluateAudiences(visitorCode); }, [initialize, visitorCode, evaluateAudiences, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { evaluateAudiences } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); evaluateAudiences(visitorCode); }, [initialize, visitorCode, evaluateAudiences, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its [`initialize`](#initialize) call. | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | In most cases, only the basic error, `KameleoonException`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `Error`. #### getDataFile() To evaluate all feature flags, use [`getVariations()`](#getvariations). This method is more efficient than calling `DataFile` and iterating through flags with [`getVariation()`](#getvariation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getDataFile } = useFeatureFlag(); useEffect(() => { const dataFile = getDataFile(); }, [getDataFile]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getDataFile } = useFeatureFlag(); useEffect(() => { const dataFile = getDataFile(); }, [getDataFile]); } ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data This section provides the methods you use to manage visitor data. #### getVisitorCode() `getVisitorCode` method collected from `useVisitorCode` hook obtains a visitor code from the browser cookie. If the visitor code doesn't exist yet, the function generates a random visitor code (or uses the `defaultVisitorCode` value if you provided one) and sets the new visitor code in a cookie. The `getVisitorCode()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#setforcedvariation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Pass, save, and retrieve the default visitorCode. const visitorCode = getVisitorCode('default_visitor_code'); }, [initialize, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Pass, save, and retrieve the default visitorCode. const visitorCode = getVisitorCode('default_visitor_code'); }, [initialize, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ------------------------------------------ | -------- | ------------------------------------------------------------------- | | defaultVisitorCode optional | `string` | visitor code to be used in case there is no visitor code in cookies | If you don't provide a `defaultVisitorCode` and there is no visitor code stored in a cookie, the visitor code will be randomly generated. ##### Return value | Type | Description | | -------- | -------------------- | | `string` | result visitor code. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------ | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length was exceeded | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | *** #### addData() The `addData` function, used with the `useData` hook, collects targeting data to store for other hooks to determine if the current visitor is targeted. * The `addData()` function doesn't return any value and doesn't interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission via the [flush](#flush) method .This approach helps reduce the number of server calls made, as the data is typically grouped into a single server call triggered by the execution of [flush](#flush). The [trackConversion](#trackconversion) method also sends out any previously associated data, just like the [flush](#flush). The same holds true for [getFeatureFlagVariationKey](#getfeatureflagvariationkey) and [getFeatureVariable](#getfeatureflagvariable) methods if an experimentation rule is triggered. * `userAgent` data won't be stored in storage like other data, and it will be sent with every tracking request for bot filtration. * Check the list of [supported conditions](#targeting-conditions) to know what data types can be used for targeting Each visitor can only have one instance of associated data for most data types. However, `CustomData` is an exception. Visitors can have one instance of associated `CustomData` per `customDataIndex`. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create Kameleoon Data Types const customData = new CustomData(0, 'my_data'); const browserData = new Browser(BrowserType.Chrome); // -- Add a single data item (tracked by default) addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); }, [initialize, visitorCode, addData, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create Kameleoon Data Types const customData = new CustomData(0, 'my_data'); const browserData = new Browser(BrowserType.Chrome); // -- Add a single data item (tracked by default) addData('my_visitor_code', browserData); // -- Add multiple data items (tracked by default) addData('my_visitor_code', browserData, customData); // -- Add multiple data items from array (tracked by default) const dataArr = [browserData, customData]; addData('my_visitor_code', ...dataArr); // -- Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) addData({visitorCode: 'my_visitor_code', track: false, data: dataArr}); }, [initialize, visitorCode, addData, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default value | | ------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters. | | | track optional | `boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it's not sent to the Kameleoon Data API. | `true` | | kameleoonData optional | `KameleoonDataType[]` | number of instances of any type of `KameleoonData`, can be added solely in array or as sequential arguments | | * `kameleoonData` is variadic argument it can be passed as one or several arguments (see the example) * The index or ID of the [custom data](/user-manual/assets/custom-data/create-custom-data) can be found in your Kameleoon account. It's important to note that this index starts at `0`, which means that the first custom data you create for a given site will be assigned `0` as its ID, not `1`. ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageWrite` | Couldn't update storage data | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | See the [Data types](#data-types) reference for more details of how to manage different data types. *** #### flush() Method `flush` collected with `useData` takes the Kameleoon data associated with the visitor and sends the data tracking request along with all of the data that's been added previously using the [addData](#adddata). If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally during [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. The `isUniqueIdentifier` parameter can be useful in some edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor (fire-and-forget) flushInstant(visitorCode); // -- Instantly flush added custom data for visitor and wait for completion await flushInstant(visitorCode); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors (fire-and-forget) flushInstant(); // -- Instantly flush data for all the visitors and wait for completion await flushInstant(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; flush(internalUserId, true); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Flush data for all the visitors flush(); // -- Flush data with unique visitor identifier flag const internalUserId = 'my_user_id'; flush(internalUserId, true); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters length, if not passed all the data will be flushed (sent to the remote Kameleoon servers) | - | | isUniqueIdentifier optional | `boolean` | an optional parameter for specifying if the visitorCode is a unique identifier | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | `flush()` takes the Kameleoon data associated with the visitor and schedules the data to be sent with the next tracking request. The time of the next tracking request is defined by SDK Configuration [`trackingInterval`](#configuration-parameters) parameter. Visitor data can be added using [addData](#adddata) and [getRemoteVisitorData](#getremotevisitordata) methods. If you don't specify a `visitorCode`, the SDK flushes all of its stored data to the remote Kameleoon servers. If any previously failed tracking requests were stored locally during [offline mode](#initialize), the SDK attempts to send the stored requests before executing the latest request. If you need to send tracking requests immediately, use `flushInstant()` (the asynchronous version of `flush` that returns `Promise`). You can `await` it when you need delivery guarantees (for example, before page navigation/unload), or call it without `await` as a fire-and-forget request: * `await flushInstant(visitorCode)` sends tracking requests immediately for a specific visitor and waits for completion * `await flushInstant()` sends tracking requests immediately for all visitors and waits for completion ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor flush({ visitorCode, instant: true }); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors flush({ instant: true }); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { addData, flush } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Create instance of CustomData const customData = new CustomData(0, 'my_data'); addData(visitorCode, customData); // -- Flush added custom data for visitor flush(visitorCode); // -- Instantly flush added custom data for visitor flush({ visitorCode, instant: true }); // -- Flush data for all the visitors flush(); // -- Instantly flush data for all the visitors flush({ instant: true }); }, [initialize, visitorCode, addData, flush, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | Or an object with the type FlushParamsType, containing: | Name | Type | Description | Default | | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | visitorCode optional | `string` | unique visitor identification string, can't exceed 255 characters, if not passed, all data will be flushed (sent to the remote Kameleoon servers). | - | | instant optional | `boolean` | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). | - | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | *** #### getRemoteData() Asynchronous method `getRemoteData`, collected with the `useData` hook, returns a data stored for specified site code on a remote Kameleoon server. For example, you can use this function to retrieve user preferences, historical data, or any other data relevant to your app's logic. By storing this data on Kameleoon's highly scalable servers using the \[Data API], you can efficiently manage massive amounts of data and retrieve it for each of your visitors or users. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteData } = useData(); const getData = useCallback(async () => { // -- Get remote data const jsonData = await getRemoteData('my_data_key'); const data = JSON.parse(jsonData); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters | Name | Type | Description | | --------------------------- | -------- | ---------------------------------------------------------- | | key required | `string` | unique key that the data you try to get is associated with | ##### Return value | Type | Description | | ---------- | --------------------------------------------- | | `JSONType` | promise with data retrieved for specific key. | ##### Exceptions thrown | Type | Description | | ------------------------------- | -------------------------------------------- | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### getRemoteVisitorData() `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The `isUniqueIdentifier` parameter can be useful in edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteVisitorData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteVisitorData } = useData(); const getData = useCallback(async () => { // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be set to the storage like `addData` method does | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | | isUniqueIdentifier optional | `boolean` | optional parameter that, when `true`, specifies that the visitorCode is a unique identifier | `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | `getRemoteVisitorData()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitorCode` from the Kameleoon Data API. The method adds the data to storage for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `getRemoteVisitorData()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without the need to call the method `addData()`. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, KameleoonDataType, VisitorDataFiltersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getRemoteVisitorData } = useData(); const getData = useCallback(async (): Promise => { // -- Get remote visitor data and add it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data. const filters: VisitorDataFiltersType = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList: KameleoonDataType[] = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getRemoteVisitorData } = useData(); const getData = useCallback(async () => { // -- Get remote visitor data and add it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', }); // -- Get remote visitor data without adding it to storage. const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, }); // -- Get remote visitor data without adding it to storage, // and customizing filters for retrieving visits data const filters = { currentVisit: true, previousVisitAmount: 10, customData: true, geolocation: true, conversions: true, }; const kameleoonDataList = await getRemoteVisitorData({ visitorCode: 'my_visitor_code', shouldAddData: false, filters, }); }, [getRemoteVisitorData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters An object with the type `RemoteVisitorDataParamsType` containing: | Name | Type | Description | Default Value | | ------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | - | | shouldAddData optional | `boolean` | boolean flag identifying whether the retrieved custom data should be set to the storage like `addData` method does | `true` | | filters optional | `VisitorDataFiltersType` | filters for specifying what data should be retrieved from visits, by default only `customData` is retrieved from the current and latest previous visit | `{ previousVisitAmount: 1, currentVisit: true, customData: true }`, other filters parameters are set to `false` | ##### Return value | Type | Description | | --------------------- | --------------------------------------------- | | `KameleoonDataType[]` | promise with list of Kameleoon Data retrieved | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | | `KameleoonException.VisitAmount` | Visit amount must be a number between 1 and 25 | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | ##### Using parameters in getRemoteVisitorData() The `getRemoteVisitorData()` method offers flexibility by allowing you to define various parameters when retrieving data on visitors. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `getRemoteVisitorData()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previousVisitAmount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `getRemoteVisitorData()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `VisitorDataFiltersType` filters: | Name | Type | Description | Default | | ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previousVisitAmount optional | `number` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | currentVisit optional | `boolean` | If true, current visit data will be retrieved | `true` | | customData optional | `boolean` | If true, custom data will be retrieved. | `true` | | pageViews optional | `boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `boolean` | If true, device data will be retrieved. | `false` | | browser optional | `boolean` | If true, browser data will be retrieved. | `false` | | operatingSystem optional | `boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitorCode optional | `boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `boolean` | If true, personalization data will be retrieved. This is required for the personalization condition | `false` | | cbs optional | `boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | *** #### getVisitorWarehouseData() Asynchronous method `getVisitorWarehouseAudience` collected with `useData` hook retrieves all audience data associated with the visitor in your data warehouse using the specified `visitorCode` and `warehouseKey`. The `warehouseKey` is typically your internal user ID. The `customDataIndex` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. Refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { getVisitorWarehouseAudience } = useData(); const getData = useCallback(async (): Promise => { // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData: CustomData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useData } from '@kameleoon/react-sdk'; function MyComponent() { const { getVisitorWarehouseAudience } = useData(); const getData = useCallback(async () => { // -- Get visitor warehouse audience data using `warehouseKey` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, warehouseKey: 'my_key', }); // -- Get visitor warehouse audience data using `visitorCode` // and add it to storage. const customData = await getVisitorWarehouseAudience({ visitorCode: 'my_visitor', customDataIndex: 10, }); }, [getRemoteData]); useEffect(() => { getData(); }, [getData]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | customDataIndex required | `number` | number representing the index of the custom data you want to use to target your Warehouse Audiences | | warehouseKey optional | `string` | unique key to identify the warehouse data (usually, your internal user ID) | ##### Return value | Type | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | `Promise` | promise containing CustomData with the associated warehouse data or `null` if there was no data | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.RemoteData` | Couldn't retrieve data from Kameleoon server | *** #### setLegalConsent() Method `setLegalConsent`, collected with `useVisitorCode` hook, specifies whether the visitor has given legal consent to use personal data. Setting the `legalConsent` parameter to `false` limits the types of data that you can include in tracking requests. This helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). * Consent information is in sync between the Kameleoon Engine (app file engine.js) and the React SDK. This synchronization means that once consent is set on either the Engine or the SDK, it's automatically set for both. This feature eliminates the need for manual consent handling and ensures that SDKs operate in compliance with user preferences. If you use Kameleoon in Hybrid mode, read the consent section in the [Hybrid experimentation article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/#managing-consent-in-hybrid-mode) * When handling legal consent, it's important to use [`getVisitorCode`](#getvisitorcode) method. Additionally, `getVisitorCode` doesn't accept `domain` as an argument. Instead, pass it to the [`createClient`](#createclient) function. ```ts theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode, setLegalConsent } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); setLegalConsent(visitorCode, true); }, [initialize, getVisitorCode, setLegalConsent]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode, setLegalConsent } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); setLegalConsent(visitorCode, true); }, [initialize, getVisitorCode, setLegalConsent]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | consent required | `boolean` | a boolean value representing the legal consent status. `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code length exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | ##### Consent revocation behavior When you call `setLegalConsent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. *** ### Goals and third-party analytics This section provides the methods you use to track when a visitor action achieve one of you goals (a conversion). #### trackConversion() * 📨 *Sends Tracking Data to Kameleoon* The `trackConversion()` function, used with the `useData` hook creates and adds [`Conversion`](#conversion) data to the visitor with specified parameters and executes `flush()`. Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue` argument. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. If you specify a `visitorCode` and set `isUniqueIdentifier` to `true`, the `trackConversion()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](#cross-device-experimentation) because the SDK links the flushed data with the visitor that's associated with the specified identifier. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123 }); // -- Track conversion with unique visitor identifier flag const internalUserId = 'my_user_id'; trackConversion({ visitorCode: internalUserId, revenue: 20000, goalId: 123, isUniqueIdentifier: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | -------------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `isUniqueIdentifier` optional | `boolean` | An optional parameter for specifying if the visitorCode is a unique identifier. | `false` | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | * 📨 *Sends Tracking Data to Kameleoon* The `trackConversion()` function, used with the `useData` hook creates and adds [`Conversion`](#conversion) data to the visitor with specified parameters and executes `flush()`. Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitorCode` and `goalId`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitorCode` is usually identical to the one that was used when triggering the experiment. The `trackConversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { trackConversion } = useData(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Track conversion trackConversion({ visitorCode, revenue: 2000, goalId: 123, metadata: [new CustomData(0, 'value')], negative: true, }); }, [initialize, trackConversion, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object consisting of: | Name | Type | Description | Default | | ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `number` | Revenue of the conversion. | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `undefined` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`addData()`](#adddata) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `trackConversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```ts theme={null} addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ```js theme={null} addData(visitorCode, new CustomData(5, 'Credit Card'), new CustomData(9, 'Express Delivery')); trackConversion({ visitorCode, goalId: 1000, metadata: [new CustomData(5, 'Amex Credit Card')] }); ``` ##### Exceptions thrown | Type | Description | | ----------------------------------------- | -------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters). | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty. | | `KameleoonException.StorageWrite` | Couldn't update storage data. | *** #### getEngineTrackingCode() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `getEngineTrackingCode()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getEngineTrackingCode, getVariation } = useFeatureFlag(); const [engineCode, setEngineCode] = useState(''); const init = useCallback(async (): Promise => { await initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200` and implicit experiment id is `100` getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code and set it to state setEngineCode(getEngineTrackingCode('visitor_code')); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` }, [initialize, getVariation, getEngineTrackingCode]); useEffect(() => { init(); }, [init]); useEffect(() => { if (!engineCode) { return; } // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); // -- Remove script from the page return () => { document.body.removeChild(script); }; }, [engineCode]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getEngineTrackingCode, getVariation } = useFeatureFlag(); const [engineCode, setEngineCode] = useState(''); const init = useCallback(async () => { await initialize(); // -- Trigger feature experiment // -- E.g., result `variationKey` id is `200` and implicit experiment id is `100` getVariation({ visitorCode: 'visitor_code', featureKey: 'my_feature_key' }); // -- Get tracking code and set it to state setEngineCode(getEngineTrackingCode('visitor_code')); // -- Result engine code will look like this // ` // window.kameleoonQueue = window.kameleoonQueue || []; // window.kameleoonQueue.push(['Experiments.assignVariation', 100, 200, true]); // window.kameleoonQueue.push(['Experiments.trigger', 100, true]); // ` }, [initialize, getVariation, getEngineTrackingCode]); useEffect(() => { init(); }, [init]); useEffect(() => { if (!engineCode) { return; } // -- Insert tracking code into the page const script = document.createElement('script'); script.textContent = engineCode; document.body.appendChild(script); // -- Remove script from the page return () => { document.body.removeChild(script); }; }, [engineCode]); } ``` * To use this feature, implement both the React SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | ------------------------------------- | -------- | --------------------------------- | | `visitorCode` required | `string` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `string` | JavaScript code to insert into the page. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | ------------------------------------------------------------- | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | *** ### Events This section provides the methods you use to handle events. #### setEventHandler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `null` as the `handler` removes the current handler for the selected event type. Collect the `setEventHandler` method with the `useInitialize` hook. To clear the current handler for an event type, pass `null` as the `handler`: ```tsx theme={null} setEventHandler(EventType.DataFileUpdate, null); ``` ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, DataFileUpdateEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); setEventHandler(EventType.DataFileUpdate, (eventData: DataFileUpdateEventDataType) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async () => { await initialize(); setEventHandler(EventType.DataFileUpdate, (eventData) => { const source = eventData.source; // DataFileUpdateSource.Polling or DataFileUpdateSource.Streaming const dateModified = eventData.dateModified; // Data file modification date in milliseconds // -- React to the data file update }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` `DataFileUpdateEventDataType` contains information about an SDK data file update. | Name | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource.Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource.Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `dateModified` | `number` | The modification date of the updated data file, in milliseconds. | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, HttpRequestEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); setEventHandler(EventType.HttpRequest, (eventData: HttpRequestEventDataType) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, setEventHandler } = useInitialize(); const init = useCallback(async () => { await initialize(); setEventHandler(EventType.HttpRequest, (eventData) => { if (eventData.success) { // -- The SDK request completed successfully const httpStatus = eventData.httpStatus; return; } // -- The SDK request failed const { reason, httpStatus, cause } = eventData.failure; }); }, [initialize, setEventHandler]); useEffect(() => { init(); }, [init]); } ``` `HttpRequestEventDataType` contains information about a completed SDK HTTP request. The SDK reports the event once per each actual HTTP request attempt, including retries. The type is a union of the following types. Check the `success` field to narrow the type. | Type | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `HttpRequestSucceededEventDataType` | Describes an SDK HTTP request that completed successfully (`success: true`). Always carries `httpStatus`. | | `HttpRequestFailedEventDataType` | Describes an SDK HTTP request that failed because of an HTTP status, error, or cancellation (`success: false`). Always carries `failure`. | ##### Event data fields | Name | Type | Description | | ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `success` | `boolean` | `true` if the SDK HTTP request completed successfully, `false` if it failed because of an HTTP status, error, or cancellation. | | `requestType` | `HttpRequestType` | The SDK request type. Possible values are `HttpRequestType.DataFile`, `HttpRequestType.Tracking`, `HttpRequestType.RemoteVisitorData`, and `HttpRequestType.RemoteData`. | | `httpStatus` | `number` | The HTTP status code returned by the request. Present only in `HttpRequestSucceededEventDataType`. | | `failure` | `HttpRequestFailureType` | Details about why the request failed. Present only in `HttpRequestFailedEventDataType`. | | `duration` | `number` | The request duration in milliseconds. | ##### HttpRequestFailureType `HttpRequestFailureType` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason.HttpStatus`, `HttpRequestFailureReason.Error`, and `HttpRequestFailureReason.Cancelled`. | | `httpStatus` | `number` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `undefined` for exception and cancellation failures. | | `cause` | `unknown` | The exception (error) that caused the request to fail. This value is `undefined` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `eventType` required | `EventType` | The SDK event type to handle. Use `EventType.DataFileUpdate` to react to SDK data file updates, or `EventType.HttpRequest` to react to completed SDK HTTP requests. | | `handler` required | `EventCallbackType` | The handler function the SDK calls with the event data of the selected event type: `DataFileUpdateEventDataType` for `EventType.DataFileUpdate`, or `HttpRequestEventDataType` for `EventType.HttpRequest`. Pass `null` to remove the current handler. | *** ##### Sending exposure events to external tools Kameleoon offers built-in integrations with various analytics and CDP solutions, such as [Mixpanel, Google Analytics 4, Segment...](/user-manual/integrations/integrations-overview). To ensure that you can track and analyze your server-side experiments, Kameleoon provides a method `getEngineTrackingCode()` that returns the JavasScript code to be inserted in your page to automatically send the exposure events to the analytics solution you are using. The SDK builds a tracking code for your active analytics solution based on the experiments that the visitor has triggered in the last 5 seconds. For more information about hybrid experimentation, refer to this [documentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation). To benefit from this feature, you will need to implement both the React SDK and the Kameleoon JavaScript tag. Implement the \[Kameleoon asynchronous tag], which you can install before your closing `` tag in your HTML page, as it will be only used for tracking purposes. ### Data types Kameleoon Data types are helper classes used for storing data in storage in predefined forms. During the [flush](#flush) execution, the SDK collects all the data and sends it along with the tracking request. Data available in the SDK isn't available for targeting and reporting in the Kameleoon app until you add the data. For example, by using the `addData()` method. See [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for more information. If you are using hybrid mode, you can call `getRemoteVisitorData()` to automatically fill all data that Kameleoon has collected previously. #### Browser Since React SDK `10.11.0`, `Browser` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. Browser contains browser information. Each visitor can only have one `Browser`. Adding a second `Browser` overwrites the first one. | Name | Type | Description | | ------------------------------- | ------------- | ----------------------------------------------------------------------------------------------- | | browser required | `BrowserType` | predefined browser type (`Chrome`, `InternetExplorer`, `Firefox`, `Safari`, `Opera`, `Other`) | | version optional | `number` | version of the browser, floating point number represents major and minor version of the browser | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); addData('my_visitor_code', browser); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Browser, BrowserType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new browser data to client const browser = new Browser(BrowserType.Chrome, 86.1); addData('my_visitor_code', browser); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### UniqueIdentifier `UniqueIdentifier` data is used as marker for unique visitor identification. If you add `UniqueIdentifier` for a visitor, `visitorCode` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). Associating a `UniqueIdentifier` with a visitor notify SDK that the visitor is linked to another visitor. The `UniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you do have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. Each visitor can only have one `UniqueIdentifier`. Adding another `UniqueIdentifier` overwrites the first one. | Name | Type | Description | | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | value required | `boolean` | value that specifies if the visitor is associated with another visitor, provided `false` will imply that the visitor isn't associated with any other visitor | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UniqueIdentifier } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new unique identifier to a visitor addData('my_visitor_code', new UniqueIdentifier(true)); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UniqueIdentifier } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new unique identifier to a visitor addData('my_visitor_code', new UniqueIdentifier(true)); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goalId` in the Kameleoon app. `ConversionParametersType` conversionParameters - an object with conversion parameters described below | Name | Type | Description | Default | | ---------------------------------- | -------------- | ----------------------------------------------- | ----------- | | `goalId` required | `number` | ID of the goal. | | | `revenue` optional | `float` | Revenue of the conversion | `0` | | `negative` optional | `boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `CustomData[]` | Metadata of the conversion. | `undefined` | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Conversion, ConversionParametersType, CustomData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Defined conversion parameters const conversionParameters: ConversionParametersType = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); addData('my_visitor_code', conversion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Conversion, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Defined conversion parameters const conversionParameters = { goalId: 123, revenue: 10000, negative: true, metadata: [new CustomData(0, 'value')], }; // -- Add new conversion data to client const conversion = new Conversion(conversionParameters); addData(visitorCode, conversion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. * Generally, the React SDK will attempt to use a `localStorage` cookie for the conditions. If not possible, SDK can use `Cookie` data as an alternative. * Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. | Name | Type | Description | | ------------------------------ | -------------- | ------------------------------------------------------------------- | | cookie required | `CookieType[]` | A list of `CookieType` objects consisting of cookie keys and values | ```ts theme={null} import { KameleoonClient, CookieType, Cookie, useInitialize, useData, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new cookie data to client const cookieData: CookieType[] = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); addData('my_visitor_code', cookie); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { KameleoonClient, CookieType, Cookie, useInitialize, useData, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new cookie data to client const cookieData = [ { key: 'key_1', value: 'value_1' }, { key: 'key_2', value: 'value_2' }, ]; const cookie = new Cookie(cookieData); addData('my_visitor_code', cookie); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ##### Methods `Cookie` data has a static utility method `fromString` that you can use to create a cookie instantly by parsing a string that contains valid cookie data. The method accepts `string` as parameter and returns an initialized `Cookie` instance. ```ts theme={null} import { Cookie } from '@kameleoon/react-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie: Cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` ```js theme={null} import { Cookie } from '@kameleoon/react-sdk'; const cookieString = 'key_1=value_1; key_2=value_2'; const cookie = Cookie.fromString(cookieString); // -- The result cookie will contain the following cookie array // [ // { key: 'key_1', value: 'value_1' }, // { key: 'key_2', value: 'value_2' }, // ] ``` #### GeolocationData `GeolocationData` contains the visitor's geolocation details Each visitor can only have one `GeolocationData`. Adding a second `GeolocationData` overwrites the first one. An object parameter with the type `GeolocationInfoType` containing the following fields: | Name | Type | Description | | ----------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------- | | country required | `string` | The country of the visitor | | region optional | `string` | The region of the visitor | | city optional | `string` | The city of the visitor | | postalCode optional | `string` | The postal code of the visitor | | coordinates optional | `[number, number]` | Coordinates array tuple of two position values (longitude and latitude). Coordinate number represents decimal degrees | ```ts theme={null} import { KameleoonClient, GeolocationData, GeolocationInfoType, useData, useInitialize, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add geolocation data const geolocationInfo: GeolocationInfoType = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); addData('my_visitor_code', geolocationData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```js theme={null} import { KameleoonClient, GeolocationData, useData, useInitialize, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add geolocation data const geolocationInfo = { country: 'France', region: 'Île-de-France', city: 'Paris', postalCode: '75008', coordinates: [48.8738, 2.295], }; const geolocationData = new GeolocationData(geolocationInfo); addData('my_visitor_code', geolocationData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### CustomData To retain custom data for future visits, the SDK transmits `CustomData` with a `Visitor` scope during the next tracking request. You can configure the scope in the data settings on the [custom data dashboard](https://app.kameleoon.com/customData/dashboard). `CustomData` allows you to associate any type of data with each visitor easily. This data can then be used as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `number`/`string` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | overwrite optional | `boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | | value required | `string[]` | The custom data value. It must be stringified to match the `string` type. *Note:* value is variadic. | | * Each visitor is allowed only one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Defined conversion parameters const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData( customDataIndex, dataItemOne, dataItemTwo, ); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add new custom data to client addData('my_visitor_code', customData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, CustomData } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Defined conversion parameters const dataItemOne = 'abc'; const dataItemTwo = JSON.stringify(100); const dataItemThree = JSON.stringify({ a: 200, b: 300 }); const customDataIndex = 0; // -- Create custom data using single parameter const customData = new CustomData(customDataIndex, dataItemOne); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, dataItemOne); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', dataItemOne); // -- Create custom data using variadic number of parameters const customData = new CustomData( customDataIndex, dataItemOne, dataItemTwo, ); // -- Create custom data using an array of values const dataList = [dataItemOne, dataItemTwo, dataItemThree]; const customData = new CustomData(customDataIndex, ...dataList); // -- Create custom data using overwrite flag const customData = new CustomData(customDataIndex, false, ...dataList); // -- Create custom data using name instead of index const customData = new CustomData('customDataName', false, ...dataList); // -- Add new custom data to client addData('my_visitor_code', customData); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### Device Since React SDK `10.11.0`, `Device` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. In React Native, the `Device` is automatically detected based on the `DPI` from `react-native.Dimensions`. Device contains information about your device. Each visitor can only have one `Device`. Adding a second `Device` overwrites the first one. | Name | Type | Description | | ---------------------------------- | ------------ | ------------------------------------------------------------- | | deviceType required | `DeviceType` | possible types for device type (`PHONE`, `TABLET`, `DESKTOP`) | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Device, DeviceType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new device data to client const device = new Device(DeviceType.Desktop); addData('my_visitor_code', device); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, Device, DeviceType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new device data to client const device = new Device(DeviceType.Desktop); addData('my_visitor_code', device); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### OperatingSystem Since React SDK `10.11.0`, `OperatingSystem` is automatically detected based on the `User-Agent` string. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. In React Native, the `OperatingSystem` is automatically detected based on the `react-native.Platform`. `OperatingSystem` contains the visitor's operating system information. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the previous one. | Name | Type | Description | | --------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- | | operatingSystem required | `OperatingSystemType` | possible types for device type: `WINDOWS_PHONE`, `WINDOWS`, `ANDROID`, `LINUX`, `MAC`, `IOS` | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, OperatingSystem, OperatingSystemType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); addData('my_visitor_code', operatingSystem); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, OperatingSystem, OperatingSystemType, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add operating system data const operatingSystem = new OperatingSystem(OperatingSystemType.Windows); addData('my_visitor_code', operatingSystem); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### PageView Since React SDK `10.11.0`, `PageView` is automatically detected based on the `window.location?.href` and `document.title`. However, you can still manually override it if needed. **React Native:** Support for this feature is currently experimental and may require adjustments to work correctly. PageView contains information about your web page. Each visitor can have one `PageView` per unique URL. Adding a `PageView` with the same URL as an existing one will notify SDK that the visitor revisited page `PageViewParametersType` pageViewParameters - an object with page view parameters described below | Name | Type | Description | | ---------------------------------- | ---------- | ---------------------------------------------------------------------------------- | | urlAddress required | `string` | url address of the page to track | | title required | `string` | title of the web page | | referrer optional | `number[]` | an optional parameter containing a list of referrers Indices, has no default value | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, PageView, PageViewParametersType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Define page view parameters const pageViewParameters: PageViewParametersType = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add new page view data to client const pageView = new PageView(pageViewParameters); addData('my_visitor_code', pageView); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, PageView } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Define page view parameters const pageViewParameters = { urlAddress: 'www.example.com', title: 'my example', referrers: [123, 456], }; // -- Add new page view data to client const pageView = new PageView(pageViewParameters); addData('my_visitor_code', pageView); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** #### UserAgent Store information on the user-agent of the visitor. Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that could otherwise skew your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. A visitor can only have one `UserAgent`. Adding a second `UserAgent` overwrites the first one. | Name | Type | Description | | ----------------------------- | -------- | ------------------------- | | value required | `string` | value used for comparison | Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Pass the user agent to be filtered by Kameleoon when running server-side experiments for each visitor browsing your website, to avoid counting bots in your analytics. If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UserAgent } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new user agent data to client const userAgent = new UserAgent('my_unique_value'); addData('my_visitor_code', userAgent); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, UserAgent } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new user agent data to client const userAgent = new UserAgent('my_unique_value'); addData('my_visitor_code', userAgent); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `string` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, ApplicationVersion, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async (): Promise => { await initialize(); // -- Add new application version data to client const applicationVersion = new ApplicationVersion('1.2'); addData('my_visitor_code', applicationVersion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useData, ApplicationVersion, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { addData } = useData(); const init = useCallback(async () => { await initialize(); // -- Add new application version data to client const applicationVersion = new ApplicationVersion('1.2'); addData('my_visitor_code', applicationVersion); }, [initialize, addData]); useEffect(() => { init(); }, [init]); } ``` *** ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | -------------- | -------------------------- | --------------------------------------------------------------------------------- | | `featureFlags` | `Map` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `dateModified` | `number` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```ts theme={null} import { FeatureFlag } from '@kameleoon/javascript-sdk'; // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags: Map = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified: number = dateFile.dateModified; ``` ```js theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. const featureFlags = dataFile.featureFlags; // Retrieves the last modification timestamp of the DataFile. // The value is a number representing milliseconds since the Unix epoch. const dateModified = dateFile.dateModified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------------- | ------------------------ | -------------------------------------------------------------------------- | | `environmentEnabled` | `boolean` | Indicating whether the feature flag is enabled in the current environment. | | `defaultVariationKey` | `string` | The key of the default variation associated with the feature flag. | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Rule[]` | A list of `Rule` objects | ```ts theme={null} import { Variation, Rule } from '@kameleoon/react-sdk'; // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled: boolean = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey: string = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations: Map = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules: Rule[] = featureFlag.rules; ``` ```js theme={null} // Check whether the feature flag is enabled in the current environment const isEnvironmentEnabled = featureFlag.environmentEnabled; // Retrieve the key of the default variation const defaultVariationKey = featureFlag.defaultVariationKey; // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) const variations = featureFlag.variations; // Retrieve all targeting rules associated with the feature flag const rules = featureFlag.rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------ | ------------------------------------------------------ | | `variations` | `Map` | A map of `Variation` objects, keyed by variation keys. | ```ts theme={null} import { Variation } from '@kameleoon/react-sdk'; // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations: Map = rule.variations; ``` ```js theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) const variations = rule.variations; ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | ------------ | ----------------------- | --------------------------------------------------------------------------------------------------- | | name | `string` | name of the variation. | | key | `string` | key of the variation. | | id | `number` or `null` | id of the variation or `null` if the visitor landed on the default variation. | | experimentId | `number` or `null` | id of the experiment or `null` if the visitor landed on the default variation. | | variables | `Map` | map of variables for the variation, where key is the variable key and value is the variable object. | * Ensure that your code handles the case where `id` or `experimentId` may be `null`, indicating a default variation. * The `variables` map might be empty if no variables are associated with the variation. ```ts theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` ```js theme={null} // Retrieving the variation name const variationName = variation.name; // Retrieving the variation key const variationKey = variation.key; // Retrieving the variation id const variationId = variation.id; // Retrieving the experiment id const experimentId = variation.experimentId; // Retrieving the variables map const variables = variation.variables; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | key | `string` | The unique key identifying the variable. | | type | `string` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS**. | | value | `any` | The value of the variable, which can be of the following types: **boolean**, **number**, **String**, **Record\**, **any\[]**. | ```ts theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ```js theme={null} // Retrieving the variables map const variables = variation.variables; // Variable type can be retrieved for further processing const type = variables.get('isDiscount')?.type || ''; // Retrieving the variable value by key const isDiscount = variables.get('isDiscount')?.value || false; // Variable value can be of different types const title = variables.get('title')?.value || ''; ``` ### Deprecated methods These methods are deprecated and will be removed in the next major update. #### getFeatureFlagVariationKey() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` Use the [`getVariation`](#getvariation) method. The method `getFeatureFlagVariationKey()`, which is used with the `useFeatureFlag` hook, retrieves the variation key for a visitor identified by their `visitorCode`. This process includes checking the targeting criteria, identifying the appropriate variation assigned to the visitor, storing this information, and sending a tracking request. If a user has never been associated with a feature flag, the SDK will randomly return a variation key according to the rules of that feature flag. If the user is already linked to the feature flag, the SDK will identify the previously assigned variation key. If the user doesn't meet any of the specified rules, the SDK will return the default value defined in Kameleoon’s feature flag delivery rules. It’s important to note that the default value may not always be a variation key; it could also be a boolean value or another data type, depending on how the feature flag is configured. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getFeatureFlagVariationKey } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code using `getVisitorCode` function. const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Get the variationKey for the visitor under `visitorCode` in the feature flag. const variationKey = getFeatureFlagVariationKey(visitorCode, featureKey); }, [initialize, visitorCode, getFeatureFlagVariationKey, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getFeatureFlagVariationKey } = useFeatureFlag(); const { getVisitorCode } = useVisitorCode(); const init = useCallback(async () => { await initialize(); // -- Get visitor code using `getVisitorCode` function const visitorCode = getVisitorCode(); const featureKey = 'my_feature_key'; // -- Get the variationKey for the visitor under `visitorCode` in the found feature flag const variationKey = getFeatureFlagVariationKey(visitorCode, featureKey); }, [initialize, visitorCode, getFeatureFlagVariationKey, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | ##### Return value | Type | Description | | -------- | --------------------------------------------------------------------------------------------------- | | `string` | a string containing variable key for the allocated feature flag variation for the provided visitor. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ---------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | #### getVisitorFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getVisitorFeatureFlags` method, utilized with the `useFeatureFlag` hook, returns a list of *active* feature flags that target the visitor associated with the `visitorCode` (the visitor must have one of the allocated variations). This method only collects the feature flags that are currently active for the visitor. As a result, it doesn't include any feature flags for which the visitor is assigned to the “off” variation (default or control). If you need to retrieve all of the visitor’s feature flags, use `getFeatureFlags` instead. For example: ```ts theme={null} // -- `getVisitorFeatureFlags` doesn't trigger feature experiments; // it only returns feature flags where visitors didn't get the `off` variation. getVisitorFeatureFlags('my_visitor').forEach(({ key }) => { // -- `getFeatureFlagVariationKey` triggers a feature experiment, // as `off` is already filtered out - visitors will never take part // in an experiment where the `off` variation was allocated. getFeatureFlagVariationKey('my_visitor', key); }); ``` For cases where you need all of the visitor's feature flags, use [`getFeatureFlags`](#getfeatureflags) instead: ```ts theme={null} // -- Both `off` and other variations are processed as expected getFeatureFlags('my_visitor').forEach(({ key }) => { getFeatureFlagVariationKey('my_visitor', key); }); ``` ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getVisitorFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = getVisitorFeatureFlags(visitorCode); }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getVisitorFeatureFlags } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor const featureFlags = getVisitorFeatureFlags(visitorCode); }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | ##### Return value | Type | Description | | ------------------- | ---------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key`. | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | *** #### getActiveFeatureFlags() * 🚫 *Doesn't send Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getActiveFeatureFlags` method, collected with the `useFeatureFlag` hook, returns a `Map`, where key is feature key and value is detailed information about the visitor's variation and it's variables ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getActiveFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } init(); ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getActiveFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get active feature flags for visitor // with detailed variation and variables data const activeFeatures = getActiveFeatureFlags(visitorCode); // -- Result example: // Map { // 'feature-key-one' => { // id: 100, // key: 'variation-key-one', // experimentId: 200, // variables: [ // { key: 'variable_bool', type: VariableType.Boolean, value: true }, // ] // }, // 'feature-key-two' => { // id: null, // -> `null` because it is default variation // key: 'default-variation-key', // experimentId: null, // -> `null` because it is default variation // variables: [] // } // } }, [initialize, visitorCode, getVisitorFeatureFlags, getVisitorCode]); useEffect(() => { init(); }, [init]); } init(); ``` This method only collects the visitor's *active* feature flags. This means the result excludes all the feature flags for which the visitor is assigned to the `off` (default or control) variation. When you need all of the visitor's feature flags to iterate over, use `getFeatureFlags` instead. See the [getVisitorFeatureFlags](#getvisitorfeatureflags) *CAUTION* section method for more details. ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | ##### Return value | Type | Description | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Map` | a map of feature flags, where key is feature key and value is detailed information about the visitor's variation and it's variables | ##### Exceptions thrown | Type | Description | | ----------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length of 255 characters | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.StorageRead` | Error while reading storage data | | `KameleoonException.NumberParse` | Couldn't parse Number value | | `KameleoonException.JSONParse` | Couldn't parse JSON value | *** #### getFeatureFlagVariable() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` Use the [`getVariation`](#getvariation) method. The `getFeatureFlagVariable` method, collected with `useFeatureFlag` hook, returns a variable for the visitor under `visitorCode` in the found feature flag, this includes targeting check, finding the according variation exposed to the visitor and saving it to storage along with sending tracking request. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useFeatureFlag, VariableType, JSONType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariable } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get feature variable const result = getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key', variableKey: 'my_variable_key', }); // -- Infer the type of variable by its `type` switch (result.type) { case VariableType.BOOLEAN: const myBool: boolean = result.value; break; case VariableType.NUMBER: const myNum: number = result.value; break; case VariableType.JSON: const myJson: JSONType = result.value; break; case VariableType.STRING: case VariableType.JS: case VariableType.CSS: const myStr: string = result.value; break; default: break; } }, [initialize, getFeatureFlagVariable, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useVisitorCode, useFeatureFlag, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariable } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get feature variable const variableResult = getFeatureFlagVariable({ visitorCode, featureKey: 'my_feature_key', variableKey: 'my_variable_key', }); const { type, value } = variableResult; }, [initialize, getFeatureFlagVariable, visitorCode, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters Parameters object of type `GetFeatureFlagVariableParamsType` containing the following fields: | Name | Type | Description | | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | | variableKey required | `string` | key of the variable to be found for a feature flag with the specified `featureKey`, can be found on Kameleoon Platform | ##### Return value | Type | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureFlagVariableType` | a variable object containing `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN` then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before `initialize` was done for `kameleoonClient` | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagVariableNotFound` | No feature variable was found for the specified `visitorCode` and `variableKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | #### getFeatureFlagVariables() * 📨 *Sends Tracking Data to Kameleoon* * 🎯 *Events:* `EventType.Evaluation` (for each feature flag) Use the [`getVariations`](#getvariations) method. The `getFeatureFlagVariables` method, collected with the `useFeatureFlag`, hook returns a list of variables for the visitor under `visitorCode` in the found feature flag, this includes targeting check, finding the according variation exposed to the visitor and saving it to storage along with sending tracking request. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariables } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode(); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = getFeatureFlagVariables(visitorCode, 'my_feature_key'); }, [initialize, getFeatureFlagVariables, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag, useVisitorCode, } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getVisitorCode } = useVisitorCode(); const { getFeatureFlagVariables } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get visitor code const visitorCode = getVisitorCode('www.example.com'); // -- Get a list of variables for the visitor under `visitorCode` in the feature flag const variables = getFeatureFlagVariables(visitorCode, 'my_feature_key'); }, [initialize, getFeatureFlagVariables, getVisitorCode]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | visitorCode required | `string` | unique visitor identification string, can't exceed 255 characters length | | featureKey required | `string` | a unique key for feature flag | ##### Return value | Type | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FeatureVariableResultType[]` | a list of variable objects containing `key`, `type` and `value` fields. You can check the `type` field against `VariableType` enum. For example, if the `type` is `VariableType.BOOLEAN` then `value` will be a `boolean` type. | ##### Exceptions thrown | Type | Description | | ----------------------------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | | `KameleoonException.VisitorCodeMaxLength` | The visitor code exceeded the maximum length (255 characters) | | `KameleoonException.VisitorCodeEmpty` | The visitor code is empty | | `KameleoonException.FeatureFlagConfigurationNotFound` | No feature flag was found for the specified `featureKey` | | `KameleoonException.FeatureFlagVariationNotFound` | No feature variation was found for the specified `visitorCode` and `variableKey` | | `KameleoonException.FeatureFlagEnvironmentDisabled` | Feature flag is off for the current environment | | `KameleoonException.JSONParse` | Couldn't parse JSON value | | `KameleoonException.NumberParse` | Couldn't parse Number value | *** #### onConfigurationUpdate() Use the `onEvent` method with `EventType.ConfigurationUpdate` instead. Method `onConfigurationUpdate` collected with `useInitialize` hook fires a callback on client configuration update. This hook only works for server sent events of real time update ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, onConfigurationUpdate } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); // -- Define logic to execute on client configuration update onConfigurationUpdate(() => { // -- My Logic }); }, [initialize, onConfigurationUpdate]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, onConfigurationUpdate } = useInitialize(); const init = useCallback(async () => { await initialize(); // -- Define logic to execute on client configuration update onConfigurationUpdate(() => { // -- My Logic }); }, [initialize, onConfigurationUpdate]); useEffect(() => { init(); }, [init]); } ``` ##### Parameters | Name | Type | Description | | -------------------------------- | ------------ | ---------------------------------------------------------------------------------- | | callback required | `() => void` | callback function with no parameters that will be called upon configuration update | ##### Exceptions thrown | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed its `initialize` call | *** #### getFeatureFlags() 🚫 *Doesn't send Tracking Data to Kameleoon* The `getFeatureFlags` method collected with the `useFeatureFlag` hook returns a list of feature flags stored in the client configuration. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize } = useInitialize(); const { getFeatureFlags } = useFeatureFlag(); const init = useCallback(async (): Promise => { await initialize(); // -- Get list of all feature flags const featureFlags = getFeatureFlags(); }, [initialize, getFeatureFlags]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, useFeatureFlag } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize } = useInitialize(); const { getFeatureFlags } = useFeatureFlag(); const init = useCallback(async () => { await initialize(); // -- Get list of all feature flags const featureFlags = getFeatureFlags(); }, [initialize, getFeatureFlags]); useEffect(() => { init(); }, [init]); } ``` ##### Return value | Type | Description | | ------------------- | ---------------------------------------------------------------------- | | `FeatureFlagType[]` | list of feature flags, each feature flag item contains `id` and `key`. | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | #### onEvent() Use [`setEventHandler()`](#seteventhandler) instead. Method `onEvent`, collected with the `useInitialize` hook, fires a callback when a specific event is triggered. The callback function has access to the data associated with the event. The SDK methods in this documentation note which event types they can trigger, if any. ```tsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType, EvaluationEventDataType, } from '@kameleoon/react-sdk'; function MyComponent(): JSX.Element { const { initialize, onEvent } = useInitialize(); const init = useCallback(async (): Promise => { await initialize(); // -- Define logic to execute on SDK event onEvent(EventType.Evaluation, (eventData: EventDataType) => { // -- My Logic }); }, [initialize, onEvent]); useEffect(() => { init(); }, [init]); } ``` ```jsx theme={null} import { useEffect, useCallback } from 'react'; import { useInitialize, EventType } from '@kameleoon/react-sdk'; function MyComponent() { const { initialize, onEvent } = useInitialize(); const init = useCallback(async () => { await initialize(); // -- Define logic to execute on SDK event .onEvent(EventType.Evaluation, (eventData) => { // -- My Logic }); }, [initialize, onEvent]); useEffect(() => { init(); }, [init]); } ``` You can only assign one callback to each `EventType`. ##### Events Events are defined in the `EventType` enum. Depending on the event type, the `eventData` parameter will have a different type. | Type | `eventData` type | Description | | ------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `EventType.Evaluation` | `EvaluationEventDataType` | Triggered when the SDK evaluates any variation for a feature flag. It's triggered regardless of the result variation | | `EventType.ConfigurationUpdate` | `ConfigurationUpdateEventDataType` | Triggered when the SDK receives a configuration update from the server (when using real-time streaming) | ##### Parameters | Name | Type | Description | | -------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | event required | `EventType` | a type of the event to associate the callback action with | | callback required | `(eventData: EventDataType) => void` | a callback function with the `eventData` parameter that will be called when a configuration update occurs | ##### Exceptions thrown | Type | Description | | ----------------------------------- | --------------------------------------------------------------------------------- | | `KameleoonException.Initialization` | Method was executed before the `kameleoonClient` completed it's `initialize` call | # Ruby SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/ruby-sdk Integrate the Kameleoon Ruby SDK to run experiments and activate feature flags on Ruby back-end servers and Rails applications. With the Ruby SDK, you can run experiments and activate feature flags on your back-end Ruby server. Integrating the SDK into your web-app is easy, and its footprint (memory and network usage) is low. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Ruby SDK: 3.22.2 [Changelog](https://github.com/Kameleoon/client-ruby/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the Ruby SDK, see the [reference](#reference) section. ## Developer guide This section shows you how to integrate the SDK and start running experiments in your Ruby applications. Follow this tutorial to set up a simple A/B test to change the number of recommended products based on different variations. ### Getting started #### Install the SDK Install the SDK using a standard gem package, which is hosted on the official RubyGems repository. To install, run the following command: ```cli theme={null} gem install kameleoon-client-ruby ``` #### Configure the client You provide credentials for the Ruby SDK using a configuration file, which you can also use to customize the SDK's behavior. You can start with the [sample configuration file](/assets/developer-docs/sdks/web-sdks/client-configs/client-ruby.yaml). Add this file to the default path `/etc/kameleoon/client-ruby.yaml`. If you use another location, you must pass the path as an argument to the `Kameleoon::KameleoonClientFactory.Create()` method during initialization. These are the available keys in the latest SDK: | Key | Description | Default value | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `client_id` required | Required for authentication to the Kameleoon service. To find your **client id**, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `client_secret` required | Required for authentication to the Kameleoon service. To find your **client secret**, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `session_duration_minute` optional | Designates the predefined time interval that Kameleoon stores the visitor and their associated data in memory (RAM). Note that increasing the session duration increases the amount of RAM that needs to be allocated to store visitor data. | `30` minutes | | `refresh_interval_minute` optional | Specifies the refresh interval, in minutes, that the SDK fetches the configuration for the active experiments and feature flags. The value determines the maximum time it takes to propagate changes, such as activating or deactivating feature flags or launching experiments, to your production servers. Additionally, the SDK offers a [streaming mode](/developer-docs/feature-experimentation/technical-reference/technical-considerations/#streaming-premium-option) that uses server-sent events (SSE) to push new configurations to the SDK automatically and apply new configurations in real-time, without any delays. | `60` minutes | | `default_timeout_millisecond` optional | Specifies the timeout, in milliseconds, for network requests from the SDK. Set the value to 30 seconds or more if you don't have a stable connection. Some methods have an additional parameter that you can use to override the default timeout for that particular method. If you don't specify the timeout for a method explicitly, the SDK uses this default value. | `10000` milliseconds | | `tracking_interval_millisecond` optional | Specifies the interval for tracking requests in milliseconds. All visitors who Kameleoon evaluated for any feature flag or had data flushed are included in this tracking request, which the SDK performs once per interval. The minimum value is `1000` ms, which is also the default, and the maximum value is `5000` ms. | `1000` milliseconds | | `environment` optional | Environment from which the feature flag’s configuration is to be used. The value can be `production`, `staging`, `development`. See the [managing environments](/user-manual/experimentation/feature-experimentation/configure-your-feature-flags/manage-environments) article for details. | `production` | | `top_level_domain` *(required in hybrid mode)* | The current top-level domain for your website. Use the format: `example.com`. Don't include `https://`, `www`, or other subdomains. Kameleoon uses this information to set the corresponding cookie on the top-level domain. | `nil` | | `network_domain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `nil` | | `verbose_mode` *(deprecated)* | Boolean value (`true` or `false`) that turns on additional logging, including network requests and debug information. This field is deprecated and will be removed in SDK version `4.0.0`. Use [`KameleoonLogger.setLogLevel`](#log-levels) instead. | `nil` | The Kameleoon Ruby SDK uses the Automation API and follows the OAuth 2.0 client credentials flow. #### Initialize the client After you've installed the SDK into your app and configured the correct credentials (in `/etc/kameleoon/client-ruby.yaml` or `Kameleoon::KameleoonClientConfig`), you must set up a server-side experiment in the Kameleoon App. The next step is creating the Kameleoon client in your app code. The following code provides an example of creating the Kameleoon client. A `Kameleoon::KameleoonClient` is a singleton object that acts as a bridge between your app and Kameleoon. It includes all the methods and properties you need to run an experiment. Developers are responsible for ensuring the correct logic of their app code when implementing A/B testing with Kameleoon. A best practice is to always assume that a visitor may be excluded from the experiment if it hasn't yet been launched. This practice is simple to implement, as it aligns with the default or reference variation logic, which should always be in place. The code samples in the next section demonstrate this approach. ```ruby theme={null} # external settings file require "kameleoon" site_code = "a8st4f59bj" kameleoon_client = Kameleoon::KameleoonClientFactory.create(site_code) kameleoon_client = Kameleoon::KameleoonClientFactory.create(site_code, config_path: '/etc/kameleoon/client-ruby.yaml') # internal KameleoonClientConfig object require 'kameleoon' require 'kameleoon/kameleoon_client_config' kameleoon_config = Kameleoon::KameleoonClientConfig.new( 'client_id', # required 'client_secret', # required refresh_interval_minute: configuration_refresh_interval, # (in minutes) optional, default: 60 minutes session_duration_minute: session_duration, # (in minutes) optional, default: 30 minutes default_timeout_millisecond: default_timeout, # (in milliseconds) optional, default: 2000 milliseconds tracking_interval_millisecond: tracking_interval, # (in milliseconds) optional (1000 ms by default) environment: environment, # optional, possible values: "production" / "staging" / "development" / "staging", default: "production" top_level_domain: 'example.com', verbose_mode: verbose_mode, # optional, default: false network_domain: 'example.com' # optional ) kameleoon_client = Kameleoon::KameleoonClientFactory.create(site_code, config: kameleoon_config) ``` If you use Ruby on Rails, initialize the Kameleoon client at server start-up in the app.rb file. ```ruby theme={null} require_relative 'boot' require 'rails/all' require 'kameleoon' Bundler.require(*Rails.groups) module App class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.1 if defined?(Rails::Server) config.after_initialize do site_code = 'a8st4f59bj' kameleoon_config = Kameleoon::KameleoonClientConfig.new('client_id', 'client_secret') config.kameleoon_client = Kameleoon::KameleoonClientFactory.create(site_code, config: kameleoon_config) end end end end ``` You can then access the Kameleoon client in your controllers: ```ruby theme={null} class YourController < ApplicationController def index kameleoon_client = App::Application.config.kameleoon_client # Your controller code, using the kameleoon_client end end ``` #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`get_visitor_code()`](#get_visitor_code) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `default_visitor_code` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `get_visitor_code()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`get_variation()`](#get_variation) or [`feature_active?()`](#feature_active) method to retrieve the configuration based on the `feature_key`. The `get_variation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `feature_key` and `visitor_code`. The `feature_active?()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `get_variation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval_millisecond`](#configure-the-client). By default, this interval is set to 1000 milliseconds (1 second). The `get_variation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `get_variations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`add_data()`](#add_data) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`get_remote_visitor_data()`](#get_remote_visitor_data) method. This method asynchronously fetches data from the servers. It's important to call `get_remote_visitor_data()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`track_conversion()`](#track_conversion) method and provide the required `visitor_code` and `goal_id` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval_millisecond`](#configure-the-client)). If you prefer to send the request immediately, use the [`flush()`](#flush) method with the parameter `instant=true`. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`get_engine_tracking_code()`](#get_engine_tracking_code) method. The `get_engine_tracking_code()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `get_remote_visitor_data()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `get_remote_visitor_data()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`get_remote_visitor_data()`](#get_remote_visitor_data) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitor_code`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```ruby title="Device A" theme={null} # In this example, Custom data with index `90` was set to "Visitor" scope in Kameleoon. VISITOR_SCOPE_CUSTOM_DATA_INDEX = 90 kameleoon_client.add_data(visitor_code, CustomData.new(VISITOR_SCOPE_CUSTOM_DATA_INDEX, 'your data')) kameleoon_client.flush(visitor_code) ``` ```ruby title="Device B" theme={null} # Before working with the data, call `get_remote_visitor_data`. kameleoon_client.get_remote_visitor_data(visitor_code) # After calling, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. # So, "your data" will be available to target and track the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`get_remote_visitor_data()`](#get_remote_visitor_data) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `get_remote_visitor_data()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`track_conversion()`](#track_conversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`get_remote_visitor_data()`](#get_remote_visitor_data) method on each device. Here's an example of how to use custom data for session merging. ```ruby theme={null} # In this example, `91` represents the Custom Data's index configured as a unique identifier in Kameleoon. MAPPING_INDEX = 91 FEATURE_KEY = 'ff123' # 1. Before the visitor is authenticated # Retrieve the variation for an unauthenticated visitor. # Assume `anonymous_visitor_code` is the randomly generated ID for that visitor. anonymous_variation = kameleoon_client.get_variation(anonymous_visitor_code, FEATURE_KEY) # 2. After the visitor is authenticated # Assume `user_id` is the visitor code of the authenticated visitor. kameleoon_client.add_data(anonymous_visitor_code, CustomData.new(MAPPING_INDEX, user_id)) kameleoon_client.flush(anonymous_visitor_code, instant: true) # Indicate that `user_id` is a unique identifier. kameleoon_client.add_data(user_id, UniqueIdentifier.new(True)) # 3. After the visitor has been authenticated # Retrieve the variation for the `user_id`, which will match the anonymous visitor code's variation. user_variation = kameleoon_client.get_variation(user_id, FEATURE_KEY) is_same_variation = user_variation.key == anonymous_variation.key # true # The `user_id` and `anonymous_visitor_code` are now linked and tracked as a single visitor. kameleoon_client.track_conversion(user_id, 123, 10.0) # Additionally, the linked visitors will share all fetched remote visitor data. kameleoon_client.get_remote_visitor_data(user_id) ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`get_visitor_code()`](#get_visitor_code) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitor_code`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitor_code`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `accountId`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```ruby theme={null} bucketing_key = Kameleoon::CustomData.new(index, 'new_visitor_code') kameleoon_client.add_data(visitor_code, bucketing_key) ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`add_data()`](#add_data) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `newVisitorCode` refers to the identifier you wish to use for your bucketing (for example, the new `userId` or `accountId`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `add_data()` method, all hash calculations for assigning users to variations will use this `newVisitorCode` (your custom key) instead of the default `visitor_code`. Using the `newVisitorCode` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `newVisitorCode` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitor_code`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `String`. * It must be unique for the entity you intend to bucket (for example, if using a `userId`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```ruby theme={null} require 'kameleoon/logging/kameleoon_logger' # The `NONE` log level does not allow logging. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::NONE # The `ERROR` log level only allows logging issues that may affect the SDK's main behaviour. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::ERROR # The `WARNING` log level allows logging issues which may require additional attention. # It extends the `ERROR` log level. # The `WARNING` log level is a default log level. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::WARNING # The `INFO` log level allows logging general information on the SDK's internal processes. # It extends the `WARNING` log level. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::INFO # The `DEBUG` level logs additional details about the SDK’s internal processes and extends the `INFO` level # with more granular diagnostic output. # This information is not intended for end-user interpretation but can be sent to our support team # to assist with internal troubleshooting. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::DEBUG ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```ruby theme={null} require 'logger' require 'kameleoon/logging/logger' module Kameleoon class CustomLogger < Kameleoon::Logging::Logger def initialize @internal_logger = Logger.new(STDOUT) end def log(level, message) case level when Kameleoon::Logging::LogLevel::ERROR @internal_logger.error(message) when Kameleoon::Logging::LogLevel::WARNING @internal_logger.warn(message) when Kameleoon::Logging::LogLevel::INFO @internal_logger.info(message) when Kameleoon::Logging::LogLevel::DEBUG @internal_logger.debug(message) end end end end # Log level filtering is applied separately from log handling logic. # The custom logger will only accept logs that meet or exceed the specified log level. # Ensure the log level is set correctly. Kameleoon::Logging::KameleoonLogger.log_level = Kameleoon::Logging::LogLevel::DEBUG # Optional, defaults to `Kameleoon::Logging::LogLevel::WARNING`. Kameleoon::Logging::KameleoonLogger.logger = CustomLogger.new ``` ## Reference This is a full reference documentation of the Ruby SDK. ### Initialization #### create() The starting point for using the SDK is the initialization step. All interactions with the SDK are done through an object named Kameleoon::KameleoonClient, therefore you need to create this object. ```ruby theme={null} kameleoon_config = Kameleoon::KameleoonClientConfig.new('client_id', 'client_secret') kameleoon_client = Kameleoon::KameleoonClientFactory.create('a8st4f59bj', config: kameleoon_config) kameleoon_client = Kameleoon::KameleoonClientFactory.create('a8st4f59bj', config_path: '/etc/kameleoon/client-ruby.yaml') ``` ##### Arguments | Name | Type | Description | | ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | site\_code | String | This is a [unique key](/user-manual/faq#how-do-i-find-my-sitecode) of the Kameleoon project you are using with the SDK. This field is mandatory. | | configuration\_file\_path | String | Path to the SDK configuration file. This field is optional, and set to `/etc/kameleoon/client-ruby.yaml` by default. | | config | Kameleoon::KameleoonClientConfig | Configuration SDK object that you can pass instead of using a configuration file. This field is optional. | ##### Exceptions thrown | Type | Description | | ------------------------------------- | ------------------------------------------------------------------------------------ | | Kameleoon::Exception::SiteCodeIsEmpty | Exception indicating that the specified site code is empty string, which is invalid. | #### wait\_init() Use `wait_init()` when your application should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: `true` when the client is initialized, and `false` if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return `true`. If no initialization result is available before the timeout expires, the call returns `false`. If you don't provide a `timeout_millisecond` value, the SDK uses the default timeout from [`default_timeout_millisecond`](#configure-the-client). The failure that prevented the SDK from initializing is reported to the log. Call `wait_init()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready?()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `wait_init()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready?()` starts returning `true` as soon as a retry succeeds. ```ruby theme={null} # Waits up to the default timeout of the client configuration if kameleoon_client.wait_init # The SDK has been initialized end # Waits no longer than the specified timeout if kameleoon_client.wait_init(1000) # The SDK has been initialized end ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | ----------------------------- | | `timeout_millisecond` optional | `Integer` | The maximum time to wait for an initialization result, in milliseconds. | `default_timeout_millisecond` | ##### Return value | Type | Description | | --------- | -------------------------------------------------------------------------------------------------------------- | | `Boolean` | `true` if the SDK is ready, or `false` if the initial configuration fetch failed or the timeout elapsed first. | #### is\_ready?() `is_ready?()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`wait_init()`](#wait_init), this method returns immediately without blocking or throwing. * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. * Call `wait_init()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready?()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `wait_init()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready?()` starts returning `true` as soon as a retry succeeds. ```ruby theme={null} ready = kameleoon_client.is_ready? ``` ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `Boolean` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | ### Feature flags and variations #### feature\_active?() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Previously called `activate_feature` - removed since SDK version `3.0.0`. This method takes a **visitor\_code** and **feature\_key** as mandatory arguments to check if the specified feature will be active for a user. If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly (**true** if the user should have this feature or **false** if they shouldn't). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the previous **FeatureFlag** value. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `feature_active?` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} visitor_code = kameleoon_client.get_visitor_code(cookies) feature_key = "new_checkout" has_new_checkout = false begin has_new_checkout = kameleoon_client.feature_active?(visitor_code, feature_key) # disabling tracking has_new_checkout = kameleoon_client.feature_active?(visitor_code, feature_key, track: false) rescue Kameleoon::Exception::FeatureNotFound # The user will not be counted in the experiment, but should see the reference variation. has_new_checkout = false end if has_new_checkout # Implement new checkout code here end ``` The `feature_active?()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ##### Parameters | Name | Type | Description | | ----------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | visitor\_code | `String` | Unique identifier of the user. This field is mandatory. | | feature\_key | `String` | Key of the feature you want to expose to a user. This field is mandatory. | | is\_unique\_identifier (Deprecated) | `Boolean` | When set to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. | | track | `Boolean` | An optional parameter to enable or turn off tracking of the feature evaluation (`true` by default). | ##### Return value | Type | Description | | --------- | --------------------------------------------------------------------- | | `Boolean` | Value of the feature that's registered for a given **visitor\_code**. | ##### Exceptions thrown | Type | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (empty, or longer than 255 characters). | #### get\_variation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitor_code` and `feature_key` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ruby theme={null} feature_key = "new_checkout" begin variation = kameleoon_client.get_variation(visitor_code, feature_key) # disabling tracking variation = kameleoon_client.get_variation(visitor_code, feature_key, track: false) rescue Kameleoon::Exception::FeatureNotFound # The error has occurred; the feature flag isn't found in the current configuration. rescue Kameleoon::Exception::FeatureEnvironmentDisabled # The feature flag is disabled for the environment rescue Kameleoon::Exception::VisitorCodeInvalid # The visitor code you passed to the method is invalid and can't be accepted by SDK end # Fetch a variable value for the assigned variation title = variation.variables['title'].value case variation.key when 'on' # Main variation key is selected for visitorCode when 'alternative_variation' # Alternative variation key else # Default variation key end ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | --------- | ------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | | | `feature_key` required | `String` | Key of the feature you want to expose to a visitor. | | | `track` optional | `Boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------- | ------------------------------------------------------------------------------------- | | `Variation` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag. | ##### Exceptions thrown | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | #### get\_variations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitor_code` as a mandatory argument, while `only_active` and `track` are optional. * If `only_active` is set to `true`, the method `get_variations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```ruby theme={null} begin variations = kameleoon_client.get_variations(visitor_code) # only active variations variations = kameleoon_client.get_variations(visitor_code, only_active: true) # disable tracking variations = kameleoon_client.get_variations(visitor_code, track: false) rescue Kameleoon::Exception::VisitorCodeInvalid # Handle exception end ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | | | `only_active` optional | `Boolean` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `Boolean` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Hash` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### set\_forced\_variation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `force_targeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#get_visitor_code)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```ruby theme={null} experiment_id = 9516 begin # Forcing the variation "on" for the experiment 9516 for the visitor kameleoon_client.set_forced_variation(visitor_code, experiment_id, 'on') # Forcing the variation "on" while preserving segmentation and targeting conditions during the experiment kameleoon_client.set_forced_variation(visitor_code, experiment_id, 'on', force_targeting: false) # Resetting the forced variation for the experiment 9516 for the visitor kameleoon_client.set_forced_variation(visitor_code, experiment_id, nil) rescue Kameleoon::Exception::KameleoonError => ex # Handling the exception end ``` ##### Parameters | Name | Type | Description | Default | | | ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | | `visitor_code` required | `String` | Unique identifier of the visitor. | | | | `experiment_id` required | `Integer` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | | `variation_key` required | \`String | NilClass\` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `nil`, the forced variation will be reset. | | | `force_targeting` optional | `Boolean` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | | ##### Exceptions thrown | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | In most cases, only the basic error, `KameleoonError`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `StandardError`. #### evaluate\_audiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluate_audiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```ruby theme={null} begin kameleoon_client.evaluate_audiences(visitor_code) rescue Kameleoon::Exception::KameleoonError => ex # Handling the exception end ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | -------- | --------------------------------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | ##### Exceptions thrown | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | In most cases, only the basic error, `KameleoonError`, needs to be handled, as demonstrated in the example. However, if different types of errors require a response, handle each one separately based on specific requirements. Additionally, for enhanced reliability, general language errors can be handled by including `StandardError`. #### get\_data\_file() To evaluate all feature flags, use [`get_variations()`](#get_variations). This method is more efficient than calling `DataFile` and iterating through flags with [`get_variation()`](#get_variation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```ruby theme={null} begin datafile = kameleoon_client.get_data_file rescue StandardError => e # Recommended (but optional) safeguard for unexpected exceptions from third-party libraries end ``` ##### Return value | Type | Description | | ---------- | ------------------------------------------------------------ | | `DataFile` | The [`DataFile`](#datafile) containing the SDK configuration | ### Visitor data #### get\_visitor\_code() Previously called `obtain_visitor_code` - removed since SDK version `3.0.0`. Call the `get_visitor_code` helper method to obtain the Kameleoon **visitor\_code** for the current visitor. This method is important when using Kameleoon in a mixed front-end and back-end environment, where user identification accuracy must be guaranteed. The implementation logic is as follows: 1. Check for a **kameleoonVisitorCode** cookie or query parameter associated with the current HTTP request. If found, use this as the visitor identifier. 2. If no cookie or parameter is found, check for the **default\_visitor\_code** argument. If found, use this as the identifier. `default_visitor_code_` lets you use your own identifiers as visitor codes should you wish, which can have the added benefit of matching Kameleoon visitors with your own users without any additional look-ups in a matching table. 3. If no cookie, parameter, or argument is found, randomly generate a unique identifier. In all cases, the server-side (via HTTP header) **kameleoonVisitorCode** cookie is set with the value. In later visits, the identifier that you set is the value returned by the method. For more information, refer to [this article](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). If you provide your own `visitor_code`, you must guarantee its uniqueness-the SDK can't check it. Also, note that the length of the `visitor_code` is limited to **255** characters. Any excess characters will throw an exception. The `get_visitor_code()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#set_forced_variation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```ruby theme={null} visitor_code = kameleoon_client.get_visitor_code(cookies) visitor_code = kameleoon_client.get_visitor_code(cookies, default_visitor_code) ``` ##### Parameters | Name | Type | Description | | ---------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | cookies | Hash | Cookies on the current HTTP request should be passed as a Hash object (`{:cookie_name => cookie_value}`). If you use Rails, you can pass the **cookies** variable. This field is mandatory. | | default\_visitor\_code | String | This parameter will be used as the **visitor\_code** if no existing **kameleoonVisitorCode** cookie is found in the request. This field is optional, and by default, a random **visitor\_code** will be generated. | ##### Return value | Type | Description | | ------ | ---------------------------------------------------------------------------------------------------------------------- | | String | A **visitor\_code** that will be associated with this particular user and should be used with most of the SDK methods. | #### add\_data() The `add_data()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `add_data()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`track_conversion()`](#track_conversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`get_variation()`](#get_variation) and [`get_variations()`](#get_variations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```ruby theme={null} require "kameleoon" require "kameleoon/data" # Add a single data item (tracked by default) kameleoon_client.add_data(visitor_code, Kameleoon::Browser.new(Kameleoon::BrowserType::CHROME)) # Add multiple data items (tracked by default) kameleoon_client.add_data( visitor_code, Kameleoon::PageView.new("https://url.com", "title", [3]), Kameleoon::UserAgent("UserAgent") ) # Add multiple data items stored locally for targeting only (not sent to the Kameleoon Data API) kameleoon_client.add_data( visitor_code, Kameleoon::Data::PageView.new("https://url.com", "title", [3]), Kameleoon::Data::UserAgent.new("UserAgent"), track: false ) ``` ##### Parameters | Name | Type | Description | Default value | | -------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | | | `data` required | `*Data` | Collection of Kameleoon data types. | | | `track` optional | `Boolean` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * 📨 *Sends Tracking Data to Kameleoon* `flush()` takes the Kameleoon data associated with the visitor and all of the data that was added previously using the `add_data` method, that hasn't yet been sent when calling one of [these methods](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics), and sends a tracking request. `flush()` is non-blocking, as the server call is made asynchronously. `flush()` lets you control when the data associated with a given `visitor_code` is sent to the server. For instance, if you call `add_data()` a dozen times, it would be inefficient to send data to the server each time `add_data()` is invoked, so you only have to call `flush()` once at the end. If you specify a `visitor_code`, the `flush()` method uses it as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} require "kameleoon" require "kameleoon/data" visitor_code = kameleoon_client.get_visitor_code(cookies) kameleoon_client.add_data(visitor_code, Kameleoon::Browser.new(Kameleoon::BrowserType::CHROME)) kameleoon_client.add_data( visitor_code, Kameleoon::PageView.new("https://url.com", "title", [3]), Kameleoon::Interest.new(0) ) kameleoon_client.add_data(visitor_code, Kameleoon::Conversion.new(32, 10, false)) kameleoon_client.flush(visitor_code) # Interval tracking (most performant way for tracking) kameleoon_client.flush(visitor_code, instant: true) # Instant tracking # if you operate with unique ID kameleoon_client.add_data(Kameleoon::UniqueIdentifier.new(true)) kameleoon_client.flush(visitor_code) ``` ##### Arguments | Name | Type | Description | | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | String | Unique identifier of the user. This field is mandatory. | | is\_unique\_identifier | Boolean | When `true`, the SDK links the flushed data to the visitor associated with the specified identifier. | | instant | Boolean | Boolean flag indicating whether the data should be sent instantly (`true`) or according to the scheduled tracking interval (`false`). This field is optional. | #### get\_remote\_data() Previously named: `retrieve_data_from_remote_source` - removed since SDK version `3.0.0`. The `get_remote_data()` method lets you retrieve data (according to a **key** passed as argument) for a specified **siteCode** (specified in `Kameleoon::KameleoonClientFactory.create()`) stored on a remote Kameleoon server. Usually, data is stored on remote servers using the Data API. This method, along with the availability of highly scalable servers for this purpose, provides a convenient way to store massive amounts of data that can be retrieved for each of your visitors/users. ```ruby theme={null} kameleoon_client.get_remote_data('test') # default timeout kameleoon_client.get_remote_data('test', 1000) # 1000 milliseconds timeout begin kameleoon_client.get_remote_data('test') rescue => e #catch error end ``` ##### Parameters | Name | Type | Description | | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | key | String | The key that the data you try to retrieve is associated with. This field is mandatory. | | timeout | Integer | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it will use the `default_timeout` value from configuration file or 2000 milliseconds if it's not specified in the file. | ##### Return value | Type | Description | | ---- | ----------------------------------------------------------------- | | Hash | Hash object associated with retrieving data for specific **key**. | ##### Exceptions thrown | Type | Description | | ----- | ----------------------------------------------------------------------------------------------------------------- | | Error | Error indicating that the request timed out or the retrieved data can't be parsed with the `JSON.parse()` method. | #### get\_remote\_visitor\_data() `get_remote_visitor_data()` is an asynchronous method for retrieving Kameleoon Visits Data for the `visitor_code` from the Kameleoon Data API. The method stores data for other methods to use when making targeting decisions. Data obtained using this method plays an important role when you want to: * use data collected from other devices. * access a user's history, such as previously visited pages during past visits. * use data that's only accessible on the client-side, like datalayer variables and goals that only convert on the front-end. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `get_remote_visitor_data()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches them to the visitor without having to call the `add_data()` method. It's particularly useful for [synchronizing custom data between multiple devices](/developer-docs/sdks/web-sdks/nodejs-sdk#synchronizing-custom-data-across-devices). The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} visitor_code = 'visitorCode' # Visitor data will be fetched and automatically added for `visitor_code` data_array = kameleoon_client.get_remote_visitor_data(visitor_code) # default timeout data_array = kameleoon_client.get_remote_visitor_data(visitor_code, 1000) # 1 second timeout # If you only want to fetch data and add it yourself manually, set `add_data` to `false` data_array = kameleoon_client.get_remote_visitor_data(visitor_code, add_data: false) # default timeout data_array = kameleoon_client.get_remote_visitor_data(visitor_code, 1000, add_data: false) # 1 second timeout # If you want to fetch custom list of data types filter = RemoteVisitorDataFilter(25, customData: false, conversions: true, experiments: true) data_array = kameleoon_client.get_remote_visitor_data(visitor_code, filter: filter) # If you want to the SDK to link the extracted data with the visitor associated with the specified identifier kameleoon_client.add_data(Kameleoon::UniqueIdentifier.new(true)) data_array = kameleoon_client.get_remote_visitor_data(visitor_code) ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | visitor\_code | String | The visitor code for which you want to retrieve the assigned data. This field is mandatory. | | timeout | Integer | Timeout (in milliseconds). This parameter specifies the maximum amount of time the method can block to wait for a result. This field is optional; if not provided, it uses the `default_timeout` value from the configuration file or 2000 milliseconds if it's not specified in the file. | | add\_data | Boolean | A boolean indicating whether the method should automatically add retrieved data for a visitor. If not specified, the default value is **true**. This field is optional. | | filter | `Kameleoon::Types::RemoteVisitorDataFilter` | Filter that specifies which data should be retrieved from visits. By default, only `CustomData` is retrieved from the current and latest previous visit (`RemoteVisitorDataFilter.new(previousVisitAmount: 1, currentVisit: true, customData: true)` or `RemoteVisitorDataFilter.new`). Other filters parameters are set to `false`. This field is optional. | | is\_unique\_identifier (Deprecated) | Boolean | An optional parameter for specifying if the visitorCode is a unique identifier. If not provided, the default value is `false`. The field is optional. | ##### Return value | Type | Description | | ----- | ----------------------------------------------- | | Array | An array of data assigned to the given visitor. | ##### Using parameters in get\_remote\_visitor\_data() The `get_remote_visitor_data()` method offers flexibility by letting you define various parameters when retrieving visitor data. Whether you're targeting based on goals, experiments, or variations, the same approach applies across all data types. For example, suppose you want to retrieve data on visitors who completed a goal "Order transaction." You can specify parameters within the `get_remote_visitor_data()` method to refine your targeting. For instance, if you want to target only users who converted on the goal in their last five visits, you can set the `previous_visit_amount` parameter to 5 and `conversions` to true. The flexibility shown in this example isn't limited to goal data. You can use parameters within the `get_remote_visitor_data()` method to retrieve data on a variety of visitor behaviors. Here is the list of available `Kameleoon::Types::RemoteVisitorDataFilter` options: | Name | Type | Description | Default | | ----------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | previous\_visit\_amount optional | `Integer` | Number of previous visits to retrieve data from. Number between `1` and `25` | `1` | | current\_visit optional | `Boolean` | If true, current visit data will be retrieved | `true` | | custom\_data optional | `Boolean` | If true, custom data will be retrieved. | `true` | | page\_views optional | `Boolean` | If true, page data will be retrieved. | `false` | | geolocation optional | `Boolean` | If true, geolocation data will be retrieved. | `false` | | device optional | `Boolean` | If true, device data will be retrieved. | `false` | | browser optional | `Boolean` | If true, browser data will be retrieved. | `false` | | operating\_system optional | `Boolean` | If true, operating system data will be retrieved. | `false` | | conversions optional | `Boolean` | If true, conversion data will be retrieved. | `false` | | experiments optional | `Boolean` | If true, experiment data will be retrieved. | `false` | | kcs optional | `Boolean` | If true, Kameleoon Conversion Score (KCS) will be retrieved. Requires the [AI Predictive Targeting add-on](/user-manual/ai-predictive-targeting/target-users-based-on-likelihood-to-convert) | `false` | | visitor\_code optional | `Boolean` | If true, Kameleoon will retrieve the `visitorCode` from the most recent visit and use it for the current visit. This is necessary if you want to ensure that the visitor, identified by their `visitorCode`, always receives the same variation across visits for [Cross-device experimentation](/developer-docs/cross-device-experimentation). | `true` | | personalization optional | `Boolean` | If true, personalization data will be retrieved. This is required for the personalization condition. | `false` | | cbs optional | `Boolean` | If true, Contextual Bandit score data will be retrieved. | `false` | #### get\_visitor\_warehouse\_audience() Retrieves all audience data associated with the visitor in your data warehouse using the specified `visitor_code` and `warehouse_key`. The `warehouse_key` is typically your internal user ID. The `custom_data_index` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. You can refer to the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting) for additional details. The method returns a `CustomData` object, confirming that the data has been added to the visitor and is available for targeting purposes. ```ruby theme={null} begin warehouse_audience_data = kameleoon_client.get_visitor_warehouse_audience(visitor_code, custom_data_index) # default timeout warehouse_audience_data = kameleoon_client.get_visitor_warehouse_audience(visitor_code, custom_data_index, 1000) # 1 second timeout warehouse_audience_data = kameleoon_client.get_visitor_warehouse_audience(visitor_code, custom_data_index, warehouse_key: warehouse_key) # default timeout warehouse_audience_data = kameleoon_client.get_visitor_warehouse_audience(visitor_code, custom_data_index, 1000, warehouse_key: warehouse_key) # 1 second timeout # Your custom code rescue => e # Handle exception end ``` ##### Parameters | Name | Type | Description | | ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | String | A unique visitor identification string, can't exceed 255 characters length. | | custom\_data\_index | Integer | An integer representing the index of the custom data you want to use to target your BigQuery Audiences. | | warehouse\_key | String | A unique key to identify the warehouse data (usually your internal user ID). This field is optional. | | timeout | Integer | Timeout (in milliseconds). This parameter specifies the maximum amount of time to wait for a result. This field is optional. If not provided, the default value is 10000 milliseconds. | ##### Return value | Type | Description | | ----------------------- | ------------------------------------------------------------------------------- | | `Kameleoon::CustomData` | A `CustomData` instance confirming that the data has been added to the visitor. | ##### Exceptions thrown | Type | Description | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (it's either empty or longer than 255 characters). | | StandardError | Exception indicating that the request timed out or any other reason of failure. | #### set\_legal\_consent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting the `consent` parameter to `false` limits the types of data that you can include in tracking requests. This method helps you adhere to legal and regulatory requirements while responsibly managing visitor data. You can find more information on personal data in the [consent management policy](/user-manual/project-management/consent-management-policy). ```ruby theme={null} visitor_code = kameleoon_client.get_visitor_code(cookies) kameleoon_client.set_legal_consent(visitor_code, true, cookies) ``` ##### Parameters | Name | Type | Description | | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | visitor\_code | String | The user's unique identifier. This field is required. | | consent | Boolean | A boolean value representing the legal consent status. `true` indicates the visitor has given legal consent; `false` indicates the visitor never provided, or has withdrawn, legal consent. This field is required. | | cookies | Hash | The HTTP response where values in the cookies will be adjusted based on the legal consent status. This field is optional. | ##### Exceptions thrown | Type | Description | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid. It's either empty or longer than 255 characters. | ##### Consent revocation behavior When you call `set_legal_consent()` with `consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### track\_conversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitor_code` and `goal_id`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitor_code` is usually identical to the one that was used when triggering the experiment. The `track_conversion()` method doesn't return any value. This method is non-blocking as the server call is made asynchronously. The parameter `isUniqueIdentifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `isUniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitor_code` that was originally assigned to the visitor, but you do have access to an internal ID that is connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} require "kameleoon" require "kameleoon/data/page_view" require "kameleoon/data/browser" require "kameleoon/data/conversion" visitor_code = kameleoon_client.get_visitor_code(cookies) goal_id = 83023 kameleoon_client.add_data(visitor_code, Kameleoon::Conversion.new(32, 10, false)) kameleoon_client.track_conversion(visitor_code, goal_id) # Add metadata cd = Kameleoon::CustomData.new(1, "metadata"); kameleoon_client.track_conversion(visitorCode, goalId, metadata: [cd]) ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | | | `goal_id` required | `Integer` | ID of the goal. | | | `revenue` optional | `Float` | Revenue of the conversion. | `0` | | `negative` optional | `Boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `Array` | Lets you set specific values for custom data which have been defined as metadata for the goal in the Kameleoon App. Example: `[CustomData{id: 5, value: "Payment Type"}, CustomData{id: 6, value: "Delivery Method"}]`. In this example, `5` and `6` are the indexes of the custom data (5 = “Payment Type”, 6 = “Delivery Method”). | `nil` | | `isUniqueIdentifier` *(deprecated)* | `Boolean` | An optional parameter for specifying if the visitor\_code is a unique identifier. | `false` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`add_data()`](#add_data) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `track_conversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::CustomData.new(5, "Credit Card"), Kameleoon::CustomData.new(9, "Express Delivery")) kameleoon_client.track_conversion(visitor_code, 10, metadata: [Kameleoon::CustomData.new(5, "Amex Credit Card")]) ``` ##### Exceptions | Type | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | `VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_engine\_tracking\_code() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `get_engine_tracking_code()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```ruby theme={null} engine_tracking_code = kameleoon_client.get_engine_tracking_code(visitor_code) ``` * To use this feature, implement both the Ruby SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | -------------------------------------- | -------- | --------------------------------- | | `visitor_code` required | `String` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------- | ---------------------------------------- | | `String` | JavaScript code to insert into the page. | ### Events #### set\_event\_handler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `nil` as the `handler` removes the current handler for the selected event type. ```ruby theme={null} class DataFileUpdateHandler # Called when the SDK data file (configuration) is updated. def on_update(event) source = event.source # :polling or :streaming date_modified = event.date_modified # Data file modification date in milliseconds. # React to the data file update. end end kameleoon_client.set_event_handler(Kameleoon::Events::EventType::DATAFILE_UPDATE, MyDataFileUpdateHandler.new) # Clear the handler. kameleoon_client.set_event_handler(Kameleoon::Events::EventType::DATAFILE_UPDATE, nil) ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | `Symbol` | The update source. [`Source::POLLING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`Source::STREAMING`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `date_modified` | `Integer` | The modification date of the updated data file, in milliseconds. | ```ruby theme={null} class HttpRequestHandler # Called when an SDK HTTP request completes successfully. def on_request_succeeded(request_type, http_status, duration_millis) # The SDK request completed successfully. end # Called when an SDK HTTP request fails. def on_request_failed(request_type, failure, duration_millis) reason = failure.reason # One of the HttpRequestFailure::Reason constants. http_status = failure.http_status cause = failure.cause # The SDK request failed. end end kameleoon_client.set_event_handler(Kameleoon::Events::EventType::HTTP_REQUEST, HttpRequestHandler.new) # Clear the handler. kameleoon_client.set_event_handler(Kameleoon::Events::EventType::HTTP_REQUEST, nil) ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `on_request_succeeded()` for successful responses and `on_request_failed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ------------------------ | -------------------------------------------------------------------------------------------- | | `on_request_succeeded()` | Called when an SDK HTTP request completes successfully. | | `on_request_failed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | ----------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `request_type` | `Symbol` | The SDK request type. Possible values are `RequestType::DATAFILE`, `RequestType::TRACKING`, `RequestType::REMOTE_VISITOR_DATA`, `RequestType::REMOTE_DATA`, and `RequestType::ACCESS_TOKEN`. | | `http_status` | `Integer` | The HTTP status code returned by the request. Passed only to `on_request_succeeded()`. | | `failure` | `HttpRequestFailure` | Details about why the request failed. Passed only to `on_request_failed()`. | | `duration_millis` | `Integer` | The request duration in milliseconds. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | ------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | `Symbol` | The failure reason. Possible values are `Reason::HTTP_STATUS`, `Reason::EXCEPTION`, and `Reason::CANCELLED`. | | `http_status` | `Integer` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `nil` for exception and cancellation failures. | | `cause` | `Exception` | The exception (error) that caused the request to fail. This value is `nil` when the request failed because of an HTTP status or cancellation. | ##### Parameters | Name | Type | Description | | ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event_type` required | `Symbol` | The SDK event type to handle. Use `EventType::DATAFILE_UPDATE` to react to SDK data file updates, or `EventType::HTTP_REQUEST` to react to completed SDK HTTP requests. | | `handler` required | `Object` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventType::DATAFILE_UPDATE`, or an `HttpRequestHandler` for `EventType::HTTP_REQUEST`. Pass `nil` to remove the current handler. | ### Data types #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | -------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------- | | `browser_type` required | `BrowserType` | List of browsers: `CHROME`, `INTERNET_EXPLORER`, `FIREFOX`, `SAFARI`, `OPERA`, `OTHER`. | | `version` optional | `Float` | Version of the browser, floating point number represents major and minor version of the browser | ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::Browser.new(Kameleoon::BrowserType::CHROME)) kameleoon_client.add_data(visitor_code, Kameleoon::Browser.new(Kameleoon::BrowserType::SAFARI, 10.0)) ``` #### PageView | Name | Type | Description | | --------- | ------ | -------------------------------------------------- | | url | String | URL of the page viewed. This field is mandatory. | | title | String | Title of the page viewed. This field is mandatory. | | referrers | Array | Referrers of viewed pages. This field is optional. | The referrer's index (ID) is available in the Acquisition channel configuration page of the Back-Office. Be careful: this index starts at 0, so the first [acquisition channel](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) you create for a site would have the ID 0, not 1. ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::PageView.new("https://url.com", "title", [3])) ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goal_id` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | ------------------- | ----------------------------------------------- | ------- | | `goal_id` required | `Integer` | ID of the goal. | | | `revenue` optional | `Float` | Revenue of the conversion | `0` | | `negative` optional | `Boolean` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `Array` | Metadata of the conversion. | `nil` | ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::Conversion.new(32, 10)) kameleoon_client.add_data(visitor_code, Kameleoon::Conversion.new(33, 0, true)) kameleoon_client.add_data( visitor_code, Kameleoon::Conversion.new(34, metadata: [ Kameleoon::CustomData.new(3, 'metadata1', 'md2'), Kameleoon::CustomData.new(5, 'md3'), ]) ) ``` #### CustomData `CustomData` allows any type of data to be easily associated with a visitor. You can then use it as a targeting condition in [segments](/user-manual/assets/segments/create-a-segment/) or as a filter/breakdown in experiment reports. To learn more about custom data, refer to this [article](/developer-docs/custom-data). | Name | Type | Description | Default | | ---------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `Integer`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `Array` | Values of the custom data to be stored. | | | overwrite optional | `Boolean` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor can only have one `CustomData` for each unique `index`. Adding another `CustomData` with the same `index` will replace the existing `CustomData`. * The custom data `index` can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance configuration isn't up to date or the name isn't registered, will result in the data being ignored. ```ruby theme={null} custom_data = Kameleoon::CustomData.new(1, 'value') # With several values custom_data = Kameleoon::CustomData.new(1, 'value1', 'value2') # To set the 'overwrite' flag to false custom_data = Kameleoon::CustomData.new(1, 'value', overwrite: false) # To use a name instead of the index custom_data = Kameleoon::CustomData.new('my-custom-data', 'value') # From hash custom_data = Kameleoon::CustomData.new({ 'id' => 1, 'values' => ['value1', 'value2'] }) # From hash with the 'overwrite' flag set to false custom_data = Kameleoon::CustomData.new({ 'id' => 1, 'values' => ['value'], 'overwrite' => false }) # From hash with a name instead of the index custom_data = Kameleoon::CustomData.new({ 'name' => 'my-custom-data', 'values' => ['value'] }) kameleoon_client.add_data(visitor_code, custom_data) ``` #### Device | Name | Type | Description | | ------ | ---------- | ----------------------------------------------------------------------------- | | device | DeviceType | List of devices: **PHONE**, **Device**, **DESKTOP**. This field is mandatory. | ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::Device.new(Kameleoon::DeviceType::DESKTOP)) ``` #### UserAgent Store information on the visitor's user-agent. Server-side experiments are more vulnerable to **bot traffic** than client-side experiments. To address this vulnerability, Kameleoon uses the IAB/ABC International Spiders and Bots List to identify known bots and spiders. Kameleoon also uses the `UserAgent` field to filter out bots and other unwanted traffic that could otherwise skew your conversion metrics. For more details, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, pass the value **curl/8.0** of the userAgent to exclude them from your analytics. | Name | Type | Description | | ----- | ------ | --------------------------------------------------------------------------------------- | | value | String | The User-Agent value that will be sent with tracking requests. This field is mandatory. | ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::UserAgent.new("Your User Agent")) ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitor_code` is used as the unique visitor identifier, which is useful for [Cross-device experimentation](/developer-docs/cross-device-experimentation). When you add `UniqueIdentifier` for a visitor, the SDK links the flushed data to the visitor associated with the specified identifier. The `UniqueIdentifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. | Name | Type | Description | | ----- | ------- | --------------------------------------------------------------------------------------------- | | value | Boolean | Parameter for specifying if the visitor\_code is a unique identifier. This field is required. | ```ruby theme={null} kameleoon_client.add_data(visitorCode, Kameleoon::UniqueIdentifier.new(true)) ``` #### OperatingSystem `OperatingSystem` contains information about the visitor's operating system. Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first. | Name | Type | Description | | ---- | ------------------- | ------------------------------------------------------------------------------------------------------------------ | | type | OperatingSystemType | List of types: **WINDOWS**, **MAC**, **IOS**, **LINUX**, **ANDROID**, **WINDOWS\_PHONE**. This field is mandatory. | ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::OperatingSystem.new(Kameleoon::OperatingSystemType::ANDROID)) ``` #### Cookie `Cookie` contains information about the cookie stored on the visitor's device. Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first. | Name | Type | Description | | ------- | ---- | ------------------------------------------------------------------------------------------------------------- | | cookies | Hash | Hash object (`{:cookie_name => cookie_value}`) consisting of cookie keys and values. This field is mandatory. | ```ruby theme={null} cookie = Kameleoon::Cookie.new({ "k1" => "v1", "k2" => "v2" }) kameleoon_client.add_data(visitor_code, cookie) ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `String` | The country of the visitor. | | `region` optional | `String` | The region of the visitor. | | `city` optional | `String` | The city of the visitor. | | `postal_code` optional | `String` | The postal code of the visitor. | | `latitude` optional | `Float` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `Float` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```ruby theme={null} kameleoon_client.add_data(visitor_code, Kameleoon::Geolocation.new("France", "Île-de-France", "Paris")) ``` #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `String` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```ruby theme={null} kameleoon_client_sdk.add_data(visitorCode, Kameleoon::ApplicationVersion.new("10")) # major kameleoon_client_sdk.add_data(visitorCode, Kameleoon::ApplicationVersion.new("10.20")) # major.minor kameleoon_client_sdk.add_data(visitorCode, Kameleoon::ApplicationVersion.new("10.20.30")) # major.minor.patch ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------- | --------------------------- | --------------------------------------------------------------------------------- | | `feature_flags` | `Hash` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `date_modified` | `Integer` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```ruby theme={null} # Retrieves the hash of feature flags from the DataFile. # The hash is keyed by feature flag identifiers, with each value being a FeatureFlag object. feature_flags = datafile.feature_flags # Retrieves the last modification timestamp of the DataFile. # The value is an Integer representing milliseconds since the Unix epoch. date_modified = datafile.date_modified ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ----------------------- | ------------------------- | -------------------------------------------------------------------------- | | `environment_enabled` | `Boolean` | Indicating whether the feature flag is enabled in the current environment. | | `default_variation_key` | `String` | The key of the default variation associated with the feature flag. | | `variations` | `Hash` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Array` | A list of `Rule` objects | ```ruby theme={null} # Check whether the feature flag is enabled in the current environment is_environment_enabled = feature_flag.environment_enabled # Retrieve the key of the default variation default_variation_key = feature_flag.default_variation_key # Retrieve the default variation object default_variation = feature_flag.default_variation # Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object) variations = feature_flag.variations # Retrieve all targeting rules associated with the feature flag rules = feature_flag.rules ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------- | ------------------------------------------------------ | | `variations` | `Hash` | A map of `Variation` objects, keyed by variation keys. | ```ruby theme={null} # Retrieve all variations of the rule as a map (key = variation key, value = Variation object) variations = rule.variations ``` #### Variation `Variation` contains information about the assigned variation to the visitor (or the default variation, if no specific assignment exists). | Name | Type | Description | | -------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | name | `String` | The name of the variation. | | key | `String` | The unique key identifying the variation. | | id | `Integer` or `NilClass` | The ID of the assigned variation (or `nil` if it's the default variation). | | experiment\_id | `Integer` or `NilClass` | The ID of the experiment associated with the variation (or `nil` if default). | | variables | `Hash` | A hash containing the variables of the assigned variation, keyed by variable names. This could be empty if no variables are associated. | * The `Variation` object provides details about the assigned variation and its associated experiment, while the [`Variable`](#variable) object contains specific details about each variable within a variation. * Ensure that your code handles the case where `id` or `experiment_id` may be `nil`, indicating a default variation. * The `variables` hash might be empty if no variables are associated with the variation. ```ruby theme={null} # Retrieving the variation name variation_name = variation.name # Retrieving the variation key variation_key = variation.key # Retrieving the variation id variation_id = variation.id # Retrieving the experiment id experiment_id = variation.experiment_id # Retrieving the variables map variables = variation.variables ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | key | `String` | The unique key identifying the variable. | | type | `String` | The type of the variable. Possible values: **BOOLEAN**, **NUMBER**, **STRING**, **JSON**, **JS**, **CSS** | | value | `Object` | The value of the variable, which can be of the following types: **Boolean**, **Integer**, **Float**, **String**, **Hash**, **Array**. | ```ruby theme={null} # Retrieving the variables map variables = variation.variables # Variable type can be retrieved for further processing type = variables["isDiscount"].type # Retrieving the variable value by key is_discount = variables["isDiscount"].value # Variable value can be of different types (e.g., String) title = variables["title"].value ``` ### Deprecated methods These methods are deprecated and will be removed in SDK version `4.0.0`. #### get\_feature\_variation\_key() * 📨 *Sends Tracking Data to Kameleoon* Use [`get_variation()`](#get_variation) instead. To get a feature variation key, call the `get_feature_variation_key` method. This method takes a **visitor\_code** and **feature\_key** as mandatory arguments to get a user's variation key. If such a user has never been associated with this feature flag, the SDK returns a variation key randomly (according to the feature flag rules). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the previous **variation key** value. If the user doesn't match any of the rules, the default value will be returned, which you can define in your account. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `get_feature_variation_key` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} visitor_code = kameleoon_client.get_visitor_code(cookies) feature_key = "feature_key" variation_key = "" begin variation_key = kameleoon_client.get_feature_variation_key(visitor_code, feature_key) case variation_key when 'on' # main variation key is selected for visitorCode when 'alternative_variation' # alternative variation key else # default variation key end rescue Kameleoon::Exception::FeatureNotFound # The user will not be counted in the experiment, but should see the reference variation. rescue Kameleoon::Exception::VisitorCodeInvalid # The visitor code you passed to the method isn't valid and can't be accepted by SDK. rescue Kameleoon::Exception::FeatureEnvironmentDisabled # The feature is disabled for the environment. end ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ------- | ---------------------------------------------------------------------------------------------------- | | visitor\_code | string | Unique identifier of the user. This field is mandatory. | | feature\_key | string | Key of the feature you want to expose to a user. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Boolean | When `true`, the SDK links the flushed data to the visitor associated with the specified identifier. | ##### Return value | Type | Description | | ------ | ---------------------------------------------------------------------------------- | | string | Variation key of the feature flag that's registered for a given **visitor\_code**. | ##### Exceptions thrown | Type | Description | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK''s internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | Kameleoon::Exception::FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (empty, or longer than 255 characters). | #### get\_active\_feature\_list\_for\_visitor() Use [`get_active_features()`](#get_active_features) instead. This method only takes `visitorCode` as an input parameter. The result only contains active feature flags for a given visitor. ```ruby theme={null} active_feature_flag_list = kameleoon_client.get_active_feature_list_for_visitor(visitor_code) ``` ##### Arguments | Name | Type | Description | | ------------- | ------ | ------------------------------------------------------- | | visitor\_code | String | Unique identifier of the user. This field is mandatory. | ##### Return value | Type | Description | | ----- | ------------------------------------------------------------------------ | | Array | List of feature flag keys which are active for a given **visitor\_code** | ##### Exceptions thrown | Type | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (empty, or longer than 255 characters). | #### get\_active\_features() `get_active_features` method retrieves information about the active feature flags that are available for the specified visitor code. This method is deprecated and will be removed in SDK version `4.0.0`. Use [`get_variations()`](#get_variations) instead. ```ruby theme={null} active_features = kameleoon_client.get_active_features(visitor_code) ``` ##### Arguments | Name | Type | Description | | ------------- | -------- | ------------------------------------------------------- | | visitor\_code | `String` | Unique identifier of the user. This field is mandatory. | ##### Return value | Type | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------- | | `Hash` | A hash that contains the assigned variations of the active features using the active feature IDs as keys. | ##### Exceptions thrown | Type | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (empty, or longer than 255 characters). | #### get\_feature\_variable() * 📨 *Sends Tracking Data to Kameleoon* Use [`get_variation()`](#get_variation) instead. Previously called `obtain_feature_variable` - deprecated since SDK version `2.1.0` and will be removed in a future releases. To get a variable of the variation key associated with a user, call the `get_feature_variable` method. This method takes a **visitor\_code**, **feature\_key** and **variable\_name** as mandatory arguments to get a variable of the variation key for a given user. If such a user has never been associated with this feature flag, the SDK returns a variable value of variation key randomly (according to the feature flag rules). If a user with a given **visitor\_code** is already registered with this feature flag, it will detect the **variable** value for the previously associated **variation**. If the user doesn't match any of the rules, the default variable will be returned. You must ensure that proper error handling is set up in your code as shown in the example to the right to catch potential exceptions. If you specify a `visitor_code`, the `get_feature_variable` method uses the `visitor_code` as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation). When you specify a `visitor_code` and set the `is_unique_identifier` parameter to `true`, the SDK links the flushed data to the visitor associated with the specified identifier. The parameter `is_unique_identifier` is deprecated. Use [`UniqueIdentifier`](#uniqueidentifier) instead. The `is_unique_identifier` can also be useful in other edge-case scenarios, such as when you can't access the anonymous `visitorCode` that was originally assigned to the visitor, but you have access to an internal ID that's connected to the anonymous visitor using session merging capabilities. ```ruby theme={null} visitor_code = kameleoon_client.get_visitor_code(cookies) feature_key = "feature_key" variation_key = "" variable_name = "variable_name" begin variable_value = kameleoon_client.get_feature_variable(visitor_code, feature_key, variable_name) # your custom code depending of variable_value, e.g. case variable_value when 'value-1' # your custom code if variable == 'value-1' when 'value-2' # your custom code if variable == 'value-2' end rescue Kameleoon::Exception::FeatureNotFound # The user will not be counted in the experiment, but should see the reference variation. rescue Kameleoon::Exception::FeatureVariableNotFound # Requested variable not defined in Kameleoon rescue Kameleoon::Exception::FeatureEnvironmentDisabled # The feature is disabled for the environment. rescue Kameleoon::Exception::VisitorCodeInvalid # The visitor code you passed to the method isn't valid and can't be accepted by SDK. end ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ------- | ----------------------------------------------------------------------------------------------------- | | visitor\_code | string | Unique identifier of the user. This field is mandatory. | | feature\_key | string | Key of the feature you want to expose to a user. This field is mandatory. | | variable\_name | string | Name of the variable for which you want to get the value. This field is mandatory. | | is\_unique\_identifier (Deprecated) | Boolean | When `true`, the SDK links the flushed data with to visitor associated with the specified identifier. | ##### Return value | Type | Description | | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | any | Value of a variation's variable that's registered for a given **visitor\_code** for this feature flag. Possible types: boolean, number, string, hash | ##### Exceptions thrown | Type | Description | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | Kameleoon::Exception::FeatureEnvironmentDisabled | Exception indicating that feature flag is off in the visitor's current environment (for example, production, staging, or development). | | Kameleoon::Exception::VisitorCodeInvalid | Exception indicating that the provided visitor code is invalid (empty, or longer than 255 characters). | | Kameleoon::Exception::FeatureVariableNotFound | Exception indicating that the requested variable hasn't been found. Check that the variable's key matches the key in your code. | #### get\_feature\_variation\_variables() Use [`get_variation()`](#get_variation) instead. To retrieve the all feature variables, call the `get_feature_variation_variables` method. A feature variable can be changed using the Kameleoon web app. This method takes **feature\_key** and **variation\_key** as mandatory arguments. It will return the data with the object type, as defined on the web interface. It throws an error (`FeatureNotFound`) if the requested feature flag hasn't been found in the SDK's client configuration. If the variation key isn't found, the method throws (`FeatureVariationNotFound`) error. ```ruby theme={null} featureKey := "test_feature_variables" variationKey := "on" begin data = kameleoon_client.get_feature_variation_variables(feature_key, variable_key) rescue Kameleoon::Exception::FeatureNotFound # The feature is not activated in Kameleoon rescue Kameleoon::Exception::FeatureVariationNotFound # Requested variation not defined in Kameleoon rescue Kameleoon::Exception::FeatureEnvironmentDisabled # The feature is disabled for the environment. end ``` ##### Parameters | Name | Type | Description | | -------------- | ------ | -------------------------------------------------------------------- | | feature\_key | string | Key of the feature flag you want to obtain. This field is mandatory. | | variation\_key | string | Key of the variation you want to obtain. This field is mandatory. | ##### Return value | Type | Description | | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hash | Data associated with this feature flag and variation. The values can be a String, Boolean, Number or Hash (depending on the type defined in the web interface). | ##### Exceptions thrown | Type | Description | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Kameleoon::Exception::FeatureNotFound | Exception indicating that the requested feature ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the feature flag hasn't yet been activated on Kameleoon's side (but code implementing the feature is already deployed on the web-app's side). | | Kameleoon::Exception::FeatureEnvironmentDisabled | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | Kameleoon::Exception::FeatureVariationNotFound | Exception indicating that the requested variation ID hasn't been found in the SDK's internal configuration. This exception is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | #### get\_feature\_list() Returns a list of feature flag keys currently available for the SDK. ```ruby theme={null} feature_list = kameleoon_client.get_feature_list ``` ##### Return value | Type | Description | | ------- | ------------------------- | | `Array` | List of feature flag keys | #### on\_update\_configuration() The `on_update_configuration()` method allows you to handle the event when configuration has updated data. It takes one input parameter, **handler**. The handler that will be called when the configuration is updated using a real-time configuration event. ```ruby theme={null} kameleoon_client.on_update_configuration( # configuration was updated ) ``` ##### Parameters | Name | Type | Description | | --------- | ---------- | -------------------------------------------------------------------------------------------------------- | | `handler` | `Callable` | The handler that will be called when the configuration is updated using a real-time configuration event. | # Rust SDK Source: https://docs.kameleoon.com/developer-docs/sdks/web-sdks/rust-sdk Integrate the Kameleoon Rust SDK to run experiments and activate feature flags in Rust services and web back-ends. With the Kameleoon Rust SDK, you can run experiments and activate feature flags in your Rust services and web back-ends. **Getting started**: For help getting started, see the [developer guide](#developer-guide). **Changelog**: Latest version of the Rust SDK: 0.9.7 [Changelog](https://github.com/Kameleoon/client-rust/blob/master/CHANGELOG.md). **SDK methods**: For the full reference documentation of the Rust SDK, see the [reference](#reference) section. ## Developer guide This guide is designed to help you integrate the Rust SDK quickly and start evaluating feature flags in your Rust app. ### Getting started #### Install the Rust client If you are working in the current workspace, add the SDK as a path dependency together with `tokio`, because several SDK methods are asynchronous: ```toml title="Cargo.toml" theme={null} [dependencies] kameleoon-client = "0.9.4" ``` #### Additional configuration Create a `client-config.json` configuration file to provide credentials and customize SDK behavior. You can also [download a sample configuration](/assets/developer-docs/sdks/web-sdks/client-configs/client-config.json) file. Save this file to the default path, `/etc/kameleoon/client-config.json`, but you can save it anywhere in the classpath as `client-config.json`. The Rust SDK can be configured either with a JSON file used by `create_with_path()` or by building a `KameleoonClientConfig` with `create_with_config()` instance directly in code. The following table shows the available properties that you can set: | Key (Code / Config File) | Description | Default value | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | | `client_id` / `clientId` required | Required for authentication to the Kameleoon service. To find your `client_id`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `client_secret` / `clientSecret` required | Required for authentication to the Kameleoon service. To find your `client_secret`, see the [API credentials](/user-manual/account-and-team-management/users-and-teams/api-credentials) documentation. | | | `session_duration` / `sessionDurationMinutes` optional | Time interval, in minutes, during which the SDK keeps a visitor and their associated data in memory. | `30` minutes | | `refresh_interval` / `refreshIntervalMinutes` optional | Interval, in minutes, used to refresh the active experiments and feature flags configuration. | `60` minutes | | `default_timeout` / `defaultTimeoutMillis` optional | Default timeout, in milliseconds, for SDK network requests. | `10000` milliseconds | | `tracking_interval` / `trackingIntervalMillis` optional | Interval, in milliseconds, used to batch tracking requests. Values are clamped to the `[1000, 5000]` range. | `1000` milliseconds | | `environment` / `environment` optional | Environment from which the feature flag configuration should be used. The value can be `production`, `staging`, or `development`. | `production` | | `top_level_domain` / `topLevelDomain` optional | The current top-level domain for your website. Use the format `example.com` without protocol or subdomains. | `None` | | `proxy_host` / `proxyHost` optional | Proxy host for outgoing SDK calls. Supported formats: `https://my.prox`, `https://my.prox:4545`, `socks5://192.168.1.1:9000`. | `None` | | `network_domain` / `networkDomain` optional | Custom domain used by SDKs for outgoing requests, often for proxying. Must be a valid domain (for example, example.com or sub.example.com). Invalid formats default to Kameleoon's value. | `None` | #### Initialize the Kameleoon client After you have installed the SDK and configured your credentials, create a `KameleoonClient` by using `KameleoonClientFactory`. ```rust theme={null} use std::time::Duration; use kameleoon_client::config::KameleoonClientConfigBuilder; use kameleoon_client::factory::KameleoonClientFactory; async fn create_client() -> Result { let site_code = "a8st4f59bj"; let client = KameleoonClientFactory::create_with_path(site_code, "/etc/kameleoon/client-config.json")?; client.initialize().await?; Ok(client) } ``` ```rust theme={null} use std::time::Duration; use kameleoon_client::config::KameleoonClientConfigBuilder; use kameleoon_client::factory::KameleoonClientFactory; async fn create_client() -> Result { let site_code = "a8st4f59bj"; let config = KameleoonClientConfigBuilder::default() .client_id("".to_owned()) // mandatory .client_secret("".to_owned()) // mandatory .refresh_interval(Duration::from_mins(60)) // optional (60 minutes by default) .session_duration(Duration::from_mins(30)) // optional (30 minutes by default) .default_timeout(Duration::from_millis(10_000)) // optional (10000 ms by default) .tracking_interval(Duration::from_millis(1_000)) // optional (1000 ms by default) .environment("development".to_owned()) // optional .top_level_domain(".example.com") // mandatory if you use hybrid mode (engine or web experiments) .proxy_host("http://192.168.0.25:8080".to_owned()) // optional .network_domain("example.com".to_owned()) // optional .build() .unwrap(); let client = KameleoonClientFactory::create_with_config(site_code, config)?; client.initialize().await?; Ok(client) } ``` A `KameleoonClient` is the main object used to evaluate feature flags, add visitor data, and send tracking requests. * It's recommended to use `KameleoonClient` as a singleton object, as it serves as the bridge between your app and the Kameleoon platform. It exposes all required methods and properties to run experiments efficiently. * The Rust SDK initializes asynchronously. You should call [`initialize()`](#initialize) before relying on feature evaluation in production code. #### Activating a feature flag ##### Assigning a unique ID to a user To assign a unique ID to a user, you can use the [`get_visitor_code()`](#get_visitor_code) method. If a **visitor code** doesn’t exist (from the request headers cookie), the method generates a random unique ID or uses a `default_visitor_code` that you would have generated. The ID is then set in a response headers cookie. If you are using Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation), calling the `get_visitor_code()` method ensures that the unique ID (**visitor code**) is shared between the app file `engine.js` (previously named, `kameleoon.js`) and the SDK. ##### Retrieving a flag configuration To implement a feature flag in your code, you must first create the feature flag in your Kameleoon account. To determine the status or variation of a feature flag for a specific user, you should use the [`get_variation()`](#get_variation) or [`is_feature_active()`](#is_feature_active) method to retrieve the configuration based on the `feature_key`. The `get_variation()` method handles both simple feature flags with ON/OFF states and more complex flags with multiple variations. The method retrieves the appropriate variation for the user by checking the feature rules, assigning the variation, and returning it based on the `feature_key` and `visitor_code`. The `is_feature_active()` method can be used if you want to retrieve the configuration of a simple feature flag that has only an ON or OFF state, as opposed to more complex feature flags with multiple variations or targeting options. If your feature flag has associated variables (such as specific behaviors tied to each variation) `get_variation()` also enables you to access the [`Variation`](#variation) object, which provides details about the assigned variation and its associated experiment. This method checks whether the user is targeted, finds the visitor’s assigned variation, and saves it to storage. When `track=true`, the SDK will send the exposure event to the specified experiment on the next tracking request, which is automatically triggered based on the SDK’s [`tracking_interval`](#additional-configuration). By default, this interval is set to 1000 milliseconds (1 second). The `get_variation()` method allows you to control whether tracking is done. If `track=false`, no exposure events will be sent by the SDK. This is useful if you prefer not to track data through the SDK and instead rely on client-side tracking managed by the Kameleoon engine, for example. Additionally, setting `track=false` is helpful when using the `get_variations()` method, where you might only need the variations for all flags without triggering any tracking events. If you want to know more about how tracking works, view [this article](/developer-docs/feature-experimentation/technical-reference/faq-global#when-does-the-sdk-send-a-tracking-request-for-analytics) ##### Adding data points to target a user or filter / breakdown visits in reports To target a user, ensure you've added relevant data points to their profile before retrieving the feature variation or checking if the flag is active. Use the [`add_data()`](#add_data) method to add these data points to the user's profile. To retrieve data points collected on other devices or to access past user data (collected client-side when using Kameleoon in Hybrid mode), use the [`get_remote_visitor_data()`](#get_remote_visitor_data) method. This method asynchronously fetches data from the servers. It's important to call `get_remote_visitor_data()` *before* retrieving the variation or checking if the feature flag is active, as this data might be required to assign a user to a given variation. To learn more about available targeting conditions, see the [detailed article on the subject](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). Additionally, the data points you add to the visitor profile will be available when analyzing your experiments, allowing you to filter and break down your results by factors like device and browser. Kameleoon Hybrid mode automatically collects a variety of data points on the client-side, making it easy to break down your results based on these pre-collected data points. See the complete list [here](/user-manual/experiment-analytics/analyze-results/results-page/results-page-settings#breakdown-audience). If you need to track additional data points beyond what's automatically collected, you can use Kameleoon's [Custom Data feature](#customdata). Custom Data allows you to capture and analyze specific information relevant to your experiments. Don't forget to call the [`flush()`](#flush) method to send the collected data to Kameleoon servers for analysis. To ensure your results are accurate, it's recommended to filter out bots by using the [`UserAgent`](#useragent) data type. ##### Tracking goal conversions When a user completes a desired action (such as making a purchase), it's recorded as a conversion. To track conversions, use the [`track_conversion()`](#track_conversion) method and provide the required `visitor_code` and `goal_id` parameters. The conversion tracking request will be sent along with the next scheduled tracking request, which the SDK sends at regular intervals (defined by [`tracking_interval`](#additional-configuration)). If you prefer to send the request immediately, use the [`flush_instant()`](#flush) method. ##### Sending events to analytics solutions To track conversions and send exposure events to your customer analytics solution, you must first implement Kameleoon in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). Then, use the [`get_engine_tracking_code()`](#get_engine_tracking_code) method. The `get_engine_tracking_code()` method retrieves the unique tracking code required to send exposure events to your analytics solution. Using this method allows you to record events and send them to your desired analytics platform. ### Using a custom bucketing key By default, Kameleoon uses a unique, anonymous visitor ID (`visitor_code`) to assign users to feature flag variations. This ID is typically generated and stored on the user's device (in a browser cookie for client-side and server-side SDKs—in persistent storage for mobile SDKs). However, in certain scenarios you may need to ensure all users of the same organization see the same variant of a feature flag. The **Custom Bucketing Key** option allows you to override this default behavior by providing your own custom identifier for bucketing. This override ensures that Kameleoon's assignment logic uses your specified key instead of the default `visitor_code`. #### Use cases Using a custom bucketing key is essential for maintaining consistency and accuracy in your feature flag assignments, particularly in these situations: * **Account-level or organizational experiments:** For B2B products or scenarios where you want to assign all users from the same organization to the same variation, you can use an identifier like an `account_id`. Custom bucketing keys are crucial for A/B testing features that impact an entire team or company. By implementing a custom bucketing key, you ensure greater consistency and accuracy in your experiments, leading to more reliable results and a better user experience. #### Technical details When you configure a custom bucketing key for a feature flag, you provide Kameleoon with a specific identifier from your app's data: ```rust theme={null} use kameleoon_client::data::CustomData; client.add_data(visitor_code, [CustomData::new(42, vec!["new_visitor_code".to_owned()])])?; ``` * **Providing the custom key:** You provide your custom identifier to the Kameleoon SDK using the [`add_data()`](#add_data) method. In this method, you will pass your chosen custom bucketing key as a [`CustomData`](#customdata) object. Here, `new_visitor_code` refers to the identifier you wish to use for your bucketing (for example, the new `user_id` or `account_id`). For the custom bucketing key to function correctly, it must also be defined and configured for the feature flag during the flag creation or editing process. Without this corresponding configuration, the SDK's bucketing won't apply your custom key. For detailed instructions on how to set this up in Kameleoon, refer to this [article](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/create-a-feature-flag#Advanced_Flag_Settings). * **Bucketing logic:** Once a custom bucketing key is provided through the `add_data()` method, all hash calculations for assigning users to variations will use this `new_visitor_code` (your custom key) instead of the default `visitor_code`. Using the `new_visitor_code` means that the bucketing decision is tied to your custom identifier, ensuring consistent assignments across various contexts where that identifier is present. * **Data tracking and analytics:** It's crucial to note that while the `new_visitor_code` (your custom key) is used for bucketing decisions, **all subsequent data (tracking events and conversions, for example) is sent and associated with the *original* `visitor_code`.** This separation ensures that your analytics accurately reflect individual user journeys and interactions within your experiment's broader context, even when bucketing is performed at a higher level (like an account) or across multiple devices/sessions. Your original visitor data remains intact for comprehensive reporting. #### Technical requirements To effectively use a custom bucketing key: * The key must be a `&str`. * It must be unique for the entity you intend to bucket (for example, if using a `user_id`, each user's ID should be unique). * The key must be available to the SDK at the exact moment the feature flag decision is evaluated for that user or request. ### Targeting conditions The Kameleoon SDKs support a variety of predefined targeting conditions that you can use to target users in your campaigns. For the list of conditions this SDK supports, see [use visit history to target users](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation). You can also use your own [external data to target users](/developer-docs/apis/data-api-rest/tutorials/storing-and-retrieving-external-data-to-target-users). ### Cross-device experimentation To support visitors who access an app from multiple devices, Kameleoon allows the synchronization of previously collected visitor data across each of the visitor's devices and reconciliation of their visit history across devices through cross-device experimentation. Case studies and detailed information on how Kameleoon handles data across devices are available in the [article on cross-device experimentation](/developer-docs/cross-device-experimentation). #### Synchronizing custom data across devices Although custom mapping synchronization is used to align visitor data across devices, it's not always necessary. Below are two scenarios where custom mapping sync isn't required: **Same user ID across devices** If the same user ID is used consistently across all devices, synchronization is handled automatically without a custom mapping sync. It's enough to call the `get_remote_visitor_data()` method when you want to sync the data collected between multiple devices. **Multi-server instances with consistent IDs** In complex setups involving multiple servers (for example, distributed server instances), where the same user ID is available across servers, synchronization between servers (with `get_remote_visitor_data()`) is sufficient without additional custom mapping sync. Customers who need additional data can refer to the [`get_remote_visitor_data()`](#get_remote_visitor_data) method description for further guidance. In the below code, it's assumed that the same unique identifier (in this case, the `visitor_code`, which can also be referred to as `userId`) is used consistently between the two devices for accurate data retrieval. If you want to sync collected data in real time, you need to choose the scope **Visitor** for your custom data. ```rust title="Device A" theme={null} // In this example, a Custom data with index `90` was set to "Visitor" scope in Kameleoon. use kameleoon_client::data::CustomData; const VISITOR_SCOPE_CUSTOM_DATA_INDEX: u32 = 90; client.add_data(visitor_code, [CustomData::new(VISITOR_SCOPE_CUSTOM_DATA_INDEX, vec!["your data".to_owned()])])?; client.flush_instant(visitor_code).await?; ``` ```rust title="Device B" theme={null} // Before working with the data, call the `get_remote_visitor_data` method. client.get_remote_visitor_data(visitor_code, None).await?; // After calling the method, the SDK on Device B will have access to CustomData of Visitor scope defined on Device A. // So, "your data" will be available for targeting and tracking the visitor. ``` #### Using custom data for session merging [Cross-device experimentation](/developer-docs/cross-device-experimentation) allows for combining a visitor's history across each of their devices (history reconciliation). History reconciliation allows merging different visitor sessions into one. To reconcile visit history, use [`CustomData`](#customdata) to provide a unique identifier for the visitor. For more information, see the [dedicated documentation](/developer-docs/cross-device-experimentation/#activating-cross-device-history-reconciliation). After cross-device reconciliation is enabled, calling [`get_remote_visitor_data()`](#get_remote_visitor_data) with the parameter `userId` retrieves all known data for a given user. Sessions with the same identifier will always be shown the same variation in an experiment. In the Visitor view of your experiment's results pages, these sessions will appear as a single visitor. The SDK configuration ensures that associated sessions always see the same variation of the experiment. However, there are some limitations regarding cross-device variation allocation. These limitations are outlined [here](/developer-docs/cross-device-experimentation#critical-points-and-practical-insights). Follow the [activating cross-device history reconciliation](#cross-device-experimentation) guide to set up your custom data on the Kameleoon platform. Afterwards, you can use the SDK normally. The following methods that may be helpful in the context of session merging: * `get_remote_visitor_data()` with added `UniqueIdentifier(true)` - to retrieve data for all linked visitors. * [`track_conversion()`](#track_conversion) or [`flush()`](#flush) with added `UniqueIdentifier(true)` data - to track some data for specific visitor that's associated with another visitor. As the custom data you use as the identifier must be set to **Visitor scope**, you need to use [cross-device custom data synchronization](/developer-docs/cross-device-experimentation) to retrieve the identifier with the [`get_remote_visitor_data()`](#get_remote_visitor_data) method on each device. Here's an example of how to use custom data for session merging. ```rust theme={null} // In this example, 91 represents the Custom Data's index configured as a unique identifier in Kameleoon. use kameleoon_client::data::{CustomData, UniqueIdentifier}; const MAPPING_INDEX: u32 = 91; const FEATURE_KEY: &str = "ff123"; let anonymous_visitor_code = "anonymous-visitor"; let user_id = "authenticated-user"; // 1. Before the visitor is authenticated // Retrieve the variation for an unauthenticated visitor. // Assume anonymousVisitorCode is the randomly generated ID for that visitor. let anonymous_variation = client.get_variation(anonymous_visitor_code, FEATURE_KEY)?; // 2. After the visitor is authenticated // Assume `userId` is the visitor code of the authenticated visitor. client.add_data(anonymous_visitor_code, [CustomData::new(MAPPING_INDEX, vec![user_id.to_owned()])])?; client.flush_instant(anonymous_visitor_code).await?; // Indicate that `userId` is a unique identifier. client.add_data(user_id, [UniqueIdentifier::new(true)])?; // 3. After the visitor was authorized // Retrieve the variation for the `userId`, which will match the anonymous visitor code's variation. let user_variation = client.get_variation(user_id, FEATURE_KEY)?; let is_same_variation = user_variation.key.as_ref() == anonymous_variation.key.as_ref(); // true // `userId` and `anonymousVisitorCode` are now linked and can be tracked as a single visitor. client.track_conversion(user_id, 123)?; // Additionally, the linked visitors share all fetched previously tracked remote data. client.get_remote_visitor_data(user_id, None).await?; ``` In this example, the app has a login page. Since the user ID is unknown at the moment of login, an anonymous visitor identifier generated by the [`get_visitor_code()`](#get_visitor_code) method is used. After the user logs in, the anonymous visitor is associated with the user ID and used as a unique identifier for the visitor. ### Logging The SDK generates logs to reflect various internal processes and issues. #### Log levels The SDK supports configuring limiting logging by a log level. ```rust theme={null} use kameleoon_client::logging::{KameleoonLogger, LogLevel}; // The `None` log level does not allow logging. KameleoonLogger::set_log_level(LogLevel::None); // The `Error` log level only allows logging issues that may affect the SDK's primary behaviour. KameleoonLogger::set_log_level(LogLevel::Error); // The `Warning` log level allows logging issues which may require attention. // It extends the `ERROR` log level. // The `WARNING` log level is the default log level. KameleoonLogger::set_log_level(LogLevel::Warning); // The `Info` log level allows logging general information on the SDK's internal processes. // It extends the `WARNING` log level. KameleoonLogger::set_log_level(LogLevel::Info); // The `Debug` level logs additional details about the SDK’s internal processes and extends the `INFO` level // with more granular diagnostic output. // This information is not intended for end-user interpretation but can be sent to our support team // to assist with internal troubleshooting. KameleoonLogger::set_log_level(LogLevel::Debug); ``` #### Custom handling of logs The SDK writes its logs to the console output by default. This behaviour can be overridden. Logging limiting by a log level is performed apart from the log handling logic. ```rust theme={null} use kameleoon_client::logging::{KameleoonLogger, LogLevel, Logger}; use log::{error, warn, info, debug}; pub struct CustomLogger; impl Logger for CustomLogger { // `log` method accepts logs from the SDK fn log(&self, log_level: LogLevel, message: &str) { // Custom log handling logic here. For example: match log_level { LogLevel::Error => error!("{}", message), LogLevel::Warning => warn!("{}", message), LogLevel::Info => info!("{}", message), LogLevel::Debug => debug!("{}", message), } } } // Log level filtering is applied separately from log handling logic. // The custom logger will only accept logs that meet or exceed the specified log level. // Ensure the log level is set correctly. KameleoonLogger::set_log_level(LogLevel::Debug); // Optional; defaults to `LogLevel.WARNING`. KameleoonLogger::set_logger(Box::new(CustomLogger)); ``` ## Reference This is the full reference documentation for the Rust SDK. ### Initialization #### create() To use the SDK, create a `KameleoonClient` from a `KameleoonClientConfig` instance with `KameleoonClientFactory::create_with_config()`/`KameleoonClientFactory::create_with_file()`. ```rust theme={null} use std::time::Duration; use kameleoon_client::config::KameleoonClientConfigBuilder; use kameleoon_client::factory::KameleoonClientFactory; let config = KameleoonClientConfigBuilder::default() .client_id("".to_owned()) // mandatory .client_secret("".to_owned()) // mandatory .refresh_interval(Duration::from_mins(60)) // optional (60 minutes by default) .session_duration(Duration::from_mins(30)) // optional (30 minutes by default) .default_timeout(Duration::from_millis(10_000)) // optional (10000 ms by default) .tracking_interval(Duration::from_millis(1_000)) // optional (1000 ms by default) .environment("development".to_owned()) // optional .top_level_domain(".example.com") // mandatory if you use hybrid mode (engine or web experiments) .proxy_host("http://192.168.0.25:8080".to_owned()) // optional .network_domain("example.com".to_owned()) // optional .build() .unwrap(); let client = KameleoonClientFactory::create_with_config(site_code, config)?; ``` ##### Parameters | Name | Type | Description | | ----------------------------------- | ----------------------- | ---------------------------------------------------- | | `site_code` required | `&str` | Unique key of the Kameleoon project used by the SDK. | | `config` required | `KameleoonClientConfig` | SDK configuration object. | ```rust theme={null} use kameleoon_client::factory::KameleoonClientFactory; let client = KameleoonClientFactory::create_with_file("a8st4f59bj", "/etc/kameleoon/client-config.json")?; ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | ------ | ---------------------------------------------------- | | `site_code` required | `&str` | Unique key of the Kameleoon project used by the SDK. | | `config_path` required | `&str` | Path to the JSON configuration file. | ##### Return value | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------- | | `Result` | A client instance on success, otherwise an initialization error. | ##### Errors | Type | Description | | ------------------------------------- | -------------------------------- | | `ErrorCode::ConfigCredentialsInvalid` | The SDK credentials are missing. | | `ErrorCode::SiteCodeIsEmpty` | The provided site code is empty. | #### initialize() Use `initialize()` when your application should wait for the Kameleoon client to finish initialization before it evaluates feature flags. The call returns the result of the configuration fetch: it completes successfully when the client is initialized, and fails if the initial configuration fetch fails before the client becomes ready. Background retries continue after that first failure. Once a retry succeeds, subsequent calls return a successful result. If no initialization result is available before the timeout expires, the call fails. If you don't provide a `timeout` value, the SDK uses the default timeout from [`default_timeout`](#additional-configuration). Call `initialize()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `initialize()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `true` as soon as a retry succeeds. ```rust theme={null} use std::time::Duration; // Initializes the client using the configured default timeout client.initialize().await?; // Initializes the client with a custom timeout of 5 seconds client.initialize_with_timeout(Duration::from_secs(5)).await?; ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------- | ---------- | ------------------------------------------------------ | ----------------- | | `timeout` optional | `Duration` | The maximum time to wait for an initialization result. | `default_timeout` | ##### Return value | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Returns `Ok(())` once the SDK is ready, or an error if the initial configuration fetch fails or no initialization result is available before the timeout expires. | ##### Errors | Type | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | No initialization result was available before the timeout expired. | | The actual fetch error | The initial fetch of the configuration failed. The error that caused the failure (such as a network or HTTP failure) is returned as-is. | #### is\_ready() `is_ready()` checks whether the SDK is ready for use, which means its configuration has been successfully loaded. Unlike [`initialize()`](#initialize), this method returns immediately without blocking or throwing. * Once the SDK becomes ready, it stays ready. If a later configuration refresh fails, the SDK keeps using the previously downloaded configuration and doesn't return to a not-ready state. * Call `initialize()` once on application startup to wait until the SDK has loaded its configuration or the timeout expires. On each incoming request, use [`is_ready()`](#is_ready) as a non-blocking guard before evaluating feature flags. Even if `initialize()` fails, the SDK keeps retrying the configuration fetch in the background, so `is_ready()` starts returning `true` as soon as a retry succeeds. ```rust theme={null} if client.is_ready() { // The client is ready } ``` ##### Return value | Type | Description | | ------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `bool` | `true` if the SDK has been successfully initialized; `false` otherwise (including while initialization is still pending or has failed). | #### forget() Removes the cached SDK client associated with the specified `site_code`. ```rust theme={null} use kameleoon_client::factory::KameleoonClientFactory; // Removes the cached client for the given site code KameleoonClientFactory::forget("a8st4f59bj")?; // Removes the cached client for the given site code and environment KameleoonClientFactory::forget_with_environment("a8st4f59bj", "production")?; ``` ##### Parameters | Name | Type | Description | | ------------------------------------- | ------ | -------------------------------------------------- | | `site_code` required | `&str` | Unique identifier of the Kameleoon project. | | `environment` optional | `&str` | Environment key associated with the cached client. | ##### Return value | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether the cached client was successfully removed or an error occurred. | ### Feature flags and variations #### is\_feature\_active() * 📨 *Sends tracking data to Kameleoon (depending on the `track` option)* Determines whether a feature flag is active for a given user. If the visitor hasn't yet been evaluated for this feature flag, the SDK evaluates the targeting rules and returns the result. If the visitor already has a stored evaluation for the feature, the SDK reuses the existing result to ensure consistency. Kameleoon uses tracking to count sessions and visitors when you call certain methods, such as `is_feature_active()`, `get_variation()` or `get_variations()`. Use the default `true` value for the `track` parameter when you expose visitors to a variation and need to count them. Set the `track` parameter to `false` only if you call these methods before you expose visitors. For example, if you call `get_variations()` to retrieve all variations before you expose visitors, set the `track` parameter to `false`. This setting prevents Kameleoon from prematurely counting a session. You can then trigger tracking later when you explicitly expose the visitor. Kameleoon sends tracking data every second by default. You can configure this interval up to 5 seconds using the tracking interval configuration option. Kameleoon groups tracking events into a single session as long as the interval between events is less than 30 minutes. If more than 30 minutes elapse between tracking events, Kameleoon counts the events as separate sessions. A visit appears in your reports 30 minutes after the last recorded event in the session. The `is_feature_active()` method evaluates the served variant, not the master flag state. If you exclude rules, the method uses the **Then, for everyone else serve** default state. If you select **Off** for this default state, the method always returns `false` even when the master feature flag is **On**. ```rust theme={null} use kameleoon_client::client::IsFeatureActiveOpts; let feature_key = "new_checkout"; // Evaluates the feature flag and sends tracking data (default behavior) let active = client.is_feature_active(visitor_code, feature_key)?; // Evaluates the feature flag without sending tracking data let active_without_tracking = client.is_feature_active_with_opts( visitor_code, feature_key, IsFeatureActiveOpts::new().track(false), )?; ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------ | ------------------------------------------------------- | ------- | | `visitor_code` required | `&str` | Unique identifier of the user. | | | `feature_key` required | `&str` | Key of the feature to evaluate for the user. | | | `track` optional | `bool` | Enables or disables tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------------ | --------------------------------------------------------------------------------------------------- | | `Result` | Indicates whether the feature flag is active for the specified `visitor_code`, or returns an error. | ##### Errors | Type | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_variation() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves the [`Variation`](#variation) assigned to a given visitor for a specific feature flag. This method takes a `visitor_code` and `feature_key` as mandatory arguments. The `track` argument is optional and defaults to `true`. It returns the assigned `Variation` for the visitor. If the visitor is not associated with any feature flag rules, the method returns the default `Variation` for the given feature flag. Ensure that proper error handling is implemented in your code to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```rust theme={null} use kameleoon_client::client::GetVariationOpts; let feature_key = "new_checkout"; // Retrieves the variation assigned to the visitor (with tracking enabled by default) let variation = client.get_variation(visitor_code, feature_key)?; // Retrieves the variation without sending tracking data let variation_without_tracking = client.get_variation_with_opts( visitor_code, feature_key, GetVariationOpts::new().track(false), )?; ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------ | ------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | | | `feature_key` required | `&str` | Key of the feature you want to expose to a visitor. | | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `Result` | An assigned [`Variation`](#variation) to a given visitor for a specific feature flag on success, otherwise an error. | ##### Errors | Type | Description | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `ErrorCode::FeatureNotFound` | Exception indicating that the requested feature key wasn't found in the internal configuration of the SDK. This usually means that the feature flag isn't activated in the Kameleoon app (but code implementing the feature is already deployed in the app). | | `ErrorCode::FeatureEnvironmentDisabled` | Exception indicating that feature flag is off for the visitor's current environment (for example, production, staging, or development). | | `ErrorCode::FeatureEvaluationBlocked` | Exception indicating that feature evaluation is blocked. The reason is described in the error message. This usually occurs due to GDPR restrictions when the visitor hasn't provided legal consent. | #### get\_variations() * 📨 *Sends Tracking Data to Kameleoon (depending on the `track` parameter)* Retrieves a map of [`Variation`](#variation) objects assigned to a given visitor across all feature flags. This method iterates over all available feature flags and returns the assigned `Variation` for each flag associated with the specified visitor. It takes `visitor_code` as a mandatory argument, while `only_active` and `track` are optional. * If `only_active` is set to `true`, the method `get_variations()` will return feature flags variations provided the user is not bucketed with the `off` variation. * The `track` parameter controls whether or not the method will track the variation assignments. By default, it is set to `true`. If set to `false`, the tracking will be disabled. The returned map consists of feature flag keys as keys and their corresponding `Variation` as values. If no variation is assigned for a feature flag, the method returns the default `Variation` for that flag. Proper error handling should be implemented to manage potential exceptions. The default variation refers to the variation assigned to a visitor when they don't match any predefined delivery rules for a feature flag. In other words, it's the fallback variation applied to all users who aren't targeted by specific rules. It's represented as the variation in the "Then, for everyone else..." section in a management interface. ```rust theme={null} use kameleoon_client::client::GetVariationsOpts; // Retrieves all variations assigned to the visitor (with default options) let variations = client.get_variations(visitor_code)?; // Retrieves only active variations for the visitor let only_active_variations = client.get_variations_with_opts( visitor_code, GetVariationsOpts::new().only_active(true), )?; // Retrieves variations without sending tracking data let variations_without_tracking = client.get_variations_with_opts( visitor_code, GetVariationsOpts::new().track(false), )?; ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | | | `only_active` optional | `bool` | An optional parameter indicating whether to return variations for active (`true`) or all (`false`) feature flags. | `false` | | `track` optional | `bool` | An optional parameter to enable or turn off tracking of the feature evaluation. | `true` | ##### Return value | Type | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Result, Variation>, KameleoonError>` | Map that contains the assigned [`Variation`](#variation) objects of the feature flags using the keys of the corresponding features on success, otherwise an error. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### set\_forced\_variation() The method allows you to programmatically assign a specific [`Variation`](#variation) to a user, bypassing the standard evaluation process. This is especially valuable for controlled experiments where the usual evaluation logic isn't required or must be skipped. It can also be helpful in scenarios like debugging or custom testing. When a **forced** variation is set, it overrides Kameleoon's real-time evaluation logic. Processes like segmentation, targeting conditions, and algorithmic calculations are skipped. To preserve segmentation and targeting conditions during an experiment, set `force_targeting=false` instead. **Simulated** variations always take precedence in the execution order. If a **simulated** variation calculation is triggered, it will be fully processed and completed first. A forced variation is treated the same as an evaluated variation. It's tracked in analytics and stored in the user context like any standard evaluated variation, ensuring consistency in reporting. The method may throw exceptions under certain conditions (for example, invalid parameters, user context, or internal issues). Proper exception handling is essential to ensure that your app remains stable and resilient. It’s important to distinguish **forced** variations from **[simulated](#get_visitor_code)** variations: * **Forced variations**: Are specific to an individual experiment. * **Simulated variations**: Affect the overall **feature flag** result. ```rust theme={null} use kameleoon_client::client::SetForcedVariationOpts; let experiment_id = 202387; // Forces the visitor into "variation_2" for the given experiment client.set_forced_variation(visitor_code, experiment_id, Some("variation_2"))?; // Removes any previously forced variation for the visitor in this experiment client.set_forced_variation(visitor_code, experiment_id, None)?; // Forces the visitor into "variation_2" with custom options // In this case, targeting rules are respected (force_targeting = false) client.set_forced_variation_with_opts( visitor_code, experiment_id, Some("variation_2"), SetForcedVariationOpts::new().force_targeting(false), )?; ``` ##### Parameters | Name | Type | Description | Default | | ----------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | | | `experiment_id` required | `u32` | **Experiment Id** that will be targeted and selected during the evaluation process. | | | `variation_key` required | `Option<&str>` | **Variation Key** corresponding to a `Variation` that should be forced as the returned value for the experiment. If the value is `None`, the forced variation will be reset. | | | `force_targeting` optional | `bool` | Indicates whether targeting for the experiment should be forced and skipped (`true`) or applied as in the standard evaluation process (`false`). | `true` | ##### Return value | Type | Description | | ---------------------------- | --------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether the forced variation was successfully set or an error occurred. | ##### Errors | Type | Description | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `ErrorCode::FeatureExperimentNotFound` | Exception indicating that the requested experiment id hasn't been found in the SDK's internal configuration. This is usually normal and means that the rule's corresponding experiment hasn't yet been activated on Kameleoon's side. | | `ErrorCode::FeatureVariationNotFound` | Exception indicating that the requested variation key(id) hasn't been found in the internal configuration of the SDK. This is usually normal and means that the variation's corresponding experiment hasn't yet been activated on Kameleoon's side. | #### evaluate\_audiences() * 📨 *Sends Tracking Data to Kameleoon* This method evaluates visitors against all available Audiences Explorer segments and tracks those who match. `evaluate_audiences()` should be called **after all relevant visitor data has been set or updated**, and **just before** getting a feature variation or checking a feature flag. This approach ensures that the visitor is evaluated against the most current data available, allowing for accurate audience assignment based on all criteria. After calling this method, you can perform a detailed analysis of segment performance in Audiences Explorer. ```rust theme={null} client.evaluate_audiences(visitor_code)?; ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | ------ | --------------------------------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ---------------------------- | --------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether audience evaluation succeeded or an error occurred. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_datafile() To evaluate all feature flags, use [`get_variations()`](#get_variations). This method is more efficient than calling `DataFile` and iterating through flags with [`get_variation()`](#get_variation). Returns the current SDK configuration as a [`DataFile`](#datafile) object. ```rust theme={null} let datafile = client.get_datafile()?; ``` ##### Return value | Type | Description | | --------------------------------------- | -------------------------------------------------------------------------------------------- | | `Result, KameleoonError>` | The [`DataFile`](#datafile) containing the SDK configuration on success, otherwise an error. | ##### Errors | Type | Description | | --------------------------- | --------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | ### Visitor data #### get\_visitor\_code() Use `get_visitor_code()` to obtain the current visitor's Kameleoon `visitor_code`. The method works with any cookie store that implements the `CookieAccessor` trait. The implementation logic is as follows: 1. The SDK checks whether a `kameleoonVisitorCode` cookie is already available through the provided accessor. 2. If the cookie is absent, the SDK uses `default_visitor_code` when you provide one. 3. Otherwise, the SDK generates a new visitor code and stores it through the accessor. For more information, refer to [Hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation/). If you provide your own `visitor_code`, its uniqueness must be guaranteed on your side. Also note that the length of `visitor_code` is limited to `255` characters. The `get_visitor_code()` method allows you to set **simulated** variations for a visitor. When cookies (from a **request** or **document**) contain the key `kameleoonSimulationFFData`, the standard evaluation process is bypassed. Instead, the method directly returns a [`Variation`](#variation) based on the provided data. You can apply simulations in two ways: * **Automatically (recommended):** If using Kameleoon Web Experimentation or the SDK in [Hybrid mode](/developer-docs/feature-experimentation/get-started/hybrid-experimentation#linking-feature-experiments-with-front-end-tracking-code), the cookie is created automatically when simulating a variant's display using the [Simulation Panel](/user-manual/experimentation/feature-experimentation/using-the-rollout-planner/validation-and-rollback/using-simulation-mode). * **Manually:** Set the `kameleoonSimulationFFData` cookie manually. It’s important to distinguish **simulated** variations from **[forced](#set_forced_variation)** variations: * **Simulated variations**: Affect the overall **feature flag** result. * **Forced variations**: Are specific to an individual experiment. ⚙️ **Manual setup** Ensure the `kameleoonSimulationFFData` cookie follows this format: * `kameleoonSimulationFFData={"featureKey":{"expId":10,"varId":20}}`: Simulates the variation with `varId` of experiment `expId` for the given `featureKey`. * `kameleoonSimulationFFData={"featureKey":{"expId":0}}`: Simulates the default variation (defined in the **Then, for everyone else in Production, serve** section) for the given `featureKey`. ⚠️ To ensure the cookie value works correctly, encode it as a URI component using a method such as [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). ```rust theme={null} use std::collections::HashMap; use kameleoon_client::cookies::CookieAccessor; // A simple in-memory cookie store, useful for testing or non-HTTP environments struct MemoryCookies { values: HashMap, } impl CookieAccessor for MemoryCookies { // Store the cookie value by key (max_age and top_level_domain are unused here) fn set<'a>(&mut self, key: &str, value: &str, _max_age: u32, _top_level_domain: Option<&'a str>) { self.values.insert(key.to_owned(), value.to_owned()); } // Retrieve a cookie value by key fn get(&self, key: &str) -> Option<&str> { self.values.get(key).map(String::as_str) } } // Generate or retrieve a visitor code using auto-generated value let visitor_code = client.get_visitor_code(&mut cookies, None)?; // Generate or retrieve a visitor code using a predefined user ID let visitor_code = client.get_visitor_code(&mut cookies, Some("user_id"))?; ``` ```rust theme={null} use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Response} }; use axum_extra::extract::cookie::{Cookie, CookieJar}; use kameleoon_client::client::KameleoonClient; use kameleoon_client::cookies::CookieAccessor; use time::Duration; // Wrapper around Axum's CookieJar to implement the CookieAccessor trait struct AxumCookies(CookieJar); impl CookieAccessor for AxumCookies { // Build and add a cookie with path, max_age, and optional domain, then insert into the jar fn set<'a>(&mut self, key: &str, value: &str, max_age: u32, top_level_domain: Option<&'a str>) { let mut builder = Cookie::build((key.to_owned(), value.to_owned())).path("/").max_age(Duration::seconds(i64::from(max_age))); if let Some(domain) = top_level_domain { builder = builder.domain(domain.to_owned()); } self.0 = std::mem::take(&mut self.0).add(builder.build()); } // Retrieve a cookie value by key from the jar fn get(&self, key: &str) -> Option<&str> { self.0.get(key).map(|cookie| cookie.value()) } } async fn get_visitor_code( State(client): State, jar: CookieJar, ) -> Result<(StatusCode, CookieJar, String), Response> { let mut cookies = AxumCookies(jar); // Retrieve or generate a visitor code; map any SDK error to a 500 response let visitor_code = client .get_visitor_code(&mut cookies, None) .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response())?; // Return the status, updated cookie jar, and visitor code in the response Ok((StatusCode::OK, cookies.0, visitor_code)) } ``` ```rust theme={null} use actix_web::{ HttpRequest, HttpResponse, cookie::Cookie, http::{StatusCode, header}, web}; use kameleoon_client::client::KameleoonClient; use kameleoon_client::cookies::CookieAccessor; use kameleoon_client::error::KameleoonError; use time::Duration; // Holds both request cookies (read-only) and response cookies (to be set on the client) struct ActixCookies<'a> { request: Ref<'a, Vec>>, response: Vec>, } impl CookieAccessor for ActixCookies<'_> { // Build a cookie with path, max_age, and optional domain; // update it if it already exists in the response, otherwise append it fn set<'a>(&mut self, key: &str, value: &str, max_age: u32, top_level_domain: Option<&'a str>) { let mut builder = Cookie::build(key.to_owned(), value.to_owned()).path("/").max_age(Duration::seconds(i64::from(max_age))); if let Some(domain) = top_level_domain { builder = builder.domain(domain.to_owned()); } let next_cookie = builder.finish(); match self.response.iter_mut().find(|cookie| cookie.name() == key) { Some(cookie) => *cookie = next_cookie, None => self.response.push(next_cookie), } } // Check response cookies first, then fall back to request cookies fn get(&self, key: &str) -> Option<&str> { self.response .iter() .find(|cookie| cookie.name() == key) .map(|cookie| cookie.value()) .or_else(|| self.request.iter().find(|cookie| cookie.name() == key).map(|cookie| cookie.value())) } } fn get_visitor_code(client: web::Data, request: HttpRequest) -> Result { // Parse cookies from the incoming request let request_cookies = request.cookies().map_err(|error| error.to_string())?; let mut cookies = ActixCookies { request: request_cookies, response: Vec::new(), }; // Retrieve or generate a visitor code using the SDK let visitor_code = client.get_visitor_code(&mut cookies, None)?; // Attach any new/updated cookies to the response headers let mut response = HttpResponse::build(StatusCode::OK); for cookie in cookies.response { response.append_header((header::SET_COOKIE, cookie.encoded().to_string())); } Ok(response.body(visitor_code)) } ``` ##### Parameters | Name | Type | Description | | ---------------------------------------------- | -------------------------- | ------------------------------------------------------------------ | | `cookies` required | `&mut impl CookieAccessor` | Mutable cookie accessor used to read and store the visitor cookie. | | `default_visitor_code` optional | `Option<&str>` | Visitor code to use when no cookie is present. | ##### Return value | Type | Description | | -------------------------------- | ------------------------------------------------------------------------------------- | | `Result` | String representing a unique visitor code used in SDK on success, otherwise an error. | ##### Errors | Type | Description | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `ErrorCode::InvalidSimVarCookieFormat` | Indicates that the `kameleoonSimulationFFData` cookie contains malformed JSON and the simulated variations couldn't be parsed. | #### add\_data() The `add_data()` method adds [targeting data](#data-types) to storage so other methods can use the data to decide whether or not to target the current visitor. The `add_data()` method does not return any value and does not interact with Kameleoon back-end servers on its own. Instead, all the declared data is saved for future transmission using the [`flush()`](#flush) method. This approach reduces the number of server calls made, as the data is typically grouped into a single server call that's triggered by the `flush()`. The [`track_conversion()`](#track_conversion) method also sends out any previously associated data, just like the `flush()`. The same holds true for [`get_variation()`](#get_variation) and [`get_variations()`](#get_variations) methods if an experimentation rule is triggered. Each visitor can only have one instance of associated data for most data types. However, [`CustomData`](#customdata) is an exception. Visitors can have one instance of associated `CustomData` per index. ```rust theme={null} use kameleoon_client::data::{Browser, BrowserKind, PageView, UserAgent}; client.add_data(visitor_code, [Browser::new(BrowserKind::Chrome, Some(123.0))])?; client.add_data( visitor_code, [ PageView::new("https://example.com/pricing".to_owned(), Some("Pricing".to_owned()), vec![3]).into(), UserAgent::new("Mozilla/5.0".to_owned()).into(), ], )?; client.add_data_with_track( visitor_code, vec![PageView::new("https://example.com/checkout".to_owned(), Some("Checkout".to_owned()), vec![])], false, )?; ``` ##### Parameters | Name | Type | Description | Default value | | -------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | | | `data` required | `impl IntoIterator>` | Collection of Kameleoon data types. | | | `track` optional | `bool` | Specifies whether the added data is eligible for tracking. When set to `false`, the data is stored locally and used only for targeting evaluation; it is not sent to the Kameleoon Data API. | `true` | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether data was successfully added or an error occurred. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### flush() * *📨 Sends tracking data to Kameleoon* The `flush()` method aggregates all Kameleoon data associated with a visitor and sends a tracking request to the server. This request includes any data previously added via the [`add_data`](#add_data) method that hasn't yet been transmitted through other tracking mechanisms (see the referenced methods for details). The `flush()` operation is non-blocking, as the server call is performed asynchronously. This method provides control over when data linked to a specific `visitor_code` is transmitted. For example, if `add_data()` is called multiple times, sending a request after each invocation would be inefficient. Instead, you can batch these updates and call `flush()` once to send all accumulated data in a single request. The `flush()` method uses the provided `visitor_code` as the unique visitor identifier. * `flush()`: Queues a flush operation according to the configured tracking interval. * `flush_instant()`: Sends tracking data immediately without waiting for the interval. ```rust theme={null} // Queues a flush operation for the given visitor_code. // Data will be sent according to the configured tracking interval (non-blocking). client.flush(visitor_code)?; // Immediately sends all pending tracking data for the given visitor_code. // This is an async operation and must be awaited. client.flush_instant(visitor_code).await?; ``` ##### Parameters | Name | Type | Description | | -------------------------------------- | ------ | --------------------------------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------ | | `Result<(), KameleoonError>` | Indicates whether the operation was successfully scheduled or executed, or if an error occurred. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_remote\_data() The `get_remote_data()` method lets you retrieve remote data stored on Kameleoon servers for the specified `key`. In most setups, this data is written through the Kameleoon Data API and can later be fetched by your Rust service whenever you need additional app context. This method is useful when you want to keep structured information on Kameleoon's remote infrastructure and reuse it from your back-end without maintaining a separate retrieval mechanism. ```rust theme={null} let data = client.get_remote_data("test-key").await?; ``` ##### Parameters | Name | Type | Description | | ----------------------------- | ------ | --------------------------------------------------------- | | `key` required | `&str` | Key associated with the remote data you want to retrieve. | ##### Return value | Type | Description | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `Result` | Payload associated with the specified `key` on success, otherwise an error. In most cases, the payload is JSON serialized as a string. | ##### Errors | Type | Description | | --------------------------- | -------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::Network` | Returned when the remote data request fails or the server responds with a non-success status code. | #### get\_remote\_visitor\_data() `get_remote_visitor_data()` is an asynchronous method for retrieving Kameleoon visit data for the provided `visitor_code`. The method adds the data to local visitor storage so other SDK methods can use it for targeting decisions. Data obtained using this method is especially useful when you want to: * use data collected from other devices. * access a visitor's history, such as previously viewed pages from past visits. * use data that's only available on the client side, such as datalayer variables and front-end goal conversions. Read [this article](/developer-docs/feature-experimentation/targeting-and-segmentation/native-segmentation) for a better understanding of possible use cases. By default, `get_remote_visitor_data()` automatically retrieves the latest stored custom data with `scope=Visitor` and attaches it to the visitor without the need to call [`add_data()`](#add_data). This is especially useful for [synchronizing custom data across multiple devices](/developer-docs/cross-device-experimentation/). ```rust theme={null} use kameleoon_client::types::RemoteVisitorDataFilter; // Fetch remote visitor data without any filter // This will return all available data for the given visitor client.get_remote_visitor_data(visitor_code, None).await?; // Create a filter to limit the returned data let filter = RemoteVisitorDataFilter { // Include data from the last 5 visits previous_visit_amount: 5, // Include conversion events (e.g., goals, transactions) conversions: true, // Include page view history page_views: true, // Use default values for all other fields ..Default::default() }; // Fetch remote visitor data using the specified filter // This will return only the data matching the filter criteria client.get_remote_visitor_data(visitor_code, Some(filter)).await?; ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | --------------------------------- | ---------------------------------------------------------------- | ------------------------------------ | | `visitor_code` required | `&str` | Visitor code whose data should be fetched. | | | `filter` optional | `Option` | Filter describing which remote visitor data should be retrieved. | `RemoteVisitorDataFilter::default()` | ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------ | | `Result<(), KameleoonError>` | Returns successfully when the data is fetched and added locally, otherwise an error. | ##### Errors | Type | Description | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `ErrorCode::Network` | Returned when the remote visitor data request fails, can't be parsed, or the server responds with a non-success status code. | ##### Using parameters in get\_remote\_visitor\_data() The `get_remote_visitor_data()` method lets you control which data is retrieved for a visitor. The same filtering approach works across goals, experiments, variations, and other visitor data. For example, if you want to target users who converted on a goal during their last five visits, you can set `previous_visit_amount` to `5` and `conversions` to `true`. The flexibility shown in this example isn't limited to goal data. You can use the filter to retrieve many different visitor behaviors and make them available to targeting and reporting logic in your Rust app. ##### `RemoteVisitorDataFilter` fields | Name | Type | Description | Default | | ----------------------- | ------ | ------------------------------------------------------------- | ------- | | `previous_visit_amount` | `u32` | Number of previous visits to retrieve data from. | `1` | | `current_visit` | `bool` | If `true`, current visit data will be retrieved. | `true` | | `custom_data` | `bool` | If `true`, custom data will be retrieved. | `true` | | `visitor_code` | `bool` | If `true`, the most recent visitor code will be reused. | `true` | | `page_views` | `bool` | If `true`, page view data will be retrieved. | `false` | | `geolocation` | `bool` | If `true`, geolocation data will be retrieved. | `false` | | `device` | `bool` | If `true`, device data will be retrieved. | `false` | | `browser` | `bool` | If `true`, browser data will be retrieved. | `false` | | `operating_system` | `bool` | If `true`, operating system data will be retrieved. | `false` | | `conversions` | `bool` | If `true`, conversion data will be retrieved. | `false` | | `experiments` | `bool` | If `true`, experiment data will be retrieved. | `false` | | `kcs` | `bool` | If `true`, Kameleoon Conversion Score data will be retrieved. | `false` | | `personalizations` | `bool` | If `true`, personalization data will be retrieved. | `false` | | `cbs` | `bool` | If true, Contextual Bandit score data will be retrieved. | `false` | #### get\_visitor\_warehouse\_audience() This method retrieves audience data associated with a visitor in your warehouse integration by using the specified `visitor_code` and, optionally, a `warehouse_key`. The `warehouse_key` is typically your internal user ID. The `custom_data_index` parameter corresponds to the Kameleoon custom data that Kameleoon uses to target your visitors. When the call succeeds, the SDK converts the returned audience list into [`CustomData`](#customdata), adds it to the visitor locally, and makes it available for targeting purposes. For more background, see the [warehouse targeting documentation](/user-manual/integrations/data-warehouses/bigquery/use-bigquery-as-a-source-audience-targeting). ```rust theme={null} // Fetch audience data for a visitor using only the visitor_code. client.get_visitor_warehouse_audience(visitor_code, None, 98).await?; // Fetch audience data for a visitor using both visitor_code and warehouse_key. // Useful when your warehouse uses a different identifier than visitor_code. client.get_visitor_warehouse_audience(visitor_code, Some("internal-user-id"), 98).await?; ``` ##### Parameters | Name | Type | Description | Default | | ------------------------------------------- | -------------- | --------------------------------------------------------------------------- | -------------- | | `visitor_code` required | `&str` | Visitor whose warehouse audiences should be retrieved. | | | `warehouse_key` optional | `Option<&str>` | External warehouse key, usually your internal user ID. | `visitor_code` | | `custom_data_index` required | `u32` | Custom data index configured in Kameleoon for warehouse audience targeting. | | ##### Return value | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Success when the warehouse audience data is retrieved and stored locally as `CustomData`. | ##### Errors | Type | Description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | | `ErrorCode::Network` | Returned when the warehouse audience request fails, can't be parsed, or the server responds with a non-success status code. | #### set\_legal\_consent() You must use this method to specify whether the visitor has given legal consent to use personal data. Setting `legal_consent` to `false` limits the types of data that can be included in tracking requests. This helps you adhere to legal and regulatory requirements while responsibly managing visitor data. For more information, see the [consent management policy](/user-manual/project-management/consent-management-policy). If you provide a cookie accessor, the SDK also updates the visitor cookies according to the consent status. ```rust theme={null} // Set consent and update cookies client.set_legal_consent(visitor_code, true, Some(&mut cookies))?; // Set consent without updating cookies client.set_legal_consent(visitor_code, true, None)?; ``` ##### Parameters | Name | Type | Description | Default | | --------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `visitor_code` required | `&str` | The user's unique identifier. | | | `legal_consent` required | `bool` | `true` indicates the visitor has given legal consent, `false` indicates the visitor has never provided, or has withdrawn, legal consent. | | | `cookies` optional | `Option<&mut impl CookieAccessor>` | Optional cookie accessor used to update cookies. | `None` | ##### Return value | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether the visitor consent state was updated successfully, otherwise an error. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ##### Consent revocation behavior When you call `set_legal_consent()` with `legal_consent=false`, the SDK doesn't delete the `kameleoonVisitorCode` cookie. Instead, it stops extending the cookie's expiration date, allowing the cookie to persist until it naturally expires. If your compliance requirements demand the immediate removal of the cookie file upon opt-out, you must delete it manually using your framework’s native cookie management methods. The SDK won't remove the file automatically. ### Goals and third-party analytics #### track\_conversion() * 📨 *Sends Tracking Data to Kameleoon* Use this method to track a conversion for a specific [goal](/user-manual/assets/goals/create-a-goal) and user. This method requires `visitor_code` and `goal_id`. In addition, this method also accepts an optional `revenue`, `negative` and `metadata` arguments. The `visitor_code` is usually identical to the one that was used when triggering the experiment. This method is non-blocking as the server call is made asynchronously. ```rust theme={null} use kameleoon_client::client::TrackConversionOpts; use kameleoon_client::data::CustomData; // Track a goal client.track_conversion(visitor_code, goal_id)?; // Track a goal with revenue client.track_conversion_with_opts(visitor_code, goal_id, TrackConversionOpts::new().revenue(100.0))?; // Track a goal with negative revenue client.track_conversion_with_opts( visitor_code, goal_id, TrackConversionOpts::new().revenue(100.0).negative(true) )?; // Track a goal with custom metadata client.track_conversion_with_opts( visitor_code, goal_id, TrackConversionOpts::new().metadata(vec![CustomData::new(4, vec!["true".to_owned()])]), )?; ``` ##### Parameters | Name | Type | Description | Default | | -------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | | | `goal_id` required | `u32` | ID of the goal. | | | `revenue` optional | `f32` | Revenue of the conversion. | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `Vec` | Metadata of the conversion. [Must be defined beforehand in the Kameleoon App](/user-manual/assets/goals/create-a-goal#metadata). | `vec![]` | metadata values are accessible through [raw data exports](/user-manual/experiment-analytics/analyze-results/results-page/results-page-actions#Export) and [the results page](/user-manual/experiment-analytics/analyze-results/data-and-metrics/goal-metadata). If the `metadata` parameter is provided, Kameleoon will use these specified values for the current conversion instead of what was previously collected using the [`add_data()`](#add_data) method. If the parameter is omitted, Kameleoon will use the last tracked values for those [`CustomData`](#customdata) prior to the conversion and within the same visit. Kameleoon will only consider the metadata values that are explicitly passed as parameters to the `track_conversion()` method. In the example below, Kameleoon will associate the conversion only with the custom data value explicitly provided as a parameter (here: index 5 with the value 'Amex Credit Card'). ```rust theme={null} client.add_data( visitor_code, [ CustomData::new(5, vec!["Credit Card".to_owned()]), CustomData::new(9, vec!["Express Delivery".to_owned()]) ] )?; client.track_conversion_with_opts( visitor_code, goal_id, TrackConversionOpts::new().metadata(vec![CustomData::new(5, vec!["Amex Credit Card".to_owned()])]), )? ``` ##### Return value | Type | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------- | | `Result<(), KameleoonError>` | Indicates whether the conversion was successfully queued for asynchronous tracking, otherwise an error. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | #### get\_engine\_tracking\_code() Kameleoon integrates with several analytics solutions, including Mixpanel, Google Analytics 4, and Segment. To track server-side experiments correctly, call the `get_engine_tracking_code()` method after the visitor triggers an experiment. The SDK returns JavaScript queue commands for the experiments that the visitor triggered during the previous five seconds. When you insert this code into the page, Engine.js processes the commands and sends the exposure events through the active analytics integration. Refer to [hybrid experimentation](/developer-docs/feature-experimentation/get-started/hybrid-experimentation) for more information on implementing this method. ```rust theme={null} let tracking_code = client.get_engine_tracking_code(visitor_code)?; ``` * To use this feature, implement both the Rust SDK and Kameleoon [Engine.js](/developer-docs/web-experimentation/implementation-and-deployment/standard-implementation). Because Engine.js is used only for tracking in this flow, you can install the asynchronous tag before the closing `` tag. * If you only want to track experiments in Kameleoon and don't need to send exposure events to third-party analytics tools, use the [JavaScript / TypeScript SDK](/developer-docs/sdks/web-sdks/js-sdk). This option works well for [serverless edge compute platforms](/developer-docs/feature-experimentation/implementation-and-deployment/serverless-edge-compute-starter-kits). The JavaScript / TypeScript SDK automatically tracks variations when you call [`getVisitorCode`](/developer-docs/sdks/web-sdks/js-sdk#getvisitorcode), as long as you add the corresponding experiment assignments to `window.kameleoonQueue`. * You can insert the returned tracking code directly into an HTML ` ``` In this example, `123456` and `234567` are experiment IDs, and `7890` and `8901` are variation IDs. In your implementation, the SDK generates these values in the returned tracking code. ##### Parameters | Name | Type | Description | | -------------------------------------- | ------ | --------------------------------- | | `visitor_code` required | `&str` | Unique identifier of the visitor. | ##### Return value | Type | Description | | -------------------------------- | ------------------------------------------------------------------------ | | `Result` | JavaScript code to insert into the page on success; otherwise, an error. | ##### Errors | Type | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | | `ErrorCode::VisitorCodeInvalid` | Exception indicating that the provided visitor code isn't valid. It's either empty or longer than 255 characters. | ### Events #### set\_event\_handler() Use this method to register a handler for SDK events. The SDK calls the handler when the selected event occurs. Registering a new handler for the same event type replaces the previous handler. Passing `None` as the `handler` removes the current handler for the selected event type. The `EventHandler` variant you pass determines which event type the handler listens to, so a handler and the events it receives can't get out of sync. ```rust theme={null} use kameleoon_client::events::{DataFileUpdateEvent, DataFileUpdateHandler, EventHandler}; struct Observer; impl DataFileUpdateHandler for Observer { fn on_update(&self, event: &DataFileUpdateEvent) { let source = event.source; // DataFileUpdateSource::Polling or DataFileUpdateSource::Streaming let date_modified = event.date_modified; // Data file modification date in milliseconds. // React to the data file update. } } client.set_event_handler(EventHandler::data_file_update(Observer)); // Clear the handler. client.set_event_handler(EventHandler::DataFileUpdate(None)); ``` `DataFileUpdateEvent` contains information about an SDK data file update. | Name | Type | Description | | --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `source` | `DataFileUpdateSource` | The update source. [`DataFileUpdateSource::Polling`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) indicates a scheduled data file refresh, and [`DataFileUpdateSource::Streaming`](/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) indicates a real-time update received through streaming mode. | | `date_modified` | `u64` | The modification date of the updated data file, in milliseconds. | ```rust theme={null} use std::time::Duration; use kameleoon_client::events::{EventHandler, HttpRequestFailure, HttpRequestHandler, RequestType}; struct Observer; impl HttpRequestHandler for Observer { fn on_request_succeeded(&self, request_type: RequestType, http_status: u16, duration: Duration) { // The SDK request completed successfully. } fn on_request_failed(&self, request_type: RequestType, failure: &HttpRequestFailure, duration: Duration) { let reason = failure.reason(); // HttpRequestFailureReason::HttpStatus, ::Error, or ::Cancelled let http_status = failure.http_status(); let error = failure.error(); // The SDK request failed. } } client.set_event_handler(EventHandler::http_request(Observer)); // Clear the handler. client.set_event_handler(EventHandler::HttpRequest(None)); ``` `HttpRequestHandler` lets you react to completed SDK HTTP requests. The SDK calls `on_request_succeeded()` for successful responses and `on_request_failed()` for failed responses, exceptions, or cancelled requests. | Method | Description | | ------------------------ | -------------------------------------------------------------------------------------------- | | `on_request_succeeded()` | Called when an SDK HTTP request completes successfully. | | `on_request_failed()` | Called when an SDK HTTP request fails because of an HTTP status, exception, or cancellation. | ##### Callback parameters | Name | Type | Description | | -------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `request_type` | `RequestType` | The SDK request type. Possible values are `RequestType::DataFile`, `RequestType::Tracking`, `RequestType::RemoteVisitorData`, `RequestType::RemoteData`, and `RequestType::AccessToken`. | | `http_status` | `u16` | The HTTP status code returned by the request. Passed only to `on_request_succeeded()`. | | `failure` | `&HttpRequestFailure` | Details about why the request failed. Passed only to `on_request_failed()`. | | `duration` | `Duration` | The request duration. | ##### HttpRequestFailure `HttpRequestFailure` contains details about a failed SDK HTTP request. | Name | Type | Description | | --------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason()` | `HttpRequestFailureReason` | The failure reason. Possible values are `HttpRequestFailureReason::HttpStatus`, `HttpRequestFailureReason::Error`, and `HttpRequestFailureReason::Cancelled`. | | `http_status()` | `Option` | The HTTP status code for failures caused by an unsuccessful HTTP response. This value is `None` for exception and cancellation failures. | | `error()` | `Option<&(dyn std::error::Error + Send + Sync)>` | The exception (error) that caused the request to fail. This value is `None` when the request failed because of an HTTP status or cancellation. | Handlers are called synchronously on the SDK's single-worker runtime, so they must not block: time spent in a handler delays data file polling and tracking flushes. Record the values you need and return. A panic escaping a handler is caught and logged, and never affects SDK behavior. ##### Parameters | Name | Type | Description | | --------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `handler` required | `EventHandler` | The handler to register for the selected event type. Use a `DataFileUpdateHandler` for `EventHandler::DataFileUpdate`, or an `HttpRequestHandler` for `EventHandler::HttpRequest`. Pass `None` to remove the current handler. | ### Data types This section lists the Rust data types re-exported by the SDK in `kameleoon_client::data`. #### ApplicationVersion `ApplicationVersion` represents the semantic version number of your application. A **visitor** can have only one `ApplicationVersion`. Adding a second instance will overwrite the first one. | Name | Type | Description | | ------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | version optional | `&str` | The mobile app version. This field must follow semantic versioning. Accepted formats are `major`, `major.minor`, or `major.minor.patch`. | ```rust theme={null} use kameleoon_client::data::ApplicationVersion; // major client.add_data(visitor_code, [ApplicationVersion::new("10")])?; // major.minor client.add_data(visitor_code, [ApplicationVersion::new("10.20")])?; // major.minor.patch client.add_data(visitor_code, [ApplicationVersion::new("10.20.30")])?; ``` #### Browser The `Browser` data set stored here can be used to filter experiment and personalization reports by any value associated with it. | Name | Type | Description | | --------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `kind` required | `BrowserKind` | List of browsers: `BrowserKind::Chrome`, `BrowserKind::InternetExplorer`, `BrowserKind::Firefox`, `BrowserKind::Safari`, `BrowserKind::Opera`, `BrowserKind::Other`. | | `version` optional | `Option` | Version of the browser, floating point number represents major and minor version of the browser | ```rust theme={null} use kameleoon_client::data::{Browser, BrowserKind}; // Browser data with a version client.add_data(visitor_code, [Browser::new(BrowserKind::Safari, 26.4)])?; // Browser data without a version client.add_data(visitor_code, [Browser::new(BrowserKind::Chrome, None)])?; ``` #### Conversion The `Conversion` data set stored here can be used to filter experiment and personalization reports by any goal associated with it. * Each visitor can have multiple `Conversion` objects. * You can find the `goal_id` in the Kameleoon app. | Name | Type | Description | Default | | ---------------------------------- | ----------------- | ----------------------------------------------- | -------- | | `goal_id` required | `u32` | ID of the goal. | | | `revenue` optional | `f32` | Revenue of the conversion | `0` | | `negative` optional | `bool` | Defines if the revenue is positive or negative. | `false` | | `metadata` optional | `Vec` | Metadata of the conversion. | `vec![]` | ```rust theme={null} use kameleoon_client::data::{Conversion, ConversionOpts, CustomData}; // Add a simple conversion with ID 32 client.add_data(visitor_code, [Conversion::new(32)])?; // Add conversion with ID 33 including revenue and marked as negative client.add_data(visitor_code, [Conversion::new_with_opts(33, ConversionOpts::new().revenue(10.0).negative(true))])?; // Add conversion with ID 34 including revenue, negative flag, and custom metadata client.add_data( visitor_code, [ Conversion::new_with_opts( 34, ConversionOpts::new().revenue(10.0).negative(true).metadata(vec![ CustomData::new(3, vec!["metadata1".to_owned(), "md2".to_owned()]), CustomData::new(5, vec!["md3".to_owned()]), ]), ) ], )?; ``` #### Cookie `Cookie` contains information about the cookies stored on the visitor's device. | Name | Type | Description | | ------- | ------------------------- | ----------------------------------------------- | | cookies | `HashMap` | A string map containing cookie keys and values. | Each visitor can only have one `Cookie`. Adding a second `Cookie` overwrites the first one. ```rust theme={null} use std::collections::HashMap; use kameleoon_client::data::Cookie; client.add_data(visitor_code, [Cookie::new(HashMap::from([("segment".to_owned(), "vip".to_owned())]))])?; ``` #### CustomData `CustomData` enables the association of any type of data with each visitor, making it an effective tool for targeting conditions in [segments](/user-manual/assets/segments/create-a-segment/). Additionally, it can be used as a filter or breakdown in experiment reports. For more information about custom data, refer to this [article](/developer-docs/custom-data). Define custom data types in the Kameleoon app or the Data API and use them from the SDK. | Name | Type | Description | Default | | ---------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | index/name required | `u32`/`String` | Index or Name of the custom data. **Either `index` or `name` must be provided** to identify the data. | | | values required | `Vec` | Values of the custom data to be stored. | | | overwrite optional | `bool` | Flag to explicitly control how the values are stored and how they appear in reports. [See more](/developer-docs/custom-data#default-logic-when-overwrite-parameter-is-false-or-omitted) | `true` | * Each visitor is allowed only one `CustomData` for each unique `index`(`name`). Adding another `CustomData` with the same `index`(`name`) will replace the existing one. * The custom data ‘index’ can be found in the [Custom Data dashboard](/user-manual/assets/custom-data/manage-custom-data) under the “INDEX” column. * To prevent the SDK from sending data with the selected index to Kameleoon servers for privacy reasons, enable the option: **Use this data only locally for targeting purposes** when creating custom data. * Adding a `CustomData` instance created with a name when the SDK instance isn't initialized or the name isn't registered, will result in the data being ignored. ```rust theme={null} use kameleoon_client::data::{CustomData, CustomDataOpts}; client.add_data(visitor_code, [CustomData::new_with_index(1, vec!["value".to_owned()])])?; // With several values client.add_data( visitor_code, [CustomData::new_with_index( 1, vec!["value1".to_owned(), "value2".to_owned()], )], )?; // To set the `overwrite` flag to false client.add_data( visitor_code, [CustomData::new_with_index_opts( 1, vec!["value".to_owned()], CustomDataOpts::new().overwrite(false), )], )?; // To use a name instead of the index client.add_data( visitor_code, [CustomData::new_with_name( "my-custom-data".to_owned(), vec!["value".to_owned()], )], )?; // To use a name instead of the index and set the `overwrite` flag to false client.add_data( visitor_code, [CustomData::new_with_name_opts( "my-custom-data", vec!["value".to_owned()], CustomDataOpts::new().overwrite(false), )], )?; ``` #### Device You can use device data to filter experiment and personalization reports by any associated value. | Name | Type | Description | | ---- | ------------ | ------------------------------------------------------------------ | | kind | `DeviceKind` | Device type. Possible values are `Phone`, `Tablet`, and `Desktop`. | ```rust theme={null} use kameleoon_client::data::{Device, DeviceKind}; client.add_data(visitor_code, [Device::new(DeviceKind::Desktop)])?; ``` #### Geolocation `Geolocation` contains the visitor's geolocation details. | Name | Type | Description | | ------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `country` required | `&str` | The country of the visitor. | | `region` optional | `Option` | The region of the visitor. | | `city` optional | `Option` | The city of the visitor. | | `postal_code` optional | `Option` | The postal code of the visitor. | | `latitude` optional | `Option` | The latitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | | `longitude` optional | `Option` | The longitude coordinate representing the location of the visitor. Coordinate number represents decimal degrees. | * Each visitor can have only one `Geolocation`. Adding a second `Geolocation` overwrites the first one. ```rust theme={null} use kameleoon_client::data::GeolocationBuilder; let geolocation = GeolocationBuilder::default() .country("France") .region("Ile-de-France".to_owned()) .city("Paris".to_owned()) .postal_code("75009".to_owned()) .latitude(48.8720171) .longitude(2.3338352) .build() .unwrap(); client.add_data(visitor_code, [geolocation])?; ``` #### OperatingSystem `OperatingSystem` contains information about the operating system on the visitor's device. | Name | Type | Description | | ---- | --------------------- | ------------------------------------------------------------------------------------------------------------- | | kind | `OperatingSystemKind` | Operating system family. Possible values are `Windows`, `Mac`, `IOS`, `Linux`, `Android`, and `WindowsPhone`. | Each visitor can only have one `OperatingSystem`. Adding a second `OperatingSystem` overwrites the first one. ```rust theme={null} use kameleoon_client::data::{OperatingSystem, OperatingSystemKind}; client.add_data(visitor_code, [OperatingSystem::new(OperatingSystemKind::Windows)])?; ``` #### PageView Store page view events. | Name | Type | Description | Default | | --------- | ---------------- | -------------------------------------------- | -------- | | url | `String` | URL of the page viewed. | | | title | `Option` | Title of the page viewed. | `None` | | referrers | `Vec` | Referrer indices of previously viewed pages. | `vec![]` | The referrer index is available in the Kameleoon app on the [acquisition channel configuration](/user-manual/assets/advanced-targeting-tools/create-an-acquisition-channel) page. Be careful: the index starts at `0`, so the first acquisition channel you create has the ID `0`, not `1`. ```rust theme={null} use kameleoon_client::data::{PageView, PageViewOpts}; // new() - full constructor with url, optional title, and referrers client.add_data(visitor_code, [PageView::new("https://example.com", Some("Homepage"), vec![3])])?; // new_with_url() - minimal constructor, only requires a URL client.add_data(visitor_code, [PageView::new_with_url("https://example.com")])?; // new_with_opts() - constructor using PageViewOpts builder for optional fields let opts = PageViewOpts::builder().title("Homepage").referrers(vec![3]).build(); client.add_data(visitor_code, [PageView::new_with_opts("https://example.com", opts)])?; ``` #### UniqueIdentifier If you don't add `UniqueIdentifier` for a visitor, `visitor_code` is used as the unique visitor identifier, which is useful for [cross-device experimentation](/developer-docs/cross-device-experimentation/). When you add `UniqueIdentifier::new(true)`, the SDK links flushed data with the visitor associated with the specified identifier. This can be useful in situations where you can't access the anonymous `visitor_code` originally assigned to a visitor, but you do have access to an internal identifier connected to that visitor through session merging. | Name | Type | Description | | ------- | ------ | ---------------------------------------------------------------------------- | | `value` | `bool` | Whether the current `visitor_code` should be treated as a unique identifier. | ```rust theme={null} use kameleoon_client::data::UniqueIdentifier; client.add_data(visitor_code, [UniqueIdentifier::new(true)])?; ``` #### UserAgent Server-side experiments are more likely to be affected by bot traffic than client-side experiments. Kameleoon uses the IAB/ABC International Spiders and Bots List to recognize known bots and spiders, and it also uses the `UserAgent` field to filter out other unwanted traffic that might distort your conversion metrics. For more information, see the help article on [bot filtering](/user-manual/faq#how-does-kameleoon-filter-bot-traffic-from-my-results). If you use internal bots, send the user-agent value `curl/8.0` to exclude them from analytics. | Name | Type | Description | | ----- | -------- | --------------------------------------------- | | value | `String` | User-Agent value sent with tracking requests. | ```rust theme={null} use kameleoon_client::data::UserAgent; client.add_data(visitor_code, [UserAgent::new("Mozilla/5.0")])?; ``` ### Returned types #### DataFile The `DataFile` contains the SDK configuration details. It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | --------------- | -------------------------------- | --------------------------------------------------------------------------------- | | `feature_flags` | `HashMap, FeatureFlag>` | A map of [`FeatureFlag`](#featureflag) objects, keyed by feature flag keys. | | `date_modified` | `u64` | The timestamp (in milliseconds) indicating when the `DataFile` was last modified. | ```rust theme={null} // Retrieves the map of feature flags from the DataFile. // The map is keyed by feature flag identifiers, with each value being a FeatureFlag object. let feature_flags: &HashMap, FeatureFlag> = &datafile.feature_flags; // Retrieves the last modification timestamp of the DataFile. // The value is a u64 representing milliseconds since the Unix epoch. let date_modified: u64 = datafile.date_modified; ``` #### FeatureFlag The `FeatureFlag` represents a set of properties that define a feature flag itself (for example, its [`Variations`](#variation), [`Rules`](#rule), environment status, and other related details). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ----------------------- | ------------------------------ | -------------------------------------------------------------------------- | | `environment_enabled` | `bool` | Indicating whether the feature flag is enabled in the current environment. | | `default_variation_key` | `&str` | The key of the default variation associated with the feature flag. | | `variations` | `HashMap, Variation>` | A map of `Variation` objects, keyed by variation keys. | | `rules` | `Vec` | A list of `Rule` objects | ```rust theme={null} // Check whether the feature flag is enabled in the current environment. let environment_enabled: bool = feature_flag.environment_enabled; // Retrieve the key of the default variation. let default_variation_key: &str = feature_flag.default_variation_key.as_ref(); // Retrieve the default variation object. let default_variation: &Variation = feature_flag.default_variation(); // Retrieve all variations of the feature flag as a map (key = variation key, value = Variation object). let variations: &HashMap, Variation> = &feature_flag.variations; // Retrieve all targeting rules associated with the feature flag. let rules: &Vec = &feature_flag.rules; ``` #### Rule The `Rule` represents a set of properties that define a rule itself (for example, its [`Variations`](#variation)). It can be extended with additional information if required by clients. If you need more details, contact your Customer Success Manager. | Name | Type | Description | | ------------ | ------------------------------ | ------------------------------------------------------ | | `variations` | `HashMap, Variation>` | A map of `Variation` objects, keyed by variation keys. | ```rust theme={null} // Retrieve all variations of the rule as a map (key = variation key, value = Variation object) let variations: &HashMap, Variation> = &rule.variations; ``` #### Variation `Variation` contains information about the visitor's assigned variation, or the default variation when no specific assignment exists. | Name | Type | Description | | -------------- | --------------- | ----------------------------------------------------------------------------------------------------- | | name | `Arc` | The name of the variation. | | key | `Arc` | The unique key identifying the variation. | | id | `Option` | The ID of the assigned variation, or `None` for a default variation. | | experiment\_id | `Option` | The ID of the experiment associated with the variation, or `None` for a default variation. | | variables | `Vec` | Variables associated with the variation. This collection can be empty when no variables are attached. | * `Variation` describes the assigned or default variation, while [`Variable`](#variable) contains the details of each individual variable. * `id` and `experiment_id` can be `None`, which indicates a default variation that's not tied to a specific experiment assignment. Additional helper methods: | Method | Return type | Description | | ------------------- | ------------------- | ---------------------------------------- | | `is_active()` | `bool` | Returns `false` for the `off` variation. | | `get_variable(key)` | `Option<&Variable>` | Returns a variation variable by key. | ```rust theme={null} // Retrieving the variation name let variation_name: &str = variation.name.as_ref(); // Retrieving the variation key let variation_key: &str = variation.key.as_ref(); // Retrieving the variation id let variation_id = variation.id; // Retrieving the experiment id let experiment_id = variation.experiment_id; // Retrieving the variables `Vec` let variables = &variation.variables; // Checking if the variation is active (i.e., currently being served to visitors) let is_active = variation.is_active(); // Retrieving a variable by its key, returning `None` if not found let variable = variation.get_variable("title")?; ``` #### Variable `Variable` contains information about a variable associated with the assigned variation. | Name | Type | Description | | ----- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | key | `Arc` | The unique key identifying the variable. | | kind | `Arc` | The variable type. Possible values include `BOOLEAN`, `NUMBER`, `STRING`, `JSON`, `JS`, and `CSS`. | | value | [`JsonValue`](#jsonvalue) | The value of the variable. Depending on `kind`, it can hold a boolean, number, string, JSON string, JavaScript snippet, or CSS snippet. | ```rust theme={null} use kameleoon_client::types::JsonValue; // Retrieve the list of variables associated with the variation let variables = &variation.variables; // Access the variable key let variable_key: &str = variable.key.as_ref(); // Access the variable type (kind) for conditional handling let kind: &str = variable.kind.as_ref(); // Extract the value as a number (returns `None` if not a number) let number: Option = variable.value.as_number(); // Extract the value as a boolean (returns `None` if not a boolean) let apply_discount: Option = variable.value.as_bool(); // Extract the value as a string slice (returns `None` if not a string) let title: Option<&str> = variable.value.as_str(); ``` ##### JsonValue `JsonValue` represents the value of a variation variable in Rust. | Value | Description | | ----------------------------- | ------------------------------------- | | `JsonValue::Boolean(bool)` | Represents a boolean value. | | `JsonValue::Number(f64)` | Represents a numeric value. | | `JsonValue::String(Arc)` | Represents a string value. | | `JsonValue::Json(Arc)` | Represents a JSON-encoded value. | | `JsonValue::Js(Arc)` | Represents a JavaScript code snippet. | | `JsonValue::Css(Arc)` | Represents a CSS code snippet. | ### Deprecated methods These methods are deprecated and will be removed in SDK version `1.0.0`. #### get\_feature\_keys() If you want to iterate over all feature flags and call [`get_variation()`](#get_variation) on each, use the [`get_variations()`](#get_variations) method instead. Returns a list of feature flag keys currently available for the SDK. ```rust theme={null} let feature_keys = client.get_feature_keys()?; ``` ##### Return value | Type | Description | | ------------------------------------- | --------------------------------------------------------- | | `Result, KameleoonError>` | List of feature flag keys on success, otherwise an error. | ##### Errors | Type | Description | | --------------------------- | --------------------------------------------------- | | `ErrorCode::Initialization` | Indicates that the SDK isn't yet fully initialized. | #### on\_datafile\_update() The `on_datafile_update()` method allows you to handle datafile update events. It accepts a single parameter, **handler**, which is called whenever the configuration is updated through either [polling](https://docs.kameleoon.com/developer-docs/feature-experimentation/technical-reference/technical-considerations#polling-default) or [streaming](https://docs.kameleoon.com/developer-docs/feature-experimentation/technical-reference/technical-considerations#streaming-premium-option) datafile update events. ```rust theme={null} // Register a callback that is invoked whenever the configuration // is updated through a polling or streaming datafile update event. client.on_datafile_update(Some(Box::new(|| { // Custom logic to execute after the datafile has been updated. println!("Kameleoon datafile updated"); }))); // Unregister the datafile update callback. // No callback will be invoked for future datafile updates. client.on_datafile_update(None); ``` ##### Parameters | Name | Type | Description | | --------- | ------------------------------------- | ------------------------------------------------------------------ | | `handler` | `Option>` | The handler that will be called when the configuration is updated. | # Writing efficient custom code in Web Experimentation Source: https://docs.kameleoon.com/developer-docs/web-experimentation/best-practices/best-practices-for-ab-testing Write efficient, performant custom JavaScript for Kameleoon Web Experimentation using the Activation API, scoped execution, and CSS-first visual changes. In the [Kameleoon App](https://app.kameleoon.com), there are multiple entry points where you can add custom JavaScript that executes on your website. This article provides specific recommendations for each available entry point. Please note that all Kameleoon scripts run once per page load. Before implementing custom scripts, consider using [Kameleoon Command Queue](../../apis/activation-api-js/api-reference/command-queue) for optimized execution. ## Kameleoon Command Queue The most efficient way to interact with Kameleoon’s API—whether for **converting goals**, **setting custom data**, or **triggering events**—is to leverage existing triggers in your Tag Manager (e.g., Google Tag Manager) alongside the Kameleoon Command Queue. This should be your primary approach when adding JavaScript to Kameleoon. Instead of writing lengthy JavaScript code directly in Kameleoon, you can achieve the same results with just two lines of code within your Tag Manager. ### Examples **- Convert a revenue goal** In your Tag Manager, create a trigger on the confirmation page to capture the revenue, and add the following code: ```javascript theme={null} // Replace kameleoonGoalID and {{revenue}} with your actual values window.kameleoonQueue = window.kameleoonQueue || []; window.kameleoonQueue.push(['Goals.processConversion', kameleoonGoalID, {{revenue}}]) ``` For guidance on creating a goal conversion from GTM, see [this documentation](/user-manual/assets/goals/create-a-goal-conversion-tag-from-gtm). **-Set a Custom Data value when the visitor logs in** Instead of writing custom JS in Kameleoon, set the value directly via your Tag Manager: ```javascript theme={null} // Replace "customDataName" and "customDataValue" with your actual values window.kameleoonQueue = window.kameleoonQueue || []; window.kameleoonQueue.push(['Data.setCustomData', "customDataName", "customDataValue"]); ``` **- Trigger an event when a product is added to the cart** Use this to target visitors based on specific actions: ```javascript theme={null} // Replace "customEventName" with your actual event name window.kameleoonQueue = window.kameleoonQueue || []; window.kameleoonQueue.push(['Events.trigger', "customEventName"]); ``` If `kameleoonQueue` cannot be utilized, please refer to the specific recommendations for each available script detailed below. ## Global Script & variation scripts ### Optimize JavaScript execution with the Activation API Utilize Kameleoon’s [Activation API](../../apis/activation-api-js/api-reference/api-reference) to apply variations at the appropriate time. This approach helps prevent performance issues and ensures a seamless experience for visitors. Below are some essential functions of the Activation API that can be used to enhance visitor experience: * [`runWhenConditionTrue`](../../apis/activation-api-js/api-reference/api-reference#runwhenconditiontrue) : Executes the code when a specific condition is met. By default, it runs every 200 milliseconds. However, be careful with conditions that may never resolve, as this can lead to unnecessary looping. Note that the loop will continue running until the function explicitly returns `true`. Returning `false` or `undefined` will not stop the loop or trigger the callback. ❌ Incorrect usage (Infinite loop risk): The following example will never return `true` on all pages other than the product page, causing the loop to run indefinitely: ```javascript theme={null} Kameleoon.API.Core.runWhenConditionTrue(() => { return window.dataLayer && window.dataLayer.find(layer => layer.pageType === "product"); }, () => { // Product page logic here }); ``` ✅ Recommended approach: Always return true to exit the loop, and then handle the logic inside the callback: ```javascript theme={null} let pageTypeLayer; Kameleoon.API.Core.runWhenConditionTrue(() => { pageTypeLayer = window.dataLayer && window.dataLayer.find(layer => layer.pageType); return pageTypeLayer; }, () => { if (pageTypeLayer?.pageType === "product") { // Product page logic here } }); ``` It is preferable to retrieve information using the URL or cookies/localStorage instead of depending on `runWhenConditionTrue` to wait for the `dataLayer` or other late-loading objects, like when trying to get information about the page type. This approach ensures faster execution and better performance. * [`runWhenElementPresent`](../../apis/activation-api-js/api-reference/api-reference#runwhenelementpresent): This method ensures that your code runs only when a specific element is present in the DOM, avoiding unnecessary delays caused by dynamically loaded elements. #### Key considerations * Uses Mutation Observers by default (recommended for performance). * If Mutation Observers are disabled on your site, you can specify a polling interval as the 3rd argument of this method. * If the element is dynamically inserted into the DOM, set `isDynamicElement` to `true` as the 4th argument of this method: Use the code below sparingly and only when absolutely necessary. Limit its usage to the minimum number of cases and avoid applying it to multiple elements simultaneously to maintain optimal page performance. Be mindful of the potential for an **infinite loop** if the source code of the page modifies the element each time it is updated. ```javascript theme={null} Kameleoon.API.Core.runWhenElementPresent('#myDynamicElement', ([elem]) => { // Logic once the element loaded }, null, true); // isDynamicElement is set to true ``` ### Set your code's scope to specific pages To optimize performance, make sure your code executes only on the relevant pages. This is particularly important when using loop-based methods like `runWhenConditionTrue` and `runWhenElementPresent`, as running these methods across the entire site can lead to unnecessary executions and impact performance. For instance, if you are waiting for revenue information in the dataLayer, wrap your code in a condition that checks whether the URL corresponds to the confirmation page. This approach ensures that the code runs only on the confirmation page instead of being triggered site-wide. **Example:** Targeting a specific confirmation page before running a loop ```javascript theme={null} if (document.location.href.includes('/confirmation')) { let revenueLayer; Kameleoon.API.Core.runWhenConditionTrue(() => { revenueLayer = window.dataLayer && window.dataLayer.find(layer => layer.revenue); return revenueLayer; }, () => { Kameleoon.API.Goals.processConversion(goalId, revenueLayer.revenue); }); } ``` Similarly, if you're waiting for an element to be loaded on the page, make sure the element is expected to be displayed at some point and only execute the script on the relevant page. ```javascript theme={null} if (document.location.href.includes('/subscription')) { Kameleoon.API.Core.runWhenElementPresent("#form", () => { // Add the rest of your logic here }); } ``` ### 3. Prefer CSS for visual changes instead of using JavaScript, especially when dealing with variations in scripts Using CSS instead of JavaScript results in faster and smoother rendering. CSS can be utilized for hiding or modifying elements, swapping blocks, and changing styles or text. ### Handling Single Page Applications (SPAs) If an experiment is running on a single-page application (SPA) or a dynamically updating page, specific implementation steps are required. Unlike traditional websites, SPAs do not reload entire pages, so Kameleoon needs to identify when content updates happen. Please refer to the [guide](/user-manual/experimentation/web-experimentation/advanced-experiment-types/set-up-experiment-single-page-app) on setting up an experiment on a single-page application for best practices. ## Segments / Triggers The first best practice is to base targeting on data that is immediately available when the page loads. This includes information such as the page URL, browser type, device type, and any data stored in cookies or local storage. If your targeting requires data that is not instantly accessible, follow these guidelines to ensure efficient execution and avoid unnecessary delays. ### 1. Custom Javascript condition This condition allows you to use custom JavaScript to target visitors. There are two main options available: \####- a. Checking the condition immediately This script executes every 75 milliseconds before the DOM is fully loaded, and then every 250 milliseconds afterward. By default, it returns `undefined`, which causes the script to continue running in a loop until it explicitly returns `true` (to include the visitor) or `false` (to exclude them). Always ensure that your condition eventually resolves to either `true` or `false` to prevent unnecessary looping. Here’s an optimized example: ```javascript theme={null} if (window.dataLayer && window.dataLayer.some(layer => layer.pageType == "homepage")) return true; else if (window.dataLayer && window.dataLayer.some(layer => layer.pageType != "homepage")) return false; ``` \####- b. Running the condition asynchronously Use this script when you need to wait for a response from an external web service or API before targeting the visitor. Once you receive the response, call setTargeting(true) or setTargeting(false). See the code below. * If you want to target a specific element, use the native condition “Element on the page” instead. * If you want to wait for information on the page, use the “check condition immediately” option above. ```javascript theme={null} const userId = localStorage.getItem("user_id"); if (!userId) { setTargeting(false); } // Example API endpoint - replace with your actual endpoint const apiUrl = `https://api.example.com/check-segment?userId=${userId}`; fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json" } }) .then(response => response.json()) .then(data => { if (data && data.isInSegment) { setTargeting(true); } else { setTargeting(false); } }) .catch(error => { setTargeting(false); }); ``` ### 2. Custom event Using custom events is an effective and passive way to target visitors. Rather than continuously monitoring conditions, this method listens for a specific custom event that you define and triggers targeting when that event occurs. This approach is particularly useful in two key scenarios: #### Scenario 1: When the targeting logic is complex It’s better to avoid embedding all the logic directly in a JavaScript condition within the targeting. Instead, you can define a custom event in the Global Script. For example, if you want to target visitors on the cart page who have at least three products in their cart and a total amount above \$30, you would trigger an event in the Global Script once these conditions are met, rather than writing all of the logic within the JavaScript segment condition. #### Scenario 2: When multiple segments share similar logic If several segments require the same or slightly different variations of the same condition, using a custom event prevents redundant code and enhances maintainability. Example: You need to target visitors based on different cart amounts. Instead of duplicating the logic in multiple segments, you define a single script in the Global Script and trigger different custom events for each segment: ```javascript theme={null} if (document.location.href.includes('/cart')) { Kameleoon.API.Core.runWhenElementPresent("#cartContainer", ([cartContainer]) => { if (cartContainer.length >= 3) { const totalAmount = Number(cartContainer.querySelector('#totalAmount').innerText); if (totalAmount >= 90) Kameleoon.API.Events.trigger("3ArticlesAbove90"); // Segment 1 else if (totalAmount >= 60) Kameleoon.API.Events.trigger("3ArticlesAbove60"); // Segment 2 else if (totalAmount >= 30) Kameleoon.API.Events.trigger("3ArticlesAbove30"); // Segment 3 } }); } ``` ### Custom Data Custom data, like custom events, helps to avoid duplicate code by allowing you to store and reuse values. You can define custom data in the Global Script or in other scripts, and then utilize it within segments. However, custom data differs from custom events in two key ways: #### a. By the scope options: "Page", "Visit", or "Visitor" Unlike **custom events**, which only apply to the current page, custom data can persist across multiple pages and sessions: * **Visit Scope**: This keeps the value throughout a visitor's session. * **Visitor Scope**: This stores the value for up to 365 days, depending on the cookie policy. For example, if you want to display a banner on all pages once a visitor logs in, you can set a custom data value to "true" with a visit scope. This ensures that the visitor remains targeted with the banner across all pages during their session after logging in. #### b. By enhanced experiment insights Custom data can serve as filters and breakdown criteria on the results page of experiments. This enables deeper insights and allows for more effective analysis of visitor behavior. To discover all available targeting conditions in the segment builder, please refer to this [documentation](/user-manual/assets/segments/create-a-segment). # Code debt cleanup Source: https://docs.kameleoon.com/developer-docs/web-experimentation/best-practices/code-debt-cleanup Reduce technical debt in your Kameleoon setup by archiving old experiments, removing unused segments and goals, and optimizing your global script. Over time, as experiments, segments, and goals accumulate, your testing environment can become cluttered, leading to inefficiencies and potential errors. Regularly reviewing and cleaning up outdated elements is essential to maintaining a streamlined, high-performing Kameleoon setup. This article outlines the key steps to identifying and removing obsolete experiments, segments, and code to reduce technical debt, improve accuracy, and ensure a smoother workflow for your optimization efforts. Debt cleanup is a time investment that pays off. Taking the time to clean up technical debt—whether it's outdated experiments, unused segments, or unnecessary code—greatly improves performance in the long run. Before implementing the actions listed below, **save a performance benchmark** of your website so you can measure the improvements afterward. * 🧪 Use [PageSpeed Insights](https://pagespeed.web.dev/) (or any other performance analysis tool you’re comfortable with) to analyze your site (desktop and mobile). Once the report is generated, click **Copy Link** (top-right)—this will save the report with data from the exact day of analysis (See screenshot below). ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/pagespeed-insights.jpg) * 🧩 Use the [Kameleoon Chrome Extension](/user-manual/experimentation/web-experimentation/graphic-based-experiments/getting-started-with-the-graphic-editor#kameleoon-graphic-editor-extension) to check performance and the size of experiments segments, goals, custom data and custom scripts. See screenshot below. ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/kameleoon-code-extension.jpg) Once you’ve completed the cleanup actions below, run both analyses above again and **compare the results** to see the impact of your optimizations. ## 1. Web experiments/personalizations On the **Experiments** page: ### Delete outdated draft experiments Use the menu on the right to filter for draft experiments, then delete all experiments older than three months (use the **key date** filter), along with recent ones you no longer need. To do this: 1. Right-click an experiment's **three-dots menu** and click **"Select"**. 2. Click **"Select all experiments"** above. 3. Choose **"Delete"** from the right menu. Alternatively, you can **Archive** them. ### Stop paused experiments Filter for paused experiments, then stop all experiments older than three months and any recent experiments you’re certain won’t be relaunched. Since bulk selection isn’t available for stopping experiments, you must manually select them one by one and click **Stop** in the right menu. ### Divert/stop online experiments Filter for online experiments and divert 100% of traffic to the winning variation if the data is reliable. This diversion ensures all visitors benefit from the optimized version without being split across variations. You can divert traffic in the experiment traffic allocation settings. If the winning variation is the original, you can stop the experiment. ### Transfer and clean up diverted experiments Filter for diverted experiments, then request your IT team to integrate the winning variation’s code into the site’s source code. Once implemented, stop and archive the experiment, which has the benefits of: * Eliminating unnecessary experiment scripts and improving page load speed. * Ensuring all users experience the optimized version without experiment conditions, since tools like Kameleoon may be blocked by ad blockers, for example. * Reducing the risk of conflicts between old experiments and new optimizations. ### Archive stopped experiments Filter for stopped experiments, then archive all remaining stopped experiments. To do this: 1. Right-click an experiment's **three-dots menu**. 2. Click **Select** > **Select all experiments**. 3. Click **Archive** in the right menu. Alternatively, delete them. For **Personalizations**, follow the same steps above. For **Feature Flags**, follow this [guide](/user-manual/experimentation/feature-experimentation/create-and-manage-flags/manage-feature-flag-technical-debt). ## 2. Segments/triggers On the **Segments and triggers** pages: ### Delete outdated draft segments Use the right menu to filter for segments attached to “0 campaign”, then delete all segments older than three months (use the “key date” filter), along with recent ones you no longer need. To do this: 1. Right-click a segment's **three-dots menu**. 2. Click **Select** > **Select all experiments** > **Delete** When applying the filter, some segments may still appear linked to campaigns. These are archived campaigns, which you can identify by hovering over the **i** icon—archived experiment names will be grayed out (See screenshot below). ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/segments.jpg) ### Optimize segments based on JS Filter segments for those that are attached to "one or more campaign(s)." Then, follow the guidelines in [this documentation](./best-practices-for-ab-testing) to optimize all segments based on a "JS custom condition." To quickly identify these segments, visit your website and open the browser console. Then, run the following code: ```javascript theme={null} const obtainSegmentsBasedOnJS = info => Kameleoon.Internals.runtime.targetingSegments.filter( item => JSON.stringify(item.tree).match(info) ); // All segments based on a JS condition include the keyword "return" or "setTargeting" console.log(obtainSegmentsBasedOnJS('return')); console.log(obtainSegmentsBasedOnJS('setTargeting')); ``` The code above will return the list of segments using a JS custom condition (See screenshot below). ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/javascript.jpg) The JS condition will be moved to Triggers as of Q3 2025. ### Rename segments Kameleoon runs all segments that have been created or modified in your account within the past 90 days, as long as their targeting conditions do not include “page URL” or “Custom JavaScript” — even if they are not linked to any experiment. This process is used for [opportunity detection](/user-manual/ai-opportunity-detection/get-started). If you want to exclude certain segments from this process, you can rename them using one of the following prefixes: `[DEV]`, `[TEST]`, or `[QA]`. ## 3. Goals On the **Goals** page: ### Disable unconverted goals Use the menu on the right to filter for goals linked to “1 or more campaign(s)”. Then, disable any goal that hasn't recorded a conversion in the last 24 hours, if you think it's no longer relevant. To check the conversion count, hover over the **i** icon for each goal—a pop-in will show the number of conversions in the last 24 hours (see screenshot below). ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/goals.jpg) ### Delete unused goals Filter for goals attached to “0 campaign”, then delete any you no longer need. Even if not linked to a campaign, active goals still run online—so removing unused goals helps improve performance. To do this: 1. Right-click a goal's **three-dots menu**. 2. Click **Select** > **Select all experiments**. 3. Click **Delete** in the right menu. Alternatively, you can **Disable** them. ### Delete inactive goals Filter for “inactive” goals, then delete all those you no longer need. ## 4. Custom data All custom data runs online—so removing unused custom data helps improve performance. On the **Custom data** page: ### Delete unused custom data Delete all custom data you no longer need. ### Optimize custom data based on JavaScript If a custom data is defined using **custom JavaScript code**, ensure the code does not run in an infinite loop. You can follow [this guide](../../custom-data#custom-code) to avoid performance issues. To identify which custom data use JavaScript, visit your website, open the browser console, and run the following code: ```javascript theme={null} Object.values(Kameleoon.Internals.runtime.Gatherer.configuration.customData) .filter(custom_data => custom_data.method === "CUSTOM_CODE"); ``` The code will return the list of custom data using JavaScript (See screenshot below). ![](https://storage.googleapis.com/kameleoon-storage-documentation/developers/images/code-debt-cleanup/custom-data.jpg) ## 5. Global script Once you've completed all the steps above, navigate to: *Admin ⇒ Projects ⇒ Configuration ⇒ General ⇒ Global custom script*, then remove any code in the script that references the deleted segments, goals, or custom data. To optimize your Global Script further, follow the guidelines outlined [here](./best-practices-for-ab-testing). These tips will help ensure your code runs efficiently, remains maintainable, and performs well across all experiments. To make refactoring the Global Script easier and safer, you can split it into two versions using a cookie condition. This cookie condition lets you test the new script live in your browser while keeping the original script active for all other visitors. Just add a custom cookie, like `newKamGS=true`, in your browser, then use the following code to conditionally load the Global Script: ```javascript theme={null} if (document.cookie.includes('newKamGS=true')) { // new Global Script (for your own QA/testing) } else { // old Global Script (for all users) } ``` # Single Kameleoon project across domains vs. multiple projects Source: https://docs.kameleoon.com/developer-docs/web-experimentation/best-practices/single-kameleoon-project-across-domains-versus-multiple-projects Compare single shared and per-site Kameleoon project structures to choose the right setup for your multi-domain A/B testing strategy. When implementing an A/B testing and personalization strategy across multiple websites, it is important to decide whether to use a separate project (one siteCode) for each site or adopt a single shared project for all sites. This choice affects performance, scalability, test deployment, and overall management ease. This guide compares both approaches, highlighting their pros and cons to assist you in selecting the most suitable structure based on your specific needs. Click on [this link](/user-manual/faq#how-do-i-find-my-sitecode) for a quick guide on locating the siteCode. ## One project (siteCode) per site ### Pros *** * **Granular team management:** Assign specific permissions for each site or project. * **Independent configurations:** Each project can have its own unique goals, experiments, and integrations. Additionally, you can easily replicate tests, goals, and segments across different projects when necessary. * **Easier debugging and risk isolation:** An issue in one site does not impact others. * **Flexible testing:** Various testing strategies can be implemented for different countries or sites. ### Cons *** * **Inefficient:** There is a greater effort required for setup and maintenance. Duplicating configurations and tests across different projects involves manual work, although this process can be streamlined with the Automation API. * **Greater complexity:** Managing global test complexity can be more challenging when running tests across multiple sites, as it necessitates duplication and manual aggregation of results. * **Maintenace:** Maintaining consistency becomes more difficult, as ensuring identical configurations across sites poses challenges. Additionally, each project requires separate integrations with third-party tools, leading to increased integration effort. ## One project (siteCode) for multiple sites (shared siteCodes) ### Pros *** * **Simplified management**: Goals, segments, and third-party integrations are centralized for easier handling. * **Easier global testing**: A/B tests can be conducted smoothly across multiple regions. * **Faster deployment**: There's no need to duplicate tests, goals, or segments for each individual site. * **Unified data analysis**: Comparing performance across different countries and sites is made easier with a single dashboard. ### Cons *** * **Increased performance impact:** A single script is loaded across all sites, even if some features are not needed. * **More complex targeting:** Additional criteria, such as URL patterns, are necessary to ensure that experiments only affect relevant users. * **Higher risk exposure:** Problems in the shared script (for instance, the **global custom script**) can impact all sites. * **Limited flexibility:** If the sites are not identical, workarounds may be required to address the differences. ## Recommendation Use separate projects when sites are significantly different or when independent control over team permissions, integrations, and experiments is needed. Use a shared project if the sites are largely identical, allowing for unified data and simpler test deployment. # Website development - Best practices for A/B testing Source: https://docs.kameleoon.com/developer-docs/web-experimentation/best-practices/website-development Structure and develop your website with clean HTML, modular code, and SPA-aware patterns to make A/B testing with Kameleoon reliable and maintainable. To develop a website that is easy to use for A/B testing campaigns, it is crucial to consider several technical and structural elements. Here are the key aspects. ## 1. Clean and semantic HTML structure * **Proper use of HTML tags**: Use semantic tags (`
`, `