JavaScript Client Library
@supabase/supabase-jsView on GitHubThis reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
Initializing
Create a new client for use in the browser.
Parameters
- supabaseUrlRequiredstring
The unique Supabase URL which is supplied when you create a new project in your project dashboard.
- supabaseKeyRequiredstring
The unique Supabase Key which is supplied when you create a new project in your project dashboard.
- optionsOptionalSupabaseClientOptions
1234import { } from '@supabase/supabase-js' // Create a single supabase client for interacting with your database const = ('https://xyzcompany.supabase.co', 'public-anon-key')
Upgrade guide
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
Upgrade the client library
Install the latest version
1npm install @supabase/supabase-js@2
Optionally if you are using custom configuration with createClient
then follow below:
1234const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { schema: 'custom', persistSession: false,})
Read more about the constructor options.
Auth methods
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
Sign in with email and password
123const { user, error } = await supabase .auth .signIn({ email, password })
Sign in with magic link
123const { error } = await supabase .auth .signIn({ email })
Sign in with a third-party provider
123const { error } = await supabase .auth .signIn({ provider })
Sign in with phone
123const { error } = await supabase .auth .signIn({ phone, password })
Sign in with phone using OTP
1234const { error } = await supabase .auth .api .sendMobileOTP(phone)
Reset password for email
1234const { data, error } = await supabase .auth .api .resetPasswordForEmail(email)
Get the user's current session
Note that auth.getSession
reads the auth token and the unencoded session data from the local storage medium. It doesn't send a request back to the Supabase Auth server unless the local session is expired.
You should never trust the unencoded session data if you're writing server code, since it could be tampered with by the sender. If you need verified, trustworthy user data, call auth.getUser
instead, which always makes a request to the Auth server to fetch trusted data.
1const session = supabase.auth.session()
Get the logged-in user
1const user = supabase.auth.user()
Update user data for a logged-in user
123const { user, error } = await supabase .auth .update({ attributes })
Use a custom access_token
JWT with Supabase
1const { user, error } = supabase.auth.setAuth(access_token)
Cookie methods
The cookie-related methods like setAuthCookie
and getUserByCookie
have been removed.
For Next.js you can use the Auth Helpers to help you manage cookies. If you can't use the Auth Helpers, you can use server-side rendering.
Some the PR for additional background information.
Data methods
.insert()
/ .upsert()
/ .update()
/ .delete()
don't return rows by default: PR.
Previously, these methods return inserted/updated/deleted rows by default (which caused some confusion), and you can opt to not return it by specifying returning: 'minimal'
. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a .select()
call at the end.
Insert and return data
123const { data, error } = await supabase .from('my_table') .insert({ new_data })
Update and return data
1234const { data, error } = await supabase .from('my_table') .update({ new_data }) .eq('id', id)
Upsert and return data
123const { data, error } = await supabase .from('my_table') .upsert({ new_data })
Delete and return data
1234const { data, error } = await supabase .from('my_table') .delete() .eq('id', id)
Realtime methods
Subscribe
1234const userListener = supabase .from('users') .on('*', (payload) => handleAllEventsPayload(payload.new)) .subscribe()
Unsubscribe
1userListener.unsubscribe()
Fetch data
- By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use
range()
queries to paginate through your data. select()
can be combined with Modifiersselect()
can be combined with Filters- If using the Supabase hosted platform
apikey
is technically a reserved keyword, since the API gateway will pluck it out for authentication. It should be avoided as a column name.
123const { data, error } = await supabase .from('cities') .select()
Insert data
- By default, every time you run
insert()
, the client library will make aselect
to return the full record. This is convenient, but it can also cause problems if your Policies are not configured to allow theselect
operation. If you are using Row Level Security and you are encountering problems, try setting thereturning
param tominimal
.
12345const { data, error } = await supabase .from('cities') .insert([ { name: 'The Shire', country_id: 554 } ])
Update data
update()
should always be combined with Filters to target the item(s) you wish to update.
1234const { data, error } = await supabase .from('characters') .update({ name: 'Han Solo' }) .match({ name: 'Han' })
Upsert data
- Primary keys should be included in the data payload in order for an update to work correctly.
- Primary keys must be natural, not surrogate. There are however, workarounds for surrogate primary keys.
- If you need to insert new data and update existing data at the same time, use Postgres triggers.
123const { data, error } = await supabase .from('messages') .upsert({ id: 3, message: 'foo', username: 'supabot' })
Delete data
delete()
should always be combined with filters to target the item(s) you wish to delete.- If you use
delete()
with filters and you have RLS enabled, only rows visible throughSELECT
policies are deleted. Note that by default no rows are visible, so you need at least oneSELECT
/ALL
policy that makes the rows visible.
1234const { data, error } = await supabase .from('cities') .delete() .match({ id: 666 })
Call a Postgres function
You can call Postgres functions as Remote Procedure Calls, logic in your database that you can execute from anywhere. Functions are useful when the logic rarely changes—like for password resets and updates.
123create or replace function hello_world() returns text as $$ select 'Hello world';$$ language sql;
12const { data, error } = await supabase .rpc('hello_world')
Using filters
Filters can be used on select()
, update()
, and delete()
queries.
If a Postgres function returns a table response, you can also apply filters.
Applying Filters
You must apply your filters to the end of your query. For example:
123456789const { data, error } = await supabase .from('cities') .select('name, country_id') .eq('name', 'The Shire') // Correctconst { data, error } = await supabase .from('cities') .eq('name', 'The Shire') // Incorrect .select('name, country_id')
Chaining
Filters can be chained together to produce advanced queries. For example:
12345const { data, error } = await supabase .from('cities') .select('name, country_id') .gte('population', 1000) .lt('population', 10000)
Conditional Chaining
Filters can be built up one step at a time and then executed. For example:
12345678910111213const filterByName = nullconst filterPopLow = 1000const filterPopHigh = 10000let query = supabase .from('cities') .select('name, country_id')if (filterByName) { query = query.eq('name', filterByName) }if (filterPopLow) { query = query.gte('population', filterPopLow) }if (filterPopHigh) { query = query.lt('population', filterPopHigh) }const { data, error } = await query
Column is equal to a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .eq('name', 'The shire')
Column is not equal to a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .neq('name', 'The shire')
Column is greater than a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .gt('country_id', 250)
Column is greater than or equal to a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .gte('country_id', 250)
Column is less than a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .lt('country_id', 250)
Column is less than or equal to a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .lte('country_id', 250)
Column matches a pattern
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .like('name', '%la%')
Column matches a case-insensitive pattern
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .ilike('name', '%la%')
Column is a value
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .is('name', null)
Column is in an array
1234const { data, error } = await supabase .from('characters') .select('name, book_id') .in('name', ['Harry', 'Hermione'])
Column contains every element in a value
-
.contains()
can work on array columns or range columns. It is very useful for finding rows where a tag array contains all the values in the filter array.1234.contains('arraycol',["a","b"]) // You can use a javascript array for an array column.contains('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column..contains('rangecol','(1,2]') // Use Postgres range syntax for range column..contains('rangecol',`(${arr}]`) // You can insert an array into a string.
1234const { data, error } = await supabase .from('countries') .select('name, id, main_exports') .contains('main_exports', ['oil'])
Contained by value
-
.containedBy()
can work on array columns or range columns.1234.containedBy('arraycol',["a","b"]) // You can use a javascript array for an array column.containedBy('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column..containedBy('rangecol','(1,2]') // Use Postgres range syntax for range column..containedBy('rangecol',`(${arr}]`) // You can insert an array into a string.
1234const { data, error } = await supabase .from('countries') .select('name, id, main_exports') .containedBy('main_exports', ['cars', 'food', 'machine'])
Greater than a range
1234const { data, error } = await supabase .from('countries') .select('name, id, population_range_millions') .rangeGt('population_range_millions', '[150, 250]')
Greater than or equal to a range
1234const { data, error } = await supabase .from('countries') .select('name, id, population_range_millions') .rangeGte('population_range_millions', '[150, 250]')
Less than or equal to a range
1234const { data, error } = await supabase .from('countries') .select('name, id, population_range_millions') .rangeLt('population_range_millions', '[150, 250]')
Mutually exclusive to a range
1234const { data, error } = await supabase .from('countries') .select('name, id, population_range_millions') .rangeAdjacent('population_range_millions', '[70, 185]')
With a common element
-
.overlaps()
can work on array columns or range columns.1234.overlaps('arraycol',["a","b"]) // You can use a javascript array for an array column.overlaps('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column..overlaps('rangecol','(1,2]') // Use Postgres range syntax for range column..overlaps('rangecol',`(${arr}]`) // You can insert an array into a string.
1234const { data, error } = await supabase .from('countries') .select('name, id, main_exports') .overlaps('main_exports', ['computers', 'minerals'])
Match a string
123456const { data, error } = await supabase .from('quotes') .select('catchphrase') .textSearch('catchphrase', `'fat' & 'cat'`, { config: 'english' })
Match an associated value
1234const { data, error } = await supabase .from('characters') .select('name, book_id') .match({name: 'Harry', book_id: 156})
Don't match the filter
-
.not()
expects you to use the raw PostgREST syntax for the filter names and values.12345.not('name','eq','Luke') .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. .not('id','in','(6,7)') // Use Postgres list () for in filter. .not('id','in',`(${arr})`) // You can insert a javascript array.
1234const { data, error } = await supabase .from('countries') .select('name, country_id') .not('name', 'eq', 'The Shire')
Match at least one filter
-
.or()
expects you to use the raw PostgREST syntax for the filter names and values.123.or('id.in.(6,7), arraycol.cs.{"a","b"}') // Use Postgres list () for in filter. Array {} for array column and 'cs' for contains..or(`id.in.(${arrList}),arraycol.cs.{${arr}}`) // You can insert a javascipt array for list or array on array column..or(`id.in.(${arrList}),rangecol.cs.[${arrRange})`) // You can insert a javascipt array for list or range on a range column.
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .or('id.eq.20,id.eq.30')
Match the filter
.filter()
expects you to use the raw PostgREST syntax for the filter names and values, so it should only be used as an escape hatch in case other filters don't work.1234.filter('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. .filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. .filter('id','in','(6,7)') // Use Postgres list () for in filter. .filter('id','in',`(${arr})`) // You can insert a javascript array.
1234const { data, error } = await supabase .from('characters') .select('name, book_id') .filter('name', 'in', '("Harry","Hermione")')
Using modifiers
Modifiers can be used on select()
queries.
If a Postgres function returns a table response, you can also apply modifiers to the rpc()
function.
Order the results
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .order('id', { ascending: false })
Limit the number of rows returned
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .limit(1)
Limit the query to a range
1234const { data, error } = await supabase .from('cities') .select('name, country_id') .range(0,3)
Retrieve one row of data
12345const { data, error } = await supabase .from('cities') .select('name, country_id') .limit(1) .single()
Retrieve zero or one row of data
12345const { data, error } = await supabase .from('instruments') .select('name, section_id') .eq('name', 'violin') .maybeSingle()
Create a new user
- By default, the user will need to verify their email address before logging in. If you would like to change this, you can disable "Email Confirmations" by going to Authentication -> Settings on supabase.com/dashboard
- If "Email Confirmations" is turned on, a
user
is returned butsession
will be null - If "Email Confirmations" is turned off, both a
user
and asession
will be returned - When the user confirms their email address, they will be redirected to localhost:3000 by default. To change this, you can go to Authentication -> Settings on supabase.com/dashboard
- If signUp() is called for an existing confirmed user:
- If "Enable email confirmations" is enabled on the "Authentication" -> "Settings" page, an obfuscated / fake user object will be returned.
- If "Enable email confirmations" is disabled, an error with a message "User already registered" will be returned.
- To check if a user already exists, refer to getUser().
1234const { user, session, error } = await supabase.auth.signUp({ email: 'example@email.com', password: 'example-password',})
Listen to auth events
123supabase.auth.onAuthStateChange((event, session) => { console.log(event, session)})
Sign in a user
- A user can sign up either via email or OAuth.
- If you provide
email
without apassword
, the user will be sent a magic link. - The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on supabase.com/dashboard
- Specifying a
provider
will open the browser to the relevant login page.
1234const { user, session, error } = await supabase.auth.signIn({ email: 'example@email.com', password: 'example-password',})
Sign out a user
1const { error } = await supabase.auth.signOut()
Send a password reset request
Sends a password reset request to an email address.
- When the user clicks the reset link in the email they are redirected back to your application. You can configure the URL that the user is redirected to via the
redirectTo
param. See redirect URLs and wildcards to add additional redirect URLs to your project. - After the user has been redirected successfully, prompt them for a new password and call
updateUser()
:
123const { data, error } = await supabase.auth.update({ password: new_password,})
1234const { data, error } = await supabase.auth.api.resetPasswordForEmail( email, { redirectTo: 'https://example.com/update-password' })
Update a user
User email: By Default, email updates sends a confirmation link to both the user's current and new email. To only send a confirmation link to the user's new email, disable Secure email change in your project's email auth provider settings.
User metadata: It's generally better to store user data in a table within your public schema (i.e., public.users
). Use the update()
method if you have data which rarely changes or is specific only to the logged in user.
1const { user, error } = await supabase.auth.update({email: 'new@email.com'})
Update the access token
1234567891011function apiFunction(req, res) { // Assuming the access token was sent as a header "X-Supabase-Auth" const { access_token } = req.get('X-Supabase-Auth') // You can now use it within a Supabase Client const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key") const { user, error } = supabase.auth.setAuth(access_token) // This client will now send requests as this user const { data } = await supabase.from('your_table').select()}
Retrieve a user
This method gets the user object from memory.
1const user = supabase.auth.user()
Retrieve a session
1const session = supabase.auth.session()
Retrieve a user
- Fetches the user object from the database instead of local storage.
- Note that user() fetches the user object from local storage which might not be the most updated.
- Requires the user's access_token.
123const { user, error } = await supabase.auth.api.getUser( 'ACCESS_TOKEN_JWT',)
Invokes a Supabase Edge Function.
Invokes a function
Invokes a Supabase Edge Function.
- Requires an Authorization header.
- Invoke params generally match the Fetch API spec.
Parameters
- functionNameRequiredstring
The name of the Function to invoke.
- optionsRequiredFunctionInvokeOptions
Options for invoking the Function.
Return Type
123const { data, error } = await supabase.functions.invoke('hello', { body: JSON.stringify({ foo: 'bar' })})
Subscribe to channel
- Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by managing replication.
- If you want to receive the "previous" data for updates and deletes, you will need to set
REPLICA IDENTITY
toFULL
, like this:ALTER TABLE your_table REPLICA IDENTITY FULL;
123456const mySubscription = supabase .from('*') .on('*', payload => { console.log('Change received!', payload) }) .subscribe()
Remove a subscription
- Removing subscriptions is a great way to maintain the performance of your project's database. Supabase will automatically handle cleanup 30 seconds after a user is disconnected, but unused subscriptions may cause degradation as more users are simultaneously subscribed.
1supabase.removeSubscription(mySubscription)
Remove all subscriptions
- Removing subscriptions is a great way to maintain the performance of your project's database. Supabase will automatically handle cleanup 30 seconds after a user is disconnected, but unused subscriptions may cause degradation as more users are simultaneously subscribed.
1supabase.removeAllSubscriptions()
Retrieve subscriptions
1const subscriptions = supabase.getSubscriptions()
Create a bucket
- Policy permissions required:
buckets
permissions:insert
objects
permissions: none
123const { data, error } = await supabase .storage .createBucket('avatars', { public: false })
Retrieve a bucket
- Policy permissions required:
buckets
permissions:select
objects
permissions: none
123const { data, error } = await supabase .storage .getBucket('avatars')
List all buckets
- Policy permissions required:
buckets
permissions:select
objects
permissions: none
123const { data, error } = await supabase .storage .listBuckets()
Update a bucket
- Policy permissions required:
buckets
permissions:update
objects
permissions: none
123const { data, error } = await supabase .storage .updateBucket('avatars', { public: false })
Delete a bucket
- Policy permissions required:
buckets
permissions:select
anddelete
objects
permissions: none
123const { data, error } = await supabase .storage .deleteBucket('avatars')
Empty a bucket
- Policy permissions required:
buckets
permissions:select
objects
permissions:select
anddelete
123const { data, error } = await supabase .storage .emptyBucket('avatars')
Upload a file
- Policy permissions required:
buckets
permissions: noneobjects
permissions:insert
- For React Native, using either
Blob
,File
orFormData
does not work as intended. Upload file usingArrayBuffer
from base64 file data instead, see example below.
12345678const avatarFile = event.target.files[0]const { data, error } = await supabase .storage .from('avatars') .upload('public/avatar1.png', avatarFile, { cacheControl: '3600', upsert: false })
Download a file
- Policy permissions required:
buckets
permissions: noneobjects
permissions:select
1234const { data, error } = await supabase .storage .from('avatars') .download('folder/avatar1.png')
List all files in a bucket
- Policy permissions required:
buckets
permissions: noneobjects
permissions:select
12345678const { data, error } = await supabase .storage .from('avatars') .list('folder', { limit: 100, offset: 0, sortBy: { column: 'name', order: 'asc' }, })
Replace an existing file
- Policy permissions required:
buckets
permissions: noneobjects
permissions:update
andselect
- For React Native, using either
Blob
,File
orFormData
does not work as intended. Update file usingArrayBuffer
from base64 file data instead, see example below.
12345678const avatarFile = event.target.files[0]const { data, error } = await supabase .storage .from('avatars') .update('public/avatar1.png', avatarFile, { cacheControl: '3600', upsert: false })
Move an existing file
- Policy permissions required:
buckets
permissions: noneobjects
permissions:update
andselect
1234const { data, error } = await supabase .storage .from('avatars') .move('public/avatar1.png', 'private/avatar2.png')
Copy an existing file
- Policy permissions required:
buckets
permissions: noneobjects
permissions:insert
andselect
1234const { data, error } = await supabase .storage .from('avatars') .copy('public/avatar1.png', 'private/avatar2.png')
Delete files in a bucket
- Policy permissions required:
buckets
permissions: noneobjects
permissions:delete
andselect
1234const { data, error } = await supabase .storage .from('avatars') .remove(['folder/avatar1.png'])
Create a signed URL
- Policy permissions required:
buckets
permissions: noneobjects
permissions:select
1234const { signedURL, error } = await supabase .storage .from('avatars') .createSignedUrl('folder/avatar1.png', 60)
Create signed URLs
- Policy permissions required:
buckets
permissions: noneobjects
permissions:select
1234const { data, error } = await supabase .storage .from('avatars') .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60)
Retrieve public URL
- The bucket needs to be set to public, either via updateBucket() or by going to Storage on supabase.com/dashboard, clicking the overflow menu on a bucket and choosing "Make public"
- Policy permissions required:
buckets
permissions: noneobjects
permissions: none
1234const { publicURL, error } = supabase .storage .from('public-bucket') .getPublicUrl('folder/avatar1.png')