Overview

Image illustrating code in-app code beside a rendered in-app

OneSignal offers two in-app message editing experiences: a Drag and Drop GUI and the more advanced HTML Editor. The HTML Editor enables complete control over the appearance, behavior, and responsiveness of full-screen in-app messages.

What you can do with the HTML Editor:

  • Layouts: Create complex layouts including side-by-side buttons.
  • Media: Embed videos, animations, slides, and more.
  • Responsiveness: Optimize for portrait, landscape, and multiple device sizes.
  • Forms: Embed forms directly into your in-apps.
  • Fonts: Use custom fonts to match your brand.

The HTML Editor is ideal for developers fluent in HTML, CSS, and JavaScript. For non-technical users, we recommend the drag-and-drop editor.

Getting started

To enable the full-screen in-app experience, upgrade your SDK to the following versions:

  • iOS: 3.9.0+
  • Android: 4.6.3+

If your app uses an older SDK, in-app messages will render in a Center Modal layout instead.

Tips:

Step 1: Create a new in-app message

Go to Messages > New In-App to create, edit, test, pause, duplicate, or delete your in-app messages.

Step 2: Use the HTML Editor

Enter your HTML code on the left-hand side and preview it live. Use Send Test In-App to test responsiveness and design.

Image showing the HTML Editor beside the preview


Key features

Click support

With the In-App JS Library, you can track interactions and trigger in-app behaviors.

  1. Track elements using data-onesignal-unique-label:
<button class="tag-user" data-onesignal-unique-label="my-tag-user-button">Tag User</button>
  1. Bind click actions with JavaScript:
document.querySelector(".tag-user").onclick = (e) => {
  OneSignalIamApi.tagUser(e, { fiz: "baz" });
};

Asset support

You can load images, videos, fonts, and other assets in your in-apps. Assets are fetched at render time.

Example:

<img src="https://media.onesignal.com/iam/default_image_20200320.png" />

Tag substitution (personalization)

Dynamically insert user data with tag substitution. For example:

<div>Hi there {{ name | default: 'you' }}!</div>

Supported contexts:

  • Text inside elements like div, p, li
  • Inside <style> blocks
  • href, src, action, and data attributes

Not supported in:

  • <script> tags
  • Nested content structures, e.g.:
<div>
  Hi {{name}}
  <span>Here's your coupon!</span>
</div>

Best practices

Design responsively

Use CSS media queries to adapt layouts to various screen sizes.

@media only screen and (max-width: 620px) {
  .btn-primary {
    width: 100% !important;
  }
}

Test across devices

Send test messages frequently to confirm behavior and layout across device types.

Use alt tags for accessibility

Add alt text to images for screen reader compatibility.

Format HTML accessibly

Name and format elements clearly to support accessibility tools like screen readers.

Account for safe areas (notches)

Modern devices have safe areas (like notches or home bars). Use safe-area-inset-* variables to pad your layout:

body {
  padding-top: var(--safe-area-inset-top);
  padding-right: var(--safe-area-inset-right);
  padding-bottom: calc(var(--safe-area-inset-bottom) + 20px);
  padding-left: var(--safe-area-inset-left);
  margin: 0;
}

Optimize images for mobile

Reduce file sizes and use proper resolution:

*OneSignal has no affiliation with imagekit.io, though it’s a helpful tool for optimization.


Sample in-app HTML

We provide a default in-app template when loading the HTML Editor. It includes common methods from our JS Library.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Onesignal In-App Message</title>
    <style>
      body {
        margin: 0;
        padding-top: var(--safe-area-inset-top);
        padding-right: var(--safe-area-inset-right);
        padding-bottom: calc(var(--safe-area-inset-bottom) + 20px);
        padding-left: var(--safe-area-inset-left);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
        display: flex;
        align-items: center;
      }
      .center-modal {
        position: relative;
        background: #fff;
        margin: 18px;
        padding: 24px;
        border-radius: 8px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 85%;
        width: 100%;
        box-shadow: rgb(0 0 0 / 30%) 0px 0px 12.5px, rgb(0 0 0 / 15%) 0px 0px 2.5px;
      }
      .center-modal .close-button {
        position: absolute;
        top: 0;
        right: 0;
        background: rgba(0, 0, 0, 0);
        border: none;
        z-index: 1;
        display: flex;
        justify-content: center;
        flex-direction: column;
        align-items: center;
        min-width: 48px;
        min-height: 48px;
      }
      .center-modal img {
        width: 100%;
        height: 100%;
        margin-bottom: 12px;
        object-fit: contain;
      }
      .center-modal h1 {
        margin: 0 0 12px;
        color: #222;
        font-size: 24px;
        text-align: center;
      }
      .center-modal button {
        font-size: 16px;
        color: #fff;
        background-color: #1f8feb;
        width: 100%;
        padding: 12px;
        border-radius: 4px;
        border: none;
        margin-bottom: 12px;
      }
      .button-container {
        display: flex;
        flex-direction: column;
      }
      @media screen and (min-width: 480px) {
        .button-container {
          flex-direction: row;
          gap: 12px;
          width: 100%;
        }
        .button-column {
          width: 50%;
        }
        .center-modal {
          height: 80%;
        }
      }
    </style>
  </head>
  <body>
    <div class="center-modal">
      <div class="close-button" data-onesignal-unique-label="close-button">
        <svg width="10" height="10" viewBox="0 0 8 8" fill="none">
          <path d="M7.803 1.148a.5.5 0 0 0-.707-.707L4 3.05 1.148.44a.5.5 0 0 0-.707.707L3.05 4 .44 6.852a.5.5 0 1 0 .707.707L4 4.95l2.852 2.61a.5.5 0 1 0 .707-.707L4.951 4 7.803 1.148Z" fill="#111" />
        </svg>
      </div>
      <h1>Heading</h1>
      <img src="https://media.onesignal.com/iam/default_image_20200320.png" />
      <div class="button-container">
        <div class="button-column">
          <button class="tag-user" data-onesignal-unique-label="my-tag-user-button">Tag User</button>
          <button class="push-prompt" data-onesignal-unique-label="my-push-prompt-button">Prompt Push</button>
          <button class="location-prompt" data-onesignal-unique-label="my-location-prompt-button">Prompt Location</button>
        </div>
        <div class="button-column">
          <button class="add-click-name" data-onesignal-unique-label="my-add-click-name-button">Add Click Name</button>
          <button class="send-outcome" data-onesignal-unique-label="my-send-outcome-button">Send Outcome</button>
        </div>
      </div>
    </div>
    <script>
      document.querySelector(".close-button").onclick = (e) => {
        OneSignalIamApi.close(e);
      };
      document.querySelector(".tag-user").onclick = (e) => {
        OneSignalIamApi.tagUser(e, { fiz: "baz" });
      };
      document.querySelector(".push-prompt").onclick = (e) => {
        OneSignalIamApi.triggerPushPrompt(e);
      };
      document.querySelector(".location-prompt").onclick = (e) => {
        OneSignalIamApi.triggerLocationPrompt(e);
      };
      document.querySelector(".add-click-name").onclick = (e) => {
        OneSignalIamApi.addClickName(e, "test_click_name");
      };
      document.querySelector(".send-outcome").onclick = (e) => {
        OneSignalIamApi.sendOutcome(e, "test_outcome");
      };
    </script>
  </body>
</html>

FAQ