
The in-app template builder allows you to:
- create in-app message templates from scratch and edit them by using HTML, CSS, and JavaScript  
    
  <div class="admonition admonition-important"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg></div><div class="admonition-body"><div class="admonition-content">

  The `SRInApp.close()` method must be included in every in-app message.

  </div></div></div>

- use the ready-made templates from the **Predefined templates** folder.

The **Predefined templates** folder provides you with templates for the most common campaign scenarios such as sending an abandoned cart, displaying a carousel with item recommendations, and displaying a simple banner with a button. 

Modification of the ready-made templates doesn't require applying changes to the template code. The template builder contains a user-friendly configuration form that brings editing down to filling out fields that define the properties of the template. This makes editing templates possible by any user regardless of the programming skills. You can [simplify the editing of your own templates as well](#template-editing-simplification) by creating custom configuration form adjusted to your needs. Thanks to this you can edit your template or create its variations dedicated for different scenarios.

You can personalize the content of the message by [using snippets](#adding-a-snippet-to-the-template-code) and [Jinjava inserts](/developers/inserts) to inject data such as profile attributes (for example, name), recommendations, analysis results.

[Snippets](#adding-a-snippet-to-the-template-code) also let you re-use the same content in multiple templates, or create a reference to a fragment that you only need to update in one place to see the change in all templates where it's used.

## Character limits

The template to be used in an in-app message campaign cannot exceed 60,000 characters.

## Good practices

### Campaign planning recommendations

Using a large number of in-app messages in your application can impact rendering time, message delivery, and battery usage.

To maintain optimal application performance when using in-app campaigns:
- Avoid assigning more than 10 in-app messages to the same trigger event.
- Avoid having more than 20 in-app messages active at the same time in your application.
- Review and archive in-app campaigns that you no longer need.  

### Template construction

When creating or editing in-app message content:

- Place the the `SRInApp.close()` (or `SRInApp.hide()`) method at the beginning of the JS script.
- Use try/catch to handle possible fatal errors in the JS script.
- Handle situations where Jinjava inserts return empty data.
- When adding external links to your message:
    - Only link to sites you trust.
    - Don't link to large images that may negatively affect performance.
    - Don't link to resources whose CSS/HTML may be blocked. If you have resources loaded from your own URLs, set `Synerise.settings.inAppMessaging.contentBaseUrl` and use relative paths in HTML/CSS.

## Editing a ready-made template
---

1. Go to <img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/icons/experience-hub-icon.svg" alt="Experience Hub icon" class="icon"> **Experience Hub > In-app messages**. 
2. On the left pane, select **Templates**.  
3. From the list of template folders, select **Predefined templates**. 
4. Select one of the templates to edit. 
    - [Abandoned cart](/use-cases/abandoned-basket-inapp)
    - Bottom bar
    - [Carousel with context recommendations](/use-cases/in-app-cross-sell)
    - [Carousel with recommendation campaign](/use-cases/in-app-recommendations)
    - [Fullscreen](/use-cases/inapp-mandatory-upgrade)
    - Modal  
    - [Price alert](/use-cases/in-app-price-drop-last-seen-products) 
    - [Swiping mechanism with recommendation campaign](/use-cases/in-app-swiping-mechanism)
    - Top bar
    - [Walkthrough](/use-cases/inapp-walkthrough)  
  **Result**: You are redirected to the code editor.
4. You can edit the template in two ways:
    - Edit the code of the template ([add inserts](#adding-a-snippet-to-the-template-code), [add variables](#adding-a-variable)).  
        The code of the template is embedded in `<body` when the message is displayed, so it can't include the `<html>` or `<head>` elements.
    - Go to the **Config** tab and fill out the form.
5. After you make changes to the template, you can check the [preview](#previewing-templates).  
6. If the template is ready, in the upper right corner click **Save this template > Save as**. 
7. On the pop-up:  
    1. In the **Template name** field, enter the name of the template.  
    2. From the **Template folder** dropdown list, select the folder where the template will be saved.  
    3. Confirm by clicking **Apply**.  


## Creating a template
---

1. Go to <img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/icons/experience-hub-icon.svg" alt="Experience Hub icon" class="icon"> **Experience Hub > In-app messages**. 
2. On the left pane, select **Templates**.  
3. In the upper right corner, click **New Template**.  
4. In the pop-up that opens, select **Code editor**.
5. Use the **HTML**, **CSS**, and **JavaScript** tabs to define the properties of the template.  
    The code of the template is embedded in `<body>` when the message is displayed, so it can't include the `<html>` or `<head>` elements.
6. If the template is ready, in the upper right corner click **Save this template > Save as**. 
7. On the pop-up:  
    1. In the **Template name** field, enter the name of the template.  
    2. From the **Template folder** dropdown list, select the folder where the template will be saved.  
    3. Confirm by clicking **Apply**.  


### Selecting in-app message type

To select the way of displaying your in-app message within a mobile application:

1. Above the builder view, in **In-app type**, select **Inline** or **Overlay**.  
    This option is not available if you opened the template editor directly from the in-app campaign creator.
2. If you selected **Overlay**:
   1.  select an option from the **Display type** dropdown list:  
        - **Fullscreen** - In-app messages are displayed in the center of the mobile application, with no clickable elements within the application.
        - **Top bar** - In-app messages are shown at the top bar of the application, and user interaction is limited to the area below the message.
        - **Bottom bar** - In-app messages appear at the bottom bar of the application, with user interaction restricted to the area above the message.
   2. If you want to manage the display of in-app messages over safe area in your mobile application, use the **Cover safe area** option.  
    A safe area is the portion of a view that is not covered by elements such as a navigation bar, tab bar, or toolbar. Safe areas are important for ensuring visibility and access to a device's interactive features.  
    
      <div class="admonition admonition-important"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg></div><div class="admonition-body"><div class="admonition-content">

      Available from the following application versions:  
         - iOS: `5.1.0` and higher
         - Android: `6.1.0` and higher

      </div></div></div>


       - By enabling this option, the in-app message will extend into bars, notches and other UI elements.
       - By disabling this option, the in-app message won't cover system UI elements such as top bars, notches, and so on. This is the default setting.


<figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/in-app-code-builder-display-type.png" class="full" alt="View of the Display type option in the template builder"><figcaption>View of the Display type option in the template builder</figcaption></figure>

### Adding a snippet to the template code

[Snippets](/docs/assets/snippets) let you:
- insert data such as profile attribute, recommendations, or analysis results into the communication.
- create re-usable pieces of static content, so you don't need to manually copy and paste between templates.
- create dynamic pieces of content that are updated in all templates when you update the snippet definition.

1. Click **Snippets**.  
    **Result**: The snippet widget opens.  
2. Add a snippet as described in [Snippets](/docs/assets/snippets).


## JavaScript methods in in-app messages

JavaScript methods can be used to let the SDK Listeners and Delegates [handle actions from in-app messaging campaigns](/developers/mobile-sdk/campaigns/action-handling#handling-actions-from-campaigns). The methods only work when the Listeners/Delegates are implemented and running.

Every message must include a method to close or hide the message.

### Use a Mobile SDK method

You can use the `SRInApp.internalMethod` method to call Synerise Mobile SDK methods from the JS code in an in-app. This simplifies using those methods, without creating JS code to authorize and make API requests.

**Syntax**:  
```javascript
SRInApp.internalMethod(String methodName, String arguments, onSuccess(String response), onError(Any error))
```
where:
- `methodName` is the mobile SDK method. See table below for available methods.
- `arguments` is a stringified JSON object with the method arguments, described in the method reference for each mobile SDK method. Some methods don't require any arguments.
- `onSuccess` is the function to call when the method succeeds.  
    In its arguments, `response` is a stringified response from the mobile SDK method. The structure depends on the mobile SDK method.
- `onError` is the function to call when the method fails.

| `methodName`                           | Corresponding SDK method                                                                                                                                                                                                                                                           |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Promotions/getPromotionsWithApiQuery` | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#get-all-promotions-of-a-customer">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#get-promotions-with-query-parameters">iOS</a></li></ul>           |
| `Promotions/getPromotionByUuid`                  | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#get-promotion-by-uuid">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#get-promotion-by-uuid">iOS</a></li></ul>                                     |
| `Promotions/getPromotionByCode`                  | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#get-promotion-by-code">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#get-promotion-by-code">iOS</a></li></ul>                                     |
| `Promotions/activatePromotionByUuid`             | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#activate-promotion-by-uuid">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#activate-promotion-by-uuid">iOS</a></li></ul>                           |
| `Promotions/activatePromotionByCode`             | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#activate-promotion-by-code">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#activate-promotion-by-code">iOS</a></li></ul>                           |
| `Promotions/deactivatePromotionByUuid`           | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#deactivate-promotion-by-uuid">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#deactivate-promotion-by-uuid">iOS</a></li></ul>                       |
| `Promotions/deactivatePromotionByCode`           | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#deactivate-promotion-by-code">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#deactivate-promotion-by-code">iOS</a></li></ul>                       |
| `Promotions/activatePromotionsBatch`           | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#activate-promotions-in-a-batch">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#activate-promotions-in-a-batch">iOS</a></li></ul>                   |
| `Promotions/deactivatePromotionsBatch`         | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#deactivate-promotions-in-a-batch">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#deactivate-promotions-in-a-batch">iOS</a></li></ul>               |
| `Vouchers/getOrAssignVoucher`            | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#get-or-assign-voucher-from-pool">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#get-or-assign-voucher-from-pool">iOS</a></li></ul>                 |
| `Vouchers/assignVoucherCode`                    | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#assign-voucher-code-from-pool">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#assign-voucher-code-from-pool">iOS</a></li></ul>                     |
| `Vouchers/getAssignedVoucherCodes`             | <ul><li><a href="/developers/mobile-sdk/method-reference/android/promotions/#get-voucher-codes-assigned-to-customer">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/promotions/#get-voucher-codes-assigned-to-customer">iOS</a></li></ul>   |
| `Content/getRecommendationsV2`                    | <ul><li><a href="/developers/mobile-sdk/method-reference/android/content/#get-recommendations-v2">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/content/#get-recommendations-v2">iOS</a></li></ul>                                         |
| `Content/generateDocument`                      | <ul><li><a href="/developers/mobile-sdk/method-reference/android/content/#generate-document-with-query-parameters">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/content/#generate-document-with-query-parameters">iOS</a></li></ul>       |
| `Content/generateScreenView`                    | <ul><li><a href="/developers/mobile-sdk/method-reference/android/content/#generate-screen-view-with-query-parameters">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/content/#generate-screen-view-with-query-parameters">iOS</a></li></ul> |
| `Content/generateBrickworks` | <ul><li><a href="/developers/mobile-sdk/method-reference/android/content/#generate-brickworks">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/content/#generate-brickworks">iOS</a></li></ul> |
| `Client/getUuid`                               | <ul><li><a href="/developers/mobile-sdk/method-reference/android/client-session/#get-current-customer-uuid">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/client-session/#get-current-customer-uuid">iOS</a></li></ul>                     |
| `Client/destroySession` | <ul><li><a href="/developers/mobile-sdk/method-reference/android/client-session/#destroy-current-session">Android</a></li><li><a href="/developers/mobile-sdk/method-reference/ios/client-session/#destroy-current-session">iOS</a></li></ul> |

**Example**:  
This is how you can activate a promotion when a button is clicked:

<pre><code class="language-js">function internalMethodOnSuccess () {
  console.log("ACTIVATED");
};

function internalMethodOnError () {
  console.log("FAILED TO ACTIVATE");
};

(function () {
    const params = {
        uuid: '7cdc22d5-cf8c-4102-a853-a9f00d0256b0'
    };
    var button = document.querySelector(".in-app-button");
    button.addEventListener("click", function () {
        SRInApp.internalMethod("Promotions/activatePromotionByUuid", JSON.stringify(params), internalMethodOnSuccess, internalMethodOnError)
    });
})();</code></pre>



### Close a message
This method sends an [inApp.discard](/docs/assets/events/event-reference/inapp#inappdiscard) event when closing the in-app.
```javascript
SRInApp.close()
```
The method has no parameters.


<div class="admonition admonition-tip"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /></svg></div><div class="admonition-body"><div class="admonition-content">

The mobile SDK allows closing in-app messages from outside the message. See [Mobile SDK documentation](/developers/mobile-sdk/campaigns/in-app-message/overlay-in-app#closing-a-message).

</div></div></div>


### Close message and send an event

This method sends an additional event ([inApp.discard](/docs/assets/events/event-reference/inapp#inappdiscard) is sent automatically) when closing the in-app.

```javascript
SRInApp.closeAndTrigger(action,params,label)
```
- `action` is the event's action name (string) in the `context.action` format, for example `page.visit`. The maximum length is 32 characters.
- `params` is an object with free-form JSON parameters to send with the event.
- `label` is a string with a human-readable summary of the event. It is obligatory, but not saved in persistent storage and can't be used in Decision or Automation Hubs and is not displayed in **Behavioral Data Hub > Profiles**.

### Hide a message
**Only for overlay in-apps**  

This method sends an [inApp.hide](/docs/assets/events/event-reference/inapp#inapphide) event when hiding the in-app.

```javascript
SRInApp.hide()
```
The method has no parameters.

### Open URL
This method sends an [inApp.click](/docs/assets/events/event-reference/inapp#inappclick) event when a customer opens an URL by clicking a link in the message.
```javascript
SRInApp.openUrl(url)
```
`url` is the address to open (string).

### Open deeplink
This method sends an [inApp.click](/docs/assets/events/event-reference/inapp#inappclick) event when a customer clicks a link (deeplink).
```javascript
SRInApp.openDeeplink(deeplink)
```
`deeplink` is the deeplink to open (string).

### Resize a message

**Only for overlay in-apps**  

This method resizes the message.
```javascript
SRInApp.resize(resizeTarget, function)
```
where:
- `resizeTarget` informs the mobile SDK about the resize type:
    - `FULLSCREEN`
    - `TOP_BAR`
    - `BOTTOM_BAR`
- `function` is the name of the JS function that performs the resize on the device.

When the method is triggered:
1. The `resizeTarget` is sent to the mobile SDK.
2. The mobile SDK returns information about the width and height of the device's screen.
3. The `function` is triggered with parameters received from the mobile SDK and the message size changes.

**Example**:

<div class="content-tabs" data-tab-group="tabgrp-2780">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-2780-0" data-tab-group="tabgrp-2780" data-tab-active="true">HTML content</button><button class="tab-button" data-tab-id="tabgrp-2780-1" data-tab-group="tabgrp-2780">JavaScript</button></div>

<div class="tab-panel" data-tab-id="tabgrp-2780-0" data-tab-group="tabgrp-2780" data-tab-active="true">

<pre><code class="language-html">&lt;div id="webview-simulation" class="bottom-bar"&gt;
  &lt;div id="message-content"&gt;
    &lt;button onclick="SRInApp.resize('FULLSCREEN', resizeCallbackFull)"&gt;to full&lt;/button&gt;
    &lt;button onclick="SRInApp.resize('TOP_BAR', resizeCallbackTop)"&gt;to top&lt;/button&gt;
    &lt;button onclick="SRInApp.resize('BOTTOM_BAR', resizeCallbackBottom)"&gt;to bottom&lt;/button&gt;
    &lt;button onclick="SRInApp.close()"&gt;CLOSE&lt;/button&gt;
    &lt;p&gt; Example message &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>

</div>

<div class="tab-panel" data-tab-id="tabgrp-2780-1" data-tab-group="tabgrp-2780">

<pre><code class="language-js">function resizeCallbackFull (width, height) {
  const container = document.getElementById('webview-simulation');
  container.classList.remove('bottom-bar');
  container.classList.remove('top-bar');
  container.classList.add('full-screen');
  container.style.height = height + 'px';
  console.log("Changed to fullscreen");
};

function resizeCallbackTop (width, height) {
  const container = document.getElementById('webview-simulation');
  container.classList.remove('full-screen');
  container.classList.remove('bottom-bar');
  container.classList.add('top-bar');
  container.style.height = '250px';
  console.log("Changed to top bar");
};

function resizeCallbackBottom (width, height) {
  const container = document.getElementById('webview-simulation');
  container.classList.remove('full-screen');
  container.classList.remove('top-bar');
  container.classList.add('bottom-bar');
  container.style.height = '250px';
  console.log("Changed to bottom bar");
};</code></pre>

</div>
</div>


### Send a custom event
This method sends a custom event.
```javascript
SRInApp.trackCustomEvent(action, params, label)
```
- `action` is the event's action name (string) in the `context.action` format, for example `page.visit`. The maximum length is 32 characters.
- `params` is an object with free-form JSON parameters to send with the event.
- `label` is a string with a human-readable summary of the event. It is obligatory, but not saved in persistent storage and can't be used in Decision Hub and is not displayed in Behavioral Data Hub.
### In-app storage

You can store and retrieve JSON data (a key/value pair) on the device. The data is assigned to a profile and can only be accessed by that profile, so you can keep separate data sets between users of the same device.

Thanks to this storage, you can enrich your in-app scenarios, for example:
- If the user has already seen a story, hide it or display an alternative layout or version.
- Resume the story from the exact point where the user previously stopped watching.
- Show different content on the first view versus subsequent views (e.g. intro vs. summary).
- Track completed vs. partially viewed stories and adjust messaging accordingly.
- Personalize call-to-action buttons based on prior interactions (e.g. “Continue” vs. “Watch again”).

You can use these methods:
- [Save and get data](#save-and-get-data)
- [Delete data](#delete-data-one-value)
- [Delete all data](#delete-all-data)

#### Save and get data
You can save primitive data types and complex types, such as objects.

Each key/value pair is saved separately and expires after 90 days. You can reset the expiration counter by resending the same key.

- The `setItem` method is asynchronous and may include a callback.
- The `getItem` method is synchronous. If the requested value doesn't exist, it returns `undefined`


<div class="content-tabs" data-tab-group="tabgrp-2781">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-2781-0" data-tab-group="tabgrp-2781" data-tab-active="true">Primitive values</button><button class="tab-button" data-tab-id="tabgrp-2781-1" data-tab-group="tabgrp-2781">Complex values</button><button class="tab-button" data-tab-id="tabgrp-2781-2" data-tab-group="tabgrp-2781">Save + callback</button><button class="tab-button" data-tab-id="tabgrp-2781-3" data-tab-group="tabgrp-2781">Get + error handling</button></div>

<div class="tab-panel" data-tab-id="tabgrp-2781-0" data-tab-group="tabgrp-2781" data-tab-active="true">

<pre><code class="language-js">SRInApp.storage.setItem('myString', 'Hello World');
const exampleString = SRInApp.storage.getItem(myString);
console.log(exampleString); // "Hello World"

SRInApp.storage.setItem('myInt', 42);
const exampleInt = SRInApp.storage.getItem(myInt);
console.log(exampleInt); // 42

SRInApp.storage.setItem('myFloat', 3.14159);
const exampleFloat = SRInApp.storage.getItem(myFloat);
console.log(exampleFloat); // 3.14159

SRInApp.storage.setItem('myBool', true);
const exampleBool = SRInApp.storage.getItem(myBool);
console.log(exampleBool); // true

SRInApp.storage.setItem('myNull', null);
const exampleNull = SRInApp.storage.getItem(myNull);
console.log(exampleNull); // null</code></pre>

</div>

<div class="tab-panel" data-tab-id="tabgrp-2781-1" data-tab-group="tabgrp-2781">

<pre><code class="language-js">// Arrays
SRInApp.storage.setItem('myArray', [1, 2, 3, 4, 5]);
const exampleArray = SRInApp.storage.getItem(myArray);
console.log(exampleArray); // [1,2,3,4,5]

SRInApp.storage.setItem('mixedArray', ['a', 1, true, null]);
const exampleMixed = SRInApp.storage.getItem(mixedArray);
console.log(exampleMixed); // ["a",1,true,null]


// Objects
SRInApp.storage.setItem('user', { name: 'John', age: 30, active: true });
const exampleObject = SRInApp.storage.getItem(user);
console.log(exampleObject.name);   // "John"
console.log(exampleObject.age);    // 30

// Nested objects
SRInApp.storage.setItem('nested', {
    userData: {
        profile: {
            firstName: 'Jane',
            lastName: 'Doe'
        },
        settings: {
            darkMode: true,
            notifications: ['email', 'push']
        }
    },
    timestamp: 1234567890
});
const exampleNested = SRInApp.storage.getItem(nested);
console.log(exampleNested.userData.profile.firstName); // "Jane"</code></pre>

</div>

<div class="tab-panel" data-tab-id="tabgrp-2781-2" data-tab-group="tabgrp-2781">

In the `setItem` method, you can include a callback.

<pre><code class="language-js">SRInApp.storage.setItem('preferences', { theme: 'dark', lang: 'en' },
    function() {
        console.log('Preferences saved successfully!');
        // Continue with next action...
    },
    function(error) {
        console.log('Failed to save preferences:', error);
    }
);</code></pre>

</div>

<div class="tab-panel" data-tab-id="tabgrp-2781-3" data-tab-group="tabgrp-2781">

You can add logic to handle non-existing keys.

<pre><code class="language-js">const data = SRInApp.storage.getItem('nonExistentKey');
if (data === null) {
    console.log('No data found');</code></pre>

</div>
</div>


#### Delete data (one value)
You can delete a single key from the storage, with an optional callback:

<pre><code class="language-js">// Simple delete
SRInApp.storage.removeItem('userName');

// Delete with callback
SRInApp.storage.removeItem('user',
    function() {
        console.log('User data deleted!');
    },
    function(error) {
        console.log('Delete failed:', error);
    }
);</code></pre>


#### Delete all data
You can wipe all data from the storage assigned to the current profile, with an optional callback.

<pre><code class="language-js">// Simple delete all data of current user
SRInApp.storage.clear();

// Delete all storage for current user, with callback
SRInApp.storage.clear(
    function() {
        console.log('All data cleared!');
    },
    function(error) {
        console.log('Clear failed:', error);
    }
);</code></pre>


### Trigger a custom action
This method allows you to pass data to a custom action. You must create logic in your mobile application to perform the action, which is triggered when a Listener/Callback processes the JS method and returns the name and parameters of the custom action. The method generates an [inApp.customHook](/docs/assets/events/event-reference/inapp#inappcustomhook) event.
```javascript
SRInApp.handleCustomAction(name, params)
```
- `name` is used as the identifier that triggers a logic associated with it.
- `params` is an object with free-form JSON parameters to pass to the logic.

### Get device information
You can retrieve some details of the device as a JSON object.

```javascript
SRInApp.getDeviceData()
```

This returns the following object:


<pre><code class="language-json">{
    "deviceData": {
        "os": "string",
        "osVersion": "string",
        "appVersion: "string",
        "darkMode": boolean,
        "language": "string",
        "deviceWidth": number,
        "deviceHeight": number
    }
}</code></pre>


You can use data from this object in your JavaScript.

### Manage inline in-app host component

These methods let the in-app JavaScript query and control the size of the host component that contains the inline in-app. They only work for inline in-app messages — for overlay (modal) messages they are no-ops.

#### Get component size

**Only for inline in-apps**  

Returns the current size of the host component in pixels.

```javascript
SRInApp.getComponentSize()
```

This method returns an object with `width` and `height` properties (in pixels) of the component that contains the in-app.

#### Set component size

**Only for inline in-apps**  

Asks the host application to resize the component that contains the in-app. You need to implement listening to the request and changing the size (see examples in the [Mobile SDK guide](/developers/mobile-sdk/campaigns/in-app-message/inline-in-app)).

```javascript
SRInApp.setComponentSize(width, height)
```
- `width` is the requested width in pixels (number).
- `height` is the requested height in pixels (number).

#### Listen for component size changes

**Only for inline in-apps**  

Register a callback that fires whenever the host component's measured size changes (for example, due to layout changes or device rotation).


<div class="content-tabs" data-tab-group="tabgrp-2782">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-2782-0" data-tab-group="tabgrp-2782" data-tab-active="true">Android</button><button class="tab-button" data-tab-id="tabgrp-2782-1" data-tab-group="tabgrp-2782">iOS</button></div>

<div class="tab-panel" data-tab-id="tabgrp-2782-0" data-tab-group="tabgrp-2782" data-tab-active="true">

<pre><code class="language-js">SRInApp.onComponentSizeChanged = function (width, height) {
    console.log("Component resized:", width, height);
};</code></pre>

</div>

<div class="tab-panel" data-tab-id="tabgrp-2782-1" data-tab-group="tabgrp-2782">

<pre><code class="language-js">SRInApp.onComponentSizeChange = function (width, height) {
    console.log("Component resized:", width, height);
};</code></pre>

</div>
</div>


### Receive context from the application

These methods let the in-app message receive live data from the mobile application while the message is displayed. This is useful when the application state changes during the lifetime of an in-app and the message content must react to those changes.

**Example**: An in-app displays a progress bar showing how many items in the cart qualify for a promotion. When the customer adds an item to the cart, the application updates the context so the in-app can refresh the progress bar without being closed and re-opened.

#### How it works

1. On its side, the application sets context values by using  
    - `Injector.setInAppContext`
    - `Injector.inAppContext.put("key", "value")` (Android)
    - `Injector.inAppContext["KEY"] = "VALUE` or `Injector.inAppContext = ["KEY": "VALUE"]` (iOS)
2. To push the updated context to the in-app, the application calls `Injector.notifyInAppContextChange()`.
3. In the in-app JavaScript, [the `onContextFromApp` callback](#listen-for-context-changes) fires with the new context.

#### Get context on demand

You can request the current context from the application at any time:

```
SRInApp.getContextFromApp()
```

This method returns the context object that the application has set.

#### Listen for context changes

Register a callback that fires every time the application calls `Injector.notifyInAppContextChange()`:


<pre><code class="language-js">SRInApp.onContextFromApp = function (context) {
    console.log("Context updated:", context);
    // Update the in-app UI based on the new context
};</code></pre>


### Call a custom application method

This method lets the in-app JavaScript call a custom operation implemented on the application side. The call is asynchronous — the application performs the operation and returns a success or failure result to the in-app.

**Example**: During the lifetime of an in-app, you need to your own backend for additional data that is not available in Synerise. Instead of making a web request from the in-app (which lacks the security features of an app, such as SSL pinning), you delegate the request to the application.

**Syntax**:
```
SRInApp.customMethod(name, params, timeoutMs)
```
where:
- `name` is a string identifier of the custom method to call on the native side.
- `params` is a JSON object with parameters to pass to the native method.
- `timeoutMs` is the timeout in milliseconds. If the application does not respond within this time, the call fails.

The method returns a `Promise` that resolves with the result from the application or rejects on failure or timeout.

On its side, the application receives the call through a callback:

<div class="content-tabs" data-tab-group="tabgrp-2783">
<div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-2783-0" data-tab-group="tabgrp-2783" data-tab-active="true">iOS</button><button class="tab-button" data-tab-id="tabgrp-2783-1" data-tab-group="tabgrp-2783">Android</button></div>

<div class="tab-panel" data-tab-id="tabgrp-2783-0" data-tab-group="tabgrp-2783" data-tab-active="true">

```swift
func snr_inAppMessageHandledCustomMethod(data: InAppMessageData, name: String, parameters: [AnyHashable: Any], completion: InAppCustomMethodCompletion)
```

</div>

<div class="tab-panel" data-tab-id="tabgrp-2783-1" data-tab-group="tabgrp-2783">

```java
onCustomMethod(String name, HashMap<String, Object> params,
    InAppMessageData inAppMessageData,
    InAppCustomMethodCompletion completion)
```

</div>
</div>

The application performs its work and responds by calling `completion.success` or `completion.failure`, which resolves or rejects the `Promise` on the JavaScript side.

**Example**:


<pre><code class="language-js">(function () {
    var button = document.querySelector(".fetch-data-button");
    button.addEventListener("click", function () {
        SRInApp.customMethod("fetchUserData", { userId: "abc123" }, 5000)
            .then(function (result) {
                console.log("Data received:", result);
            })
            .catch(function (error) {
                console.log("Custom method failed:", error);
            });
    });
})();</code></pre>


## Template editing simplification

To make your template more accessible to users without programming skills, you can add a configuration form with variables dedicated for the template, so the user can make adjustments to the template.  

The effect of template editing simplification is that you can edit templates by filling out a user-friendly configuration form (available in the **Config** tab) whose fields define the value for each property of the template.

The process of template simplification involves replacing values with variables in the HTML, CSS, and JavaScript code elements, such as alignment, font, color in CSS, or HTML tags as title, description or buttons. You can also add a variable in the place of Jinjava elements, such as a recommendation campaign ID, voucher pool ID, or catalog name. Variables inserted in the code appear in a form the **Config** tab when editing the template.

#### List of variables


| Variable name          | Description                                                                                                                              | Example output |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------|----------------|
| **Synerise insert select** | Allows you to add a dropdown list with aggregates, expressions, metrics, voucher pools, recommendations, files, catalogs, or attributes. | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-synerise-object-example.png" class="full" alt="A field in the Config tab requiring a selection of a Synerise object"><figcaption>Example: selecting a Synerise object from the list</figcaption></figure> |
| **String**                 | Allows you to add a field that requires a string value.                                                                                  | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-string-example.png" class="full" alt="A field in the Config tab requiring a string value"><figcaption>Example: Filling out a field</figcaption></figure>  |
| **Select**                 | Allows you to add a dropdown list with configurable values.                                                                              | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-select-example.png" class="full" alt="A field in the Config tab requiring a selection from a dropdown"><figcaption>Example: selecting an option from a dropdown list </figcaption></figure>  |
| **Switch**                 | Allows you to add a field which is enabled/disabled by a toggle.                                                                         | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-switch-example.png" class="full" alt="A field in the Config tab requiring a selection of a Synerise object"><figcaption>Example: enabling an option </figcaption></figure>   |
| **Color**                  | Allows you to add a color selector. You can either select a color or enter its code manually.                                            | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-color-example.png" class="full" alt="A field in the Config tab requiring a selection of a color"><figcaption>Example: selecting a color </figcaption></figure>  |
| **Number**                 | Allows you to add a field that requires a number. You can either select a number from a dropdown or enter it manually.                   | <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/variable-number-example.png" class="full" alt="A field in the Config tab requiring a number value"><figcaption>Example: defining a number </figcaption></figure>  |

#### Results of simplifying template editing

Instead of modifying the design of the template directly in the code, a user can go to the **Config** tab and define the properties of the template by filling out configuration form.

The image below presents the easy-to-edit form that lets users without coding expertise change the variable values:

<figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/Mobile/_gfx/mobile-config-form.png" class="full" alt="Defining the settings of a string variable"><figcaption>Defining the settings of a string variable</figcaption></figure>

### Adding a variable

1. Select one of the code editor tabs.  
    The tabs may be **JSON**, **HTML**, **CSS**, and **JavaScript**, depending on the communication type.
2. Position the cursor in the place where you want to add the variable.
3. On the right side, click **+ Variable**.  
    **Result**: A sidebar appears.
4. In the **Identifier** field, enter the ID of the variable.  
    This will be the title of the field unless you define the **Label** field.  
    The first character of the ID can't be a number.
5. From the **Type** dropdown list, select the type of variable.  
    
   <div class="content-tabs" data-tab-group="tabgrp-2784">
   <div class="tab-buttons"><button class="tab-button" data-tab-id="tabgrp-2784-0" data-tab-group="tabgrp-2784" data-tab-active="true">String</button><button class="tab-button" data-tab-id="tabgrp-2784-1" data-tab-group="tabgrp-2784">Select</button><button class="tab-button" data-tab-id="tabgrp-2784-2" data-tab-group="tabgrp-2784">Synerise insert</button><button class="tab-button" data-tab-id="tabgrp-2784-3" data-tab-group="tabgrp-2784">Switch</button><button class="tab-button" data-tab-id="tabgrp-2784-4" data-tab-group="tabgrp-2784">Color</button><button class="tab-button" data-tab-id="tabgrp-2784-5" data-tab-group="tabgrp-2784">Number</button></div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-0" data-tab-group="tabgrp-2784" data-tab-active="true">

   Allows you to add a field that requires a string value.  

   1. In the **Label (Optional)** field, enter the name of the field.  
     If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation of the field's purpose.
   3. In the **Default Value** field, enter the default value.

   </div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-1" data-tab-group="tabgrp-2784">

   Allows you to add a dropdown list with configurable values.  

   1. In the **Label (Optional)** field, enter the name of the field. If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation what this field is for.
   3. In the **Display Name** field, enter the name that will be visible in a dropdown.
   4. In the **Value** field, enter a value.
   5. In the **Default Value** field, enter the default value.

   <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/Mobile/_gfx/mobile-json-raw.png" class="full" alt="A select variable"><figcaption>A select variable during configuration </figcaption></figure>

   </div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-2" data-tab-group="tabgrp-2784">

   Allows you to add a dropdown list with aggregates, expressions, metrics, voucher pools, recommendations, files, catalogs, and attributes.  

   1. In the **Label (Optional)** field, enter the name of the field. If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation what this field is for.
   3. From the **Insert Type** dropdown list, select the type of resource:
       - **Aggregates, AI recommendations, Expressions, Metrics, Voucher pools**: creates a dropdown list of available resources of the selected type. When the user selects a resource in the form, its ID is inserted into the code of the template. This ID can be used in [Jinjava](/developers/inserts/insert-usage) to display the value of the selected resource.
       - **Catalogs**: creates a dropdown list of catalogs. When the user selects a catalog in the form, its name is inserted into the code of the template. This name can be used in [Jinjava](/developers/inserts/insert-usage#catalogs) to retrieve a value from the catalog.
       - **Files**: creates a dropdown list of [files](/docs/assets/files-explorer). When user selects a file, its URL is inserted into the code.
       - **Profile attributes**: creates a dropdown list of profile attributes. When a user selects an attribute, its name is inserted into the code of the template. This name can be used in [Jinjava](/developers/inserts/insert-usage#customer-attributes) to retrieve the attribute value.
   3. In the **Default Value (Optional)** field, enter the default value.  
       **Result**: A dropdown with the insert is added to the form in the **Config** tab. From the dropdown list, you can select an item of the chosen type (for example, aggregates). As a result, the value of variable will be ID of the selected item.
       <figure><img src="/api/docs/image/8aa18695b9ddf90415ab9b9c2fdf6e90734cca60/docs/campaign/_gfx/synerise-insert-result.png" class="full" alt="Synerise insert select in the configuration form"><figcaption>Synerise insert select in the configuration form</figcaption></figure>

   </div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-3" data-tab-group="tabgrp-2784">

   Allows you to add a field which is enabled/disabled by a toggle.  

   1. In the **Label (Optional)** field, enter the name of the field. If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation what this field is for.
   3. In the **Default Value** field, select the default value (true/false).

   </div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-4" data-tab-group="tabgrp-2784">

   Allows you to add a color selector. You can either select a color or enter its code manually.  

   1. In the **Label (Optional)** field, enter the name of the field. If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation what this field is for.
   3. In the **Default Value** field, enter the default value.

   </div>

   <div class="tab-panel" data-tab-id="tabgrp-2784-5" data-tab-group="tabgrp-2784">

   Allows you to add a field that requires a number. You can either select a number from a dropdown or enter it manually.  

   1. In the **Label (Optional)** field, enter the name of the field. If this field is empty, the name of the field will be taken from the **Identifier** field.  
   2. In the **Description (Optional)** field, enter a short explanation what this field is for.
   3. In the **Default Value** field, enter the default value.

   </div>
   </div>


    
6. If you want to add the variable to a group that can be more easily displayed together in the form:  
    1. Click **Variable Group**.
    2. Select or create a group:
        - To select a group, click its name.
        - To create a group:
            1. Click **Add new group**.
            2. Enter a group name.
            3. Enter a group ID.
            4. Click **Apply**.  
    **Result**: On the **Config** tab, the groups can be collapsed and expanded.
5. In the upper right corner, click **Add**.  
   **Result**: In the template code, a variable appears (it starts with `####`). It also becomes available on the **Config** tab.  
6. Optionally, to modify the order of variables appearing in the configuration form, add the `order` parameter to the variable formula (for example, `#### type: "string", id: "string", label: "Text", order: 1 !####`).

## Previewing templates


<div class="admonition admonition-note"><div class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg></div><div class="admonition-body"><div class="admonition-content">

For inline in-app messages, the **Preview Alignment** option is for test purposes only. It does not reflect how the message will be displayed in the container in a real app.

</div></div></div>


1. To check the preview of the template for a particular customer or a product, click the **Preview** button on the upper left side. 
2. Enter the ID of a customer or a product. 
3. Click **Apply**.

