Managing User Data

For security purposes, the auth schema is not exposed on the auto-generated API.

Even though Supabase provides an auth.users table, it can be helpful to create tables in the public schema for storing user data that you want to access via the API.

Creating user tables#

When you create tables to store user data, it's helpful to reference the auth.users table in the primary key to ensure data integrity. Also specify the on delete cascade clause when referencing auth.users. Omitting it may cause problems when deleting users. For example, a public.profiles table might look like this:

create table public.profiles (
  id uuid not null references auth.users on delete cascade,
  first_name text,
  last_name text,

  primary key (id)

alter table public.profiles enable row level security;


Only use primary keys as foreign key references for schemas and tables like auth.users which are managed by Supabase. PostgreSQL lets you specify a foreign key reference for columns backed by a unique index (not necessarily primary keys).

Primary keys are guaranteed not to change. Columns, indices, constraints or other database objects managed by Supabase may change at any time and you should be careful when referencing them directly.

Deleting Users#

You may delete users directly or via the management console at Authentication > Users. Note that deleting a user from the auth.users table does not automatically sign out a user. As Supabase makes use of JSON Web Tokens (JWT), a user's JWT will remain "valid" until it has expired. Should you wish to immediately revoke access for a user, do considering making use of a Row Level Security policy as described below.


You cannot delete a user if they are the owner of any objects in Supabase Storage.

You will encounter an error when you try to delete an Auth user that owns any Storage objects. If this happens, try deleting all the objects for that user, or reassign ownership to another user.

Public access#

Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies. If we wanted the data to be readable by everyone but only allow logged-in users to update their own data, the Policies would look like this:

create policy "Public profiles are viewable by everyone."
  on profiles for select
  using ( true );

create policy "Users can insert their own profile."
  on profiles for insert
  with check ( auth.uid() = id );

create policy "Users can update own profile."
  on profiles for update
  using ( auth.uid() = id );

Private access#

If the data should only be readable by the user who owns the data, we just need to change the for select query above.

create policy "Profiles are viewable by users who created them."
  on profiles for select
  using ( auth.uid() = id );

The nice thing about this pattern? We can now query this table via the API and we don't need to include data filters in our API queries - the Policies will handle that for us:

// This will return nothing while the user is logged out
const { data } = await supabase.from('profiles').select('id, username, avatar_url, website')

// After the user is logged in, this will only return
// the logged-in user's data - in this case a single row
const { error } = await supabase.auth.signIn({ email })
const { data: profile } = await supabase
  .select('id, username, avatar_url, website')

Bypassing Row Level Security#

If you need to fetch a full list of user profiles, we supply a service_key which you can use with your API and Client Libraries to bypass Row Level Security.

Make sure you NEVER expose this publicly. But it can be used on the server-side to fetch all of the profiles.

Accessing User Metadata#

You can assign metadata to users on sign up:

const { data, error } = await supabase.auth.signUp({
  email: 'example@email.com',
  password: 'example-password',
  options: {
    data: {
      first_name: 'John',
      age: 27,

User metadata is stored on the raw_user_meta_data column of the auth.users table. To view the metadata:

const {
  data: { user },
} = await supabase.auth.getUser()
let metadata = user.user_metadata

Advanced techniques#

Using triggers#

If you want to add a row to your public.profiles table every time a user signs up, you can use triggers. If the trigger fails however, it could block the user sign ups - so make sure that the code is well-tested.

For example:

-- inserts a row into public.profiles
create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = public
as $$
  insert into public.profiles (id)
  values (new.id);
  return new;

-- trigger the function every time a user is created
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
Need some help?

Not to worry, our specialist engineers are here to help. Submit a support ticket through the Dashboard.