Skip to content
Private betaTreebars is letting teams in from the waitlist.Join the waitlist

HTML in-app messages

An HTML message talks to your app or site through one object, treebars. Its methods are the standard ones, and most messages need none of them, because a few attributes do the tracking.

Where it runs

The script in a message — yours, and the small runtime we add for the attributes below — runs where the SDK hosts the bridge: the Web SDK 0.4.0 and the Android SDK 0.4.0 and the iOS SDK 0.4.0 and later. The Android and iOS SDKs host it from their next release; until then an app draws the markup with scripts off, so keep a way out that needs none, such as a link to treebars://dismiss. The editor says how many of your devices can run it.

The message is drawn in a sandboxed frame: it cannot read your page or your app's storage, and it has no fetch, XHR or WebSocket. Images, fonts and styles load from any https address. Every link goes to the SDK, which records the press and then opens it.

A site whose Content-Security-Policy refuses inline scripts stops the message's frame. The SDK then draws it in a frame of ours instead — add frame-src https://frame.treebars.com to your policy. A site that allows no outside frame shows no HTML message, and the campaign report counts it as blocked.

On a website the SDK shows a message with no set-up past init: In-app messages on a website has when it appears, how to switch it off, and what to check when it does not.

Tracking without writing any

These work with no script of your own. Put them on the elements, and the message does the rest.

Attribute
data-tb-screen
Example
data-tb-screen="welcome"
What it does
One screen of a flow. The first shows; the rest wait. data-tb-final on a screen records the flow as completed.
Attribute
data-tb-id
Example
data-tb-id="cta"
What it does
Reports the element’s clicks under that id.
Attribute
data-tb-action
Example
next, back, screen:done, dismiss, submit, deeplink:app://orders, url:https://…, landing:https://…, push, copy:CODE, share:text, store-review, custom, app:add_to_cart, navigate:Cart
What it does
Acts, after the click is recorded. app: and navigate: hand your app a named action, with data-tb-props as its payload, which it subscribes to with onAction (SDK 0.11.0).
Attribute
data-tb-event
Example
data-tb-event="offer_claimed" data-tb-props='{"plan":"annual"}'
What it does
Records one of your events.
Attribute
data-tb-save
Example
data-tb-save="email"
What it does
Keeps the answer when the form submits: an email or phone as an opt-in, anything else as a trait.
Attribute
data-tb-then
Example
data-tb-then="next"
What it does
On whatever submits — the button, or its form — or on a push ask: where the message goes once the answers are sent or the permission is answered, such as a thanks screen or dismiss.
Attribute
data-tb-track
Example
data-tb-track="off"
What it does
Turns automatic click tracking off inside it.
Three screens, no scripthtml
<main>
  <section data-tb-screen="welcome">
    <h1>Welcome, {{ user.first_name | default: "there" }}</h1>
    <button data-tb-id="start" data-tb-action="next">Get started</button>
    <button data-tb-id="skip" data-tb-action="dismiss">Not now</button>
  </section>
  <section data-tb-screen="interests" hidden>
    <fieldset data-tb-save="interests">
      <label><input type="checkbox" value="shoes"> Shoes</label>
      <label><input type="checkbox" value="kitchen"> Kitchen</label>
    </fieldset>
    <button data-tb-id="continue" data-tb-event="interests_selected" data-tb-action="next">Continue</button>
  </section>
  <section data-tb-screen="done" data-tb-final hidden>
    <p>Your code is SPRING10.</p>
    <button data-tb-id="copy" data-tb-action="copy:SPRING10">Copy code</button>
    <a href="treebars://dismiss">Close</a>
  </section>
</main>

The methods

Every method returns a Promise, and every one resolves: a refusal is { ok: false, reason }, never an error, so a template brought over from another platform — written against a bridge that returns nothing, and never catching — behaves as it did there.

Outcomes

Recording what the person did with the message, and closing it.

Method
dismissMessage()
What it does
Records in_app_dismissed (once per display) and closes the message. Closing this way IS counted, so a template need not record the dismissal itself first.
Where
All
Method
trackDismiss(widgetId?)
What it does
Records in_app_dismissed with the element; does not close. A dismissMessage() after it does not count twice.
Where
All
Method
trackClick(widgetId?)
What it does
Records in_app_clicked with the element (a whole number is also button_index); neither closes nor navigates. A widgetId of 0 is kept as "0" rather than read as no id at all.
Where
All
Method
trackRating(rating)
What it does
Records in_app_rated with the rating.
Where
All

Events

Your own events, attributed to the campaign every time.

Method
trackEvent(eventName, generalAttrJson?, locationAttrJson?, dateAttrJson?, isNonInteractive?, shouldAttachCampaignMeta?)
What it does
Records the event with the campaign, delivery and message always attached; the location and date objects are merged into its properties. At most 20 a display, and a repeat of one name within 250 ms is dropped. A name starting in_app_ or reserved by the SDK is refused. The fifth and sixth arguments are accepted and IGNORED: a message on screen is activity, and the campaign is always attached.
Where
All

Identity

Present by name, and refused: a message cannot change who is reading it.

Method
identifyUser(idOrAttributes)
What it does
Refuses, and lint flags it at publish. Who a device belongs to is decided by the app, never by a message.
Where
All
Method
setUniqueId(uniqueId)
What it does
Refuses, and lint flags it at publish. Who a device belongs to is decided by the app, never by a message.
Where
All
Method
setAlias(alias)
What it does
Refuses, and lint flags it at publish. Who a device belongs to is decided by the app, never by a message.
Where
All

Profile

Addresses as opt-ins, and traits on a signed-in person.

Method
setEmailId(email)
What it does
Keeps the address as this person’s opt-in (the in-app form’s save_as path), signed in or not; never changes who they are.
Where
All
Method
setMobileNumber(mobileNumber)
What it does
Keeps the number as this person’s opt-in, as setEmailId does.
Where
All
Method
setUserName(value)
What it does
Sets the trait name.
Where
All
Method
setFirstName(value)
What it does
Sets the trait first_name.
Where
All
Method
setLastName(value)
What it does
Sets the trait last_name.
Where
All
Method
setGender(value)
What it does
Sets the trait gender.
Where
All
Method
setBirthDate(value)
What it does
Sets the trait birth_date (an ISO 8601 date).
Where
All
Method
setUserLocation(latitude, longitude)
What it does
Sets the trait location.
Where
All
Method
setUserAttribute(name, value)
What it does
Sets a trait the template declares in its manifest; a platform trait or an undeclared one is refused.
Where
All
Method
setUserAttributeDate(name, iso8601)
What it does
As setUserAttribute, for a date.
Where
All
Method
setUserAttributeLocation(name, latitude, longitude)
What it does
As setUserAttribute, for a location.
Where
All

Navigation

Each records the press before it goes anywhere. None closes the message.

Method
navigateToScreen(screenName, dataJson?)
What it does
Records the click, then hands the app a screen and its key-values (setClickActionListener, the iOS delegate, inAppCampaignCustomAction; on the web, the TREEBARS_ONSITE_MESSAGE_CLICKED event).
Where
All
Method
openDeepLink(url, dataJson?)
What it does
Records the click, then opens the deep link.
Where
All
Method
openRichLanding(url, dataJson?)
What it does
Records the click, then opens the page in an in-app browser (a new tab on the web).
Where
All
Method
openWebURL(url, dataJson?)
What it does
Records the click, then opens the page in the system browser (this tab on the web).
Where
All

Device

What the host app or the browser can do for the page.

Method
copyText(text, message?)
What it does
Copies the text, and shows message as a toast. navigator.clipboard.writeText inside a message is routed here, because a sandbox or a WebView usually refuses it.
Where
All
Method
call(mobileNumber)
What it does
Opens the dialer with the number.
Where
All
Method
sms(mobileNumber, message?)
What it does
Opens the SMS composer with the number and the text.
Where
All
Method
share(text)
What it does
Opens the share sheet (on the web, where the browser has one).
Where
All
Method
customAction(dataJson)
What it does
Records the click, then hands the key-values to the app: setClickActionListener on Android, the delegate’s inAppClicked on iOS, inAppCampaignCustomAction in React Native, TREEBARS_ONSITE_MESSAGE_CLICKED on the web. An object is accepted as well as a JSON string.
Where
All
Method
requestNotificationPermission()
What it does
Asks for push permission, and resolves with the answer.
Where
All
Method
showPushOptIn()
What it does
The same as requestNotificationPermission, under the name iOS developers expect.
Where
All
Method
handleNotificationPopUp()
What it does
The same as requestNotificationPermission.
Where
All
Method
navigateToNotificationSettings()
What it does
Opens this app’s notification settings.
Where
Android, iOS
Method
navigateToSettings()
What it does
Opens this app’s settings.
Where
Android, iOS

Ours

The methods beyond the standard set.

Method
appAction(name, payload?)
What it does
Records the click with the action’s name, then hands the app the action and its payload: the app subscribes with onAction(name, …) on every SDK, and one fired before it has is held for a few seconds. navigate is reserved, with { screen, props }. A name that is not lower-case letters, digits, dots and underscores, or a payload over 4 KB, is refused.
Where
All
Method
getContext()
What it does
Resolves the locale, direction, theme, platform, safe-area insets, the screen on show, the delivery and campaign, and the bridge version.
Where
All
Method
showScreen(screenId)
What it does
Goes to a screen, and records in_app_screen_viewed with how: "jump".
Where
All
Method
nextScreen()
What it does
Goes to the next screen.
Where
All
Method
previousScreen()
What it does
Goes back to the screen before.
Where
All
Method
setState(key, value)
What it does
Keeps a value across this display’s screens.
Where
All
Method
getState(key)
What it does
Reads a value setState kept.
Where
All
Method
submitForm(formId?)
What it does
Checks and submits the screen’s inputs, and records in_app_form_submitted.
Where
All
Method
claimReward(poolId)
What it does
Asks the server for a reward from a pool, which it decides — never the page. Until games land (H6) it resolves { ok: false, reason: "not_available" }.
Where
All
Method
requestStoreReview()
What it does
Shows the store’s review prompt.
Where
Android, iOS
Method
resize(height?)
What it does
Tells the host how tall the content is; the Screens runtime calls it by itself, and the shim measures when no height is given.
Where
All
Method
getStoredValue(key)
What it does
Reads a value kept for this person and this campaign, on our server — so a seven-day challenge keeps its streak across days and devices. Also readable at send as {{ template.stored.<key> }}.
Where
All
Method
setStoredValue(key, value)
What it does
Keeps a value for this person and this campaign (at most 32 keys, 1 KB each). null removes it.
Where
All

Your own events

trackEvent records only an event the message declares — a data-tb-event, a trackEvent('name', …) written with its name in quotes, or a name in a manifest, <script type="application/json" id="tb-manifest">{ "events": ["quiz_answered"] }</script> for a name your script builds. At most 20 a display, and a repeat of one name within 250 ms is dropped. The campaign is attached every time, so the fifth and sixth arguments some bridges take for that are accepted here and ignored.

Examples

A lead-gen button, after importhtml
<button onclick="treebars.setEmailId(email.value);
                 treebars.trackEvent('response_submitted', {email: email.value}, {}, {}, false, true);
                 treebars.trackClick(1); treebars.dismissMessage()">Sign me up</button>
A push prompt that changes its own copy on a nojavascript
const answer = await treebars.requestNotificationPermission();
if (answer === 'denied') treebars.showScreen('push-later');
else treebars.nextScreen();
A streak that lasts across days and devicesjavascript
const streak = (await treebars.getStoredValue('streak')) ?? 0;
await treebars.setStoredValue('streak', streak + 1);

Writing one with an agent

Everything on this page, as one file an agent can read — Claude Code, Cursor, or your own model through the Treebars connector: agents.md, with the bridge as TypeScript in treebars.d.ts. Both are built from the same tables the SDKs and publish run on, so they describe what your devices do today. Point the agent at the first and give it a brief; what it writes is checked by the same rules as publish, and becomes a draft a person applies.

Bringing a template from another platform

The only global is treebars. A template that still calls the global its own platform gave it is refused at publish, naming it, and the code view rewrites it in one click — the calls themselves do not change. Personalisation is written our way: {{ user.first_name }} for {{UserAttribute['First Name']}}, and a token inside a script is escaped for the script.

localStorage is refused inside the message's frame. Use getStoredValue and setStoredValue, which keep a value for the person in the campaign on our side.