> For the complete documentation index, see [llms.txt](https://docs.amply.tools/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amply.tools/developer-guide/showing-custom-popups.md).

# 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](/developer-guide/handling-deeplinks.md).

## 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).

## Rendering a popup from a campaign deeplink

{% tabs %}
{% tab title="iOS (Swift)" %}

```swift
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())
```

{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
import tools.amply.sdk.actions.DeepLinkListener

class PopupHandler(
    private val popupRouter: PopupRouter
) : DeepLinkListener {

    override fun onDeepLink(url: String, info: Map<String, Any>): Boolean {
        if (!url.startsWith("yourapp://popup/")) return false

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

        popupRouter.show(
            id = popupId,
            title = title,
            body = body,
            cta = cta,
            metadata = info
        )
        return true
    }
}

amply.registerDeepLinkListener(PopupHandler(popupRouter))
```

{% endtab %}

{% tab title="React Native (TS)" %}

```tsx
import Amply from '@amplytools/react-native-amply-sdk';
import { useEffect } from 'react';
import { usePopupController } from './popups';

export function useAmplyPopups() {
  const popups = usePopupController();

  useEffect(() => {
    let unsubscribe: (() => void) | undefined;
    let unmounted = false;

    Amply.addDeepLinkListener(event => {
      if (!event.url.startsWith('yourapp://popup/')) return;

      const popupId = event.url.replace('yourapp://popup/', '');
      const { title = '', body = '', cta = 'OK' } = event.info as {
        title?: string; body?: string; cta?: string;
      };

      popups.show({ id: popupId, title, body, cta, metadata: event.info });
    })
      .then(unsub => {
        if (unmounted) unsub();
        else unsubscribe = unsub;
      });

    return () => {
      unmounted = true;
      unsubscribe?.();
    };
  }, [popups]);
}
```

{% endtab %}
{% endtabs %}

## 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.

## Recommended pattern

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.

## Related

* [Handling deeplinks](/developer-guide/handling-deeplinks.md) — the full deeplink API this page builds on
* [Concepts — Scenarios and campaigns](/concepts/scenarios-and-campaigns.md) — how the dashboard models popups
* [User Guide — Creating a campaign](/user-guide/creating-a-campaign.md) — PM's view of configuring a popup campaign


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.amply.tools/developer-guide/showing-custom-popups.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
