The @capgo/capacitor-ibeacon package enables iBeacon detection and monitoring in your Capacitor apps, providing proximity detection, region monitoring, and beacon ranging capabilities. In this tutorial, we will guide you through the installation, configuration, and usage of this package in your Ionic Capacitor app.
To install the @capgo/capacitor-ibeacon package, run the following command in your project's root directory:
npm install @capgo/capacitor-ibeacon
npx cap sync
Add the following permissions to your Info.plist file:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to detect nearby beacons</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to monitor beacons in the background</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to detect nearby beacons</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
The plugin uses native CoreLocation framework and works out of the box on iOS after adding these permissions.
Add the necessary permissions to your AndroidManifest.xml:
<manifest>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
The Android implementation requires the AltBeacon library. Add it to your app's build.gradle:
dependencies {
implementation 'org.altbeacon:android-beacon-library:2.20+'
}
Before using beacon features, request location permissions:
import { CapacitorIbeacon } from '@capgo/capacitor-ibeacon';
async function requestPermissions() {
// Request "When In Use" permission (for foreground use)
const { status } = await CapacitorIbeacon.requestWhenInUseAuthorization();
console.log('Permission status:', status);
if (status === 'authorized_when_in_use' || status === 'authorized_always') {
console.log('Permission granted');
} else {
console.log('Permission denied');
}
}
async function requestAlwaysPermission() {
// Request "Always" permission (for background monitoring)
const { status } = await CapacitorIbeacon.requestAlwaysAuthorization();
console.log('Always permission status:', status);
}
Verify that the device supports beacon detection:
async function checkCapabilities() {
// Check if Bluetooth is enabled
const { enabled } = await CapacitorIbeacon.isBluetoothEnabled();
if (!enabled) {
console.log('Please enable Bluetooth');
return false;
}
// Check if ranging is available
const { available } = await CapacitorIbeacon.isRangingAvailable();
if (!available) {
console.log('Beacon ranging is not available on this device');
return false;
}
return true;
}
Monitoring allows you to detect when users enter or exit a beacon region, even in the background:
async function startMonitoring() {
await CapacitorIbeacon.startMonitoringForRegion({
identifier: 'MyStoreRegion',
uuid: 'B9407F30-F5F8-466E-AFF9-25556B57FE6D',
major: 1, // Optional
minor: 2 // Optional
});
console.log('Started monitoring for beacon region');
}
async function stopMonitoring() {
await CapacitorIbeacon.stopMonitoringForRegion({
identifier: 'MyStoreRegion',
uuid: 'B9407F30-F5F8-466E-AFF9-25556B57FE6D'
});
console.log('Stopped monitoring');
}
Ranging provides continuous updates about nearby beacons and their distances (foreground only):
async function startRanging() {
await CapacitorIbeacon.startRangingBeaconsInRegion({
identifier: 'MyStoreRegion',
uuid: 'B9407F30-F5F8-466E-AFF9-25556B57FE6D'
});
console.log('Started ranging beacons');
}
async function stopRanging() {
await CapacitorIbeacon.stopRangingBeaconsInRegion({
identifier: 'MyStoreRegion',
uuid: 'B9407F30-F5F8-466E-AFF9-25556B57FE6D'
});
console.log('Stopped ranging');
}
Set up event listeners to respond to beacon detection:
import { PluginListenerHandle } from '@capacitor/core';
let rangingListener: PluginListenerHandle;
let enterListener: PluginListenerHandle;
let exitListener: PluginListenerHandle;
async function setupBeaconListeners() {
// Listen for ranging updates (continuous distance measurements)
rangingListener = await CapacitorIbeacon.addListener(
'didRangeBeacons',
(data) => {
console.log('Beacons detected:', data.beacons.length);
data.beacons.forEach(beacon => {
console.log(`Beacon: ${beacon.uuid}`);
console.log(`Major: ${beacon.major}, Minor: ${beacon.minor}`);
console.log(`Distance: ${beacon.accuracy.toFixed(2)}m`);
console.log(`Proximity: ${beacon.proximity}`);
console.log(`RSSI: ${beacon.rssi}`);
// Handle based on proximity
switch (beacon.proximity) {
case 'immediate':
console.log('Very close to beacon (< 0.5m)');
break;
case 'near':
console.log('Near beacon (0.5m - 3m)');
break;
case 'far':
console.log('Far from beacon (> 3m)');
break;
case 'unknown':
console.log('Distance unknown');
break;
}
});
}
);
// Listen for region entry
enterListener = await CapacitorIbeacon.addListener(
'didEnterRegion',
(data) => {
console.log('Entered beacon region:', data.region.identifier);
// Show welcome message, trigger content, etc.
showWelcomeNotification();
}
);
// Listen for region exit
exitListener = await CapacitorIbeacon.addListener(
'didExitRegion',
(data) => {
console.log('Exited beacon region:', data.region.identifier);
// Show goodbye message, save data, etc.
showGoodbyeNotification();
}
);
// Listen for region state determination
const stateListener = await CapacitorIbeacon.addListener(
'didDetermineStateForRegion',
(data) => {
console.log(`Region ${data.region.identifier}: ${data.state}`);
// state can be 'inside', 'outside', or 'unknown'
}
);
}
function removeListeners() {
rangingListener?.remove();
enterListener?.remove();
exitListener?.remove();
}
Turn your device into an iBeacon transmitter:
async function startAdvertising() {
try {
await CapacitorIbeacon.startAdvertising({
uuid: 'B9407F30-F5F8-466E-AFF9-25556B57FE6D',
major: 1,
minor: 2,
identifier: 'MyBeacon',
measuredPower: -59 // Optional: calibrated RSSI at 1 meter
});
console.log('Started advertising as iBeacon');
} catch (error) {
console.error('Failed to start advertising:', error);
}
}
async function stopAdvertising() {
await CapacitorIbeacon.stopAdvertising();
console.log('Stopped advertising');
}
For smoother distance measurements on Android:
async function enableFiltering() {
await CapacitorIbeacon.enableARMAFilter({ enabled: true });
console.log('ARMA filtering enabled');
}
Here's a complete example of a beacon service:
import { CapacitorIbeacon } from '@capgo/capacitor-ibeacon';
import { PluginListenerHandle } from '@capacitor/core';
export class BeaconService {
private listeners: PluginListenerHandle[] = [];
private isInitialized = false;
async initialize(): Promise<boolean> {
try {
// Check permissions
const { status } = await CapacitorIbeacon.requestWhenInUseAuthorization();
if (status !== 'authorized_when_in_use' && status !== 'authorized_always') {
console.error('Location permission not granted');
return false;
}
// Check Bluetooth
const { enabled } = await CapacitorIbeacon.isBluetoothEnabled();
if (!enabled) {
console.error('Bluetooth is not enabled');
return false;
}
// Check ranging availability
const { available } = await CapacitorIbeacon.isRangingAvailable();
if (!available) {
console.error('Ranging not available');
return false;
}
this.setupListeners();
this.isInitialized = true;
return true;
} catch (error) {
console.error('Failed to initialize beacon service:', error);
return false;
}
}
private async setupListeners() {
// Ranging listener
this.listeners.push(
await CapacitorIbeacon.addListener('didRangeBeacons', (data) => {
this.handleRangedBeacons(data.beacons);
})
);
// Entry listener
this.listeners.push(
await CapacitorIbeacon.addListener('didEnterRegion', (data) => {
console.log('Welcome! Entered region:', data.region.identifier);
this.onEnterRegion(data.region);
})
);
// Exit listener
this.listeners.push(
await CapacitorIbeacon.addListener('didExitRegion', (data) => {
console.log('Goodbye! Left region:', data.region.identifier);
this.onExitRegion(data.region);
})
);
// State listener
this.listeners.push(
await CapacitorIbeacon.addListener('didDetermineStateForRegion', (data) => {
console.log(`Region ${data.region.identifier} state: ${data.state}`);
})
);
}
async startMonitoringRegion(
uuid: string,
identifier: string,
major?: number,
minor?: number
) {
if (!this.isInitialized) {
throw new Error('Beacon service not initialized');
}
await CapacitorIbeacon.startMonitoringForRegion({
identifier,
uuid,
major,
minor
});
console.log(`Started monitoring region: ${identifier}`);
}
async startRangingRegion(uuid: string, identifier: string) {
if (!this.isInitialized) {
throw new Error('Beacon service not initialized');
}
await CapacitorIbeacon.startRangingBeaconsInRegion({
identifier,
uuid
});
console.log(`Started ranging in region: ${identifier}`);
}
async stopMonitoringRegion(uuid: string, identifier: string) {
await CapacitorIbeacon.stopMonitoringForRegion({
identifier,
uuid
});
console.log(`Stopped monitoring region: ${identifier}`);
}
async stopRangingRegion(uuid: string, identifier: string) {
await CapacitorIbeacon.stopRangingBeaconsInRegion({
identifier,
uuid
});
console.log(`Stopped ranging in region: ${identifier}`);
}
private handleRangedBeacons(beacons: any[]) {
if (beacons.length === 0) {
return;
}
// Find nearest beacon
const nearest = beacons.reduce((prev, current) => {
return current.accuracy < prev.accuracy ? current : prev;
});
console.log('Nearest beacon:', {
uuid: nearest.uuid,
major: nearest.major,
minor: nearest.minor,
distance: `${nearest.accuracy.toFixed(2)}m`,
proximity: nearest.proximity
});
// Handle proximity-based actions
this.handleProximityChange(nearest);
}
private handleProximityChange(beacon: any) {
switch (beacon.proximity) {
case 'immediate': // < 0.5m
this.onImmediateProximity(beacon);
break;
case 'near': // 0.5m - 3m
this.onNearProximity(beacon);
break;
case 'far': // > 3m
this.onFarProximity(beacon);
break;
case 'unknown':
console.log('Beacon distance unknown');
break;
}
}
private onEnterRegion(region: any) {
// Implement your logic for entering a region
// E.g., show welcome notification, load content, etc.
console.log('Implementing enter region logic for:', region);
}
private onExitRegion(region: any) {
// Implement your logic for exiting a region
// E.g., save state, show goodbye message, etc.
console.log('Implementing exit region logic for:', region);
}
private onImmediateProximity(beacon: any) {
console.log('Very close to beacon - trigger immediate action');
// E.g., unlock door, show detailed info, etc.
}
private onNearProximity(beacon: any) {
console.log('Near beacon - show relevant content');
// E.g., display product info, show directions, etc.
}
private onFarProximity(beacon: any) {
console.log('Far from beacon - show general info');
// E.g., show overview, general notifications, etc.
}
cleanup() {
this.listeners.forEach(listener => listener.remove());
this.listeners = [];
this.isInitialized = false;
}
}
// Initialize the beacon service
const beaconService = new BeaconService();
async function initBeacons() {
const initialized = await beaconService.initialize();
if (initialized) {
// Start monitoring for your beacon region
await beaconService.startMonitoringRegion(
'B9407F30-F5F8-466E-AFF9-25556B57FE6D',
'MyStore',
1,
2
);
// Start ranging for distance updates
await beaconService.startRangingRegion(
'B9407F30-F5F8-466E-AFF9-25556B57FE6D',
'MyStore'
);
}
}
// Clean up when component unmounts
function cleanup() {
beaconService.cleanup();
}
Trigger notifications or content when users approach your store:
private onEnterRegion(region: any) {
if (region.identifier === 'MyStore') {
showNotification('Welcome to our store! Check out today\'s deals!');
loadPromotionalContent();
}
}
Guide users through a building:
private handleRangedBeacons(beacons: any[]) {
const sortedBeacons = beacons.sort((a, b) => a.accuracy - b.accuracy);
const nearest = sortedBeacons[0];
// Update navigation based on nearest beacon
updateNavigationRoute(nearest.major, nearest.minor);
}
Automatically check-in when users enter a location:
private async onEnterRegion(region: any) {
if (region.identifier === 'Office') {
await checkInEmployee();
}
}
Provide contextual information about exhibits:
private onNearProximity(beacon: any) {
// Each beacon represents a different exhibit
const exhibitInfo = getExhibitInfo(beacon.major, beacon.minor);
displayExhibitContent(exhibitInfo);
}
Request Appropriate Permissions
Battery Optimization
Handle Bluetooth State
Error Handling
try {
await CapacitorIbeacon.startMonitoringForRegion(region);
} catch (error) {
console.error('Failed to start monitoring:', error);
// Show user-friendly error message
}
Clean Up Resources
Test on Real Devices
UIBackgroundModes includes location (iOS)ACCESS_BACKGROUND_LOCATION is added (Android 10+)measuredPower at exactly 1 meter from beaconThe @capgo/capacitor-ibeacon plugin provides powerful beacon detection and monitoring capabilities for your Capacitor app. By following this tutorial, you can implement proximity-based features, indoor navigation, attendance tracking, and location-aware experiences.
For more information, visit the official documentation or check the GitHub repository.