Push
Send native push notifications to iOS and the web. Lux talks to Apple (APNs) and the W3C Web Push standard (VAPID) directly — no OneSignal, no Firebase SDK. Push is its own standalone scope: it works with or without Lux Auth.
The model
A device is registered against an opaque subject_id — any string you choose.
It may be a Lux Auth user id, but it doesn't have to be: if you keep your own auth
(e.g. Supabase), use your user id as the subject. You send by that same id, and Lux fans the
notification out to every device registered under it.
Registration and sending on another subject's behalf require a secret key —
your server is the trust boundary. A signed-in user with a Lux Auth session can self-register
their own device (subject = auth.uid()) with a publishable key.
1. Configure APNs
In the dashboard, open your project → Push and paste your Apple credentials: the
Team ID, an APNs Auth Key (.p8) and its Key ID, and your app's bundle id
as the topic. Use the sandbox environment for development builds. The .p8 is
stored encrypted and pushed down to your project.
2. Register a device
The device gets its push token from the OS, then hands it to your server, which registers it in Lux under your subject id:
import { createClient } from '@luxdb/sdk';
// Server-side, with your project secret key.
const db = createClient(process.env.LUX_URL, process.env.LUX_SECRET_KEY);
// Register a device for one of your users (any id you like).
await db.push.registerFor(user.id, {
token: apnsHexToken, // the device token from the OS
platform: 'ios',
});If you use Lux Auth, the client can register its own device directly with the publishable key — the subject is taken from the session, never asserted by the client:
// Client-side, signed in with a Lux Auth session.
await db.push.register({ token: apnsHexToken, platform: 'ios' });On iOS the token comes from the standard registration callback — no SDK required to obtain it:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let hex = deviceToken.map { String(format: "%02x", $0) }.joined()
// POST `hex` to your server, which calls db.push.registerFor(...)
}3. Send
Send to one subject, or many at once. Delivery is asynchronous and at-least-once; dead tokens are pruned automatically.
// One subject → all their devices.
await db.push.send(user.id, {
title: 'Alice mentioned you',
subtitle: 'Acme · #engineering',
body: 'Can you take a look at the deploy?',
thread_id: 'workspace:acme:channel:eng', // lock-screen grouping
image: 'https://cdn.example.com/thumb.png',
data: { type: 'mention', channel_id: 'eng' },
});
// Many subjects in one call.
await db.push.send([alice.id, bob.id], { title: 'Standup', body: 'Starting now' });Notification fields
title and body render the alert. The rest map to
APNs: subtitle, thread_id (grouping), category (action buttons), sound, badge, image (attaches a thumbnail via a
notification-service-extension), and content_available for silent /
background delivery. data is an arbitrary string map delivered to the
client for routing.
Managing devices
// List a subject's active devices (secret key).
const { data: devices } = await db.push.devices(user.id);
// Remove a device by id.
await db.push.unregister(deviceId);Web Push (browsers & desktop)
The same db.push surface delivers to browsers and desktop over the W3C Web
Push standard — real VAPID encryption (RFC 8291), no vendor account. In the dashboard, open Push → Web Push and click Enable Web Push: Lux
generates a VAPID keypair, holds the private key, and gives you the public key to use as the
browser's applicationServerKey.
In the browser, subscribe and register the subscription as the device token. The SDK helper handles the permission prompt, the service-worker subscription, and registration in one call:
// Browser, signed in with a Lux Auth session (self-register).
const publicKey = await db.push.getVapidPublicKey();
await db.push.subscribeWebPush({ vapidPublicKey: publicKey });
// Or register a subscription for a subject with your secret key (server-side).
await db.push.registerFor(user.id, {
token: JSON.stringify(subscription), // the PushSubscription from the browser
platform: 'web',
});Your service worker receives the decrypted payload and shows the notification — the same title, body, and data fields
you send to iOS:
// sw.js
self.addEventListener('push', (event) => {
const n = event.data.json();
event.waitUntil(
self.registration.showNotification(n.title, { body: n.body, data: n.data })
);
});Sending is identical to iOS: address a subject_id and Lux fans out to that
subject's devices across every platform. Dead subscriptions (a browser that unsubscribed,
HTTP 410) are pruned automatically.