Generate a Firebase Cloud Messaging (FCM) service worker for any Vite app β env-aware config, a no-store dev preview that honors your Vite
base, and zero runtime dependencies.
firebase-messaging-sw.js at build time (emitted to your build outDir).no-store + weak ETag, served under your Vite base.FIREBASE_* env vars.outputDir) for frameworks that serve a static folder.transform(code) escape hatch.npm i -D vite-plugin-firebase-messaging-sw
# or: pnpm add -D vite-plugin-firebase-messaging-sw
# or: yarn add -D vite-plugin-firebase-messaging-sw
Requires Node β₯ 18 and Vite β₯ 4.
// vite.config.ts
import { defineConfig } from 'vite';
import { generateFirebaseMessagingSw } from 'vite-plugin-firebase-messaging-sw';
export default defineConfig({
plugins: [
generateFirebaseMessagingSw({
firebase: {
apiKey: process.env.FIREBASE_API_KEY!,
authDomain: process.env.FIREBASE_AUTH_DOMAIN!,
projectId: process.env.FIREBASE_PROJECT_ID!,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET!,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID!,
appId: process.env.FIREBASE_APP_ID!,
measurementId: process.env.FIREBASE_MEASUREMENT_ID, // optional
},
outputDir: 'static', // optional: SvelteKit serves /static at the site root
analytics: {
impressionUrl: 'https://your-tracker/impression',
openUrl: 'https://your-tracker/open',
},
}),
],
});
| Phase | Behavior |
|---|---|
dev (vite) |
Served at the SW route under your Vite base with Cache-Control: no-store + weak ETag. The SW is built once per dev session (stable across requests). |
build (vite build) |
Emitted as a build asset to the root of your build outDir. |
outputDir |
Also written to disk (dev & build), skipped when unchanged. |
The Firebase service worker must be served from the site root for FCM to register it. If you deploy under a sub-path or your build root differs from your web root, use
outputDir(e.g."static"/"public") to place it where itβll be served at/firebase-messaging-sw.js.
Any field omitted from firebase falls back to the matching env var, so you can pass firebase: {} and rely entirely on the environment:
| Field | Env var |
|---|---|
apiKey |
FIREBASE_API_KEY |
authDomain |
FIREBASE_AUTH_DOMAIN |
projectId |
FIREBASE_PROJECT_ID |
storageBucket |
FIREBASE_STORAGE_BUCKET |
messagingSenderId |
FIREBASE_MESSAGING_SENDER_ID |
appId |
FIREBASE_APP_ID |
measurementId |
FIREBASE_MEASUREMENT_ID |
Explicit firebase values win over env vars. If any required field is still missing, the plugin throws with the exact list of missing fields.
generateFirebaseMessagingSw({
firebase: ({ mode }) =>
mode === 'production' ? prodFirebaseConfig : devFirebaseConfig,
});
| Option | Type | Default | Description |
|---|---|---|---|
firebase |
FirebaseConfig \| (ctx) => FirebaseConfig |
required | Firebase web config or a resolver. ctx is { mode, command, config }. |
filename |
string |
"firebase-messaging-sw.js" |
Output filename. |
outputDir |
string |
β (no mirror) | Also write the SW to this directory on disk. |
firebaseCompatVersion |
string |
"9.22.2" |
Firebase compat SDK version loaded from the gstatic CDN. Bump to a newer release as needed. |
meta.appEnv |
string |
current mode | Arbitrary environment label embedded in the SW. |
meta.target |
string |
"web" |
Arbitrary target label. |
analytics |
FcmAnalytics |
{ srcParam: 'src', cidParam: 'cid', srcValue: 'fcm' } |
Tracking endpoints + query-param names. |
minify |
boolean |
true in production |
Apply the built-in light minifier. |
transform |
(code: string) => string |
β | Final transform over the generated SW (runs after minify β plug in your own minifier here if you want). |
FirebaseConfigtype FirebaseConfig = {
apiKey: string;
authDomain: string;
projectId: string;
storageBucket: string;
messagingSenderId: string;
appId: string;
measurementId?: string;
};
/* Auto-generated at 2025-09-09T13:45:00.000Z */
importScripts("https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/9.22.2/firebase-messaging-compat.js");
(function(){
const firebaseConfig = { /* your config */ };
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
self.registration.showNotification(
payload.notification?.title || "Notification",
{ body: payload.notification?.body || "", data: payload.data || {} }
);
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification?.data?.url || "/"));
});
})();
The firebase/analytics values are safely escaped before being embedded, so URLs containing quotes or backslashes wonβt break the generated script. Customize the handlers via transform(code).
MIT Β© dev.zarghami