Skip to content

Repository files navigation

svelte-firekit

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


Installation

npm install svelte-firekit firebase

Setup

1. Initialize Firebase

Call 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});

2. Wrap your app

<!-- +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>

Authentication

import{firekitAuth,firekitUser}from'svelte-firekit';

Sign in

// 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');

Registration & profile

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');

Multi-factor authentication (MFA)

// 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');}

Reactive user state

<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}
PropertyTypeDescription
userUserProfile | nullCurrent user profile
loadingbooleanAuth state initializing
isAuthenticatedbooleanSigned in and not anonymous
isAnonymousbooleanAnonymous session
initializedbooleanFirst 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 ms

Auth components

All 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>

Firestore

Reactive document

<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-subscribes

One-time fetch:

constpost=firekitDocOnce<Post>('posts/post-id');

Reactive collection

<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// boolean

Fluent 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');

Document mutations

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});});

Timestamps and options

// 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'}});

Bundles

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);}

Realtime Database

<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>

Firebase Storage

<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}

File upload validation

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>

Cloud Functions

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');

Firebase AI (Gemini)

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.streaming

Switch 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)]);

Remote Config

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);

Performance Monitoring

import{firekitPerformance}from'svelte-firekit';// Simple traceconststop=awaitfirekitPerformance.startTrace('load-dashboard');// ... work ...stop();// Timed blockconstduration=awaitfirekitPerformance.measure('render-posts',async()=>{awaitloadPosts();});

Analytics

import{firekitAnalytics}from'svelte-firekit';awaitfirekitAnalytics.logEvent('purchase',{value: 9.99,currency: 'USD'});awaitfirekitAnalytics.setUserId('uid123');awaitfirekitAnalytics.setUserProperties({plan: 'pro'});awaitfirekitAnalytics.logScreenView('Dashboard');

Messaging (FCM)

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);});

In-App Messaging

import{firekitInAppMessaging}from'svelte-firekit';// Suppress during critical flows (e.g. checkout)firekitInAppMessaging.suppress();// ... complete checkout ...firekitInAppMessaging.unsuppress();// firekitInAppMessaging.suppressed (reactive boolean)// firekitInAppMessaging.supported

Network / Offline Status

Track 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 write

Presence

import{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();

App Check

<script>import { ReCaptchaEnterpriseProvider } from'svelte-firekit';</script>
<FirebaseApp
{config}
appCheckOptions={{
provider: newReCaptchaEnterpriseProvider('SITE_KEY'),
isTokenAutoRefreshEnabled: true
}}
>
{@renderchildren()}
</FirebaseApp>

Context helpers

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();

SSR

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();

API Reference

Services

ImportDescription
firekitAuthAuth operations (sign-in, register, MFA, SAML/OIDC, redirect)
firekitUserReactive current user state
firekitMutationsFirestore CRUD, batch, transactions
firekitPresenceUser presence tracking via RTDB
firekitRemoteConfig()Remote Config per-instance
firekitPerformancePerformance Monitoring traces
firekitAnalyticsAnalytics event logging
firekitMessagingFirebase Cloud Messaging
firekitInAppMessagingIn-App Messaging suppression control
firekitNetworkReactive network/offline status
firekitAppCheckApp Check initialization

Reactive classes

ImportDescription
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

Utilities

ImportDescription
validateFile()Pre-upload file validation (size, type, dimensions)

Components

ComponentDescription
<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

License

MIT © Giovani Rodriguez

About

A comprehensive library integrating SvelteKit and Firebase for building robust micro SaaS applications.

Topics

Resources

Stars

35 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages