Supabase provides a built-in GraphQL API powered by the pg_graphql extension. Enable it in the SQL Editor, then query your database tables through the GraphQL endpoint at /graphql/v1 using standard GraphQL syntax. The API auto-generates types, queries, and mutations from your database schema, respects RLS policies, and works with any GraphQL client including Apollo Client and urql.
Using Supabase's Built-in GraphQL API with pg_graphql
Supabase includes pg_graphql, a PostgreSQL extension that auto-generates a GraphQL schema from your database tables and exposes it at your project's /graphql/v1 endpoint. This means you do not need a separate GraphQL server. This tutorial walks you through enabling the extension, querying data with GraphQL, setting up mutations for inserts and updates, and integrating with Apollo Client for a full frontend workflow. All GraphQL operations respect your existing RLS policies.
Prerequisites
- A Supabase project with at least one table created
- RLS policies configured on your tables
- Access to the SQL Editor in the Supabase Dashboard
- Basic familiarity with GraphQL query syntax
Step-by-step guide
Enable the pg_graphql extension
Enable the pg_graphql extension
The pg_graphql extension is available on all Supabase projects but needs to be enabled. Open the SQL Editor in the Dashboard and run the create extension command. Once enabled, Supabase automatically generates a GraphQL schema from all tables in the public schema that have RLS enabled. The GraphQL endpoint becomes available at https://<project-ref>.supabase.co/graphql/v1.
1-- Enable the pg_graphql extension2create extension if not exists pg_graphql;34-- Verify it is installed5select * from pg_extension where extname = 'pg_graphql';Expected result: The pg_graphql extension appears in the pg_extension table. The GraphQL endpoint at /graphql/v1 is now active.
Query data using the GraphQL endpoint
Query data using the GraphQL endpoint
Send GraphQL queries to the /graphql/v1 endpoint using any HTTP client. Include your Supabase API key in the apikey header and the user's JWT in the Authorization header for authenticated requests. The auto-generated schema creates a collection type for each table (e.g., todosCollection for a todos table) with edges and nodes following the Relay connection specification.
1// Query using fetch2const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!3const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!45const response = await fetch(`${SUPABASE_URL}/graphql/v1`, {6 method: 'POST',7 headers: {8 'Content-Type': 'application/json',9 'apikey': SUPABASE_ANON_KEY,10 'Authorization': `Bearer ${SUPABASE_ANON_KEY}`11 },12 body: JSON.stringify({13 query: `14 query GetTodos {15 todosCollection {16 edges {17 node {18 id19 task20 is_complete21 created_at22 }23 }24 }25 }26 `27 })28})2930const { data } = await response.json()31console.log(data.todosCollection.edges)Expected result: The response returns a data object with todosCollection.edges containing an array of node objects, each representing a row from the todos table.
Insert and update data with GraphQL mutations
Insert and update data with GraphQL mutations
pg_graphql auto-generates mutation types for INSERT, UPDATE, and DELETE operations. Insert mutations use insertIntoCollection, update mutations use updateCollection, and delete mutations use deleteFromCollection. All mutations respect RLS policies, so the authenticated user must have the appropriate INSERT, UPDATE, or DELETE policy on the table.
1// Insert a new todo2const insertQuery = `3 mutation InsertTodo {4 insertIntotodosCollection(objects: [{5 task: "Learn GraphQL with Supabase"6 is_complete: false7 }]) {8 records {9 id10 task11 is_complete12 }13 }14 }15`1617// Update a todo18const updateQuery = `19 mutation UpdateTodo {20 updatetodosCollection(21 filter: { id: { eq: 1 } }22 set: { is_complete: true }23 ) {24 records {25 id26 task27 is_complete28 }29 }30 }31`3233// Delete a todo34const deleteQuery = `35 mutation DeleteTodo {36 deleteFromtodosCollection(37 filter: { id: { eq: 1 } }38 ) {39 records {40 id41 }42 }43 }44`Expected result: Each mutation returns the affected records with the fields you requested. The database is updated according to the mutation type.
Set up Apollo Client with Supabase GraphQL
Set up Apollo Client with Supabase GraphQL
For a production frontend, use Apollo Client to manage GraphQL state, caching, and subscriptions. Configure the Apollo HttpLink to point to your Supabase GraphQL endpoint and include the required authentication headers. When the user signs in, update the Authorization header with their JWT.
1import { ApolloClient, InMemoryCache, HttpLink, ApolloProvider } from '@apollo/client'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.NEXT_PUBLIC_SUPABASE_URL!,6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!7)89const httpLink = new HttpLink({10 uri: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/graphql/v1`,11 headers: {12 apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,13 },14 fetch: async (uri, options) => {15 const { data: { session } } = await supabase.auth.getSession()16 const headers = options?.headers as Record<string, string> || {}17 if (session?.access_token) {18 headers['Authorization'] = `Bearer ${session.access_token}`19 } else {20 headers['Authorization'] = `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`21 }22 return fetch(uri, { ...options, headers })23 }24})2526const apolloClient = new ApolloClient({27 link: httpLink,28 cache: new InMemoryCache()29})Expected result: Apollo Client is configured and sends authenticated GraphQL requests to the Supabase endpoint. Data is cached locally for fast re-renders.
Filter and paginate GraphQL queries
Filter and paginate GraphQL queries
The auto-generated schema includes filter arguments for each collection that support eq, neq, gt, lt, gte, lte, in, and like operators. Pagination uses the first and after arguments following the Relay cursor specification. Combine filters with ordering using the orderBy argument to build precise queries.
1const filteredQuery = `2 query FilteredTodos($cursor: Cursor) {3 todosCollection(4 filter: { is_complete: { eq: false } }5 orderBy: [{ created_at: DescNullsLast }]6 first: 107 after: $cursor8 ) {9 edges {10 node {11 id12 task13 created_at14 }15 cursor16 }17 pageInfo {18 hasNextPage19 endCursor20 }21 }22 }23`Expected result: The query returns the first 10 incomplete todos ordered by creation date, with cursor information for fetching the next page.
Complete working example
1import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.NEXT_PUBLIC_SUPABASE_URL!,6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!7)89const httpLink = new HttpLink({10 uri: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/graphql/v1`,11 headers: { apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! },12 fetch: async (uri, options) => {13 const { data: { session } } = await supabase.auth.getSession()14 const headers = (options?.headers as Record<string, string>) || {}15 headers['Authorization'] = `Bearer ${16 session?.access_token ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY17 }`18 return fetch(uri, { ...options, headers })19 }20})2122export const apolloClient = new ApolloClient({23 link: httpLink,24 cache: new InMemoryCache()25})2627// Example: Fetch todos28export const GET_TODOS = gql`29 query GetTodos($first: Int, $after: Cursor) {30 todosCollection(first: $first, after: $after) {31 edges {32 node { id task is_complete created_at }33 cursor34 }35 pageInfo { hasNextPage endCursor }36 }37 }38`3940// Example: Insert a todo41export const INSERT_TODO = gql`42 mutation InsertTodo($task: String!) {43 insertIntotodosCollection(objects: [{ task: $task, is_complete: false }]) {44 records { id task is_complete }45 }46 }47`4849// Example: Update a todo50export const UPDATE_TODO = gql`51 mutation UpdateTodo($id: BigInt!, $isComplete: Boolean!) {52 updatetodosCollection(53 filter: { id: { eq: $id } }54 set: { is_complete: $isComplete }55 ) {56 records { id task is_complete }57 }58 }59`Common mistakes when using Supabase with GraphQL
Why it's a problem: Querying the GraphQL endpoint without the apikey header, getting a 401 Unauthorized error
How to avoid: Always include the apikey header with your SUPABASE_ANON_KEY in every GraphQL request, in addition to the Authorization header.
Why it's a problem: Expecting table names to map directly to GraphQL types without the Collection suffix
How to avoid: pg_graphql appends Collection to table names. A table called todos becomes todosCollection in queries. Use the GraphQL schema explorer to see the exact type names.
Why it's a problem: Not enabling RLS on tables, causing the GraphQL API to return empty results for all queries
How to avoid: Tables without RLS enabled are not exposed through the GraphQL API. Enable RLS and add at least a SELECT policy for the data to appear.
Why it's a problem: Using the service role key in client-side Apollo configuration, bypassing all RLS policies
How to avoid: Always use the SUPABASE_ANON_KEY for client-side requests. The service role key should only be used in server-side code.
Best practices
- Enable RLS on all tables before querying via GraphQL — tables without RLS are not exposed in the GraphQL schema
- Use the anon key for unauthenticated queries and the user's JWT for authenticated queries
- Implement cursor-based pagination with first and after for large datasets instead of loading all records
- Use Apollo Client's InMemoryCache to reduce redundant network requests for the same data
- Add a custom fetch function to Apollo's HttpLink to dynamically inject the user's access token
- Check the auto-generated schema in the Supabase Dashboard's GraphQL playground before writing queries
- Never expose the service role key in client-side code — it bypasses all RLS policies
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to use GraphQL with my Supabase project instead of the REST API. Show me how to enable pg_graphql, query data with filters and pagination, run mutations for CRUD operations, and set up Apollo Client with proper authentication headers.
Enable the pg_graphql extension on a Supabase project and set up Apollo Client to query the GraphQL endpoint at /graphql/v1. Include query examples with collection filters, cursor-based pagination, insert mutations, and update mutations. Show how to pass the user's JWT for authenticated requests.
Frequently asked questions
Do I need to install a separate GraphQL server to use GraphQL with Supabase?
No. Supabase includes the pg_graphql extension which auto-generates a GraphQL API from your database schema. The endpoint is available at /graphql/v1 on every Supabase project.
Does the GraphQL API respect Row Level Security policies?
Yes. All GraphQL queries and mutations go through the same RLS enforcement as the REST API. The user's JWT determines which Postgres role (anon or authenticated) is used.
Can I use subscriptions with Supabase GraphQL?
pg_graphql does not support GraphQL subscriptions natively. For real-time data, use Supabase's Realtime feature with the JS client's channel.on('postgres_changes') API alongside your GraphQL queries.
How do I see the auto-generated GraphQL schema?
In the Supabase Dashboard, navigate to the API section and look for the GraphQL playground. You can also use an introspection query from Apollo Studio or any GraphQL IDE to explore the schema.
Should I use GraphQL or the REST API with Supabase?
The REST API via the JS client is simpler for most use cases and has better TypeScript type generation. GraphQL is beneficial when you need precise field selection, complex nested queries, or you are already using Apollo Client in your project.
Why are some of my tables not showing up in the GraphQL schema?
Tables must be in the public schema and have RLS enabled to appear in the GraphQL schema. Tables in other schemas or without RLS are excluded by default.
Can RapidDev help integrate GraphQL with my Supabase-powered application?
Yes. RapidDev can configure pg_graphql, set up Apollo Client with proper authentication, write optimized queries, and implement caching strategies for your GraphQL-powered Supabase application.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation