If you've chosen to self-host Supabase, you already know the value of owning your infrastructure. But here's something that catches many developers off guard: while Supabase's REST API works out of the box, GraphQL requires some deliberate setup on self-hosted instances. And as of February 2026, pg_graphql is no longer enabled by default even on Supabase Cloud.
This guide walks you through setting up GraphQL for your self-hosted Supabase instance using pg_graphql, a PostgreSQL extension that turns your database schema into a GraphQL API without additional servers or processes.
Why GraphQL on Supabase?
Before diving into configuration, let's address why you might want GraphQL alongside (or instead of) the REST API that Supabase provides.
The REST API powered by PostgREST is excellent for straightforward CRUD operations. But GraphQL shines when:
- Your frontend needs data from multiple related tables in a single request
- You want clients to specify exactly which fields they need (reducing payload size)
- You're building a mobile app where bandwidth matters
- Your team already has GraphQL expertise and tooling
The key advantage of pg_graphql is that it runs entirely within PostgreSQL. There's no separate GraphQL server to deploy, scale, or secure. The extension reflects your SQL schema and exposes it through a SQL function that PostgREST can call via RPC.
Understanding How pg_graphql Works
pg_graphql takes a different approach than traditional GraphQL servers. Instead of requiring you to define resolvers in JavaScript or another language, it automatically generates a GraphQL schema from your PostgreSQL tables, views, and functions.
When you execute a GraphQL query:
- The query hits PostgREST as an RPC call to
graphql.resolve() - pg_graphql parses the query and translates it to SQL
- PostgreSQL executes the SQL with your existing Row Level Security policies
- Results return in GraphQL format
This architecture means your RLS policies apply to GraphQL queries automatically. No duplicate authorization logic required.
Prerequisites
Before setting up pg_graphql, ensure your self-hosted Supabase instance is running. You'll need:
- A working Supabase deployment (via Docker Compose or Kubernetes)
- Access to your PostgreSQL database
- The Supabase Studio UI or direct psql access
Check out the system requirements documentation if you haven't deployed yet.
Enabling pg_graphql on Self-Hosted Supabase
Step 1: Enable the Extension
Connect to your database and run:
create extension if not exists pg_graphql;
If you're using Supabase Studio, navigate to Database > Extensions, search for "pg_graphql", and toggle it on.
Step 2: Verify the Installation
Run this query to confirm the extension is active:
select * from pg_extension where extname = 'pg_graphql';
You should see pg_graphql listed with its version number.
Step 3: Expose the graphql_public Schema
For the GraphQL endpoint to work through PostgREST, you need to expose the graphql_public schema in your API settings. In your Docker Compose or environment configuration, ensure PGRST_DB_SCHEMAS includes graphql_public:
# In your docker-compose.yml or env file PGRST_DB_SCHEMAS: public,graphql_public
Restart your PostgREST service after making this change.
Step 4: Test the GraphQL Endpoint
With pg_graphql enabled, you can query the GraphQL API through the REST endpoint:
curl -X POST 'https://your-supabase-url/rest/v1/rpc/graphql' \
-H "apikey: YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'
If everything is configured correctly, you'll receive a JSON response with the root query type.
Creating Your GraphQL Schema
pg_graphql automatically generates a GraphQL schema from your PostgreSQL tables. Let's walk through creating a schema that works well with GraphQL.
Designing Tables for GraphQL
Create tables with clear relationships:
create table authors ( id uuid primary key default gen_random_uuid(), name text not null, bio text, created_at timestamptz default now() ); create table posts ( id uuid primary key default gen_random_uuid(), title text not null, content text, author_id uuid references authors(id), published boolean default false, created_at timestamptz default now() ); create table comments ( id uuid primary key default gen_random_uuid(), post_id uuid references posts(id), author_name text not null, body text not null, created_at timestamptz default now() );
Rebuilding the GraphQL Schema
After creating or modifying tables, you must rebuild the GraphQL schema:
select graphql.rebuild_schema();
This step is critical and often missed. Any schema changes—new tables, columns, or relationships—require a rebuild for pg_graphql to recognize them.
Querying Your Data
With the tables above, you can now run GraphQL queries:
query GetPostsWithAuthors {
postsCollection {
edges {
node {
id
title
published
authors {
name
}
commentsCollection {
edges {
node {
authorName
body
}
}
}
}
}
}
}
Notice how pg_graphql automatically creates:
- Collection types with
edgesandnodefor pagination - Relationships based on foreign keys
- CamelCase field names from snake_case columns
Configuring Authentication
GraphQL queries respect your PostgreSQL role permissions. For self-hosted deployments, configure authentication properly:
Setting Up RLS with GraphQL
Enable Row Level Security on your tables:
alter table posts enable row level security; create policy "Public posts are viewable by everyone" on posts for select using (published = true); create policy "Authors can edit own posts" on posts for all using (auth.uid() = author_id);
These policies apply automatically to GraphQL queries—no additional configuration needed.
Passing Authentication Headers
When making authenticated GraphQL requests, include the authorization header:
curl -X POST 'https://your-supabase-url/rest/v1/rpc/graphql' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer USER_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ postsCollection { edges { node { title } } } }"}'
The JWT token sets the PostgreSQL role and makes auth.uid() available in your RLS policies.
Mutations: Creating and Updating Data
pg_graphql supports mutations for insert, update, and delete operations:
Insert Mutation
mutation CreatePost {
insertIntoPostsCollection(
objects: [
{
title: "GraphQL on Self-Hosted Supabase"
content: "A complete guide..."
authorId: "uuid-here"
published: true
}
]
) {
affectedCount
records {
id
title
}
}
}
Update Mutation
mutation PublishPost {
updatePostsCollection(
set: { published: true }
filter: { id: { eq: "post-uuid" } }
) {
affectedCount
records {
id
published
}
}
}
Delete Mutation
mutation DeleteComment {
deleteFromCommentsCollection(
filter: { id: { eq: "comment-uuid" } }
) {
affectedCount
}
}
Exposing PostgreSQL Functions
You can expose custom PostgreSQL functions through GraphQL by creating them with specific conventions:
create or replace function search_posts(search_term text)
returns setof posts
language sql
stable
as $$
select * from posts
where title ilike '%' || search_term || '%'
or content ilike '%' || search_term || '%';
$$;
After rebuilding the schema, this function becomes available:
query SearchPosts {
searchPosts(searchTerm: "GraphQL") {
edges {
node {
id
title
}
}
}
}
GraphiQL IDE Setup
For development, you'll want a GraphQL IDE. Supabase Studio includes a built-in GraphiQL interface, but you can also use standalone tools.
Access the built-in IDE at:
https://your-supabase-studio-url/project/default/graphiql
For external tools like Altair or Insomnia, configure:
- Endpoint:
https://your-supabase-url/rest/v1/rpc/graphql - Headers:
apikeyand optionallyAuthorization
The introspection query will automatically discover your schema.
Performance Considerations
pg_graphql translates GraphQL to SQL efficiently, but keep these points in mind:
Query Depth
Deep nested queries can generate complex joins. Consider limiting depth in production or implementing query complexity analysis at your API gateway.
N+1 Queries
pg_graphql batches related data fetches, avoiding the classic N+1 problem. However, extremely wide queries spanning many relationships may still benefit from optimization.
Pagination
Always use pagination for collections:
query PaginatedPosts {
postsCollection(first: 10, after: "cursor") {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
title
}
}
}
}
Common Issues and Solutions
Schema Not Updating
If new tables don't appear in your GraphQL schema:
- Ensure the table is in the
publicschema (or a schema exposed to the API) - Run
select graphql.rebuild_schema(); - Verify the role has SELECT permission on the table
Authentication Errors
If queries fail with permission errors:
- Check that your JWT is valid and not expired
- Verify RLS policies allow the operation
- Ensure the
anonorauthenticatedrole has appropriate grants
Empty Response for Empty Database
This is expected behavior. pg_graphql reflects your actual schema—if you have no tables, you'll only see introspection types. Create tables first, then rebuild the schema.
Simplifying Self-Hosted Operations
Managing pg_graphql alongside backups, authentication providers, and other configurations adds operational overhead. This is where management tools become valuable.
Supascale provides a unified interface for self-hosted Supabase management, including:
- One-click extension management
- Automated backups that include your GraphQL schema
- Environment variable configuration through a UI
- Custom domain setup for your API endpoints
At $39.99 for a one-time purchase with unlimited projects, it eliminates much of the operational burden of self-hosting.
GraphQL vs REST: When to Use Which
Both APIs have their place. Use the REST API when:
- You need server-side caching at the HTTP level
- Your queries are simple CRUD operations
- You're using Supabase Realtime subscriptions (not yet supported via GraphQL)
Use GraphQL when:
- Clients need flexibility in data fetching
- You're building for mobile with bandwidth constraints
- Your data model has deep relationships
- Your team has existing GraphQL infrastructure
Many projects use both—REST for real-time and simple operations, GraphQL for complex data fetching.
Next Steps
With GraphQL running on your self-hosted Supabase instance, consider exploring:
- Database performance tuning for optimal query execution
- Connection pooling to handle increased load
- Monitoring your instance to track GraphQL query performance
GraphQL on self-hosted Supabase gives you the flexibility of modern API design without sacrificing the control and cost benefits of running your own infrastructure. The pg_graphql extension's tight PostgreSQL integration means your security model remains consistent across both REST and GraphQL endpoints.
