To connect Supabase to Hasura, use the PostgreSQL connection string from your Supabase project settings and add it as a database source in the Hasura Console. Hasura connects directly to the underlying PostgreSQL database, giving you an instant GraphQL API with permissions, subscriptions, and relationships. You can use Hasura's permission system alongside or instead of Supabase RLS, depending on your architecture.
Connecting Hasura GraphQL Engine to a Supabase Database
This tutorial shows you how to connect Hasura to your Supabase PostgreSQL database. Hasura provides a powerful GraphQL engine with fine-grained permissions, subscriptions, and remote schemas. By pointing Hasura at your Supabase database, you get instant GraphQL queries and mutations on your existing tables while keeping Supabase auth, storage, and Edge Functions. This is ideal for teams that prefer GraphQL over REST.
Prerequisites
- A Supabase project with tables and data
- A Hasura Cloud account or self-hosted Hasura instance
- Access to Supabase Dashboard Project Settings for the connection string
- Basic understanding of PostgreSQL and GraphQL
Step-by-step guide
Find your Supabase database connection string
Find your Supabase database connection string
In the Supabase Dashboard, navigate to Project Settings in the left sidebar, then click on Database. Under Connection string, you will find the URI format connection string. Copy the one labeled Session Mode (port 5432) for Hasura — this gives direct access to PostgreSQL. Do not use the pooler connection (port 6543) unless Hasura specifically requires it for high-concurrency scenarios. Replace [YOUR-PASSWORD] with the database password you set when creating the project.
1# Connection string format2postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgresExpected result: You have a PostgreSQL connection string with your project reference, password, and host.
Add Supabase as a database source in Hasura Console
Add Supabase as a database source in Hasura Console
Open your Hasura Console (Hasura Cloud dashboard or local console at localhost:8080). Navigate to the Data tab and click Connect Database. Choose PostgreSQL as the database type. Enter a display name like 'supabase' and paste the connection string from the previous step. Click Connect Database to establish the link. Hasura will introspect the schema and detect all tables, views, and relationships.
1# If using Hasura CLI with docker-compose, add the database URL as an env var2# docker-compose.yml3services:4 graphql-engine:5 image: hasura/graphql-engine:v2.38.06 environment:7 HASURA_GRAPHQL_DATABASE_URL: postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres8 HASURA_GRAPHQL_ADMIN_SECRET: your-admin-secret9 HASURA_GRAPHQL_ENABLE_CONSOLE: 'true'Expected result: Hasura is connected to your Supabase database and shows all detected schemas, tables, and relationships.
Track tables and configure relationships
Track tables and configure relationships
After connecting, Hasura shows untracked tables and relationships. Click Track All to expose all your Supabase tables through the GraphQL API. Hasura automatically detects foreign key relationships and creates object (one-to-one) and array (one-to-many) relationships. Review the detected relationships and rename them if needed for a cleaner GraphQL schema. You can also add custom relationships manually.
Expected result: All tables in the public schema are tracked and their foreign key relationships appear in the Hasura GraphQL schema.
Set up Hasura permissions using Supabase auth JWTs
Set up Hasura permissions using Supabase auth JWTs
To use Supabase auth with Hasura, configure Hasura to verify Supabase JWTs. Set the HASURA_GRAPHQL_JWT_SECRET environment variable with your Supabase project's JWT secret (found in Dashboard under Project Settings > API > JWT Settings). This lets Hasura read the user's role and ID from the JWT. Then create Hasura permission rules that reference the x-hasura-user-id session variable, which maps to the Supabase auth user ID.
1# Set JWT secret in Hasura environment2HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256","key":"your-supabase-jwt-secret-here"}'34# The JWT from Supabase contains:5# {6# "sub": "user-uuid", -> maps to x-hasura-user-id7# "role": "authenticated"8# }910# In Hasura Console, set custom claims mapping:11# x-hasura-user-id: sub12# x-hasura-default-role: authenticated13# x-hasura-allowed-roles: [authenticated, anonymous]Expected result: Hasura can verify Supabase JWTs and extract user identity for permission checks.
Create row-level permissions in Hasura
Create row-level permissions in Hasura
With JWT verification configured, create permission rules in the Hasura Console under the Permissions tab for each table. For the 'authenticated' role, set select, insert, update, and delete permissions with row-level rules like user_id equals X-Hasura-User-Id. This is Hasura's equivalent of Supabase RLS. Choose whether to use Hasura permissions, Supabase RLS, or both — using both is the most secure but requires maintaining two permission systems.
Expected result: Authenticated users can only access their own data through the Hasura GraphQL API, matching the row-level security of your Supabase RLS policies.
Query the GraphQL API from your frontend
Query the GraphQL API from your frontend
With everything configured, query your Supabase data through the Hasura GraphQL endpoint. Send the Supabase JWT as a Bearer token in the Authorization header. Hasura processes the JWT, applies permissions, and returns the filtered data. You can use any GraphQL client like Apollo, urql, or plain fetch. The GraphQL endpoint is your Hasura URL with /v1/graphql appended.
1// Query Supabase data through Hasura GraphQL2const HASURA_URL = 'https://your-hasura-instance.hasura.app/v1/graphql'34async function fetchTodos(supabaseAccessToken: string) {5 const response = await fetch(HASURA_URL, {6 method: 'POST',7 headers: {8 'Content-Type': 'application/json',9 'Authorization': `Bearer ${supabaseAccessToken}`,10 },11 body: JSON.stringify({12 query: `13 query GetMyTodos {14 todos(order_by: { created_at: desc }) {15 id16 title17 is_complete18 user {19 email20 }21 }22 }23 `24 }),25 })2627 const { data, errors } = await response.json()28 if (errors) throw new Error(errors[0].message)29 return data.todos30}Expected result: Your frontend fetches data from Supabase via Hasura GraphQL with user-specific permissions enforced.
Complete working example
1import { createClient } from '@supabase/supabase-js'23// Initialize Supabase client for auth4const supabase = createClient(5 process.env.NEXT_PUBLIC_SUPABASE_URL!,6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!7)89const HASURA_URL = process.env.NEXT_PUBLIC_HASURA_URL!1011// Generic GraphQL query function12async function graphqlQuery<T>(13 query: string,14 variables?: Record<string, unknown>15): Promise<T> {16 const { data: { session } } = await supabase.auth.getSession()17 if (!session) throw new Error('Not authenticated')1819 const response = await fetch(`${HASURA_URL}/v1/graphql`, {20 method: 'POST',21 headers: {22 'Content-Type': 'application/json',23 'Authorization': `Bearer ${session.access_token}`,24 },25 body: JSON.stringify({ query, variables }),26 })2728 const result = await response.json()29 if (result.errors) {30 throw new Error(result.errors[0].message)31 }32 return result.data as T33}3435// Fetch todos with related user data36async function getTodos() {37 return graphqlQuery<{ todos: Array<{ id: number; title: string; is_complete: boolean }> }>(`38 query GetTodos {39 todos(order_by: { created_at: desc }) {40 id41 title42 is_complete43 }44 }45 `)46}4748// Create a new todo via GraphQL mutation49async function createTodo(title: string) {50 return graphqlQuery<{ insert_todos_one: { id: number; title: string } }>(`51 mutation CreateTodo($title: String!) {52 insert_todos_one(object: { title: $title, is_complete: false }) {53 id54 title55 }56 }57 `, { title })58}5960// Toggle todo completion61async function toggleTodo(id: number, isComplete: boolean) {62 return graphqlQuery(`63 mutation ToggleTodo($id: Int!, $isComplete: Boolean!) {64 update_todos_by_pk(65 pk_columns: { id: $id },66 _set: { is_complete: $isComplete }67 ) {68 id69 is_complete70 }71 }72 `, { id, isComplete })73}Common mistakes when connecting Supabase to Hasura
Why it's a problem: Using the Supabase pooler connection (port 6543) when Hasura needs a direct connection (port 5432)
How to avoid: Use the Session Mode connection string on port 5432 for Hasura. The pooler on port 6543 uses PgBouncer in transaction mode which may not work with Hasura's connection requirements.
Why it's a problem: Tracking the auth and storage schemas in Hasura, which exposes internal Supabase tables
How to avoid: Only track tables in the public schema. The auth and storage schemas are managed by Supabase and should not be exposed through Hasura.
Why it's a problem: Not configuring JWT verification in Hasura, leaving the GraphQL API accessible without authentication
How to avoid: Set HASURA_GRAPHQL_JWT_SECRET with your Supabase JWT secret and create role-based permissions for every tracked table.
Best practices
- Use environment variables for the database connection string in both Hasura Cloud and self-hosted deployments
- Configure Hasura JWT verification with your Supabase JWT secret for authentication integration
- Only track the public schema in Hasura — leave auth and storage schemas to Supabase
- Set up row-level permissions in Hasura using X-Hasura-User-Id mapped from the JWT sub claim
- Keep Supabase RLS enabled as a safety net even if you use Hasura permissions primarily
- Use Hasura's GraphQL subscriptions for real-time features instead of or alongside Supabase Realtime
- Monitor Hasura query performance and add database indexes for frequently used GraphQL queries
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Supabase project and want to add a Hasura GraphQL layer on top. Walk me through connecting Hasura to Supabase's PostgreSQL database, configuring JWT auth, and setting up permissions so users can only access their own data.
Help me connect my Supabase database to Hasura. I need the connection string configuration, JWT secret setup for Supabase auth integration, and example GraphQL queries that work with Hasura permissions and Supabase access tokens.
Frequently asked questions
Why would I use Hasura instead of Supabase's built-in API?
Hasura provides a full-featured GraphQL engine with nested queries, subscriptions, remote schemas, and fine-grained permissions. Use it if your team prefers GraphQL over REST, needs complex query capabilities, or wants to federate multiple data sources.
Can I use both Supabase's REST API and Hasura simultaneously?
Yes. Supabase's PostgREST API and Hasura both connect to the same PostgreSQL database. You can use Supabase's API for simple CRUD and Hasura for complex GraphQL queries. They operate independently.
Do I need to disable Supabase RLS when using Hasura permissions?
No. You can use both simultaneously for defense in depth. Hasura permissions apply at the GraphQL layer, while RLS applies at the database layer. If Hasura connects with the postgres role, RLS is bypassed; if with the anon or authenticated role, RLS applies.
Does Hasura support Supabase auth out of the box?
Not natively, but you can configure Hasura to verify Supabase JWTs by setting the JWT secret. This gives Hasura access to the user's ID and role from the Supabase auth token.
Is there a performance impact from adding Hasura as a layer?
Hasura adds minimal latency (typically 1-5ms) because it connects directly to PostgreSQL and generates optimized SQL. For most applications the overhead is negligible compared to the benefits of GraphQL.
Can RapidDev help integrate Supabase with Hasura?
Yes. RapidDev can set up a Hasura GraphQL layer on your Supabase database, configure auth integration, design permissions, and help you build a production GraphQL API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation