For the complete documentation index, see llms.txt. This page is also available as Markdown.

Showing custom popups

Some campaigns don't navigate the user to a new screen — they show a popup (a promo modal, a discount sheet, an in-app announcement) on top of the current screen. The SDK does not render the popup itself. Instead, it tells your app to render a popup by sending a deeplink with information the popup needs.

A PM should read this as: "the campaign tells the app 'show popup X with these parameters' via the normal deeplink mechanism. The app owns the visual — typography, imagery, layout — so the popup always matches the app's design."

Use this when you need campaigns to show in-app popups (promos, announcements, upgrade offers). Don't use this when the campaign should only navigate to a full screen or open an external URL — that's covered in Handling deeplinks.

How popups are delivered

The SDK uses a single mechanism — the deeplink listener — to hand any campaign action to your app. For popups, the campaign's action configuration emits a deeplink URL that identifies the popup and its parameters. Your app recognizes this URL, parses the parameters from the info map, and renders a native popup.

A typical pattern:

  • Campaign URL: yourapp://popup/discount_offer

  • Info map: { "title": "20% off", "cta": "Upgrade now", "plan_id": "annual_pro" }

  • Your app matches on yourapp://popup/ prefix and shows a modal with that content.

This keeps visual rendering in the app (so popups always look native) and keeps content in the dashboard (so the PM can change copy, imagery, and targeting without a release).

import AmplySDK
import UIKit

class PopupHandler: DeepLinkListener {
    func onDeepLink(url: String, info: [String: Any]) -> Bool {
        guard url.hasPrefix("yourapp://popup/") else { return false }

        let popupId = String(url.dropFirst("yourapp://popup/".count))
        let title = info["title"] as? String ?? ""
        let body  = info["body"]  as? String ?? ""
        let cta   = info["cta"]   as? String ?? "OK"

        DispatchQueue.main.async {
            PopupRouter.shared.show(
                id: popupId,
                title: title,
                body: body,
                cta: cta,
                metadata: info
            )
        }
        return true
    }
}

amply.registerDeepLinkListener(listener: PopupHandler())

Why the app owns the rendering

The SDK deliberately does not render popups. This means:

  • Popups always match your app's fonts, colors, and typography.

  • Popups can use your existing design system — buttons, cards, animations.

  • The popup behaves correctly on every screen (tab bars, keyboards, sheets).

  • Accessibility, dark mode, and localization follow your app's rules.

The dashboard controls what to show, when to show it, and to whom. The app controls how it looks.

Keep one deeplink handler at app startup. Route by URL prefix:

  • yourapp://screen/... → navigate to a screen

  • yourapp://popup/... → render a popup

  • https://... → open externally or in an in-app browser

This keeps the integration auditable in one place. If a new campaign type appears, you add one more branch.

What happens if no listener handles the URL

If your listener returns false (or never matches the URL prefix), the SDK considers the deeplink unconsumed. No popup appears. In reports, you will see the campaign matched but the action was not consumed — useful for spotting integrations that are missing handling for a new URL prefix.

Last updated