Links
Web banner
A small script for your website that shows phone and tablet visitors an "Open in app" banner. The button goes through one of your Appy links, so the link decides where each visitor lands: your app, the App Store, Google Play, Galaxy Store, GetApps or your website.
Before you start
- Web banners are part of the Pro, Business and Enterprise plans. An account can have up to 20 banners.
- You need an Appy link for your app. The banner adds nothing to its routing: deep links, store targets, Galaxy Store, GetApps, country rules and the in-app browser page all work as they do for any other click.
- You need to be able to add a script tag to the pages of your website, directly or through a tag manager.
How it works
- 1
The script checks the visitor
On every page view,
banner.jslooks at the user agent. It stops right there on desktops, when the visitor closed this banner within the snooze period, and in Safari on iPhone or iPad when your page already has Apple's Smart App Banner tag. - 2
It loads the banner settings
One request to
https://appy.to/_banner/<id>returns the text, colors, link and page rules. The script stops if the current path is not on the banner's list. - 3
It shows the banner and counts an impression
The banner appears at the top or bottom of the screen. One impression is sent per page view.
- 4
The button opens your link
The button opens your Appy link with
utm_source=web_banner,utm_medium=banner, the banner id asutm_campaignand the page path asutm_content. From there the link routes the visitor and records the click like any other.
Install
Create the banner in the dashboard under Links > Web banner, save it and copy the snippet. Paste it just before the closing </body> tag on every page where the banner may show:
<script src="https://appy.to/banner.js" data-banner="YOUR_BANNER_ID" async></script>- The file is about 6 KB compressed, has no dependencies and loads nothing from any other host.
- It runs inside a shadow root, so your CSS does not change the banner and the banner's CSS does not touch your page.
- Adding the snippet twice, or through both your template and a tag manager, still shows one banner.
- Changes you save, pausing included, reach visitors within about three minutes: Appy caches the settings for up to a minute and browsers for two.
- Single-page apps: the banner checks the path once, when the page loads. Use Show on and Never show on with the paths visitors land on.
Options
| Option | What it does |
|---|---|
| Link | The Appy link the button opens. Pick an active link of your account. |
| App name | Used as the accessible name of the banner. Up to 40 characters. |
| Icon | PNG, JPEG or WebP, stored as a 128 px square and served from appy.to. Optional. |
| Title and second line | One line each, up to 60 and 90 characters. Long text ends with an ellipsis on narrow screens. |
| Button | Button label, up to 20 characters. |
| Colors | Background, text, button and button text, as hex values. |
| Position | Top or bottom of the screen. |
| Layout | Over the page keeps your layout as it is and covers a thin strip. Keep space for it never covers your content: at the bottom it adds the same height after your footer, at the top it fills a slot you add to your page. |
| Show on / Never show on | Path patterns, one per line, 20 at most. Empty Show on means every page. Never show on wins. |
| Snooze | How long the banner stays hidden after a visitor closes it, from 1 to 365 days. |
| Open the same page in the app | Sends the page, or a path you map from it, to your app. See Open the same page in your app. |
Path patterns
| Pattern | Matches | Does not match |
|---|---|---|
/pricing | /pricing, /pricing/, /Pricing | /pricing/team |
/products/* | /products, /products/shoes, /products/shoes/42 | /productsale |
/p/:id | /p/42 | /p/42/reviews |
/blog/*/comments | /blog/2026/10/comments | /blog/2026 |
Patterns ignore the query string, the fragment, trailing slashes and letter case. * matches any characters, / included. :name matches one path segment.
Keep space at the top
A banner that slides in above your header would push the page down after it has rendered. To keep the page still, add a slot at the start of <body> and load the script right after it, without async:
<style>
[data-appy-banner-slot] { min-height: 64px; }
[data-appy-banner-slot][data-appy-banner-state="hidden"],
[data-appy-banner-slot][data-appy-banner-state="dismissed"] { min-height: 0; }
@media (hover: hover) and (pointer: fine) { [data-appy-banner-slot] { display: none; } }
</style>
<div data-appy-banner-slot></div>
<script src="https://appy.to/banner.js" data-banner="YOUR_BANNER_ID"></script>- The slot keeps 64 px free on phones and tablets and takes no space on computers with a mouse.
- The script sets
data-appy-banner-stateon the slot toshown,hiddenordismissed. Because it runs straight after the slot, visitors who closed the banner never see the empty space. - Without a slot, a top banner with this layout falls back to Over the page.
While a banner is visible, <html> carries data-appy-banner="top" or data-appy-banner="bottom". Use it to move a sticky header or a chat button out of the way:
html[data-appy-banner="top"] .site-header {
top: 64px;
}Open the same page in your app
When a visitor reads a product page on your site, the app can open on that product instead of its home screen. Choose one of the options under Open the same page in the app:
| Option | Sent as deep_link_path |
|---|---|
| Don't send the page | Nothing. The app opens where your link's deep link points. |
| Use the same path as the web page | The page path, for example /products/42. |
| Map web paths to app paths | The first matching rule. /products/:id → product/:id turns /products/42 into product/42; /blog/* → articles/* keeps everything after /blog/. Pages without a matching rule send nothing. |
How the app receives it depends on your setup:
- Appy SDK:
deep_link_pathis one of the link parameters the SDK hands to your app, both when an installed app opens from the banner and on the first open after an install. See the iOS and Android guides. - Parameter forwarding (Business and Enterprise): the parameter is also added to your deep link and to store URLs, for example
myapp://open?deep_link_path=product%2F42. - Without either, the app opens on the link's deep link and the path is only visible in your click data.
// iOS (Swift)
Appy.shared.onDeepLink { link in
if let path = link.params["deep_link_path"] { router.open(path) }
}
// Android (Kotlin)
Appy.onDeepLink { link ->
link.params["deep_link_path"]?.let { router.open(it) }
}When the banner stays hidden
| Situation | Why |
|---|---|
| Desktop and laptop browsers | The banner is for iPhone, iPad, Android and HarmonyOS devices only. An iPad in desktop mode counts as a tablet. |
Safari on iPhone or iPad with <meta name="apple-itunes-app"> on the page | Safari already shows Apple's Smart App Banner there, so a second banner would only get in the way. Chrome, Firefox and in-app browsers on iOS ignore Apple's tag and get the Appy banner. |
| The visitor closed it | Hidden on that browser until the snooze period ends. |
| The path is not allowed | The page is not on Show on, or it is on Never show on. |
| The banner is paused, deleted or its link is inactive | The settings request answers 404 and the script does nothing. |
| The script runs in an old browser behind a strict CSP | If the banner's styles cannot load, the script shows nothing rather than an unstyled bar. |
Measurement
| Number | Where it comes from |
|---|---|
| Impressions | One per page view where the banner appeared. Requests from known bots and crawlers are not counted, using the same user agent rules as link clicks. |
| Closed | How many times visitors closed the banner. |
| Clicks | Clicks on the banner's link with utm_source=web_banner, utm_medium=banner and the banner id as utm_campaign, taken from the link's click data, bots excluded. Opens through a universal link or App Link that the SDK reports count too. |
| Click rate | Clicks divided by impressions for the selected period. |
The Web banner page shows these for the last 7, 30 or 90 days. Because clicks carry normal UTM values, the banner also shows up in the link's analytics, the report builder, exports and install attribution, with utm_content telling you which page the visitor came from.
Privacy
The script sets no cookies and sends no cookies: its requests use credentials: omit and no referrer. Impressions and closes are stored only as daily totals per banner, with no IP address, user agent or page URL. The only thing kept on the device is the time the banner may show again, in localStorage under appy_banner_dismissed_<id>. Page paths reach Appy only when a visitor taps the button, as utm_content; if your URLs contain personal data, keep those pages on Never show on.
Content-Security-Policy
Sites with a strict Content-Security-Policy need to allow https://appy.to for the script, the settings and beacon requests, and the icon:
Content-Security-Policy:
script-src 'self' https://appy.to;
connect-src 'self' https://appy.to;
img-src 'self' https://appy.tostyle-srcneeds nothing extra: the banner styles itself through a constructable stylesheet inside its shadow root, which a policy withoutunsafe-inlineallows.- Leave
img-srcout if the banner has no icon. - Browsers older than Safari 16.4, Chrome 73 or Firefox 101 fall back to a
<style>element. Under a strictstyle-srcthey show no banner at all. - If you allow scripts by nonce or hash only, add the same nonce to the snippet's
<script>tag.
Testing
- 1
Open a page on a phone
Or use the device toolbar in Chrome DevTools with a phone selected, then reload: the script reads the user agent when the page loads.
- 2
Check the settings
Open
https://appy.to/_banner/<id>in a browser. A 404 means the banner is paused, deleted or its link is inactive. - 3
Show it again after closing
Delete
appy_banner_dismissed_<id>from local storage, or use a private window. - 4
Follow the button
Tap it on a phone with the app installed and on one without it, and check the click under the link's analytics with
utm_source = web_banner.