Docs · Client

Using supabase-js

Da8a works with the official @supabase/supabase-js client. There’s no fork or plugin to install.

Install and connect

shell
$ npm i @supabase/supabase-js
src/supabase.ts
import { createClient } from '@supabase/supabase-js'

export const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,     // your project URL
  import.meta.env.VITE_SUPABASE_ANON_KEY // your anon key
)

Both values are on your project’s Overview page. The anon key is safe to ship to browsers when Row Level Security is on. Never put the service key in frontend code.

Queries

queries.ts
// Read with filters
const { data } = await supabase
  .from('todos')
  .select('id, title, done')
  .eq('done', false)
  .order('id', { ascending: false })

// Insert, update, delete
await supabase.from('todos').insert({ title: 'Write docs' })
await supabase.from('todos').update({ done: true }).eq('id', 1)
await supabase.from('todos').delete().eq('id', 1)

These calls go through your project’s auto-generated REST API, which applies your Row Level Security policies to every request.

Auth

Enable providers in the dashboard under Auth.

ProviderClient call
Email and passwordauth.signUp, auth.signInWithPassword
Magic linkauth.signInWithOtp({ email })
Google, GitHubauth.signInWithOAuth({ provider })
Phoneauth.signInWithOtp({ phone }), then auth.verifyOtp
SAML single sign-onauth.signInWithSSO({ domain })
auth.ts
await supabase.auth.signUp({ email, password })
await supabase.auth.signInWithOAuth({ provider: 'github' })

const { data: { user } } = await supabase.auth.getUser()

supabase.auth.onAuthStateChange((event, session) => {
  console.log(event, session?.user?.id)
})

For OAuth providers, enter the callback URL shown in the dashboard in the provider’s console, and add your app’s URLs to the redirect allow-list. If your site is on Chapong and linked to the project, its redirect URLs are kept in sync for you.

Row Level Security basics

With the anon key in the browser, Row Level Security (RLS) is what decides who can read and write each row. Turn it on for every table your app exposes, then add policies. Without a policy, a table with RLS on returns nothing.

policies.sql
alter table profiles enable row level security;

-- Anyone can read profiles
create policy "Public profiles" on profiles
  for select using (true);

-- Users can update only their own profile
create policy "Update own profile" on profiles
  for update to authenticated
  using (auth.uid() = id)
  with check (auth.uid() = id);

auth.uid() is the signed-in user’s id. The service key bypasses RLS, which is why it belongs only on servers and in edge function secrets. The McManus assistant can draft policies for you to review and apply.

Realtime

realtime.ts
const channel = supabase
  .channel('todo-changes')
  .on('postgres_changes',
    { event: '*', schema: 'public', table: 'todos' },
    (payload) => console.log(payload.eventType, payload.new))
  .subscribe()

// Later
supabase.removeChannel(channel)

Change events follow your RLS policies, so users only receive rows they’re allowed to see. As with Supabase, a table must be part of the supabase_realtime publication to send changes.

Storage

storage.ts
// Upload to a bucket
await supabase.storage.from('avatars').upload(`${user.id}/me.png`, file)

// Public bucket: a permanent URL
const { data } = supabase.storage.from('avatars').getPublicUrl(`${user.id}/me.png`)

// Private bucket: a link that expires after an hour
const { data: signed } = await supabase.storage
  .from('invoices').createSignedUrl('2026/10.pdf', 3600)

Create buckets and manage files in the dashboard under Storage.

Calling functions

invoke.ts
const { data, error } = await supabase.functions.invoke('hello', {
  body: { name: 'Ada' }
})

See Edge functions for writing and deploying them.