npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ciabosoftwaresolutions/capacitor-distribution-channel

v0.1.2

Published

Capacitor plugin to detect how an app was installed — TestFlight vs App Store on iOS, Play Store vs sideloaded on Android.

Readme

@ciabosoftwaresolutions/capacitor-distribution-channel

Capacitor plugin to detect how an app was installed — TestFlight vs App Store on iOS, Play Store vs sideloaded on Android. One line of code, zero native setup.

npm version npm downloads License: MIT

If this plugin saves you time, consider buying us a coffee ☕

Buy Me A Coffee


Why

A single build can't dynamically re-point itself at a different backend — the usual approach is building separate dev/prod variants and remembering which one to archive for TestFlight vs the App Store. That's fine, but it's manual, and it's easy to accidentally ship the wrong one.

iOS actually gives you a real, Apple-documented way to tell TestFlight and App Store installs apart at runtime: the app's receipt file path. This plugin exposes that check, plus the closest equivalent on Android.

import { DistributionChannel } from '@ciabosoftwaresolutions/capacitor-distribution-channel';

const { channel } = await DistributionChannel.getDistributionChannel();
// channel === 'testflight' | 'appstore' | 'development' | 'playstore' | 'sideload' | 'web' | 'unknown'

Platform support

| | iOS | Android | |---|---|---| | Detection basis | App receipt path (Bundle.main.appStoreReceiptURL) | Install source (PackageManager install-source info) | | Distinguishes | TestFlight vs App Store vs local Xcode Debug run | Google Play vs sideloaded — not which Play track | | Accuracy | Exact — this is Apple's own documented signal | Partial — Play Store doesn't expose which release track (internal/closed/open/production) an install came from, so playstore covers all of them | | Native setup required | None | None |

Read that last row carefully for Android: playstore does not mean "production." It means "Google Play was the installer," which is true for every testing track too. There is no Android API that tells you which track an app was installed from. If you need dev/prod switching on Android, you still need separate build variants — this plugin can't help there, and doesn't pretend to.


How it works

There's no server call, no configuration, and no native project changes on either platform — the values are read directly from OS-level signals already present on the device.

iOS      App launch  ──▶  Bundle.main.appStoreReceiptURL  ──▶  'testflight' | 'appstore' | 'development'
Android  App launch  ──▶  PackageManager install-source    ──▶  'playstore' | 'sideload'

| Value | Platform | Meaning | |---|---|---| | testflight | iOS | Installed via TestFlight (sandboxReceipt). | | appstore | iOS | Installed via the App Store — production. | | development | iOS | Running directly from Xcode (Debug configuration). | | playstore | Android | Installed via Google Play — any release track. | | sideload | Android | Installed by anything other than Google Play (adb, a third-party store, a manually distributed APK). | | web | Both | Running as a plain website, not inside a native shell. | | unknown | iOS | No receipt present (rare — e.g. certain jailbreak/tamper scenarios). |


Requirements

| Tool | Minimum version | |---|---| | Capacitor | 8.0 | | iOS deployment target | 13.0 | | Android minSdkVersion | 23 | | Node | 18+ |

Installation

npm install @ciabosoftwaresolutions/capacitor-distribution-channel
npx cap sync

That's it — no Xcode capabilities, no Info.plist keys, no AndroidManifest.xml permissions, no build.gradle edits. The plugin auto-registers on both platforms.


Usage

The most common use case — pick a backend at launch:

import { DistributionChannel } from '@ciabosoftwaresolutions/capacitor-distribution-channel';

const { channel } = await DistributionChannel.getDistributionChannel();

switch (channel) {
  case 'testflight':
  case 'development':
    // point at your dev/staging backend
    setApiBaseUrl('https://api-dev.yourapp.com');
    break;
  case 'appstore':
    // point at production
    setApiBaseUrl('https://api.yourapp.com');
    break;
  case 'playstore':
  case 'sideload':
    // Android: pick your build-time-configured environment as usual —
    // this plugin cannot tell testing tracks from production here.
    setApiBaseUrl(environment.apiBaseUrl);
    break;
  case 'web':
  case 'unknown':
  default:
    setApiBaseUrl(environment.apiBaseUrl);
    break;
}

Other common uses: gating verbose logging / debug menus to non-production installs, tagging crash reports and analytics events with the install channel, or showing a "you're on the beta build" banner to TestFlight testers.


API

getDistributionChannel()

getDistributionChannel() => any

Returns how this app was installed.

iOS detection is based on the app's receipt file (Bundle.main.appStoreReceiptURL) — Apple's own documented signal for distinguishing a TestFlight install (sandboxReceipt) from a real App Store install (receipt). Builds run directly from Xcode are reported as development rather than testflight, since a Debug build also carries a sandbox receipt path but was never actually distributed.

Android detection is based on PackageManager's install-source info, which can only tell you whether Google Play was the installer — it cannot tell you which release track. See DistributionChannel for details on what each returned value means.

Returns: any


Interfaces

DistributionChannelResult

| Prop | Type | | ------------- | ------------------------------------------------------------------- | | channel | DistributionChannel |

Type Aliases

DistributionChannel

How the app was installed/distributed.

  • testflight — iOS, installed via TestFlight.
  • appstore — iOS, installed via the App Store (production).
  • development — iOS, running directly from Xcode (Debug configuration).
  • playstore — Android, installed via Google Play. This covers every release track (internal, closed, open, production) — Google Play does not expose which track an install came from, so there is no Android equivalent to the iOS testflight vs appstore distinction.
  • sideload — Android, installed by any means other than Google Play (adb install, a third-party store, a manually distributed APK, etc).
  • web — running as a plain website, not inside a native shell.
  • unknown — unable to determine (e.g. no receipt present on iOS).

'testflight' | 'appstore' | 'development' | 'playstore' | 'sideload' | 'web' | 'unknown'


FAQ

Why does a TestFlight build show development in my Simulator?

The Simulator and local Xcode Debug runs are always reported as development, regardless of how the archive would ultimately be distributed — #if DEBUG is checked before the receipt is ever inspected. To see real testflight / appstore values you need a Release build installed through TestFlight or the App Store, on a physical device.

Can I tell internal testing apart from production on Android?

No — and no Capacitor or native plugin can. Google Play's PackageManager install-source API only reports the installer package (com.android.vending), not which release track served the install. If you need that distinction, use separate Android build variants/flavors configured at build time instead.

Does this require network access?

No. Everything is read locally from the OS — no request is made, so it works offline and returns instantly.


Contributing

PRs welcome! Please open an issue first for non-trivial changes.

npm install
npm run build

Before submitting a PR, run the platform verify scripts (requires Xcode/CocoaPods for iOS, Android SDK for Android):

npm run verify:web      # build
npm run verify:ios      # pod install + xcodebuild
npm run verify:android  # gradlew build + test
npm run verify          # all three

Support

If this plugin helped you ship faster, consider buying us a coffee — it helps keep the project maintained and free for everyone. ☕

Buy Me A Coffee


License

MIT © Ciabo Software Solutions