{"id":1246,"date":"2026-05-29T09:00:00","date_gmt":"2026-05-29T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1246"},"modified":"2026-03-07T03:49:00","modified_gmt":"2026-03-07T08:49:00","slug":"chrome-custom-tabs-app-links","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/chrome-custom-tabs-app-links\/","title":{"rendered":"Chrome Custom Tabs and Android App Links"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Chrome Custom Tabs let apps open web content in a browser tab that looks like part of the app. They&#39;re faster than opening Chrome, more capable than a WebView, and used by millions of apps for in-app browsing. But when App Links are involved, Custom Tabs create a set of behaviors that confuse developers and users alike.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The core issue: when a user taps a link inside a Custom Tab, should Android open the linked app (via App Links) or keep the user in the Custom Tab? The answer depends on the Chrome version, the link verification status, and how the Custom Tab was launched. This guide covers all of it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the foundational App Links setup, see the <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">Android App Links complete guide<\/a>. For the difference between verified and unverified links, see <a href=\"https:\/\/tolinku.com\/blog\/verified-vs-unverified-app-links\/\">verified vs. unverified App Links<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Chrome Custom Tabs Work<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/developer.chrome.com\/docs\/android\/custom-tabs\/\" rel=\"nofollow noopener\" target=\"_blank\">Chrome Custom Tabs<\/a> are a feature of Chrome (and other Chromium-based browsers) that lets apps open URLs in a lightweight browser tab. The hosting app can customize the toolbar color, add action buttons, and define animations.<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Launch a Chrome Custom Tab\nval customTabsIntent = CustomTabsIntent.Builder()\n    .setToolbarColor(ContextCompat.getColor(this, R.color.primary))\n    .setShowTitle(true)\n    .build()\n\ncustomTabsIntent.launchUrl(this, Uri.parse(&quot;https:\/\/example.com\/page&quot;))\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From the user&#39;s perspective, it looks like an in-app browser. From the system&#39;s perspective, it&#39;s Chrome running in a separate process with a custom UI overlay.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Who Uses Custom Tabs<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Email apps<\/strong>: Gmail, Outlook open links in Custom Tabs<\/li>\n<li><strong>Social apps<\/strong>: Twitter\/X, Reddit, many others<\/li>\n<li><strong>Chat apps<\/strong>: Slack, Telegram (sometimes)<\/li>\n<li><strong>Any app<\/strong> that displays web content without building a full WebView<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This means your App Links need to work correctly when clicked inside a Custom Tab, because that&#39;s how many users will encounter them.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The App Links Behavior in Custom Tabs<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Default Behavior (Chrome 90+)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user taps a verified App Link inside a Chrome Custom Tab:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>First navigation<\/strong>: The Custom Tab opens the URL in the browser. It does NOT trigger App Links for the initial URL.<\/li>\n<li><strong>Subsequent navigation<\/strong>: If the user taps a link within the Custom Tab page, and that link is a verified App Link, Chrome checks the <a href=\"https:\/\/developers.google.com\/digital-asset-links\/v1\/getting-started\" rel=\"nofollow noopener\" target=\"_blank\">Digital Asset Links<\/a> file and may open the target app.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This means: if your app opens a Custom Tab pointing to <code>https:\/\/links.yourapp.com\/promo<\/code>, the system will NOT redirect back to your own app, even if that URL is a verified App Link. This is by design; it prevents a redirect loop.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Same-App Protection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Chrome specifically prevents a Custom Tab from triggering an App Link back to the app that launched it. This stops the circular navigation pattern:<\/p>\n\n\n\n<pre><code>Your App \u2192 Custom Tab \u2192 App Link \u2192 Your App \u2192 Custom Tab \u2192 ...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This protection is sensible but causes confusion when:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your marketing links and your App Links use the same domain<\/li>\n<li>You open a web page that contains links back to your own app<\/li>\n<li>You use a Custom Tab for web-based flows (OAuth, payments) that should redirect back to your app<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Cross-App Navigation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">App Links to <strong>other<\/strong> apps work normally from Custom Tabs. If a user is reading a page in your Custom Tab and taps a link that&#39;s a verified App Link for a different app, that app will open.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring Custom Tabs for App Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Warm-Up the Custom Tabs Service<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pre-connecting to the Custom Tabs service improves performance and ensures the browser is ready to handle navigation correctly.<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">class MainActivity : AppCompatActivity() {\n    private var customTabsClient: CustomTabsClient? = null\n    private var customTabsSession: CustomTabsSession? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        \/\/ Warm up Chrome Custom Tabs\n        CustomTabsClient.bindCustomTabsService(\n            this,\n            &quot;com.android.chrome&quot;,\n            object : CustomTabsServiceConnection() {\n                override fun onCustomTabsServiceConnected(\n                    name: ComponentName,\n                    client: CustomTabsClient\n                ) {\n                    customTabsClient = client\n                    client.warmup(0)\n                    customTabsSession = client.newSession(null)\n                }\n\n                override fun onServiceDisconnected(name: ComponentName) {\n                    customTabsClient = null\n                    customTabsSession = null\n                }\n            }\n        )\n    }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Handling Return Navigation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When you use a Custom Tab for a flow that should return to your app (like OAuth), use an intent flag to handle the return:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ For OAuth or payment flows that redirect back to your app\nfun launchAuthFlow(authUrl: String) {\n    val customTabsIntent = CustomTabsIntent.Builder()\n        .setSession(customTabsSession!!)\n        .build()\n\n    \/\/ Add FLAG_ACTIVITY_NO_HISTORY so the Custom Tab doesn&#39;t stay in the back stack\n    customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)\n    customTabsIntent.launchUrl(this, Uri.parse(authUrl))\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the return redirect, use a custom scheme or an App Link with an intent filter that catches the redirect URL:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;!-- In AndroidManifest.xml --&gt;\n&lt;activity\n    android:name=&quot;.AuthCallbackActivity&quot;\n    android:exported=&quot;true&quot;\n    android:launchMode=&quot;singleTask&quot;&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourapp.com&quot;\n            android:pathPrefix=&quot;\/auth\/callback&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Common Issues and Fixes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 1: App Link Doesn&#39;t Trigger from Custom Tab<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom<\/strong>: A user taps a verified App Link inside a Custom Tab, but the app doesn&#39;t open. The link loads in the browser instead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cause<\/strong>: Chrome&#39;s same-app protection is active, OR the App Link is not verified.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Verify the link is actually verified: <code>adb shell pm get-app-links --user cur &lt;package&gt;<\/code><\/li>\n<li>If the link targets the same app that launched the Custom Tab, this is expected behavior. Use a redirect through a different domain, or use an explicit intent instead.<\/li>\n<\/ol>\n\n\n\n<pre><code class=\"language-bash\"># Check verification status\nadb shell pm get-app-links --user cur com.example.app\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 2: Disambiguation Dialog Appears<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom<\/strong>: Instead of opening the app directly, Chrome shows a &quot;Open with&quot; dialog.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cause<\/strong>: The App Link is not verified (unverified links show a disambiguation dialog) or another app has registered the same domain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix<\/strong>: Ensure your <a href=\"https:\/\/developers.google.com\/digital-asset-links\/v1\/getting-started\" rel=\"nofollow noopener\" target=\"_blank\">Digital Asset Links file<\/a> is correctly hosted at <code>https:\/\/yourdomain.com\/.well-known\/assetlinks.json<\/code> and your app&#39;s SHA-256 fingerprint matches.<\/p>\n\n\n\n<pre><code class=\"language-json\">[{\n  &quot;relation&quot;: [&quot;delegate_permission\/common.handle_all_urls&quot;],\n  &quot;target&quot;: {\n    &quot;namespace&quot;: &quot;android_app&quot;,\n    &quot;package_name&quot;: &quot;com.example.app&quot;,\n    &quot;sha256_cert_fingerprints&quot;: [\n      &quot;AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99&quot;\n    ]\n  }\n}]\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 3: Custom Tab Stays Open After App Launch<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom<\/strong>: The app opens correctly, but the Custom Tab remains in the recent apps list or back stack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix<\/strong>: Use <code>FLAG_ACTIVITY_NO_HISTORY<\/code> when launching the Custom Tab, or call <code>finishAffinity()<\/code> in the Activity that handles the App Link.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 4: Links Don&#39;t Work on Non-Chrome Browsers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom<\/strong>: Custom Tabs work fine in Chrome but fail in Samsung Internet, Firefox, or other browsers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cause<\/strong>: Custom Tabs behavior varies by browser. Samsung Internet has its own Custom Tabs implementation with different App Link handling.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix<\/strong>: Test on the top 3-4 browsers used by your audience. For critical flows, consider using a standard <code>Intent.ACTION_VIEW<\/code> instead of Custom Tabs:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Fallback: open in the default browser (supports App Links reliably)\nval intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))\nstartActivity(intent)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Best Practices<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><p><strong>Don&#39;t use Custom Tabs for your own App Links.<\/strong> If you need to open a URL that should resolve back to your own app, use <code>Intent.ACTION_VIEW<\/code> instead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Use Custom Tabs for third-party web content.<\/strong> They&#39;re perfect for opening external links, documentation pages, or web content that doesn&#39;t need to redirect back to your app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Test with multiple browsers.<\/strong> Custom Tab behavior varies across Chrome, Samsung Internet, and other Chromium-based browsers. Test on the browsers your users actually use.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Handle the back button correctly.<\/strong> When a user presses back in a Custom Tab, they should return to your app, not to a blank screen. Set up the activity launch mode correctly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pre-warm the connection.<\/strong> Call <code>CustomTabsClient.warmup()<\/code> before the user taps a link. This reduces the perceived load time by several hundred milliseconds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For <a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku deep links<\/a>, the platform handles browser detection and routing automatically. When a Tolinku link is opened in a Custom Tab, the redirect chain resolves the App Link correctly without triggering the same-app protection issue. See the <a href=\"https:\/\/tolinku.com\/docs\/developer\/app-links\/\">App Links documentation<\/a> for setup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For testing your App Links in Custom Tabs and other contexts, see the <a href=\"https:\/\/tolinku.com\/blog\/testing-android-app-links\/\">testing Android App Links guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Understand how Chrome Custom Tabs interact with App Links on Android. Configure correct behavior for in-app browsers, handle navigation edge cases, and avoid common pitfalls.<\/p>\n","protected":false},"author":2,"featured_media":1245,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Chrome Custom Tabs and Android App Links: Complete Guide","rank_math_description":"Understand how Chrome Custom Tabs interact with App Links. Configure correct behavior for in-app browsers and custom tab navigation.","rank_math_focus_keyword":"chrome custom tabs app 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-chrome-custom-tabs-app-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-chrome-custom-tabs-app-links.png","footnotes":""},"categories":[10],"tags":[25,23,314,316,20,315,69],"class_list":["post-1246","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","tag-android","tag-app-links","tag-chrome","tag-custom-tabs","tag-deep-linking","tag-in-app-browser","tag-mobile-development"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1246","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=1246"}],"version-history":[{"count":3,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1246\/revisions"}],"predecessor-version":[{"id":2568,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1246\/revisions\/2568"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1245"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}