BetaMaShop is in public beta. We improve it continuously, and your feedback shapes what comes next.

Connect your Supabase

Your generated codeEnglish· 5 min read· Updated July 01, 2026

How your generated app reads your own Supabase: the three env vars set on the Netlify deploy and the clients that read them.

How-to

Your generated app talks to your own Supabase project through three environment variables and the Supabase clients in your code. This page shows the exact variable names, where MaShop sets them, and which client reads which one. Use it when your app cannot reach the database or you need to run it on your own machine.

The three environment variables

Your code reads Supabase through three variables. MaShop writes all three to your Netlify site at deploy time, with the value applied to every Netlify context (production, deploy previews, branch deploys).

VariableWhat it holdsExposed to the browser
NEXT_PUBLIC_SUPABASE_URLYour project URL, in the form https://your-ref.supabase.coYes
NEXT_PUBLIC_SUPABASE_ANON_KEYYour anon key, the public key that respects Row Level SecurityYes
SUPABASE_SERVICE_ROLE_KEYYour service-role key, which bypasses Row Level SecurityNo

The first two carry the NEXT_PUBLIC_ prefix because Next.js inlines those into the browser bundle. The anon key is safe there: it can only do what your RLS policies permit. The service-role key has no prefix and never reaches the browser. Keep it that way. Anything that reads the service-role key must run on the server.

Where the values come from

MaShop provisions a Supabase project on your account during onboarding and writes its details onto your MaShop project row: supabase_url, supabase_anon_key, and supabase_service_key (the service key is encrypted at rest). At deploy time the deploy helper reads those three columns, decrypts the service key, and pushes them to Netlify under the variable names in the table above.

The push happens only when all three values are present on your project. If your project is missing the Supabase URL, anon key, or service key, MaShop skips the env push and your site builds without database credentials, so every query fails at runtime. If you linked Supabase yourself rather than letting MaShop provision it, confirm all three values are set on the project before you deploy.

How your storefront reads Supabase

Your storefront pages are Server Components that read your database directly. The scaffold ships a single shared client at lib/supabase.ts and your pages import it:

import { createClient } from '@supabase/supabase-js';

const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

if (!url || !anonKey) {
 console.warn('[supabase] NEXT_PUBLIC_SUPABASE_URL or _ANON_KEY missing.');
}

export const supabase = createClient(url ?? '', anonKey ?? '');

This client deliberately does not throw when the variables are absent. A throw at module load would crash every page during a Netlify build where the variables are sometimes not yet present. Instead it logs the warning [supabase] NEXT_PUBLIC_SUPABASE_URL or _ANON_KEY missing. and lets the build finish. If you see that line in your build log, the env vars did not reach Netlify.

A page reads your tables straight off that client:

import { supabase } from '@/lib/supabase';

export default async function Page() {
 const { data: products } = await supabase.from('products').select('*').eq('status', 'active');
 return <main>{/* render products */}</main>;
}

Because this path uses the anon key, it returns only the rows your Row Level Security policies allow the public to read. See Row Level Security for what those policies cover.

How the backoffice reads Supabase

Your backoffice ships three clients under src/lib/supabase/, one per execution context. Pick the client that matches where your code runs.

Browser client

browser.ts builds a client for Client Components with the anon key, through @supabase/ssr so the auth session lives in cookies:

'use client';
import { createBrowserClient } from '@supabase/ssr';
import type { Database } from '@/types/database';

export function createBrowserSupabase() {
 return createBrowserClient<Database>(process.env.NEXT_PUBLIC_SUPABASE_URL!,
 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
 );
}

Server client

server.ts builds a client for Server Components and route handlers. It also uses the anon key, but reads and writes the auth cookies so the request runs as the signed-in user:

import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';
import type { Database } from '@/types/database';

export async function createServerSupabase() {
 const cookieStore = await cookies();
 return createServerClient<Database>(process.env.NEXT_PUBLIC_SUPABASE_URL!,
 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
 { cookies: { /* getAll / setAll from cookieStore */ } }
 );
}

Admin client

admin.ts builds the only client that uses SUPABASE_SERVICE_ROLE_KEY. It is marked server-only so importing it from a Client Component fails the build, it disables session persistence, and it caches a single instance per server process:

import 'server-only';
import { createClient } from '@supabase/supabase-js';
import type { Database } from '@/types/database';

export function createAdminSupabase() {
 return createClient<Database>(process.env.NEXT_PUBLIC_SUPABASE_URL!,
 process.env.SUPABASE_SERVICE_ROLE_KEY!,
 { auth: { persistSession: false } }
 );
}

This client ignores Row Level Security. Use it only for trusted server work such as admin tables and webhook handlers. Never import it into a Client Component, and never expose its result to the browser.

ClientRuns inKey usedRLS
createBrowserSupabase()Client ComponentsAnonEnforced
createServerSupabase()Server Components, route handlersAnonEnforced, as the signed-in user
createAdminSupabase()Server onlyService roleBypassed

Run the app on your own machine

To run your generated app locally, copy .env.example to .env.local and fill the same three variables with your Supabase project values:

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...

Find these in your Supabase dashboard under Project Settings, API. The backoffice .env.example also ships NEXT_PUBLIC_DEFAULT_CURRENCY (default USD) and NEXT_PUBLIC_DEFAULT_LOCALE (default en-US); both are overridable per merchant at runtime through the settings table.

When the database is unreachable

  • Build log shows [supabase] NEXT_PUBLIC_SUPABASE_URL or _ANON_KEY missing. The env vars did not reach Netlify. Confirm supabase_url, supabase_anon_key, and supabase_service_key are all set on your project, then redeploy. The deploy skips the env push if any of the three is empty.
  • Queries return no rows but throw no error. The anon key is working and Row Level Security is filtering the rows out. Review your policies in Row Level Security.
  • A Client Component fails the build complaining about server-only. Something imported createAdminSupabase() into client code. Move that call into a Server Component or a route handler.

Your Supabase project lives on your own account. MaShop never holds your customers or your data. To change which Supabase project an app uses, update the credentials on the project and redeploy. See Deploy to Netlify for the deploy itself and Your code, explained for how the rest of the generated app is laid out.

supabasesetupdatabase
Was this page helpful?
Your feedback is anonymous.