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://ioswebble.com/webble.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:
- Safari Extension — A free iOS app with a Safari Web Extension that bridges CoreBluetooth to web pages
- Content Script — Injected by the extension, polyfills
navigator.bluetoothwith the standard Web Bluetooth API - 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:
- SDK checks if the extension is installed
- If yes:
navigator.bluetoothworks natively. Your code runs unchanged. - 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
| Browser | Web Bluetooth | iOSWebBLE Action |
|---|---|---|
| Safari iOS 16+ | No (Apple blocks it) | Extension provides full support |
| Safari macOS 13+ | No | Extension provides support (V2) |
| Chrome 56+ | Native | No-op (native works) |
| Edge 79+ | Native | No-op |
| Firefox | No | Not supported |
CDN Script
The CDN script (webble.js) is a zero-dependency, <10KB script that handles everything automatically.
Basic Usage
<script src="https://ioswebble.com/webble.js"
data-key="wbl_YOUR_API_KEY"></script>
What It Does
- Detects iOS Safari and checks for the extension
- Polyfills
navigator.bluetoothvia the extension'snavigator.webble - Injects a Smart App Banner meta tag (passive install layer)
- On
requestDevice()without extension: shows an iOS-native bottom sheet install prompt - Saves return context (clipboard + localStorage) for the return-to-web-app flow
- Reports detection analytics (installed vs. not installed)
Configuration via Data Attributes
| Attribute | Description | Default |
|---|---|---|
data-key | Your API key (required) | — |
data-name | Your app name shown in prompts | Page title |
data-prompt | Set to "none" to disable auto-prompt | Auto |
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
| Hook | Purpose |
|---|---|
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
| Component | Purpose |
|---|---|
<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.
| Option | Type | Description |
|---|---|---|
key | string | API key (required) |
operatorName | string? | App name for install prompt |
banner | object | false | Banner configuration or false to disable |
onReady | () => void | Called when extension is detected |
onNotInstalled | () => void | Called when extension is NOT installed |
Banner Options
Configure how the install prompt appears (passed as banner in initIOSWebBLE or showInstallBanner).
| Option | Type | Default | Description |
|---|---|---|---|
mode | 'sheet' | 'banner' | 'sheet' | Bottom sheet (iOS-native) or lightweight banner bar |
position | 'top' | 'bottom' | 'bottom' | Bar position (banner mode only) |
text | string | Auto-generated | Custom banner text |
buttonText | string | 'Get iOSWebBLE (Free)' | CTA button text |
operatorName | string | Page title / hostname | Your app name in the prompt |
appStoreUrl | string | iOSWebBLE listing | App Store URL override |
dismissDays | number | 14 | Days to suppress after dismiss |
style | Record<string, string> | — | Custom CSS for banner bar mode |
React Hooks
useWebBLE()
Returns the core WebBLE context. Must be used inside <WebBLEProvider>.
| Property | Type | Description |
|---|---|---|
isAvailable | boolean | Whether Bluetooth is available |
isExtensionInstalled | boolean | Whether the WebBLE extension is detected |
isLoading | boolean | Whether detection is in progress |
isScanning | boolean | Whether a BLE scan is active |
devices | BluetoothDevice[] | Discovered devices |
error | Error | null | Last error |
requestDevice() | function | Request a BLE device (standard API) |
getDevices() | function | Get previously paired devices |
requestLEScan() | function | Start a BLE scan |
stopScan() | function | Stop the current scan |
useIOSWebBLE()
From @ioswebble/detect/react. Returns detection state.
| Property | Type | Description |
|---|---|---|
isInstalled | boolean | null | Extension installed (null while detecting) |
isDetecting | boolean | Whether detection is still running |
isIOSSafari | boolean | Whether on iOS Safari |
Events
The SDK dispatches custom events on the window object:
| Event | When |
|---|---|
ioswebble:ready | Extension detected and ready |
ioswebble:notinstalled | Extension NOT installed on iOS Safari |
webble:extension:ready | Extension 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:
- In Safari, tap aA in the address bar
- Tap the iOSWebBLE icon (may have a warning badge)
- Choose "Always Allow"
- 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
- On iOS Safari: the extension is not installed or not granted permissions
- On Firefox: Web Bluetooth is not supported
- On Chrome/Edge: should work natively — check
chrome://flags/#enable-experimental-web-platform-features
Privacy
iOSWebBLE is designed with privacy as a core principle:
- All BLE data processed locally. Bluetooth communication happens entirely on-device between the browser and CoreBluetooth. No data is ever proxied through a server.
- No browsing data collected. The extension cannot see page content, URLs, or any browsing activity. It only activates when a website specifically calls the Web Bluetooth API.
- Minimal analytics. The SDK reports only two data points: whether the extension is installed, and the origin domain. No PII, no device data, no cookies.
- Open source. The extension source code is available for audit on GitHub.
"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.