{"id":1305,"date":"2026-06-02T17:00:00","date_gmt":"2026-06-02T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1305"},"modified":"2026-03-07T03:49:06","modified_gmt":"2026-03-07T08:49:06","slug":"ios-paste-permission-deferred-links","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/ios-paste-permission-deferred-links\/","title":{"rendered":"iOS Paste Permission and Deferred Deep Links"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">iOS 16 introduced a system permission prompt for clipboard access. When an app reads from <code>UIPasteboard.general<\/code>, iOS shows a dialog: &quot;[App] would like to paste from [Source].&quot; The user can allow or deny. This change directly affects clipboard-based deferred deep linking, where a token is copied to the clipboard on a web click and read by the app after installation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before iOS 16, apps could read the clipboard silently (with only a banner notification on iOS 14+). Now, the user must actively consent. This creates a UX challenge: the first thing a new user sees after installing your app is a system prompt asking for paste permission, with no context about why.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide covers how to handle the paste permission prompt effectively, alternative approaches, and UX patterns that maintain a good first-launch experience. For the broader clipboard deferred linking flow, see <a href=\"https:\/\/tolinku.com\/blog\/clipboard-based-deferred-linking\/\">clipboard-based deferred linking<\/a>. For privacy implications, see <a href=\"https:\/\/tolinku.com\/blog\/deferred-linking-privacy-considerations\/\">deferred linking privacy considerations<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The iOS Paste Permission Timeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Understanding how Apple has progressively tightened clipboard access:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>iOS 13 and earlier:<\/strong> Apps could read the clipboard at any time, silently. No user notification.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>iOS 14 (2020):<\/strong> Apple added a <a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uipasteboard\" rel=\"nofollow noopener\" target=\"_blank\">paste notification banner<\/a>: &quot;[App] pasted from [Source App].&quot; This notification is informational; it does not block the paste. It appears briefly at the top of the screen. This was Apple&#39;s response to <a href=\"https:\/\/arstechnica.com\/gadgets\/2020\/06\/tiktok-and-53-other-ios-apps-still-snoop-your-sensitive-clipboard-data\/\" rel=\"nofollow noopener\" target=\"_blank\">reports of apps reading clipboard data excessively<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>iOS 15:<\/strong> Same behavior as iOS 14. Banner notification on paste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>iOS 16 (2022):<\/strong> Apple introduced the paste permission prompt. Instead of a passive banner, a modal dialog appears: &quot;[App] would like to paste from [Source App]. Allow Paste?&quot; with &quot;Don&#39;t Allow&quot; and &quot;Allow Paste&quot; buttons. The app cannot read clipboard content until the user taps &quot;Allow Paste.&quot;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>iOS 17+:<\/strong> Same permission prompt behavior. Apple also introduced <a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uipastecontrol\" rel=\"nofollow noopener\" target=\"_blank\">UIPasteControl<\/a> improvements and additional clipboard privacy features.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The UX Problem<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The paste permission prompt creates a friction point in the deferred deep linking flow:<\/p>\n\n\n\n<pre><code>1. User taps link on web \u2192 token copied to clipboard\n2. User installs app from App Store\n3. User opens app for the first time\n4. App attempts to read clipboard\n5. iOS shows: &quot;[App] would like to paste from Safari. Allow Paste?&quot;\n6. User thinks: &quot;Why does this app want to paste? I didn&#39;t copy anything.&quot;\n7. User taps &quot;Don&#39;t Allow&quot;\n8. Deferred link match fails\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem: the user does not remember copying anything. The clipboard write happened transparently as part of the link click. The paste permission prompt appears without context, and most users instinctively deny unfamiliar permissions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Studies and industry reports suggest paste permission acceptance rates range from <strong>30-50%<\/strong> without pre-prompting, and <strong>50-70%<\/strong> with a well-designed explanation screen shown before the system prompt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">UX Patterns for Better Acceptance Rates<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 1: Pre-Prompt Explanation Screen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Show an in-app screen before triggering the paste permission. Explain what will happen and why:<\/p>\n\n\n\n<pre><code class=\"language-swift\">class WelcomeViewController: UIViewController {\n\n    func handleFirstLaunch() {\n        \/\/ Check if we likely have a deferred link to resolve\n        \/\/ (e.g., the user came from an ad campaign or shared link)\n        showDeferredLinkExplanation()\n    }\n\n    func showDeferredLinkExplanation() {\n        let alert = UIAlertController(\n            title: &quot;Welcome! Were you sent here by a friend?&quot;,\n            message: &quot;If someone shared a link with you, we can take you directly to the right place. Just tap &#39;Allow Paste&#39; on the next prompt.&quot;,\n            preferredStyle: .alert\n        )\n\n        alert.addAction(UIAlertAction(title: &quot;Yes, take me there&quot;, style: .default) { _ in\n            self.attemptClipboardMatch()\n        })\n\n        alert.addAction(UIAlertAction(title: &quot;No, just get started&quot;, style: .cancel) { _ in\n            self.proceedToOnboarding()\n        })\n\n        present(alert, animated: true)\n    }\n\n    func attemptClipboardMatch() {\n        let pasteboard = UIPasteboard.general\n        \/\/ This triggers the system paste permission prompt\n        guard let text = pasteboard.string else {\n            proceedToOnboarding()\n            return\n        }\n\n        if let token = parseDeferredLinkToken(text) {\n            routeToContent(token)\n        } else {\n            proceedToOnboarding()\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The pre-prompt gives the user context. They understand why the system prompt appears, and they are more likely to allow it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 2: UIPasteControl<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uipastecontrol\" rel=\"nofollow noopener\" target=\"_blank\">UIPasteControl<\/a> is a system-provided button that grants clipboard access when tapped, without showing the permission prompt. The tap itself is the consent.<\/p>\n\n\n\n<pre><code class=\"language-swift\">class OnboardingViewController: UIViewController, UIPasteConfigurationSupporting {\n\n    var pasteConfiguration: UIPasteConfiguration? = UIPasteConfiguration(\n        acceptableTypeIdentifiers: [UTType.plainText.identifier]\n    )\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        if #available(iOS 16.0, *) {\n            let config = UIPasteControl.Configuration()\n            config.displayMode = .labelOnly\n            config.cornerStyle = .capsule\n            config.baseBackgroundColor = .systemBlue\n            config.baseForegroundColor = .white\n\n            let pasteButton = UIPasteControl(configuration: config)\n            pasteButton.frame = CGRect(x: 50, y: 300, width: 250, height: 50)\n            pasteButton.target = self\n            view.addSubview(pasteButton)\n        }\n    }\n\n    override func paste(itemProviders: [NSItemProvider]) {\n        for provider in itemProviders {\n            provider.loadObject(ofClass: String.self) { text, error in\n                guard let text = text else { return }\n                DispatchQueue.main.async {\n                    if let token = self.parseDeferredLinkToken(text) {\n                        self.routeToContent(token)\n                    }\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Advantages of UIPasteControl:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No system permission prompt (the tap is implicit consent)<\/li>\n<li>Apple-sanctioned approach for clipboard access<\/li>\n<li>Clear user intent<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Disadvantages:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You cannot customize the button&#39;s appearance beyond basic properties (color, corner style, label)<\/li>\n<li>The button must be visible and tappable by the user (you cannot trigger it programmatically)<\/li>\n<li>You need to integrate it into your onboarding flow naturally<\/li>\n<li>It requires the user to take an explicit action (tap the button)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 3: Deferred Prompt (Check Later)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do not check the clipboard on first launch. Instead, wait for a natural moment when the user would expect paste behavior:<\/p>\n\n\n\n<pre><code class=\"language-swift\">class ContentViewController: UIViewController {\n\n    func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n\n        \/\/ Check clipboard only if the user navigates to a &quot;redeem&quot; or &quot;enter code&quot; screen\n        if isFirstLaunch &amp;&amp; hasRedeemableContent() {\n            checkClipboardForToken()\n        }\n    }\n\n    func checkClipboardForToken() {\n        \/\/ This is a natural context for paste; users expect it here\n        let pasteboard = UIPasteboard.general\n        if let text = pasteboard.string, let token = parseDeferredLinkToken(text) {\n            showConfirmation(&quot;We found a shared link. Go to \\(token.contentDescription)?&quot;)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This pattern works well for apps where entering a code or redeeming a link is a natural action (referral programs, gift cards, invitation codes).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 4: Skip Clipboard Entirely<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If your paste permission acceptance rate is too low to justify the UX friction, skip clipboard matching and use alternative deferred linking methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Android:<\/strong> Rely on the Play Install Referrer (more reliable anyway).<\/li>\n<li><strong>iOS:<\/strong> Use server-side probabilistic matching as the primary method, accepting the lower accuracy.<\/li>\n<li><strong>Both:<\/strong> Use campaign-level attribution (UTM parameters in app store URLs) instead of user-level deferred linking.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is a valid choice when the deferred link content is not critical to the user&#39;s first experience.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Detecting Whether to Show the Prompt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You do not want to show the paste permission prompt to users who did not come from a deferred link. Showing it to every new user, regardless of acquisition channel (organic, search, direct install), creates unnecessary friction.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Heuristics for Filtering<\/h3>\n\n\n\n<pre><code class=\"language-swift\">func shouldAttemptClipboardMatch() -&gt; Bool {\n    \/\/ Only attempt on first launch\n    guard isFirstLaunch() else { return false }\n\n    \/\/ Check if we have any indication the user came from a link\n    \/\/ This could be a server-side flag, a campaign parameter, etc.\n\n    \/\/ Check if clipboard contains something that looks like our token\n    \/\/ Note: on iOS 16+, even checking UIPasteboard.general.hasStrings\n    \/\/ may trigger the paste notification (but not the full prompt)\n    if #available(iOS 16.0, *) {\n        \/\/ On iOS 16+, we can check if pasteboard has strings without\n        \/\/ triggering the full permission prompt\n        return UIPasteboard.general.hasStrings\n    } else {\n        return true\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note: <code>UIPasteboard.general.hasStrings<\/code> returns a boolean without reading the actual content. On iOS 16+, this check does not trigger the paste permission prompt, but it may show the paste banner on iOS 14-15. Use it as a preliminary filter.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Server-Side Hints<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A more reliable approach: when the user clicks your link on the web, record the click server-side with a session identifier. When the app opens, send a lightweight &quot;did I come from a link?&quot; check to your server using non-identifying information (e.g., a first-party cookie if available). If the server confirms a recent click, then show the clipboard prompt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This avoids showing the paste prompt to organic installs entirely.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the Paste Permission<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Simulator vs. Device<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The iOS Simulator does not accurately replicate paste permission behavior. Always test on a physical device:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Copy a token to the clipboard (in Safari on the device).<\/li>\n<li>Install your app (via Xcode or TestFlight).<\/li>\n<li>Launch the app.<\/li>\n<li>Verify the paste permission prompt appears.<\/li>\n<li>Test both &quot;Allow Paste&quot; and &quot;Don&#39;t Allow&quot; flows.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Resetting Permission State<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To test the paste permission prompt again after allowing it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Uninstall and reinstall the app. The paste permission is per-app-install, not permanent.<\/li>\n<li>On iOS 16+, the permission prompt appears each time the app reads the clipboard after a new install (not persisted across launches for the same install, but behavior varies).<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Automated Testing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For unit tests, mock <code>UIPasteboard<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-swift\">protocol PasteboardProvider {\n    var string: String? { get }\n    var hasStrings: Bool { get }\n}\n\nextension UIPasteboard: PasteboardProvider {}\n\nclass MockPasteboard: PasteboardProvider {\n    var string: String?\n    var hasStrings: Bool = false\n}\n\n\/\/ In your deferred link handler:\nclass DeferredLinkHandler {\n    let pasteboard: PasteboardProvider\n\n    init(pasteboard: PasteboardProvider = UIPasteboard.general) {\n        self.pasteboard = pasteboard\n    }\n\n    func checkForDeferredLink() -&gt; DeferredLink? {\n        guard let text = pasteboard.string else { return nil }\n        return parseDeferredLinkToken(text)\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku&#39;s Approach<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku&#39;s iOS SDK<\/a> handles the clipboard permission flow automatically. The SDK checks whether a clipboard match is likely before triggering the system prompt, reducing unnecessary permission requests for organic installs. When the prompt is needed, the SDK provides hooks for showing a pre-prompt explanation screen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Configure your deferred linking behavior in the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/deferred-deep-linking\/\">Tolinku dashboard<\/a>. For the broader deferred linking flow, see <a href=\"https:\/\/tolinku.com\/blog\/deferred-deep-linking-how-it-works\/\">how deferred deep linking works<\/a>. For clipboard-based matching details, see <a href=\"https:\/\/tolinku.com\/blog\/clipboard-based-deferred-linking\/\">clipboard-based deferred linking<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Handle the iOS 16+ paste permission prompt for clipboard-based deferred deep linking. Learn UX patterns, UIPasteControl, alternatives, and how to communicate with users.<\/p>\n","protected":false},"author":2,"featured_media":1304,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"iOS Paste Permission and Deferred Deep Links","rank_math_description":"Handle the iOS 16+ paste permission prompt for clipboard-based deferred deep linking. UX patterns, alternatives, and user communication.","rank_math_focus_keyword":"iOS paste permission deep links","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-ios-paste-permission-deferred-links.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-ios-paste-permission-deferred-links.png","footnotes":""},"categories":[11],"tags":[329,20,21,24,69,36,31,33],"class_list":["post-1305","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-clipboard","tag-deep-linking","tag-deferred-deep-linking","tag-ios","tag-mobile-development","tag-privacy","tag-swift","tag-user-experience"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1305"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1305\/revisions"}],"predecessor-version":[{"id":1306,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1305\/revisions\/1306"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1304"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}