npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-firekit

v0.2.6

Published

A Svelte library for Firebase integration

Downloads

1,129

Readme

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 -->
<script lang="ts">
  import { FirebaseApp } from 'svelte-firekit';
  import { firebaseConfig } from '$lib/firebase';
</script>

<FirebaseApp config={firebaseConfig}>
  {@render children()}
</FirebaseApp>

FirebaseApp initializes Firebase, sets up Svelte context for all services, and optionally enables App Check:

<FirebaseApp {config} appCheckOptions={{ provider: new ReCaptchaEnterpriseProvider('SITE_KEY'), isTokenAutoRefreshEnabled: true }}>
  {@render children()}
</FirebaseApp>

Authentication

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

Sign in

// Email / password
await firekitAuth.signInWithEmail('[email protected]', 'password');

// OAuth (popup)
await firekitAuth.signInWithGoogle();
await firekitAuth.signInWithGithub();
await firekitAuth.signInWithFacebook();
await firekitAuth.signInWithApple();
await firekitAuth.signInWithTwitter();
await firekitAuth.signInWithMicrosoft();

// OAuth (redirect — call getRedirectResult() on app load)
await firekitAuth.signInWithGoogleRedirect();
const result = await firekitAuth.getRedirectResult(); // null if no pending redirect

// SAML / OIDC
await firekitAuth.signInWithSAML('saml.my-provider');
await firekitAuth.signInWithOIDC('oidc.my-provider', ['email', 'profile']);

// Custom token (server-issued)
await firekitAuth.signInWithCustomToken(token);

// Anonymous
await firekitAuth.signInAnonymously();

// Phone
const { confirm } = await firekitAuth.signInWithPhoneNumber('+1234567890', 'recaptcha-container');
await confirm('123456');

Registration & profile

await firekitAuth.registerWithEmail('[email protected]', 'password', 'Jane Doe');
await firekitAuth.updateUserProfile({ displayName: 'Jane', photoURL: 'https://...' });
await firekitAuth.updateEmail('[email protected]');
await firekitAuth.updatePassword('newPassword', 'currentPassword');
await firekitAuth.sendPasswordReset('[email protected]');
await firekitAuth.sendEmailVerification();
await firekitAuth.signOut();
await firekitAuth.deleteAccount('currentPassword');

Multi-factor authentication (MFA)

// Enroll phone MFA
const verificationId = await firekitAuth.startPhoneMFAEnrollment('+1234567890', 'recaptcha-container');
await firekitAuth.completeMFAEnrollment(verificationId, '123456', 'My Phone');

// Check enrolled factors
const factors = firekitAuth.getMFAEnrolledFactors();

// Unenroll
await firekitAuth.unenrollMFA(factors[0]);

// Complete an MFA-required sign-in (catch the MultiFactorError first)
try {
  await firekitAuth.signInWithEmail(email, password);
} catch (err) {
  const resolver = firekitAuth.getMFAResolver(err);
  const verificationId = await firekitAuth.startMFASignIn(resolver, 0, 'recaptcha-container');
  const result = await firekitAuth.completeMFASignIn(resolver, verificationId, '123456');
}

Reactive user state

<script lang="ts">
  import { firekitUser } from 'svelte-firekit';
</script>

{#if firekitUser.loading}
  <p>Loading...</p>
{:else if firekitUser.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)
const user = await firekitUser.waitForAuth();        // 10s timeout (default)
const user = await firekitUser.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>
  {#snippet children(user)}<p>Welcome, {user.displayName}</p>{/snippet}
  {#snippet fallback()}<p>Loading...</p>{/snippet}
</SignedIn>

<SignedOut>
  {#snippet children(signIn)}<button onclick={signIn}>Sign in</button>{/snippet}
  {#snippet fallback()}<p>Loading...</p>{/snippet}
</SignedOut>

<!-- Route guard with redirect callback -->
<AuthGuard requireAuth={true} onUnauthorized={() => goto('/login')}>
  {#snippet children(user, signOut)}<p>Protected content</p>{/snippet}
  {#snippet fallback()}<p>Loading...</p>{/snippet}
</AuthGuard>

<!-- Custom guard with async checks (e.g. role verification) -->
<CustomGuard
  verificationChecks={[
    async (user) => user.emailVerified,
    async (user) => { const doc = await getDoc(...); return doc.data()?.role === 'admin'; }
  ]}
  onUnauthorized={() => goto('/403')}
>
  {#snippet children(user, signOut)}<p>Admin only</p>{/snippet}
  {#snippet fallback()}<p>Checking permissions...</p>{/snippet}
</CustomGuard>

Firestore

Reactive document

<script lang="ts">
  import { firekitDoc } from 'svelte-firekit';

  const post = firekitDoc<Post>('posts/post-id');
</script>

{#if post.loading}<p>Loading...</p>{/if}
{#if post.data}<h1>{post.data.title}</h1>{/if}

Using the <Doc> component:

<Doc path="posts/post-id">
  {#snippet data(post)}
    <h1>{post.title}</h1>
  {/snippet}
  {#snippet loading()}<p>Loading...</p>{/snippet}
</Doc>

Change path reactively:

post.setPath('posts/other-id'); // tears down listener, re-subscribes

One-time fetch:

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

Reactive collection

<script lang="ts">
  import { firekitCollection } from 'svelte-firekit';
  import { where, orderBy } from 'firebase/firestore';

  const posts = firekitCollection<Post>('posts', [
    where('published', '==', true),
    orderBy('createdAt', 'desc')
  ]);
</script>

{#each posts.data as post}
  <p>{post.title}</p>
{/each}

Cursor-based pagination:

await posts.setPagination(10);   // 10 per page, switches to one-time fetch
await posts.nextPage();          // next page (replaces data)
await posts.prevPage();          // previous page
await posts.loadMore();          // append next page (infinite scroll)
await posts.resetPagination();   // back to page 1

posts.currentPage  // number
posts.hasMore      // boolean

Fluent query builder:

import { FirekitQueryBuilder } from 'svelte-firekit';

const constraints = new FirekitQueryBuilder<Post>()
  .where('published', '==', true)
  .orderBy('createdAt', 'desc')
  .limit(20)
  .build();

Collection group:

const allComments = firekitCollectionGroup<Comment>('comments');

Document mutations

import { firekitMutations } from 'svelte-firekit';

await firekitMutations.add('posts', { title: 'Hello' });
await firekitMutations.set('posts/id', { title: 'Hello' });
await firekitMutations.update('posts/id', { title: 'Updated' });
await firekitMutations.delete('posts/id');

// Existence check
const exists = await firekitMutations.exists('posts/id');

// Field value helpers
await firekitMutations.update('posts/id', {
  views: firekitMutations.increment(1),
  tags: firekitMutations.arrayUnion('svelte'),
  draft: firekitMutations.deleteField(),
  updatedAt: firekitMutations.serverTimestamp()
});

// Batch (auto-chunked at 500)
await firekitMutations.batchOps([
  { type: 'set',    path: 'posts', id: 'a', data: { title: 'A' } },
  { type: 'update', path: 'posts/b', data: { views: 1 } },
  { type: 'delete', path: 'posts/c' }
]);

// Transaction
await firekitMutations.transaction(async (tx) => {
  const snap = await tx.get(ref);
  tx.update(ref, { count: snap.data().count + 1 });
});

Timestamps and options

// Auto-adds createdAt/updatedAt
await firekitMutations.add('posts', data, { timestamps: true, userId: uid });

// Retry on failure
await firekitMutations.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';

const res = await fetch('/bundles/featured.bundle');
await loadFirestoreBundle(res.body!);

const q = await getNamedQuery<Post>('featured-posts');
if (q) {
  const snap = await getDocs(q);
}

Realtime Database

<script lang="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>

{#each messages.list as msg}
  <p>{msg.text}</p>
{/each}
await counter.set(42);
await counter.update({ count: 10 });
await messages.push({ text: 'Hello!', userId: uid });
await messages.remove();
const once = await messages.fetchOnce();

Using the <Node> component:

<Node path="chat/messages">
  {#snippet data(messages)}<p>{messages}</p>{/snippet}
</Node>

Firebase Storage

<script lang="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>

{#if avatar.url}<img src={avatar.url} alt="avatar" />{/if}

{#if upload}
  <progress value={upload.progress} max={100} />
  {#if upload.downloadURL}<img src={upload.downloadURL} />{/if}
{/if}
import { deleteFile, getFileMetadata, updateFileMetadata } from 'svelte-firekit';

await deleteFile('images/old.jpg');
const meta = await getFileMetadata('images/avatar.jpg');
await updateFileMetadata('images/avatar.jpg', {
  contentType: 'image/webp',
  customMetadata: { uploadedBy: uid }
});

List files:

<script lang="ts">
  import { firekitStorageList } from 'svelte-firekit';
  const dir = firekitStorageList('uploads/2024');
</script>
{#each dir.items as item}<p>{item.name}</p>{/each}

File upload validation

Validate files before uploading — checks size, MIME type, and image dimensions.

import { validateFile } from 'svelte-firekit';

const result = await validateFile(file, {
  maxSize: 5 * 1024 * 1024,          // 5 MB
  accept: ['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:

<UploadTask path="uploads/{file.name}" {file} validate={{ maxSize: 5_000_000, accept: ['image/*'] }}>
  {#snippet uploading(task)}<progress value={task.progress} max={100} />{/snippet}
  {#snippet complete(url)}<img src={url} alt="uploaded" />{/snippet}
  {#snippet invalid(result)}
    {#each result.errors as err}<p class="error">{err.message}</p>{/each}
  {/snippet}
</UploadTask>

Using the <DownloadURL> component:

<DownloadURL path="images/avatar.jpg">
  {#snippet data(url)}<img src={url} />{/snippet}
</DownloadURL>

Cloud Functions

import { firekitCallable, firekitCallableFromURL } from 'svelte-firekit';

const sendWelcome = firekitCallable<{ userId: string }, { sent: boolean }>('sendWelcomeEmail');
const result = await sendWelcome.call({ userId: 'abc' });
// sendWelcome.loading, sendWelcome.error, sendWelcome.result

const fn = firekitCallableFromURL<Input, Output>('https://region-project.cloudfunctions.net/fn');

Firebase AI (Gemini)

import { firekitGenerate, firekitStream, firekitChat } from 'svelte-firekit';

// One-shot
const gen = firekitGenerate({ model: 'gemini-2.0-flash' });
await gen.generate('Summarize: ...');
// gen.text, gen.loading, gen.error

// Streaming
const stream = firekitStream({ model: 'gemini-2.0-flash' });
await stream.generate('Write a poem about Svelte.');
// stream.text updates token-by-token, stream.streaming

// Multi-turn chat
const chat = firekitChat({ model: 'gemini-2.0-flash' });
await chat.send('Hello!');
await chat.send('Tell me more.');
// chat.history, chat.pendingText, chat.streaming

Switch between Google AI and Vertex AI:

import { GoogleAIBackend, VertexAIBackend } from 'svelte-firekit';

const gen = firekitGenerate({ backend: 'vertexai', model: 'gemini-2.0-flash' });

Content helpers:

import { textPart, imagePart, imageUrlPart } from 'svelte-firekit';

await gen.generate([textPart('Describe this image:'), imagePart(base64)]);

Remote Config

import { firekitRemoteConfig } from 'svelte-firekit';

const rc = firekitRemoteConfig({
  defaults: { welcomeMessage: 'Hello!', featureEnabled: false },
  minimumFetchIntervalMs: 3_600_000,
  realtime: true // subscribe to live config updates
});

await rc.fetchAndActivate();

const msg = rc.getString('welcomeMessage');
const flag = rc.getBoolean('featureEnabled');
const count = rc.getNumber('itemsPerPage', 10);

Performance Monitoring

import { firekitPerformance } from 'svelte-firekit';

// Simple trace
const stop = await firekitPerformance.startTrace('load-dashboard');
// ... work ...
stop();

// Timed block
const duration = await firekitPerformance.measure('render-posts', async () => {
  await loadPosts();
});

Analytics

import { firekitAnalytics } from 'svelte-firekit';

await firekitAnalytics.logEvent('purchase', { value: 9.99, currency: 'USD' });
await firekitAnalytics.setUserId('uid123');
await firekitAnalytics.setUserProperties({ plan: 'pro' });
await firekitAnalytics.logScreenView('Dashboard');

Messaging (FCM)

import { firekitMessaging } from 'svelte-firekit';

const token = await firekitMessaging.requestPermission('YOUR_VAPID_KEY');
// firekitMessaging.token, firekitMessaging.permission, firekitMessaging.supported

// Listen for foreground messages
const unsub = await firekitMessaging.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.

<script lang="ts">
  import { firekitNetwork } from 'svelte-firekit';
</script>

{#if !firekitNetwork.online}
  <p>You're offline. Changes will sync when reconnected.</p>
{:else if firekitNetwork.hasPendingWrites}
  <p>Saving...</p>
{:else}
  <p>All changes saved</p>
{/if}

Using the <NetworkStatus> component:

<NetworkStatus>
  {#snippet online()}<span class="green">Connected</span>{/snippet}
  {#snippet offline()}<span class="red">Offline</span>{/snippet}
  {#snippet pending()}<span>Saving...</span>{/snippet}
</NetworkStatus>

Manual control:

await firekitNetwork.goOffline();   // force offline mode
await firekitNetwork.goOnline();    // reconnect
firekitNetwork.trackWrite();        // mark a pending write

Presence

import { firekitPresence } from 'svelte-firekit';

await firekitPresence.initialize(user, {
  sessionTTL: 30 * 60_000,
  trackDeviceInfo: true,
  geolocation: { enabled: true, type: 'browser', requireConsent: true }
});

await firekitPresence.setPresence('away');

const stats = firekitPresence.getStats();
// stats.onlineSessions, stats.totalSessions, stats.uniqueDevices

await firekitPresence.dispose();

App Check

<script>
  import { ReCaptchaEnterpriseProvider } from 'svelte-firekit';
</script>

<FirebaseApp
  {config}
  appCheckOptions={{
    provider: new ReCaptchaEnterpriseProvider('SITE_KEY'),
    isTokenAutoRefreshEnabled: true
  }}
>
  {@render children()}
</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:
const db = getFirestoreContext();
const auth = 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 server
const user = await firekitUser.waitForAuth();
const posts = firekitCollection<Post>('posts');
const data = await posts.waitForReady();

API Reference

Services

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

Reactive classes

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

Utilities

| Import | Description | |---|---| | validateFile() | Pre-upload file validation (size, type, dimensions) |

Components

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


License

MIT © Giovani Rodriguez