Chrome Extension with Supabase Backend: Auth, Database, and Real-Time
Building a Chrome extension is only half the battle. When you need to store user data, authenticate users across devices, or sync information in real-time, you need a robust backend. Supabase is an open-source Firebase alternative that provides exactly what modern Chrome extensions need: authentication, a PostgreSQL database, and real-time subscriptions — all with a generous free tier that is perfect for getting started.
In this comprehensive guide, we will walk through building a Chrome extension that connects to Supabase for user authentication, database operations, and real-time data synchronization. Whether you are building a note-taking extension, a productivity tool, or a team collaboration app, this guide will give you the foundation you need.
Why Supabase for Chrome Extensions?
Before we dive into the code, let us understand why Supabase is an excellent choice for Chrome extension backends. Supabase provides a complete backend-as-a-service platform built on PostgreSQL, one of the most reliable and feature-rich database systems in the world. Unlike other solutions, Supabase gives you a real SQL database, which means you can perform complex queries, joins, and aggregations that would be difficult or impossible with NoSQL alternatives.
Key Benefits of Supabase for Extensions
Supabase offers several advantages that make it particularly well-suited for Chrome extension development. First, the authentication system is drop-in ready. You can add email/password login, OAuth providers like Google and GitHub, and even magic link authentication with just a few lines of code. This means your extension can have user accounts without building your own auth system from scratch.
Second, the free tier is remarkably generous. You get 500MB of database storage, 1GB of file storage, and 50MB of bandwidth for free every month. For most new extensions, this is more than enough to get started and validate your idea. As your extension grows, you can upgrade to a paid plan with predictable pricing.
Third, Supabase provides real-time capabilities out of the box. You can subscribe to database changes and receive updates instantly when data changes. This is incredibly powerful for extensions that need to sync data across multiple devices or that need to show live updates to users.
Fourth, the client libraries are excellent. Supabase provides official JavaScript libraries that work perfectly in both the extension popup context and the background service worker. The type-safe TypeScript definitions make development a breeze.
Setting Up Your Supabase Project
The first step is to create a Supabase project and get your credentials. Head over to supabase.com and create a new project. Choose a name like “chrome-extension-demo” and set a secure password for your database. Once your project is ready, you will be taken to the dashboard where you can find your project URL and anon public key.
In the Supabase dashboard, navigate to the Settings section and then to API. Here you will find your Project URL and the anon public key. Keep these credentials handy — you will need them to connect your Chrome extension to Supabase.
Creating Your Database Schema
Now let us set up the database tables your extension will use. In the Supabase dashboard, click on the Table Editor in the left sidebar and create a new table called “profiles”. This table will store additional user information beyond what Supabase Auth provides automatically.
Create the following columns for the profiles table: id (uuid, primary key, references auth.users), username (text, nullable), created_at (timestamp with time zone, default now), and settings (jsonb, nullable). Click Save to create the table.
Next, create a second table called “notes” that will store user notes. Add these columns: id (uuid, primary key, default gen_random_uuid()), user_id (uuid, not null, references auth.users), title (text, not null), content (text, nullable), created_at (timestamp with time zone, default now), and updated_at (timestamp with time zone, default now). Click Save to create this table as well.
For security, you need to set up Row Level Security (RLS) policies. RLS ensures that users can only access their own data. Go to the Authentication section in the left sidebar and click on Policies. For the profiles table, create a new policy that allows SELECT, INSERT, UPDATE, and DELETE operations where the auth.uid() matches the user_id. Do the same for the notes table.
Finally, enable real-time for the notes table. Go to the Database section, find the notes table, and toggle on “Enable Realtime” in the table settings. This will allow your extension to receive instant updates when notes are added, modified, or deleted.
Installing Supabase in Your Extension
Now it is time to integrate Supabase into your Chrome extension. If you are starting from scratch, create a new Chrome extension project using the manifest V3 format. If you already have an extension, you can add Supabase to it.
The recommended way to use Supabase in your extension is through npm. In your extension project directory, run npm init -y to create a package.json if you do not have one already. Then install the Supabase JavaScript client:
npm install @supabase/supabase-js
If you are not using a build system, you can also use the CDN version. Add this script tag to your popup.html or background.js:
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
For Chrome extensions, it is best to use a build system like Vite, Webpack, or Parcel. This allows you to import Supabase as an ES module and tree-shake unused code. Most Chrome extension templates and starter kits support ES modules out of the box.
Initializing the Supabase Client
With Supabase installed, the next step is to initialize the client in your extension code. Create a new file called supabase.js in your extension directory. This file will handle the connection to your Supabase project.
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'YOUR_SUPABASE_PROJECT_URL';
const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY';
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
Replace the placeholder URL and key with your actual Supabase credentials. In a production extension, you might want to store these in environment variables or a config file that gets updated during your build process.
One important consideration for Chrome extensions is where to initialize the client. You will likely need to use Supabase in multiple contexts: the popup, the background service worker, and possibly content scripts. Rather than duplicating the client initialization, consider creating a shared module that all contexts can import.
Adding Authentication to Your Extension
User authentication is often the first feature extension developers need. Supabase Auth makes this straightforward with support for multiple authentication methods. Let us implement email/password authentication as well as Google OAuth.
Email and Password Login
For basic email/password authentication, you need forms for sign up and sign in. In your popup HTML, create input fields for email and password, along with buttons for signing up and signing in. Then handle the button clicks in your JavaScript.
To sign up a new user:
async function signUp(email, password) {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password
});
if (error) {
console.error('Signup error:', error.message);
return { success: false, error: error.message };
}
// Check if email confirmation is required
if (data.user && data.user.email_confirmed_at) {
return { success: true, user: data.user };
}
return { success: true, message: 'Check your email for confirmation' };
}
For signing in:
async function signIn(email, password) {
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password
});
if (error) {
console.error('Signin error:', error.message);
return { success: false, error: error.message };
}
return { success: true, user: data.user, session: data.session };
}
The sign-in response includes both the user object and a session object. The session contains an access token that Supabase uses to authenticate database requests. You do not need to manually handle this token — the Supabase client automatically manages it.
Google OAuth
For a better user experience, you can also offer Google OAuth. This allows users to sign in with their Google account without creating a new password. To set this up, you need to configure Google as an authentication provider in your Supabase dashboard.
In Supabase, go to Authentication > Providers > Google. Enable it and enter your Google OAuth credentials. You will need to create a project in the Google Cloud Console and get a client ID and client secret. For Chrome extensions, set the authorized JavaScript origin to your extension’s ID and the redirect URL to your Supabase project URL.
Once configured, signing in with Google is simple:
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'YOUR_EXTENSION_POPUP_URL'
}
});
if (error) {
console.error('OAuth error:', error.message);
return { success: false, error: error.message };
}
}
Managing Auth State
Your extension needs to respond to authentication state changes. Supabase provides a method to listen for changes:
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
console.log('User signed in:', session.user);
// Update UI to show logged-in state
} else if (event === 'SIGNED_OUT') {
console.log('User signed out');
// Update UI to show logged-out state
}
});
This is particularly useful for updating your popup UI whenever the user signs in or out. You can store the session information in chrome.storage to persist it across popup opens.
Database Operations in Your Extension
Now that users can authenticate, let us perform database operations. We will create, read, update, and delete notes using the Supabase client.
Creating Data
To create a new note in the database:
async function createNote(title, content) {
// Get the current user
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return { success: false, error: 'Not authenticated' };
}
const { data, error } = await supabase
.from('notes')
.insert([
{
user_id: user.id,
title: title,
content: content
}
])
.select();
if (error) {
console.error('Create note error:', error.message);
return { success: false, error: error.message };
}
return { success: true, note: data[0] };
}
The Supabase client automatically includes the user’s authentication token with database requests. The Row Level Security policies we set up earlier ensure that this note is only accessible to the authenticated user.
Reading Data
To fetch all notes for the current user:
async function getNotes() {
const { data, error } = await supabase
.from('notes')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Get notes error:', error.message);
return { success: false, error: error.message };
}
return { success: true, notes: data };
}
You can also fetch a single note by ID:
async function getNote(noteId) {
const { data, error } = await supabase
.from('notes')
.select('*')
.eq('id', noteId)
.single();
if (error) {
console.error('Get note error:', error.message);
return { success: false, error: error.message };
}
return { success: true, note: data };
}
Updating Data
To update an existing note:
async function updateNote(noteId, updates) {
const { data, error } = await supabase
.from('notes')
.update({
title: updates.title,
content: updates.content,
updated_at: new Date()
})
.eq('id', noteId)
.select();
if (error) {
console.error('Update note error:', error.message);
return { success: false, error: error.message };
}
return { success: true, note: data[0] };
}
Deleting Data
To delete a note:
async function deleteNote(noteId) {
const { error } = await supabase
.from('notes')
.delete()
.eq('id', noteId);
if (error) {
console.error('Delete note error:', error.message);
return { success: false, error: error.message };
}
return { success: true };
}
Real-Time Updates
One of Supabase’s most powerful features is real-time database subscriptions. This allows your extension to receive instant updates when data changes, either from other devices or from other parts of your extension.
Subscribing to Changes
To listen for changes to the notes table:
function subscribeToNotes(callback) {
const subscription = supabase
.channel('notes-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'notes'
},
(payload) => {
console.log('Change received:', payload);
callback(payload);
}
)
.subscribe();
return subscription;
}
The payload contains information about what changed. For INSERT events, payload.new contains the new row data. For UPDATE events, payload.old contains the previous data and payload.new contains the updated data. For DELETE events, payload.old contains the deleted row data.
Filtering Real-Time Events
By default, you receive all changes to the table. To filter for only your user’s notes, you can add a filter to the subscription:
async function subscribeToUserNotes(userId, callback) {
const subscription = supabase
.channel('user-notes-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'notes',
filter: `user_id=eq.${userId}`
},
(payload) => {
callback(payload);
}
)
.subscribe();
return subscription;
}
This is important for performance and security. You do not want to receive updates for notes that belong to other users, and filtering at the subscription level reduces unnecessary network traffic.
Using Real-Time in Your Extension
Real-time subscriptions are particularly useful in several scenarios. First, if your extension supports multiple devices, real-time updates ensure that users see their notes update across all devices instantly. Second, if your extension has a background script that processes data, real-time notifications can trigger that processing. Third, for collaborative features where multiple users work on the same data, real-time updates provide immediate feedback.
To use the subscription in your popup:
// When popup opens, subscribe to updates
async function initPopup() {
const { data: { user } } = await supabase.auth.getUser();
if (user) {
const subscription = subscribeToUserNotes(user.id, (payload) => {
// Update UI based on the change
if (payload.eventType === 'INSERT') {
addNoteToList(payload.new);
} else if (payload.eventType === 'UPDATE') {
updateNoteInList(payload.new);
} else if (payload.eventType === 'DELETE') {
removeNoteFromList(payload.old.id);
}
});
// Clean up when popup closes
popup.onunload = () => {
subscription.unsubscribe();
};
}
}
Best Practices for Extension Backend Development
As your extension grows, there are several best practices you should follow to ensure reliability and performance.
Error Handling
Always implement comprehensive error handling. Network requests can fail, users can lose internet connectivity, and database operations can encounter constraints. Handle each error case gracefully and provide helpful feedback to users.
State Management
Use chrome.storage to persist authentication state and user preferences. This allows your extension to restore its state when the user reopens the popup after closing it. The Supabase session can be automatically persisted, but you may want to cache other data for performance.
Security Considerations
Never expose your Supabase service role key in extension code. The service role bypasses Row Level Security and should only be used in server-side applications. The anon key is safe to include in your extension because it is designed to be public. RLS policies protect user data regardless of what key is used in the client.
Performance Optimization
Minimize the amount of data you fetch. Use select() to specify only the columns you need, use pagination for large datasets, and implement caching where appropriate. The Supabase client supports all of these optimizations.
Conclusion
Supabase provides an excellent backend solution for Chrome extensions. With its built-in authentication, PostgreSQL database, and real-time capabilities, you can build sophisticated extensions that rival native applications in functionality. The generous free tier lets you start building without upfront costs, and the straightforward pricing makes growth predictable.
In this guide, we covered setting up Supabase, connecting it to your extension, implementing authentication with both email/password and OAuth, performing CRUD operations on your database, and subscribing to real-time updates. With these foundations, you can build everything from simple note-taking apps to complex collaborative tools.
The Chrome extension ecosystem continues to evolve, and having a robust backend like Supabase opens up possibilities that were previously difficult to implement. Start with this guide, experiment with the features, and build something amazing.