A modern, embeddable Mobile Device Management SDK for TypeScript
Documentation | Quick Start | Examples | Discord
OpenMDM is the "better-auth of MDM" - a flexible, framework-agnostic SDK that lets you add device management to any application without deploying a separate MDM server.
Note
OpenMDM is pre-1.0. The core APIs are working and the packages are published to npm, but minor versions may still introduce breaking changes before 1.0. Pin exact versions in production and review the CHANGELOG before upgrading. Contributions and feedback welcome.
Headwind MDM and similar open-source MDM solutions are powerful but:
- Require separate deployment (Java/Tomcat server)
- Have their own database (can't use yours)
- Limited integration options (no SDKs)
- Session-based auth with legacy patterns
- Hard to embed in existing applications
Commercial MDMs (Intune, VMware, etc.) are:
- Expensive at scale
- Cloud-dependent
- Vendor lock-in
- Overkill for many use cases
// Install OpenMDM into YOUR application
import { createMDM } from '@openmdm/core';
import { drizzleAdapter } from '@openmdm/drizzle-adapter';
import { fcmPushAdapter } from '@openmdm/push-fcm';
const mdm = createMDM({
database: drizzleAdapter(db), // Your existing database
push: fcmPushAdapter({
credentialPath: './firebase-service-account.json',
}),
enrollment: {
deviceSecret: process.env.DEVICE_HMAC_SECRET,
autoEnroll: true,
},
});
// Now you have full MDM capabilities
const devices = await mdm.devices.list();
await mdm.devices.sendCommand(deviceId, { type: 'reboot' });- Embeddable - Works within your existing application, not as a separate service
- HTTP Adapter - First-class Hono adapter with the full REST surface (Express, Fastify and Next.js adapters are on the roadmap)
- Database Agnostic - Bring your own database with Drizzle, Prisma, or raw SQL
- Push Notifications - FCM, MQTT, and WebSocket support
- S3 Storage - Presigned URLs for APK uploads (AWS S3, MinIO, DigitalOcean Spaces)
- Webhooks - HMAC-signed outbound webhooks with retry logic
- Plugin System - Extend with kiosk, geofence, and custom plugins
- TypeScript First - Full type safety with IntelliSense support
| Package | Description | Status |
|---|---|---|
@openmdm/core |
Core MDM SDK - devices, policies, commands, events | Stable |
@openmdm/storage-s3 |
S3 storage adapter for APK uploads | Stable |
@openmdm/drizzle-adapter |
Database adapter for Drizzle ORM | Stable |
@openmdm/hono |
Hono framework adapter with REST API routes | Stable |
@openmdm/push-fcm |
Firebase Cloud Messaging push adapter | Stable |
@openmdm/push-mqtt |
MQTT push adapter for private networks | Stable |
@openmdm/client |
Device-side SDK for Android agents | Stable |
@openmdm/plugin-kiosk |
Kiosk/lockdown mode plugin | Stable |
@openmdm/plugin-geofence |
Geofencing and location-based policies | Stable |
@openmdm/cli |
Command-line tools for administration | Stable |
npm install @openmdm/core @openmdm/drizzle-adapter @openmdm/push-fcm
# or
pnpm add @openmdm/core @openmdm/drizzle-adapter @openmdm/push-fcmimport { createMDM } from '@openmdm/core';
import { drizzleAdapter } from '@openmdm/drizzle-adapter';
import { fcmPushAdapter } from '@openmdm/push-fcm';
import { kioskPlugin } from '@openmdm/plugin-kiosk';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
// Database connection
const db = drizzle(postgres(process.env.DATABASE_URL!));
// Create MDM instance
export const mdm = createMDM({
database: drizzleAdapter(db),
push: fcmPushAdapter({
credentialPath: './firebase-service-account.json',
}),
enrollment: {
deviceSecret: process.env.DEVICE_SECRET!,
autoEnroll: true,
},
auth: {
deviceTokenSecret: process.env.JWT_SECRET!,
},
webhooks: {
endpoints: [{
id: 'main',
url: 'https://your-app.com/webhooks/mdm',
events: ['*'],
enabled: true,
}],
signingSecret: process.env.WEBHOOK_SECRET,
},
serverUrl: 'https://mdm.example.com',
plugins: [
kioskPlugin({ defaultExitPassword: 'admin123' }),
],
});// List enrolled devices
const { devices, total } = await mdm.devices.list({ status: 'enrolled' });
// Get device details
const device = await mdm.devices.get('device-123');
// Send command
await mdm.commands.send({
deviceId: 'device-123',
type: 'sync',
});
// Create policy
const policy = await mdm.policies.create({
name: 'Kiosk Mode',
settings: {
kioskMode: true,
mainApp: 'com.example.app',
lockStatusBar: true,
lockNavigationBar: true,
},
});
// Apply policy to device
await mdm.devices.assignPolicy('device-123', policy.id);
// Subscribe to events
mdm.on('device.enrolled', async (event) => {
console.log('New device:', event.payload.device);
});OpenMDM ships with a first-class Hono adapter that exposes the full REST surface (enrollment, devices, policies, applications, groups, commands, events, agent wire-protocol). Hono runs on Node, Bun, Deno, Cloudflare Workers, Vercel, and more — so the same adapter works across runtimes.
import { Hono } from 'hono';
import { honoAdapter } from '@openmdm/hono';
const app = new Hono();
app.route('/mdm', honoAdapter(mdm));Using Express, Fastify, or Next.js? Native adapters are on the roadmap for 1.0. In the meantime, the
@openmdm/coreinstance exposes all MDM operations as plain functions, so you can wire your own routes against any framework.
┌─────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ OpenMDM SDK │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
│ │ │ Devices │ │ Policies│ │Commands │ │ Push │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └───────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
│ │ │ Apps │ │ Groups │ │ Events │ │Webhooks│ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └───────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ┌───────▼───────┐ ┌────▼────┐ ┌───────▼───────┐ │
│ │ Your Database │ │ FCM/ │ │ Android Agent │ │
│ │ (Drizzle) │ │ MQTT │ │ (Kotlin) │ │
│ └───────────────┘ └─────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
The Android Agent is maintained in a separate repository for easier customization and forking:
Repository: openmdm/openmdm-android
- Device enrollment with QR code or token
- FCM push notification support
- Automatic heartbeat/check-in scheduling
- Command processing (sync, lock, wipe, app install, etc.)
- Device Owner / Device Admin capabilities
- Silent app installation and permission granting
- Kiosk mode support
- Location reporting
# Clone the Android agent
git clone https://github.com/openmdm/openmdm-android
# Build the full agent app
cd openmdm-android
./gradlew :agent:assembleRelease
# Or use the library in your own app
implementation("com.github.openmdm:openmdm-android:library:0.1.0")See the openmdm-android README for full documentation.
Recommended for devices with Google Play Services.
import { fcmPushAdapter } from '@openmdm/push-fcm';
const push = fcmPushAdapter({
credentialPath: './firebase-service-account.json',
dataOnly: true,
});For private networks, air-gapped environments, or devices without Google Play Services.
import { mqttPushAdapter } from '@openmdm/push-mqtt';
const push = mqttPushAdapter({
brokerUrl: 'mqtt://mqtt.example.com:1883',
username: 'mdm-server',
password: 'secret',
qos: 1,
});Lock devices to a single app or set of apps.
import { kioskPlugin } from '@openmdm/plugin-kiosk';
const mdm = createMDM({
plugins: [
kioskPlugin({
defaultExitPassword: 'admin123',
allowRemoteExit: true,
autoRestart: true,
}),
],
});Location-based policies and alerts.
import { geofencePlugin } from '@openmdm/plugin-geofence';
const mdm = createMDM({
plugins: [
geofencePlugin({
onEnter: async (device, zone) => {
await mdm.devices.assignPolicy(device.id, 'office-policy');
},
onExit: async (device, zone) => {
await mdm.devices.assignPolicy(device.id, 'default-policy');
},
}),
],
});# Initialize project
npx openmdm init
# Run database migrations
npx openmdm migrate
# List devices
npx openmdm device list
npx openmdm device show <deviceId>
# Manage policies
npx openmdm policy list
npx openmdm policy create
npx openmdm policy apply <policyId> <deviceId>
# Generate enrollment
npx openmdm enroll qr --output enrollment.png
npx openmdm enroll token
# View statistics
npx openmdm statsopenmdm/ # This repository (SDK monorepo)
├── packages/
│ ├── core/ # Core MDM SDK
│ ├── storage/s3/ # S3 storage adapter
│ ├── adapters/
│ │ ├── drizzle/ # Drizzle ORM adapter
│ │ └── hono/ # Hono framework adapter
│ ├── push/
│ │ ├── fcm/ # FCM push adapter
│ │ └── mqtt/ # MQTT push adapter
│ ├── plugins/
│ │ ├── kiosk/ # Kiosk mode plugin
│ │ └── geofence/ # Geofencing plugin
│ ├── client/ # Device-side SDK (TypeScript types)
│ └── cli/ # CLI tools
├── examples/ # Example applications
└── docs/ # Documentation website
openmdm-android/ # Separate repository
├── agent/ # Full-featured MDM agent app
├── library/ # Core MDM library (embeddable)
└── docs/ # Android-specific docs
| Feature | OpenMDM | Headwind MDM | Commercial |
|---|---|---|---|
| Embeddable | Yes | No | No |
| Use your DB | Yes | No | No |
| Self-hosted | Yes | Yes | Some |
| Framework agnostic | Yes | No | No |
| Plugin system | Yes | Limited | Limited |
| Webhooks | Yes | Enterprise | Yes |
| S3 Storage | Yes | No | Yes |
| Kiosk mode | Yes | Yes | Yes |
| Device Owner | Yes | Yes | Yes |
| TypeScript | Native | No | No |
| Price | Free | Free/Paid | $$$ |
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test
# Type check
pnpm typecheck
# Watch mode
pnpm devWe welcome contributions! Please see our Contributing Guide for details.
- Discord - Chat with the community
- GitHub Discussions - Ask questions, share ideas
- Twitter - Updates and announcements
MIT License - see LICENSE for details.
- Headwind MDM - For years of MDM knowledge
- better-auth - For the embeddable SDK design pattern
- Android Enterprise - For the platform APIs
Built with TypeScript. Inspired by better-auth.