Documentation

Add Web Bluetooth support to iOS Safari in under 5 minutes.

Quick Start

iOSWebBLE brings the Web Bluetooth API to iOS Safari via a free Safari extension + a lightweight developer SDK. Your existing Web Bluetooth code works unchanged.

Option 1: CDN Script (Simplest)

Add one script tag before your application code:

<!-- Add before your app script -->
<script src="https://cdn.ioswebble.com/v1.js"
        data-key="wbl_YOUR_API_KEY"></script>

That's it. On iOS Safari, if the extension isn't installed, users see a native-feeling install prompt when they try to use Bluetooth. On all other browsers, the script is a no-op.

Option 2: NPM Package

npm install @ioswebble/detect
import { initIOSWebBLE } from '@ioswebble/detect';

initIOSWebBLE({
  key: 'wbl_YOUR_API_KEY',
  operatorName: 'My App',
});

Option 3: React SDK

npm install @webble/react
import { WebBLEProvider, useBluetooth } from '@webble/react';

function App() {
  return (
    <WebBLEProvider config={{ apiKey: 'wbl_YOUR_API_KEY' }}>
      <MyBluetoothApp />
    </WebBLEProvider>
  );
}

How It Works

The system has three parts:

  1. Safari Extension — A free iOS app with a Safari Web Extension that bridges CoreBluetooth to web pages
  2. Content Script — Injected by the extension, polyfills navigator.bluetooth with the standard Web Bluetooth API
  3. Developer SDK — Detects the extension, shows install prompts on iOS Safari, and provides React hooks for BLE operations

When a user visits your site on iOS Safari:

  1. SDK checks if the extension is installed
  2. If yes: navigator.bluetooth works natively. Your code runs unchanged.
  3. If no: When the user tries a BLE action, an iOS-native install prompt appears guiding them to the free App Store download

Zero lock-in. iOSWebBLE polyfills the standard Web Bluetooth API. If the user has Chrome, Edge, or any browser with native support, the SDK stays out of the way. Your code is always standard.

Browser Support

BrowserWeb BluetoothiOSWebBLE Action
Safari iOS 16+No (Apple blocks it)Extension provides full support
Safari macOS 13+NoExtension provides support (V2)
Chrome 56+NativeNo-op (native works)
Edge 79+NativeNo-op
FirefoxNoNot supported

CDN Script

The CDN script (webble.js) is a zero-dependency, <10KB script that handles everything automatically.

Basic Usage

<script src="https://cdn.ioswebble.com/v1.js"
        data-key="wbl_YOUR_API_KEY"></script>

What It Does

Configuration via Data Attributes

AttributeDescriptionDefault
data-keyYour API key (required)
data-nameYour app name shown in promptsPage title
data-promptSet to "none" to disable auto-promptAuto

Tip: The CDN script automatically injects a <meta name="apple-itunes-app"> Smart App Banner tag, giving you a passive always-on install layer for free.

React SDK

The @webble/react package provides a complete React SDK with hooks for every BLE operation.

npm install @webble/react

Provider Setup

Wrap your app with WebBLEProvider:

import { WebBLEProvider } from '@webble/react';

function App() {
  return (
    <WebBLEProvider config={{
      apiKey: 'wbl_YOUR_API_KEY',
      operatorName: 'My App',
    }}>
      <MyBluetoothApp />
    </WebBLEProvider>
  );
}

Using Hooks

import { useWebBLE, useDevice, useCharacteristic } from '@webble/react';

function HeartRateMonitor() {
  const { requestDevice, isAvailable } = useWebBLE();

  const handleConnect = async () => {
    const device = await requestDevice({
      filters: [{ services: ['heart_rate'] }],
    });
    // device is a standard BluetoothDevice
  };

  return (
    <button onClick={handleConnect} disabled={!isAvailable}>
      Connect Heart Rate Monitor
    </button>
  );
}

Available Hooks

HookPurpose
useWebBLE()Core context: availability, extension status, requestDevice
useBluetooth()Simplified BLE access with auto-detection
useDevice()Device connection state, services, connect/disconnect
useCharacteristic()Read, write, subscribe to a GATT characteristic
useNotifications()Subscribe to characteristic notifications with history
useScan()BLE scanning with device list management
useConnection()Connection state, RSSI monitoring, auto-reconnect

Components

ComponentPurpose
<DeviceScanner />Pre-built device scanner UI
<ServiceExplorer />GATT service/characteristic browser
<ConnectionStatus />Connection state indicator
<InstallationWizard />Extension install prompt (iOS-native bottom sheet)

Optional: @ioswebble/detect. If you install @ioswebble/detect as a peer dependency, the WebBLEProvider will automatically show iOS-native install prompts when an apiKey is configured. Without it, the basic InstallationWizard component is used as a fallback.

@ioswebble/detect

A lightweight (~4KB) package focused on detection and install prompting. Use this if you want fine-grained control or aren't using React.

npm install @ioswebble/detect

Programmatic API

import { initIOSWebBLE } from '@ioswebble/detect';

await initIOSWebBLE({
  key: 'wbl_YOUR_API_KEY',
  operatorName: 'FitTracker',
  banner: {
    mode: 'sheet',          // 'sheet' (default) or 'banner'
    dismissDays: 14,        // suppress after dismiss
  },
  onReady() { /* extension installed */ },
  onNotInstalled() { /* show fallback */ },
});

Auto-Init (Zero Code)

Import the auto module and configure via meta tags:

<meta name="ioswebble-key" content="wbl_YOUR_API_KEY">
<meta name="ioswebble-name" content="My App">
<script type="module">
  import '@ioswebble/detect/auto';
</script>

React Integration

import { IOSWebBLEProvider, useIOSWebBLE } from '@ioswebble/detect/react';

function Layout({ children }) {
  return (
    <IOSWebBLEProvider
      apiKey="wbl_YOUR_API_KEY"
      operatorName="FitTracker">
      {children}
    </IOSWebBLEProvider>
  );
}

Detection API

isIOSSafari()

Returns true if the current browser is Safari on iOS (including iPad).

import { isIOSSafari } from '@ioswebble/detect';

if (isIOSSafari()) {
  // Running on iOS Safari
}

isExtensionInstalled()

Returns a Promise<boolean> that resolves after checking for the extension (waits up to 2 seconds for injection).

import { isExtensionInstalled } from '@ioswebble/detect';

const installed = await isExtensionInstalled();

initIOSWebBLE(options)

Main entry point. Detects iOS Safari, checks extension, shows install prompt if needed.

OptionTypeDescription
keystringAPI key (required)
operatorNamestring?App name for install prompt
bannerobject | falseBanner configuration or false to disable
onReady() => voidCalled when extension is detected
onNotInstalled() => voidCalled when extension is NOT installed

Banner Options

Configure how the install prompt appears (passed as banner in initIOSWebBLE or showInstallBanner).

OptionTypeDefaultDescription
mode'sheet' | 'banner''sheet'Bottom sheet (iOS-native) or lightweight banner bar
position'top' | 'bottom''bottom'Bar position (banner mode only)
textstringAuto-generatedCustom banner text
buttonTextstring'Get iOSWebBLE (Free)'CTA button text
operatorNamestringPage title / hostnameYour app name in the prompt
appStoreUrlstringiOSWebBLE listingApp Store URL override
dismissDaysnumber14Days to suppress after dismiss
styleRecord<string, string>Custom CSS for banner bar mode

React Hooks

useWebBLE()

Returns the core WebBLE context. Must be used inside <WebBLEProvider>.

PropertyTypeDescription
isAvailablebooleanWhether Bluetooth is available
isExtensionInstalledbooleanWhether the WebBLE extension is detected
isLoadingbooleanWhether detection is in progress
isScanningbooleanWhether a BLE scan is active
devicesBluetoothDevice[]Discovered devices
errorError | nullLast error
requestDevice()functionRequest a BLE device (standard API)
getDevices()functionGet previously paired devices
requestLEScan()functionStart a BLE scan
stopScan()functionStop the current scan

useIOSWebBLE()

From @ioswebble/detect/react. Returns detection state.

PropertyTypeDescription
isInstalledboolean | nullExtension installed (null while detecting)
isDetectingbooleanWhether detection is still running
isIOSSafaribooleanWhether on iOS Safari

Events

The SDK dispatches custom events on the window object:

EventWhen
ioswebble:readyExtension detected and ready
ioswebble:notinstalledExtension NOT installed on iOS Safari
webble:extension:readyExtension content script injected (from extension itself)
// Listen for extension detection
window.addEventListener('ioswebble:ready', () => {
  console.log('Extension is active!');
});

Troubleshooting

Extension not detected after installation

The user may have enabled the extension but not granted website permissions. They need to:

  1. In Safari, tap aA in the address bar
  2. Tap the iOSWebBLE icon (may have a warning badge)
  3. Choose "Always Allow"
  4. Then "Always Allow on Every Website"

"Allow for One Day" expires silently. If a user chose "Allow for One Day" instead of "Always Allow," the extension will stop working the next day with no notification. Guide users to "Always Allow" in your onboarding.

Extension works on some sites but not others

The user may have chosen per-site permissions. They need to grant "Always Allow on Every Website" for consistent behavior.

HTTPS required

Web Bluetooth requires a secure context. Ensure your site uses HTTPS (or localhost for development).

navigator.bluetooth is undefined

Privacy

iOSWebBLE is designed with privacy as a core principle:

"Always Allow on Every Website" does NOT mean "always watching." It means the extension is available on every website, but only activates when a site explicitly requests Bluetooth access via the Web Bluetooth API.