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

supabase-expo-ota-updates

v0.3.0

Published

CLI and Expo config plugin for publishing OTA updates to Supabase Storage

Downloads

80

Readme

supabase-expo-ota-updates

Self-hosted OTA updates for Expo apps powered by Supabase. Ship updates instantly without app store review.

  • CLI for publishing, managing, and rolling back updates
  • Expo Config Plugin to wire expo-updates to your Supabase backend
  • React Native Runtime with update hook, HOC wrapper, progress UI, and auto-rollback on crash
  • Supabase Backend with Edge Functions, Postgres schema, and Storage

Demo

https://github.com/user-attachments/assets/9c39ef74-abe6-4cd9-b298-35cae3cbc10b

Install

npm install supabase-expo-ota-updates

Peer dependencies: expo (>=50), expo-updates (>=0.24)

Quick Start

1. Initialize

# Interactive setup (prompts for URL, key, channel, etc.)
npx supabase-expo-ota-updates init

# Or non-interactive
SUPABASE_URL=https://your-project.supabase.co \
npx supabase-expo-ota-updates init

This scaffolds migrations, edge functions, config files, and optionally deploys everything.

2. Add Plugin to Expo Config

// app.config.js
module.exports = {
  expo: {
    plugins: [
      [
        'supabase-expo-ota-updates/plugin',
        {
          url: process.env.EXPO_PUBLIC_OTA_URL,
          channel: process.env.EXPO_PUBLIC_ENV || 'PRODUCTION',
          runtimeVersionPolicy: 'nativeVersion', // or 'fingerprint'
          checkAutomatically: 'ON_LOAD',
        },
      ],
    ],
  },
};

3. Add In-App Updates (Optional)

Option A: Wrap your app (simplest)

import { OtaUpdater } from 'supabase-expo-ota-updates/runtime';

function App() {
  return <YourApp />;
}

export default OtaUpdater.wrap({
  updateMode: 'auto',
  fallback: ({ status, progress }) => (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>{status === 'downloading' ? `${Math.round(progress * 100)}%` : 'Checking for updates...'}</Text>
    </View>
  ),
})(App);

Option B: Use the hook (full control)

import { useOtaUpdate } from 'supabase-expo-ota-updates/runtime';

function App() {
  const {
    status,
    progress,
    isUpdateAvailable,
    isUpdateReady,
    updateInfo,
    checkForUpdate,
    downloadUpdate,
    applyUpdate,
  } = useOtaUpdate({ checkOnMount: true });

  if (isUpdateReady) {
    return (
      <View>
        <Text>Update ready: {updateInfo?.message}</Text>
        <Button title="Restart to update" onPress={applyUpdate} />
      </View>
    );
  }

  return <YourApp />;
}

4. Set Environment Variables

cp .env.example .env

| Variable | Required | Description | |----------|----------|-------------| | SUPABASE_URL | Yes | Supabase project URL | | SUPABASE_SERVICE_ROLE_KEY | Yes | Service role key (CLI only, never bundled) | | EXPO_PUBLIC_OTA_URL | Yes | Manifest endpoint URL | | EXPO_PUBLIC_ENV | Yes | Channel name (e.g., PRODUCTION) | | VERSION_NUMBER | Yes | App version (e.g., 1.0.0) | | IOS_BUILD_NUMBER | Yes | iOS build number | | ANDROID_BUILD_NUMBER | Yes | Android version code |

5. Validate & Publish

# Validate setup
npx supabase-expo-ota-updates doctor

# Publish iOS update
npx supabase-expo-ota-updates publish --platform ios --channel PRODUCTION

# Publish with options
npx supabase-expo-ota-updates publish --platform android \
  --channel PRODUCTION \
  --message "Bug fix for login" \
  --force-update \
  --rollout 50

CLI Reference

npx supabase-expo-ota-updates <command> [options]

| Command | Description | |---------|-------------| | init | Interactive bootstrap (setup + deploy) | | setup | Scaffold Supabase infrastructure | | publish | Build and publish OTA update | | list | List OTA updates with filters | | rollback | Rollback to previous update | | console | Interactive dashboard | | cleanup | Remove old updates | | cron | Generate SQL for scheduled cleanup | | doctor | Validate configuration | | info | Show current configuration |

publish

npx supabase-expo-ota-updates publish --platform ios [options]

| Flag | Description | |------|-------------| | --platform <ios\|android> | Target platform (required) | | --channel <name> | Channel name | | --force-update, -f | Mark as mandatory update | | --rollout <0-100> | Gradual rollout percentage | | --message, -m <text> | Update changelog | | --app-version <semver> | App version for matching | | --runtime-version <ver> | Override runtime version | | --no-build | Skip expo export step | | --dry-run | Simulate without changes |

list

npx supabase-expo-ota-updates list [options]

| Flag | Description | |------|-------------| | --platform <ios\|android> | Filter by platform | | --channel <name> | Filter by channel | | --active | Show only active updates | | --limit <n> | Number of results (default: 20) | | --format <table\|json> | Output format |

rollback

npx supabase-expo-ota-updates rollback --platform ios --channel PROD
npx supabase-expo-ota-updates rollback --platform ios --channel PROD --to <update-id>

console

Interactive terminal dashboard for managing updates:

npx supabase-expo-ota-updates console

Plugin Options

interface PluginOptions {
  url: string;                        // Manifest endpoint URL (required)
  channel: string;                    // Channel name (required)
  enabled?: boolean;                  // Enable/disable plugin (default: true)
  runtimeVersionPolicy?: string;      // 'nativeVersion' | 'fingerprint' | 'appVersion' | 'sdkVersion'
  checkAutomatically?: string;        // 'ON_LOAD' | 'NEVER' | 'ON_ERROR_RECOVERY' | 'WIFI_ONLY'
  codeSigningCertificate?: string;    // Path to code signing certificate
  codeSigningMetadata?: {             // Code signing metadata
    keyid: string;
    alg: string;
  };
}

Runtime API

useOtaUpdate(options?)

const {
  status,           // 'idle' | 'checking' | 'downloading' | 'ready' | 'error'
  progress,         // 0 to 1
  error,            // Error | null
  updateInfo,       // { id, message, isForceUpdate, rolloutPercentage }
  isUpdateAvailable,
  isUpdateReady,
  checkForUpdate,   // () => Promise<boolean>
  downloadUpdate,   // () => Promise<boolean>
  applyUpdate,      // () => Promise<void>
} = useOtaUpdate({
  checkOnMount: true,     // Check on component mount
  autoDownload: false,    // Auto-download when found
  autoApply: false,       // Auto-apply when downloaded (reloads app)
  onUpdateAvailable: (info) => {},
  onUpdateReady: (info) => {},
  onError: (error) => {},
});

OtaUpdater.wrap(config)

export default OtaUpdater.wrap({
  updateMode: 'auto',          // 'auto' | 'manual'
  checkOnMount: true,
  fallback: CustomLoadingComponent,
  onUpdateAvailable: (info) => {},
  onError: (error) => {},
})(App);

RollbackManager

Auto-rollback when the app crashes repeatedly after an update:

import { RollbackManager } from 'supabase-expo-ota-updates/runtime';

// Call at app startup (automatic in useOtaUpdate)
await RollbackManager.initialize();

// Manually confirm update success
await RollbackManager.confirmUpdateSuccess();

// Check rollback state
const state = await RollbackManager.getState();

Fingerprint Runtime Version

Use @expo/fingerprint to automatically detect native code changes:

npm install @expo/fingerprint --save-dev
// app.config.js
['supabase-expo-ota-updates/plugin', {
  url: process.env.EXPO_PUBLIC_OTA_URL,
  channel: 'PRODUCTION',
  runtimeVersionPolicy: 'fingerprint',
}]

When @expo/fingerprint is not installed, the CLI falls back to hashing key config files (package.json, app.json, Podfile.lock, etc.).

Supabase Backend

The init/setup commands create:

  • Database: ota_updates and ota_assets tables with indexes and RPC functions
  • Storage: ota-bundles bucket with public read access
  • Edge Functions: ota-manifest (serves Expo Updates Protocol v1) and ota-cleanup (retention cleanup)

Advanced Features

  • Gradual Rollout: Deterministic hash-based rollout using device ID
  • Mandatory Updates: Force users to update before using the app
  • Device Tracking: Track update delivery per device
  • Signed URLs: Optional signed storage URLs for private buckets
  • Scheduled Cleanup: pg_cron integration for automatic old update removal

Existing Supabase Project

If your project already has Supabase configured:

npx supabase-expo-ota-updates init --skip-migrations --skip-functions

Contributing

See CONTRIBUTING.md.

License

MIT