Supabase Local¶
Prerequisites¶
- Docker - Docker is used to run Supabase locally.
- Docker Compose - Docker Compose is used to run Supabase locally.
- Doppler CLI - Doppler is used to manage secrets.
- Supabase CLI - Supabase CLI is used to manage Supabase projects.
- Bun - For the web apps, we use Bun to run the project.
Setup¶
Setup Supabase¶
Go to the root/source folder of the project and run the following command to login to Supabase:
Configure config.toml accordingly:
project_id = "something"
# ...
[db.seed]
enabled = true
sql_paths = ["./seeds/*.sql"]
# ...
[studio]
enabled = true
port = 54323
api_url = "env(SUPABASE_URL)"
openai_api_key = "env(OPENAI_API_KEY)"
# ...
[storage.buckets.avatars]
public = true
file_size_limit = "50MiB"
allowed_mime_types = ["image/png", "image/jpeg"]
objects_path = "./storage/avatars"
# ...
[auth]
enabled = true
site_url = "env(BASE_URL)"
# ...
[auth.external.google]
enabled = true
client_id = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID)"
secret = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)"
redirect_uri = "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_REDIRECT_URI)"
url = ""
skip_nonce_check = false
Add the custom auth hook to the config.toml file:
[auth.hook.custom_access_token_hook]
enabled = true
uri = "pg-functions://postgres/public/custom_access_token_hook"
During the migrations, it is necessary to assign the additional permissions from the documentation
Run Supabase with Doppler¶
First make sure you have the secrets in Doppler:
SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID
SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET
SUPABASE_AUTH_EXTERNAL_GOOGLE_REDIRECT_URI
Login to Doppler and setup using the corresponding project, using the dev_personal branch:
Confirm the secrets are in the dev_personal branch:
Migrations¶
If there are new users/roles in the database other than the default ones, it is necessary to create a new migration file.
This will create a new migration file in the migrations folder. And add the corresponding query to the migration file.
For the remote schema pull, it is necessary to move temporarily the file from the migrations folder.
Remote Schema Pull¶
To pull the remote schema, run the following command:
This will create a file like 20250115064442_remote_schema.sql. After the schema is pulled, move the migration file about the roles back to the migrations folder.
Missing triggers¶
For some reason, Supabase migrations do not create the triggers. So it is necessary to create them manually. In this case, we need to create a new migration file for the auth related triggers.
Then add the following query to the migration file:
CREATE TRIGGER on_auth_user_created
AFTER INSERT
ON public.users
FOR EACH ROW EXECUTE FUNCTION handle_new_user();
CREATE TRIGGER on_auth_user_updated
AFTER UPDATE OF email, deleted_at, banned_until, email_confirmed_at
ON public.users
FOR EACH ROW EXECUTE FUNCTION handle_auth_user_update();
Custom Access Token Hook¶
The custom access token hook is used to create the access token for the user. It is necessary to create a new migration file for the custom access token hook.
GRANT EXECUTE
ON FUNCTION public.custom_access_token_hook
TO supabase_auth_admin;
GRANT USAGE ON SCHEMA public TO supabase_auth_admin;
REVOKE EXECUTE
ON FUNCTION public.custom_access_token_hook
FROM authenticated, anon, public;
-- Grant permissions for the user_roles and user_permissions tables
GRANT ALL ON TABLE public.user_roles TO supabase_auth_admin;
REVOKE ALL ON TABLE public.user_roles FROM anon, public;
GRANT ALL ON TABLE public.user_permissions TO supabase_auth_admin;
REVOKE ALL ON TABLE public.user_permissions FROM anon, public;
For reference, the hook function we use is this (add to the migration file if it is not already in the remote schema migration file):
CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event jsonb) RETURNS jsonb
LANGUAGE plpgsql STABLE
AS $$
DECLARE
claims jsonb;
user_roles jsonb;
role_permissions jsonb;
custom_permissions jsonb;
combined_permissions jsonb;
BEGIN
-- Fetch all roles for the user
SELECT jsonb_agg(role)
INTO user_roles
FROM public.user_roles
WHERE user_id = (event ->> 'user_id')::uuid;
-- If no roles are found, assign the default role 'user'
IF user_roles IS NULL THEN
user_roles := jsonb_build_array('user');
END IF;
-- Fetch all permissions associated with the user's roles
SELECT jsonb_agg(concat(rp.resource, ':', rp.action))
INTO role_permissions
FROM public.role_permissions rp
WHERE rp.role = ANY (array(SELECT jsonb_array_elements_text(user_roles)));
-- Fetch all custom permissions for the user
SELECT jsonb_agg(concat(up.resource, ':', up.action))
INTO custom_permissions
FROM public.user_permissions up
WHERE up.user_id = (event ->> 'user_id')::uuid;
-- Combine role permissions and custom permissions and remove duplicates
combined_permissions := (SELECT jsonb_agg(DISTINCT permission)
FROM jsonb_array_elements_text(coalesce(role_permissions, '[]'::jsonb) ||
coalesce(custom_permissions, '[]'::jsonb)) AS permission);
claims := event -> 'claims';
IF user_roles IS NOT NULL THEN
claims := jsonb_set(claims, '{user_roles}', user_roles);
ELSE
claims := jsonb_set(claims, '{user_roles}', '[]'::jsonb); -- Default to empty array
END IF;
claims := jsonb_set(claims, '{user_permissions}', combined_permissions);
event := jsonb_set(event, '{claims}', claims);
RETURN event;
END;
$$;
Run Supabase¶
Run the following command to start Supabase:
If another instance of Supabase is running, you may get the following error:
To fix this, stop the containers, and run the command again.
Stop Supabase¶
To stop Supabase, run the following command:
Or if it is configured in the web app, run the following command:
[!NOTE] Every time the
config.tomlfile is changed, it is necessary to restart Supabase.
Stop Supabase without backup¶
To stop Supabase without backing up the database, run the following command:
Same as above, if it is configured in the web app, run the following command: