A complete Firebase integration library for Svelte 5 and SvelteKit. Built with Svelte 5 runes throughout, works in any Svelte 5 project — not just SvelteKit.
- Firebase v12 (modular SDK)
- Svelte 5 runes (
$state,$derived) — fully reactive, no stores - SSR-safe — all services degrade gracefully on the server
- Works without SvelteKit (
$app/*,$env/*are never imported)
Documentation:sveltefirekit.com
npm install svelte-firekit firebaseCall initFirekit once before your app renders — in your root layout or entry point:
import{initFirekit}from'svelte-firekit';initFirekit({apiKey: '...',authDomain: '...',projectId: '...',storageBucket: '...',messagingSenderId: '...',appId: '...',databaseURL: '...'// required for Realtime Database});<!-- +layout.svelte -->
<scriptlang="ts">import { FirebaseApp } from'svelte-firekit';import { firebaseConfig } from'$lib/firebase';</script>
<FirebaseAppconfig={firebaseConfig}>
{@renderchildren()}
</FirebaseApp>FirebaseApp initializes Firebase, sets up Svelte context for all services, and optionally enables App Check:
<FirebaseApp {config} appCheckOptions={{ provider: newReCaptchaEnterpriseProvider('SITE_KEY'), isTokenAutoRefreshEnabled: true }}>
{@renderchildren()}
</FirebaseApp>import{firekitAuth,firekitUser}from'svelte-firekit';// Email / passwordawaitfirekitAuth.signInWithEmail('user@example.com','password');// OAuth (popup)awaitfirekitAuth.signInWithGoogle();awaitfirekitAuth.signInWithGithub();awaitfirekitAuth.signInWithFacebook();awaitfirekitAuth.signInWithApple();awaitfirekitAuth.signInWithTwitter();awaitfirekitAuth.signInWithMicrosoft();// OAuth (redirect — call getRedirectResult() on app load)awaitfirekitAuth.signInWithGoogleRedirect();constresult=awaitfirekitAuth.getRedirectResult();// null if no pending redirect// SAML / OIDCawaitfirekitAuth.signInWithSAML('saml.my-provider');awaitfirekitAuth.signInWithOIDC('oidc.my-provider',['email','profile']);// Custom token (server-issued)awaitfirekitAuth.signInWithCustomToken(token);// AnonymousawaitfirekitAuth.signInAnonymously();// Phoneconst{ confirm }=awaitfirekitAuth.signInWithPhoneNumber('+1234567890','recaptcha-container');awaitconfirm('123456');awaitfirekitAuth.registerWithEmail('user@example.com','password','Jane Doe');awaitfirekitAuth.updateUserProfile({displayName: 'Jane',photoURL: 'https://...'});awaitfirekitAuth.updateEmail('new@example.com');awaitfirekitAuth.updatePassword('newPassword','currentPassword');awaitfirekitAuth.sendPasswordReset('user@example.com');awaitfirekitAuth.sendEmailVerification();awaitfirekitAuth.signOut();awaitfirekitAuth.deleteAccount('currentPassword');// Enroll phone MFAconstverificationId=awaitfirekitAuth.startPhoneMFAEnrollment('+1234567890','recaptcha-container');awaitfirekitAuth.completeMFAEnrollment(verificationId,'123456','My Phone');// Check enrolled factorsconstfactors=firekitAuth.getMFAEnrolledFactors();// UnenrollawaitfirekitAuth.unenrollMFA(factors[0]);// Complete an MFA-required sign-in (catch the MultiFactorError first)try{awaitfirekitAuth.signInWithEmail(email,password);}catch(err){constresolver=firekitAuth.getMFAResolver(err);constverificationId=awaitfirekitAuth.startMFASignIn(resolver,0,'recaptcha-container');constresult=awaitfirekitAuth.completeMFASignIn(resolver,verificationId,'123456');}<scriptlang="ts">import { firekitUser } from'svelte-firekit';</script>
{#iffirekitUser.loading}
<p>Loading...</p>
{:elseiffirekitUser.isAuthenticated}
<p>Welcome, {firekitUser.user?.displayName}</p>
{:else}
<p>Not signed in</p>
{/if}| Property | Type | Description |
|---|---|---|
user | UserProfile | null | Current user profile |
loading | boolean | Auth state initializing |
isAuthenticated | boolean | Signed in and not anonymous |
isAnonymous | boolean | Anonymous session |
initialized | boolean | First auth state received |
// Wait for auth to initialize (useful for SSR / load functions)constuser=awaitfirekitUser.waitForAuth();// 10s timeout (default)constuser=awaitfirekitUser.waitForAuth(5_000);// custom timeout in msAll auth components wait for Firebase Auth to initialize before rendering. Use the optional fallback snippet to show a loading state.
<SignedIn>
{#snippetchildren(user)}<p>Welcome, {user.displayName}</p>{/snippet}
{#snippetfallback()}<p>Loading...</p>{/snippet}
</SignedIn>
<SignedOut>
{#snippetchildren(signIn)}<buttononclick={signIn}>Sign in</button>{/snippet}
{#snippetfallback()}<p>Loading...</p>{/snippet}
</SignedOut>
<!-- Route guard with redirect callback -->
<AuthGuardrequireAuth={true} onUnauthorized={() =>goto('/login')}>
{#snippetchildren(user, signOut)}<p>Protected content</p>{/snippet}
{#snippetfallback()}<p>Loading...</p>{/snippet}
</AuthGuard>
<!-- Custom guard with async checks (e.g. role verification) -->
<CustomGuardverificationChecks={[
async (user) =>user.emailVerified,
async (user) => { const doc =awaitgetDoc(...); returndoc.data()?.role==='admin'; }
]}
onUnauthorized={() =>goto('/403')}
>
{#snippetchildren(user, signOut)}<p>Admin only</p>{/snippet}
{#snippetfallback()}<p>Checking permissions...</p>{/snippet}
</CustomGuard><scriptlang="ts">import { firekitDoc } from'svelte-firekit';const post =firekitDoc<Post>('posts/post-id');</script>
{#ifpost.loading}<p>Loading...</p>{/if}
{#ifpost.data}<h1>{post.data.title}</h1>{/if}Using the <Doc> component:
<Docpath="posts/post-id">
{#snippetdata(post)}
<h1>{post.title}</h1>
{/snippet}
{#snippetloading()}<p>Loading...</p>{/snippet}
</Doc>Change path reactively:
post.setPath('posts/other-id');// tears down listener, re-subscribesOne-time fetch:
constpost=firekitDocOnce<Post>('posts/post-id');<scriptlang="ts">import { firekitCollection } from'svelte-firekit';import { where, orderBy } from'firebase/firestore';const posts =firekitCollection<Post>('posts', [where('published', '==', true),orderBy('createdAt', 'desc') ]);</script>
{#eachposts.dataaspost}
<p>{post.title}</p>
{/each}Cursor-based pagination:
awaitposts.setPagination(10);// 10 per page, switches to one-time fetchawaitposts.nextPage();// next page (replaces data)awaitposts.prevPage();// previous pageawaitposts.loadMore();// append next page (infinite scroll)awaitposts.resetPagination();// back to page 1posts.currentPage// numberposts.hasMore// booleanFluent query builder:
import{FirekitQueryBuilder}from'svelte-firekit';constconstraints=newFirekitQueryBuilder<Post>().where('published','==',true).orderBy('createdAt','desc').limit(20).build();Collection group:
constallComments=firekitCollectionGroup<Comment>('comments');import{firekitMutations}from'svelte-firekit';awaitfirekitMutations.add('posts',{title: 'Hello'});awaitfirekitMutations.set('posts/id',{title: 'Hello'});awaitfirekitMutations.update('posts/id',{title: 'Updated'});awaitfirekitMutations.delete('posts/id');// Existence checkconstexists=awaitfirekitMutations.exists('posts/id');// Field value helpersawaitfirekitMutations.update('posts/id',{views: firekitMutations.increment(1),tags: firekitMutations.arrayUnion('svelte'),draft: firekitMutations.deleteField(),updatedAt: firekitMutations.serverTimestamp()});// Batch (auto-chunked at 500)awaitfirekitMutations.batchOps([{type: 'set',path: 'posts',id: 'a',data: {title: 'A'}},{type: 'update',path: 'posts/b',data: {views: 1}},{type: 'delete',path: 'posts/c'}]);// TransactionawaitfirekitMutations.transaction(async(tx)=>{constsnap=awaittx.get(ref);tx.update(ref,{count: snap.data().count+1});});// Auto-adds createdAt/updatedAtawaitfirekitMutations.add('posts',data,{timestamps: true,userId: uid});// Retry on failureawaitfirekitMutations.set('posts/id',data,{retry: {enabled: true,maxAttempts: 3,baseDelay: 200,strategy: 'exponential'}});import{loadFirestoreBundle,getNamedQuery}from'svelte-firekit';import{getDocs}from'firebase/firestore';constres=awaitfetch('/bundles/featured.bundle');awaitloadFirestoreBundle(res.body!);constq=awaitgetNamedQuery<Post>('featured-posts');if(q){constsnap=awaitgetDocs(q);}<scriptlang="ts">import { firekitNode, firekitNodeList } from'svelte-firekit';const counter =firekitNode<number>('counters/visitors');const messages =firekitNodeList<Message>('chat/messages');</script>
<p>Visitors: {counter.data}</p>
{#eachmessages.listasmsg}
<p>{msg.text}</p>
{/each}awaitcounter.set(42);awaitcounter.update({count: 10});awaitmessages.push({text: 'Hello!',userId: uid});awaitmessages.remove();constonce=awaitmessages.fetchOnce();Using the <Node> component:
<Nodepath="chat/messages">
{#snippetdata(messages)}<p>{messages}</p>{/snippet}
</Node><scriptlang="ts">import { firekitDownloadUrl, firekitUploadTask } from'svelte-firekit';const avatar =firekitDownloadUrl('images/avatar.jpg');let file:File;$: upload=file?firekitUploadTask('uploads/'+file.name, file) :null;</script>
{#ifavatar.url}<imgsrc={avatar.url} alt="avatar" />{/if}
{#ifupload}
<progressvalue={upload.progress} max={100} />
{#ifupload.downloadURL}<imgsrc={upload.downloadURL} />{/if}
{/if}import{deleteFile,getFileMetadata,updateFileMetadata}from'svelte-firekit';awaitdeleteFile('images/old.jpg');constmeta=awaitgetFileMetadata('images/avatar.jpg');awaitupdateFileMetadata('images/avatar.jpg',{contentType: 'image/webp',customMetadata: {uploadedBy: uid}});List files:
<scriptlang="ts">import { firekitStorageList } from'svelte-firekit';const dir =firekitStorageList('uploads/2024');</script>
{#eachdir.itemsasitem}<p>{item.name}</p>{/each}Validate files before uploading — checks size, MIME type, and image dimensions.
import{validateFile}from'svelte-firekit';constresult=awaitvalidateFile(file,{maxSize: 5*1024*1024,// 5 MBaccept: ['image/png','image/jpeg','.webp'],maxWidth: 2048,maxHeight: 2048,minWidth: 100});if(!result.valid){result.errors.forEach((e)=>console.log(e.code,e.message));}The <UploadTask> component supports an optional validate prop and invalid snippet:
<UploadTaskpath="uploads/{file.name}" {file} validate={{ maxSize: 5_000_000, accept: ['image/*'] }}>
{#snippetuploading(task)}<progressvalue={task.progress} max={100} />{/snippet}
{#snippetcomplete(url)}<imgsrc={url} alt="uploaded" />{/snippet}
{#snippetinvalid(result)}
{#eachresult.errorsaserr}<pclass="error">{err.message}</p>{/each}
{/snippet}
</UploadTask>Using the <DownloadURL> component:
<DownloadURLpath="images/avatar.jpg">
{#snippetdata(url)}<imgsrc={url} />{/snippet}
</DownloadURL>import{firekitCallable,firekitCallableFromURL}from'svelte-firekit';constsendWelcome=firekitCallable<{userId: string},{sent: boolean}>('sendWelcomeEmail');constresult=awaitsendWelcome.call({userId: 'abc'});// sendWelcome.loading, sendWelcome.error, sendWelcome.resultconstfn=firekitCallableFromURL<Input,Output>('https://region-project.cloudfunctions.net/fn');import{firekitGenerate,firekitStream,firekitChat}from'svelte-firekit';// One-shotconstgen=firekitGenerate({model: 'gemini-2.0-flash'});awaitgen.generate('Summarize: ...');// gen.text, gen.loading, gen.error// Streamingconststream=firekitStream({model: 'gemini-2.0-flash'});awaitstream.generate('Write a poem about Svelte.');// stream.text updates token-by-token, stream.streaming// Multi-turn chatconstchat=firekitChat({model: 'gemini-2.0-flash'});awaitchat.send('Hello!');awaitchat.send('Tell me more.');// chat.history, chat.pendingText, chat.streamingSwitch between Google AI and Vertex AI:
import{GoogleAIBackend,VertexAIBackend}from'svelte-firekit';constgen=firekitGenerate({backend: 'vertexai',model: 'gemini-2.0-flash'});Content helpers:
import{textPart,imagePart,imageUrlPart}from'svelte-firekit';awaitgen.generate([textPart('Describe this image:'),imagePart(base64)]);import{firekitRemoteConfig}from'svelte-firekit';constrc=firekitRemoteConfig({defaults: {welcomeMessage: 'Hello!',featureEnabled: false},minimumFetchIntervalMs: 3_600_000,realtime: true// subscribe to live config updates});awaitrc.fetchAndActivate();constmsg=rc.getString('welcomeMessage');constflag=rc.getBoolean('featureEnabled');constcount=rc.getNumber('itemsPerPage',10);import{firekitPerformance}from'svelte-firekit';// Simple traceconststop=awaitfirekitPerformance.startTrace('load-dashboard');// ... work ...stop();// Timed blockconstduration=awaitfirekitPerformance.measure('render-posts',async()=>{awaitloadPosts();});import{firekitAnalytics}from'svelte-firekit';awaitfirekitAnalytics.logEvent('purchase',{value: 9.99,currency: 'USD'});awaitfirekitAnalytics.setUserId('uid123');awaitfirekitAnalytics.setUserProperties({plan: 'pro'});awaitfirekitAnalytics.logScreenView('Dashboard');import{firekitMessaging}from'svelte-firekit';consttoken=awaitfirekitMessaging.requestPermission('YOUR_VAPID_KEY');// firekitMessaging.token, firekitMessaging.permission, firekitMessaging.supported// Listen for foreground messagesconstunsub=awaitfirekitMessaging.onMessage((payload)=>{console.log(payload.notification?.title);});import{firekitInAppMessaging}from'svelte-firekit';// Suppress during critical flows (e.g. checkout)firekitInAppMessaging.suppress();// ... complete checkout ...firekitInAppMessaging.unsuppress();// firekitInAppMessaging.suppressed (reactive boolean)// firekitInAppMessaging.supportedTrack browser connectivity and Firestore sync state reactively.
<scriptlang="ts">import { firekitNetwork } from'svelte-firekit';</script>
{#if!firekitNetwork.online}
<p>You're offline. Changes will sync when reconnected.</p>
{:elseiffirekitNetwork.hasPendingWrites}
<p>Saving...</p>
{:else}
<p>All changes saved</p>
{/if}Using the <NetworkStatus> component:
<NetworkStatus>
{#snippetonline()}<spanclass="green">Connected</span>{/snippet}
{#snippetoffline()}<spanclass="red">Offline</span>{/snippet}
{#snippetpending()}<span>Saving...</span>{/snippet}
</NetworkStatus>Manual control:
awaitfirekitNetwork.goOffline();// force offline modeawaitfirekitNetwork.goOnline();// reconnectfirekitNetwork.trackWrite();// mark a pending writeimport{firekitPresence}from'svelte-firekit';awaitfirekitPresence.initialize(user,{sessionTTL: 30*60_000,trackDeviceInfo: true,geolocation: {enabled: true,type: 'browser',requireConsent: true}});awaitfirekitPresence.setPresence('away');conststats=firekitPresence.getStats();// stats.onlineSessions, stats.totalSessions, stats.uniqueDevicesawaitfirekitPresence.dispose();<script>import { ReCaptchaEnterpriseProvider } from'svelte-firekit';</script>
<FirebaseApp
{config}
appCheckOptions={{
provider: newReCaptchaEnterpriseProvider('SITE_KEY'),
isTokenAutoRefreshEnabled: true
}}
>
{@renderchildren()}
</FirebaseApp>After <FirebaseApp>, you can retrieve any raw Firebase service instance from Svelte context:
import{getFirestoreContext,getAuthContext,getStorageContext,getRTDBContext,getFunctionsContext,getAppCheckContext}from'svelte-firekit';// Inside a component:constdb=getFirestoreContext();constauth=getAuthContext();All services return safe defaults on the server (loading: false, empty arrays, null data) — no Firebase network calls happen during SSR. The only exception is firekitPresence, which is browser-only and will throw if you call initialize() on the server.
// Safe in load functions / SSR — resolves immediately on serverconstuser=awaitfirekitUser.waitForAuth();constposts=firekitCollection<Post>('posts');constdata=awaitposts.waitForReady();| Import | Description |
|---|---|
firekitAuth | Auth operations (sign-in, register, MFA, SAML/OIDC, redirect) |
firekitUser | Reactive current user state |
firekitMutations | Firestore CRUD, batch, transactions |
firekitPresence | User presence tracking via RTDB |
firekitRemoteConfig() | Remote Config per-instance |
firekitPerformance | Performance Monitoring traces |
firekitAnalytics | Analytics event logging |
firekitMessaging | Firebase Cloud Messaging |
firekitInAppMessaging | In-App Messaging suppression control |
firekitNetwork | Reactive network/offline status |
firekitAppCheck | App Check initialization |
| Import | Description |
|---|---|
FirekitDoc / firekitDoc() | Reactive Firestore document |
FirekitCollection / firekitCollection() | Reactive Firestore collection with pagination |
FirekitCollectionGroup / firekitCollectionGroup() | Collection group query |
FirekitNode / firekitNode() | Reactive RTDB node |
FirekitNodeList / firekitNodeList() | Reactive RTDB list |
FirekitDownloadUrl / firekitDownloadUrl() | Reactive Storage download URL |
FirekitStorageList / firekitStorageList() | Reactive Storage directory listing |
FirekitUploadTask / firekitUploadTask() | Reactive resumable upload |
FirekitGenerate / firekitGenerate() | One-shot AI generation |
FirekitStream / firekitStream() | Streaming AI generation |
FirekitChat / firekitChat() | Multi-turn AI chat session |
FirekitCallable / firekitCallable() | Typed Cloud Function caller |
FirekitCallableFromURL / firekitCallableFromURL() | Cloud Function by URL |
| Import | Description |
|---|---|
validateFile() | Pre-upload file validation (size, type, dimensions) |
| Component | Description |
|---|---|
<FirebaseApp> | Root provider, initializes Firebase |
<SignedIn> | Render when authenticated |
<SignedOut> | Render when not authenticated |
<AuthGuard> | Redirect unauthenticated users |
<CustomGuard> | Async role/permission guard |
<Doc> | Reactive Firestore document |
<Collection> | Reactive Firestore collection |
<Node> | Reactive RTDB node |
<DownloadURL> | Storage download URL |
<UploadTask> | Resumable file upload with optional validation |
<NetworkStatus> | Network/sync status display |
MIT © Giovani Rodriguez