Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Use Supabase in a Monorepo

To use Supabase in a monorepo, create a shared package that exports a configured Supabase client and generated TypeScript types. Each app in the monorepo imports the client from the shared package instead of initializing its own. Store environment variables at the app level and pass them to the shared client factory function so each app can connect to different Supabase projects if needed.

What you'll learn

  • How to structure a shared Supabase client package in a monorepo
  • How to generate and share TypeScript types across apps
  • How to manage environment variables per app in a monorepo
  • How to run Supabase CLI commands from the monorepo root
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+, Turborepo/Nx/pnpm workspacesMarch 2026RapidDev Engineering Team
TL;DR

To use Supabase in a monorepo, create a shared package that exports a configured Supabase client and generated TypeScript types. Each app in the monorepo imports the client from the shared package instead of initializing its own. Store environment variables at the app level and pass them to the shared client factory function so each app can connect to different Supabase projects if needed.

Setting Up Supabase in a Monorepo with Shared Types and Client

Monorepos let you share code between multiple apps — a web frontend, mobile app, and admin panel, for example. Instead of duplicating Supabase client initialization and types in every app, you can create a shared package that all apps import from. This tutorial shows you how to structure the shared package, generate types once and share them, and manage environment variables so each app connects to the right Supabase project.

Prerequisites

  • A monorepo set up with Turborepo, Nx, or pnpm workspaces
  • A Supabase project with at least one table
  • Supabase CLI installed globally or as a dev dependency
  • Node.js 18+ and TypeScript configured

Step-by-step guide

1

Create the shared Supabase package

In your monorepo, create a new package that will hold the Supabase client factory, generated types, and any shared utilities. With pnpm workspaces, create a folder under packages/ (for example, packages/supabase). Add a package.json with the package name and the @supabase/supabase-js dependency. This package will be the single source of truth for all Supabase interactions across your monorepo.

typescript
1# Create the shared package directory
2mkdir -p packages/supabase/src
3
4# Initialize package.json
5cd packages/supabase
6npm init -y
7
8# Install Supabase client
9pnpm add @supabase/supabase-js
10pnpm add -D typescript supabase

Expected result: A packages/supabase directory exists with @supabase/supabase-js installed.

2

Build the client factory function

Instead of hardcoding environment variables in the shared package, create a factory function that accepts the Supabase URL and key as arguments. Each app will call this factory with its own environment variables, letting you connect different apps to different Supabase projects or use different keys (anon vs service role). Export the factory function and the Database type from the package entry point.

typescript
1// packages/supabase/src/client.ts
2import { createClient, SupabaseClient } from '@supabase/supabase-js'
3import type { Database } from './types'
4
5export function createSupabaseClient(
6 url: string,
7 anonKey: string
8): SupabaseClient<Database> {
9 return createClient<Database>(url, anonKey, {
10 auth: {
11 autoRefreshToken: true,
12 persistSession: true,
13 },
14 })
15}
16
17export function createSupabaseAdmin(
18 url: string,
19 serviceRoleKey: string
20): SupabaseClient<Database> {
21 return createClient<Database>(url, serviceRoleKey, {
22 auth: {
23 autoRefreshToken: false,
24 persistSession: false,
25 },
26 })
27}
28
29export type { Database } from './types'

Expected result: The shared package exports createSupabaseClient and createSupabaseAdmin factory functions.

3

Generate and share TypeScript types

Run supabase gen types typescript from the monorepo root or the shared package directory, pointing to your linked Supabase project. Save the output to the shared package so all apps get the same type definitions. Add a script to your package.json so anyone on the team can regenerate types when the schema changes. The generated types give you full autocomplete and type safety for all table operations across every app.

typescript
1# Link to your Supabase project (run once)
2supabase link --project-ref your-project-ref
3
4# Generate types into the shared package
5supabase gen types typescript --linked > packages/supabase/src/types.ts
6
7# Add a script to the shared package's package.json
8# "scripts": {
9# "gen-types": "supabase gen types typescript --linked > src/types.ts"
10# }

Expected result: A types.ts file is generated in packages/supabase/src/ containing TypeScript types for all your tables.

4

Configure the package entry point and exports

Set up the package.json exports field so consuming apps can import the client factory and types cleanly. Configure TypeScript to compile the package, and make sure the main and types fields point to the right files. If you use Turborepo, add a build step to the turbo.json pipeline for this package.

typescript
1// packages/supabase/package.json
2{
3 "name": "@myapp/supabase",
4 "version": "1.0.0",
5 "main": "src/index.ts",
6 "types": "src/index.ts",
7 "exports": {
8 ".": "./src/index.ts"
9 },
10 "dependencies": {
11 "@supabase/supabase-js": "^2.45.0"
12 }
13}
14
15// packages/supabase/src/index.ts
16export { createSupabaseClient, createSupabaseAdmin } from './client'
17export type { Database } from './types'

Expected result: Other apps in the monorepo can import from @myapp/supabase and get full TypeScript support.

5

Use the shared client in each app

In each app (web, mobile, admin), add the shared package as a workspace dependency. Create a local supabase.ts file that calls the factory function with the app's environment variables. This keeps environment variables scoped to each app while sharing all the client logic and types. Each app can use different Supabase keys or even different projects.

typescript
1// apps/web/.env.local
2NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
3NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUz...
4
5// apps/web/src/lib/supabase.ts
6import { createSupabaseClient } from '@myapp/supabase'
7
8export const supabase = createSupabaseClient(
9 process.env.NEXT_PUBLIC_SUPABASE_URL!,
10 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
11)
12
13// apps/web/src/app/page.tsx
14import { supabase } from '@/lib/supabase'
15import type { Database } from '@myapp/supabase'
16
17type Product = Database['public']['Tables']['products']['Row']
18
19const { data } = await supabase.from('products').select('*')
20// data is typed as Product[] | null

Expected result: Each app initializes its own Supabase client using the shared factory function and gets full type safety from shared types.

Complete working example

packages/supabase/src/index.ts
1// packages/supabase/src/index.ts
2// Shared Supabase client factory for monorepo usage
3
4import { createClient, SupabaseClient } from '@supabase/supabase-js'
5import type { Database } from './types'
6
7/**
8 * Create a Supabase client for browser/client-side usage.
9 * Uses the anon key and respects RLS policies.
10 */
11export function createSupabaseClient(
12 url: string,
13 anonKey: string
14): SupabaseClient<Database> {
15 return createClient<Database>(url, anonKey, {
16 auth: {
17 autoRefreshToken: true,
18 persistSession: true,
19 },
20 })
21}
22
23/**
24 * Create a Supabase admin client for server-side usage only.
25 * Uses the service role key and bypasses all RLS policies.
26 * NEVER use this in client-side code.
27 */
28export function createSupabaseAdmin(
29 url: string,
30 serviceRoleKey: string
31): SupabaseClient<Database> {
32 return createClient<Database>(url, serviceRoleKey, {
33 auth: {
34 autoRefreshToken: false,
35 persistSession: false,
36 },
37 })
38}
39
40// Re-export types for consumer apps
41export type { Database } from './types'
42
43// Convenience type helpers
44export type Tables<T extends keyof Database['public']['Tables']> =
45 Database['public']['Tables'][T]['Row']
46
47export type InsertTables<T extends keyof Database['public']['Tables']> =
48 Database['public']['Tables'][T]['Insert']
49
50export type UpdateTables<T extends keyof Database['public']['Tables']> =
51 Database['public']['Tables'][T]['Update']

Common mistakes when using Supabase in a Monorepo

Why it's a problem: Hardcoding the Supabase URL and anon key in the shared package instead of passing them as arguments

How to avoid: Use a factory function that accepts URL and key as parameters. Each app passes its own environment variables, allowing different apps to connect to different projects.

Why it's a problem: Forgetting to regenerate types after schema changes, causing type mismatches across apps

How to avoid: Add a gen-types script to the shared package and run it in CI after every migration. Use supabase gen types typescript --linked > src/types.ts.

Why it's a problem: Installing @supabase/supabase-js separately in each app, causing version conflicts

How to avoid: Install @supabase/supabase-js only in the shared package. Apps should depend on the shared package, which provides the client as a transitive dependency.

Best practices

  • Keep the Supabase client and types in a single shared package to avoid duplication
  • Use a factory function pattern so each app can inject its own environment variables
  • Regenerate TypeScript types in CI whenever database migrations are applied
  • Export convenience type helpers like Tables<'products'> to simplify usage in consumer apps
  • Use the anon key for client-side code and the service role key only in server-side API routes
  • Scope the shared package name with your organization prefix for clarity in imports
  • Pin @supabase/supabase-js to a specific version in the shared package to prevent unexpected updates

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Turborepo monorepo with a Next.js web app and a React Native mobile app. Both need to connect to the same Supabase project. Show me how to create a shared packages/supabase package with a client factory function and generated TypeScript types that both apps import from.

Supabase Prompt

Generate a Supabase client factory module that accepts URL and key parameters, returns a typed SupabaseClient<Database>, and exports convenience type helpers for all tables. Include both a browser client (anon key, persistent session) and a server admin client (service role key, no session).

Frequently asked questions

Can I connect different apps in the monorepo to different Supabase projects?

Yes. The factory function pattern lets each app pass its own SUPABASE_URL and SUPABASE_ANON_KEY. Your web app could connect to a production project while your admin app connects to a staging project.

Should I install @supabase/supabase-js in each app or only in the shared package?

Install it only in the shared package. Apps depend on the shared package, which provides the Supabase client as a transitive dependency. This prevents version conflicts and ensures all apps use the same client version.

How do I regenerate types when my database schema changes?

Run supabase gen types typescript --linked > packages/supabase/src/types.ts from the monorepo root. Add this as a script in the shared package and include it in your CI pipeline after migrations.

Does this approach work with Lovable or V0 projects?

Lovable and V0 are self-contained browser-based editors, so they do not directly support monorepo structures. However, if you export a Lovable or V0 project to GitHub, you can restructure it into a monorepo and use this shared package approach.

How do I handle SSR with the shared Supabase client?

For SSR frameworks like Next.js, use the @supabase/ssr package alongside the shared client. Create a server-specific factory function that uses cookies instead of localStorage for session storage.

Can RapidDev help set up a monorepo with shared Supabase infrastructure?

Yes. RapidDev can architect your monorepo structure, configure shared packages for Supabase client and types, set up CI pipelines for type generation, and implement proper environment variable management across all your apps.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.