Quick Start
From nothing to an enrolled device in about 15 minutes.
Quick Start
By the end of this page, you will have:
- A Hono server with OpenMDM's routes mounted.
- A device enrolled in development mode (no real Android agent needed for the first pass).
- A policy applied and a remote command queued.
This follows on from Installation — you should already have src/mdm.ts exporting a configured MDMInstance.
1. Mount the HTTP routes
Create your Hono entrypoint and .route('/mdm', honoAdapter(mdm)) to expose every OpenMDM endpoint under /mdm:
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { honoAdapter } from '@openmdm/hono';
import { mdm } from './mdm';
const app = new Hono();
// Your own routes can live alongside OpenMDM's.
app.get('/', (c) => c.text('hello from my app'));
// All agent + admin endpoints. See the Agent Wire Protocol page
// for what actually lives under /mdm/agent/*.
app.route('/mdm', honoAdapter(mdm));
serve({ fetch: app.fetch, port: 3000 });
console.log('listening on http://localhost:3000');Start it:
pnpm tsx src/server.tsYou should see requests to /mdm/agent/config return a 401 (no device token yet) — that's the middleware working.
2. Create a policy
Before enrolling anything, create a baseline policy devices will apply on first contact. For a working example, use kiosk mode that locks the device to a single test app:
import { mdm } from '../src/mdm';
const policy = await mdm.policies.create({
name: 'Default Kiosk',
description: 'Locks devices to the reference app',
isDefault: true,
settings: {
kioskMode: true,
mainApp: 'com.example.kioskapp',
lockStatusBar: true,
lockNavigationBar: true,
},
});
console.log('Created policy:', policy.id);pnpm tsx scripts/seed-policy.tsBecause isDefault: true, any device that enrolls without an explicit policy assignment will receive this one on its first heartbeat.
3. Subscribe to events
Event handlers live in the same process as the SDK. Register them at boot time so you know what the fleet is doing:
mdm.on('device.enrolled', async (event) => {
console.log(`[mdm] ${event.payload.device.id} just enrolled`);
});
mdm.on('device.heartbeat', async (event) => {
console.log(
`[mdm] ${event.payload.device.id} heartbeat, battery ${event.payload.heartbeat.batteryLevel}%`,
);
});
mdm.on('command.completed', async (event) => {
console.log(`[mdm] command ${event.payload.commandId} done`);
});See the full list in Events.
4. Enroll a test device
In production, devices enroll via a QR code scanned by the Android agent. For local development, call the manager directly:
import { mdm } from '../src/mdm';
import { createHmac } from 'crypto';
const SECRET = process.env.DEVICE_SECRET!;
const timestamp = new Date().toISOString();
// The canonical signing form is maintained by @openmdm/client for
// the real agent. Here we inline it for a dev harness.
const request = {
model: 'Pixel 7',
manufacturer: 'Google',
osVersion: '14',
serialNumber: 'DEV-SN-0001',
imei: '',
macAddress: '',
androidId: 'dev-android-0001',
method: 'manual' as const,
timestamp,
};
const message = [
request.model,
request.manufacturer,
request.osVersion,
request.serialNumber,
request.imei,
request.macAddress,
request.androidId,
request.method,
request.timestamp,
].join('|');
const signature = createHmac('sha256', SECRET).update(message).digest('hex');
const response = await mdm.enroll({ ...request, signature });
console.log('Enrolled:', response.deviceId);
console.log('Device token:', response.token);Why so manual? Because the enrollment signature is the library's security boundary, not a convenience helper. We show the real canonical form here so you can audit it. Production agents use
generateEnrollmentSignature()from@openmdm/client, which computes the same thing.
Run it:
pnpm tsx scripts/enroll-test-device.tsYou should see the [mdm] ... just enrolled log from your event handler fire.
5. Send a command
Commands are queued to the database and delivered over push the next time the device is reachable. The managers expose one-shot helpers for the common types:
// Tell the device to sync immediately (no payload)
await mdm.devices.sync(deviceId);
// Lock the screen with an optional message
await mdm.devices.lock(deviceId, 'Return this device to IT');
// Factory reset, optionally preserving SD card data
await mdm.devices.wipe(deviceId, true);
// Reboot
await mdm.devices.reboot(deviceId);Need a type that doesn't have a shortcut? Use sendCommand:
await mdm.devices.sendCommand(deviceId, {
type: 'installApp',
payload: {
packageName: 'com.example.app',
downloadUrl: 'https://cdn.example.com/app.apk',
},
});The full set of command types is in Commands vs Policies.
6. Query the fleet
// All enrolled devices, newest first
const { devices, total } = await mdm.devices.list({
status: 'enrolled',
limit: 50,
});
console.log(`${total} devices enrolled`);
// One device
const device = await mdm.devices.get(deviceId);
console.log('Last seen:', device?.lastHeartbeat);
console.log('Battery:', device?.batteryLevel);
console.log('Policy:', device?.policyId);Or use the CLI for the same thing from a shell:
npx openmdm device list
npx openmdm device show <deviceId>
npx openmdm statsSee CLI for the full command list.
You're done
What you just built:
- A Hono server with OpenMDM mounted at
/mdm. - A default kiosk policy seeded in the database.
- A test device enrolled with a signed payload.
- An event handler that logs enrollments and heartbeats.
- A command queued for the device.
What you still need for production:
- A real Android agent built from
openmdm-androidwith yourDEVICE_SECRETcompiled in. - A Firebase project with FCM credentials, or an MQTT broker.
- Webhook receivers for anything your business cares about. See Webhooks Recipe.
- A proper authorization layer on the admin-side endpoints. OpenMDM authenticates devices for you; authenticating admins is your job.
Where to go next
- Commands vs Policies — the distinction that will save you from writing
sendCommandcalls inside policy logic. - Architecture — the data flow for enrollment, heartbeats, and command delivery. Read before optimizing anything.
- Webhooks Recipe — receive HMAC-signed events in an external system with a copy-pastable verifier.
- Kiosk Recipe — enable kiosk mode correctly, including how to let an admin unlock a stuck device.