Channel API Endpoint
Copy a setup prompt with the install steps and the full markdown guide for this plugin.
Channels are a core mechanism for managing app updates in Capgo. In self-hosted mode, you need to implement channel endpoints to handle device assignments, channel queries, and channel management operations.
Understanding Channels
Section titled “Understanding Channels”Channels allow you to:
- Control update distribution: Assign different app versions to different user groups
- A/B testing: Test new features with specific user segments
- Staged rollouts: Gradually deploy updates to minimize risk
- Environment separation: Separate development, staging, and production updates
Configuration
Section titled “Configuration”Configure the channel endpoint URL in your capacitor.config.json:
{ "plugins": { "CapacitorUpdater": { "channelUrl": "https://myserver.com/api/channel_self" } }}Channel Operations
Section titled “Channel Operations”The plugin performs different channel operations that your endpoint needs to handle:
1. List Compatible Channels (GET Request)
Section titled “1. List Compatible Channels (GET Request)”When the plugin calls listChannels(), it sends a GET request to retrieve all channels that are compatible with the device. This returns channels that match the device’s environment (dev/prod, emulator/real device) and allow either public access or self-assignment.
Request Format
Section titled “Request Format”// GET /api/channel_self// Headers:{ "Content-Type": "application/json"}
// Query parameters:interface ListChannelsRequest { app_id: string platform: "ios" | "android" | "electron" is_emulator: boolean is_prod: boolean key_id?: string}Response Format
Section titled “Response Format”[ { "id": 1, "name": "production", "public": true, "allow_self_set": false }, { "id": 2, "name": "beta", "public": false, "allow_self_set": true }]Understanding Channel Types
Section titled “Understanding Channel Types”The response includes two important flags for each channel:
-
public: true: This is a default channel. Devices cannot self-assign to it usingsetChannel(). Instead, if a device removes its channel assignment (usingunsetChannel()), it will automatically receive updates from this public channel if it matches the device’s conditions. -
allow_self_set: true: This is a self-assignable channel. Devices can explicitly assign themselves to this channel usingsetChannel(). This is useful for beta testing, A/B testing, or allowing users to opt-in to specific update tracks.
2. Get Channel (PUT Request)
Section titled “2. Get Channel (PUT Request)”When the plugin calls getChannel(), it sends a PUT request to retrieve the device’s current channel assignment.
Request Format
Section titled “Request Format”// PUT /api/channel_self// Headers:{ "Content-Type": "application/json"}
// Body:interface GetChannelRequest { device_id: string app_id: string platform: "ios" | "android" | "electron" plugin_version: string version_build: string version_code: string version_name: string is_emulator: boolean is_prod: boolean defaultChannel?: string channel?: string // For newer plugin versions, contains local channel override}Response Format
Section titled “Response Format”{ "status": "ok", "channel": "production", "allowSet": true, "message": "", "error": ""}3. Set Channel (POST Request)
Section titled “3. Set Channel (POST Request)”When the plugin calls setChannel(), it sends a POST request to assign the device to a specific channel.
Request Format
Section titled “Request Format”// POST /api/channel_selfinterface SetChannelRequest { device_id: string app_id: string channel: string platform: "ios" | "android" | "electron" plugin_version: string version_build: string version_code: string version_name: string is_emulator: boolean is_prod: boolean}Response Format
Section titled “Response Format”{ "status": "ok", "message": "Device assigned to channel successfully", "error": ""}Error Cases
Section titled “Error Cases”When a device tries to assign itself to a public channel (one with public: true), your endpoint should return an error:
{ "status": "error", "error": "public_channel_self_set_not_allowed", "message": "This channel is public and does not allow device self-assignment. Unset the channel and the device will automatically use the public channel."}When a device tries to assign itself to a channel that doesn’t allow self-assignment:
{ "status": "error", "error": "channel_self_set_not_allowed", "message": "This channel does not allow devices to self associate"}4. Unset Channel (DELETE Request)
Section titled “4. Unset Channel (DELETE Request)”When the plugin calls unsetChannel(), it sends a DELETE request to remove the device’s channel assignment.
Request Format
Section titled “Request Format”// DELETE /api/channel_selfinterface UnsetChannelRequest { device_id: string app_id: string platform: "ios" | "android" | "electron" plugin_version: string version_build: string version_code: string version_name: string}Implementation Example
Section titled “Implementation Example”Here’s a JavaScript example of how to implement the channel endpoint:
interface ChannelRequest { device_id: string app_id: string channel?: string platform: "ios" | "android" | "electron" plugin_version: string version_build: string version_code: string version_name: string}
interface ChannelResponse { status: "ok" | "error" channel?: string allowSet?: boolean message?: string error?: string}
export const handler = async (event) => { const method = event.httpMethod || event.method const body = JSON.parse(event.body || '{}') as ChannelRequest
const { device_id, app_id, channel, platform } = body
try { switch (method) { case 'GET': return await getDeviceChannel(device_id, app_id)
case 'POST': return await setDeviceChannel(device_id, app_id, channel!, platform)
case 'DELETE': return await unsetDeviceChannel(device_id, app_id)
default: return { status: "error", error: "Method not allowed" } } } catch (error) { return { status: "error", error: error.message } }}
async function getDeviceChannel(deviceId: string, appId: string): Promise<ChannelResponse> { // Query your database for device channel assignment const assignment = await database.getDeviceChannel(deviceId, appId)
if (assignment) { return { status: "ok", channel: assignment.channel, allowSet: assignment.allowSelfAssign } }
// Return default channel if no assignment found return { status: "ok", channel: "production", // Your default channel allowSet: true }}
async function setDeviceChannel( deviceId: string, appId: string, channel: string, platform: string): Promise<ChannelResponse> { // Validate channel exists and allows self-assignment const channelConfig = await database.getChannelConfig(channel, appId)
if (!channelConfig) { return { status: "error", error: "Channel not found" } }
if (!channelConfig.allowDeviceSelfSet) { return { status: "error", error: "Channel does not allow self-assignment" } }
// Check platform restrictions if (platform === "ios" && !channelConfig.ios) { return { status: "error", error: "Channel not available for iOS" } }
if (platform === "android" && !channelConfig.android) { return { status: "error", error: "Channel not available for Android" } }
if (platform === "electron" && !channelConfig.electron) { return { status: "error", error: "Channel not available for Electron" } }
// Save the assignment await database.setDeviceChannel(deviceId, appId, channel)
return { status: "ok", message: "Device assigned to channel successfully" }}
async function unsetDeviceChannel(deviceId: string, appId: string): Promise<ChannelResponse> { // Remove device channel assignment await database.removeDeviceChannel(deviceId, appId)
return { status: "ok", message: "Device channel assignment removed" }}Channel Configuration
Section titled “Channel Configuration”Your channel system should support these configuration options:
interface ChannelConfig { name: string appId: string
// Platform targeting ios: boolean // Allow updates to iOS devices android: boolean // Allow updates to Android devices electron: boolean // Allow updates to Electron apps
// Device type restrictions allow_emulator: boolean // Allow updates on emulator/simulator devices allow_device: boolean // Allow updates on real/physical devices
// Build type restrictions allow_dev: boolean // Allow updates on development builds (is_prod=false) allow_prod: boolean // Allow updates on production builds (is_prod=true)
// Channel assignment public: boolean // Default channel - devices fall back to this when no override allowDeviceSelfSet: boolean // Allow devices to self-assign via setChannel()
// Update policies disableAutoUpdate: "major" | "minor" | "version_number" | "none" disableAutoUpdateUnderNative: boolean}Device Filtering Logic
Section titled “Device Filtering Logic”When listing compatible channels (GET request), you should filter channels based on these conditions:
- Platform check: Channel must allow the device’s platform (
ios,android, orelectron) - Device type check:
- If
is_emulator=true: Channel must haveallow_emulator=true - If
is_emulator=false: Channel must haveallow_device=true
- If
- Build type check:
- If
is_prod=true: Channel must haveallow_prod=true - If
is_prod=false: Channel must haveallow_dev=true
- If
- Visibility check: Channel must be either
public=trueORallow_device_self_set=true
// Example filtering logicfunction getCompatibleChannels( platform: 'ios' | 'android' | 'electron', isEmulator: boolean, isProd: boolean, channels: ChannelConfig[]): ChannelConfig[] { return channels.filter(channel => { // Platform check if (!channel[platform]) return false
// Device type check if (isEmulator && !channel.allow_emulator) return false if (!isEmulator && !channel.allow_device) return false
// Build type check if (isProd && !channel.allow_prod) return false if (!isProd && !channel.allow_dev) return false
// Must be accessible (public or self-assignable) if (!channel.public && !channel.allowDeviceSelfSet) return false
return true })}Database Schema Example
Section titled “Database Schema Example”You’ll need to store channel configurations and device assignments:
-- Channels tableCREATE TABLE channels ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, app_id VARCHAR(255) NOT NULL,
-- Platform targeting ios BOOLEAN DEFAULT true, android BOOLEAN DEFAULT true, electron BOOLEAN DEFAULT true,
-- Device type restrictions allow_emulator BOOLEAN DEFAULT true, -- Allow emulator/simulator devices allow_device BOOLEAN DEFAULT true, -- Allow real/physical devices
-- Build type restrictions allow_dev BOOLEAN DEFAULT true, -- Allow development builds allow_prod BOOLEAN DEFAULT true, -- Allow production builds
-- Channel assignment public BOOLEAN DEFAULT false, -- Default channel (fallback) allow_device_self_set BOOLEAN DEFAULT false, -- Allow self-assignment
-- Update policies disable_auto_update VARCHAR(50) DEFAULT 'none', disable_auto_update_under_native BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(), UNIQUE(name, app_id));
-- Device channel assignments tableCREATE TABLE device_channels ( id SERIAL PRIMARY KEY, device_id VARCHAR(255) NOT NULL, app_id VARCHAR(255) NOT NULL, channel_name VARCHAR(255) NOT NULL, assigned_at TIMESTAMP DEFAULT NOW(), UNIQUE(device_id, app_id));Error Handling
Section titled “Error Handling”Handle common error scenarios:
// Channel not found{ "status": "error", "error": "Channel 'beta' not found"}
// Self-assignment not allowed{ "status": "error", "error": "Channel does not allow device self-assignment"}
// Platform not supported{ "status": "error", "error": "Channel not available for this platform"}
// Invalid request{ "status": "error", "error": "Missing required field: device_id"}Best Practices
Section titled “Best Practices”- Security: Validate all channel assignments against your business rules
- Logging: Log all channel operations for auditing and debugging
- Performance: Cache channel configurations to reduce database queries
- Validation: Verify device_id and app_id authenticity
- Rate Limiting: Implement rate limiting to prevent abuse
Integration with Updates
Section titled “Integration with Updates”Channel assignments work together with your Update API Endpoint. When a device requests an update, check its channel assignment to determine which version to serve:
async function getUpdateForDevice(deviceId: string, appId: string) { // Get device's channel assignment const channelAssignment = await getDeviceChannel(deviceId, appId) const channel = channelAssignment.channel || 'production'
// Get the version assigned to this channel const channelVersion = await getChannelVersion(channel, appId)
return { version: channelVersion.version, url: channelVersion.url, checksum: channelVersion.checksum }}This creates a complete self-hosted channel management system that gives you full control over how updates are distributed to your users.
Keep going from Channel API Endpoint
Section titled “Keep going from Channel API Endpoint”If you are using Channel API Endpoint to plan channel routing and staged rollout, connect it with Using @capgo/capacitor-updater for the native capability in Using @capgo/capacitor-updater, Channels for the implementation detail in Channels, Channels for the implementation detail in Channels, Channels for the implementation detail in Channels, and Beta Testing Solution for the product workflow in Beta Testing Solution.