Skip to content

Mount the handler in your framework

The handler accepts one standard Request and returns a Promise of Response. Your callbacks own auth and database writes. The following push and callbacks are application-defined values. Avoid middleware that consumes the request body before the handler.

app/api/push/route.ts
import { push, callbacks } from '@/lib/push.server';
export const POST = push.handler(callbacks);

Keep the VAPID private key in a server-only module. Do not prefix it with NEXT_PUBLIC_. Build a worker into public/sw.js using your PWA setup or the shared example build script.

app/routes/api.push.ts
import type { ActionFunctionArgs } from 'react-router';
import { push, callbacksFor } from '~/push.server';
export function action({ request, context }: ActionFunctionArgs) {
return push.handler(callbacksFor(context))(request);
}

Map /api/push to this resource route. Context is captured in the callback closure. It is not passed into the library.

src/pages/api/push.ts
import type { APIRoute } from 'astro';
import { push, callbacksFor } from '../../push.server';
export const prerender = false;
export const POST: APIRoute = ({ request, locals }) =>
push.handler(callbacksFor(locals))(request);

Use server output and the adapter for your runtime. A static-only deployment cannot handle subscription writes or sends.

import { Hono } from 'hono';
import { createPush } from 'pushpilot/server';
import { callbacksFor } from './application-auth-and-storage';
const app = new Hono<{ Bindings: Env }>();
app.post('/api/push', (context) => {
const push = createPush({
vapid: {
publicKey: context.env.VAPID_PUBLIC_KEY,
privateKey: context.env.VAPID_PRIVATE_KEY,
subject: context.env.VAPID_SUBJECT,
},
});
return push.handler(callbacksFor(context))(context.req.raw);
});
export default app;

Env and callbacksFor belong to the application. Secrets and auth come from the Hono context. Reuse an instance when its configuration is stable. For large recipient lists, use caller-owned queue consumers within your current Workers limits.

The repository contains local runnable examples under examples/hono, examples/next, examples/react-router, and examples/astro. Their shared store and password are only for local testing.

For Astro’s Node standalone adapter, configure security.allowedDomains with the deployment’s trusted hostname, protocol, and port. Without a trusted hostname, the adapter can reconstruct a different request origin and cause same-origin POST requests to fail. The runnable Astro example allowlists its two local development origins. Keep the handler’s Origin check enabled.