# How to Use Supabase with GraphQL

- Tool: Supabase
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Supabase (all plans), pg_graphql extension, @supabase/supabase-js v2+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
-- Enable the pg_graphql extension
create extension if not exists pg_graphql;

-- Verify it is installed
select * 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.

### 2. 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.

```
// Query using fetch
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!
const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

const response = await fetch(`${SUPABASE_URL}/graphql/v1`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'apikey': SUPABASE_ANON_KEY,
    'Authorization': `Bearer ${SUPABASE_ANON_KEY}`
  },
  body: JSON.stringify({
    query: `
      query GetTodos {
        todosCollection {
          edges {
            node {
              id
              task
              is_complete
              created_at
            }
          }
        }
      }
    `
  })
})

const { data } = await response.json()
console.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.

### 3. 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.

```
// Insert a new todo
const insertQuery = `
  mutation InsertTodo {
    insertIntotodosCollection(objects: [{
      task: "Learn GraphQL with Supabase"
      is_complete: false
    }]) {
      records {
        id
        task
        is_complete
      }
    }
  }
`

// Update a todo
const updateQuery = `
  mutation UpdateTodo {
    updatetodosCollection(
      filter: { id: { eq: 1 } }
      set: { is_complete: true }
    ) {
      records {
        id
        task
        is_complete
      }
    }
  }
`

// Delete a todo
const deleteQuery = `
  mutation DeleteTodo {
    deleteFromtodosCollection(
      filter: { id: { eq: 1 } }
    ) {
      records {
        id
      }
    }
  }
`
```

**Expected result:** Each mutation returns the affected records with the fields you requested. The database is updated according to the mutation type.

### 4. 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.

```
import { ApolloClient, InMemoryCache, HttpLink, ApolloProvider } from '@apollo/client'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

const httpLink = new HttpLink({
  uri: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/graphql/v1`,
  headers: {
    apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  },
  fetch: async (uri, options) => {
    const { data: { session } } = await supabase.auth.getSession()
    const headers = options?.headers as Record<string, string> || {}
    if (session?.access_token) {
      headers['Authorization'] = `Bearer ${session.access_token}`
    } else {
      headers['Authorization'] = `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`
    }
    return fetch(uri, { ...options, headers })
  }
})

const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})
```

**Expected result:** Apollo Client is configured and sends authenticated GraphQL requests to the Supabase endpoint. Data is cached locally for fast re-renders.

### 5. 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.

```
const filteredQuery = `
  query FilteredTodos($cursor: Cursor) {
    todosCollection(
      filter: { is_complete: { eq: false } }
      orderBy: [{ created_at: DescNullsLast }]
      first: 10
      after: $cursor
    ) {
      edges {
        node {
          id
          task
          created_at
        }
        cursor
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
`
```

**Expected result:** The query returns the first 10 incomplete todos ordered by creation date, with cursor information for fetching the next page.

## Complete code example

File: `supabase-graphql-client.ts`

```typescript
import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

const httpLink = new HttpLink({
  uri: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/graphql/v1`,
  headers: { apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! },
  fetch: async (uri, options) => {
    const { data: { session } } = await supabase.auth.getSession()
    const headers = (options?.headers as Record<string, string>) || {}
    headers['Authorization'] = `Bearer ${
      session?.access_token ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
    }`
    return fetch(uri, { ...options, headers })
  }
})

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})

// Example: Fetch todos
export const GET_TODOS = gql`
  query GetTodos($first: Int, $after: Cursor) {
    todosCollection(first: $first, after: $after) {
      edges {
        node { id task is_complete created_at }
        cursor
      }
      pageInfo { hasNextPage endCursor }
    }
  }
`

// Example: Insert a todo
export const INSERT_TODO = gql`
  mutation InsertTodo($task: String!) {
    insertIntotodosCollection(objects: [{ task: $task, is_complete: false }]) {
      records { id task is_complete }
    }
  }
`

// Example: Update a todo
export const UPDATE_TODO = gql`
  mutation UpdateTodo($id: BigInt!, $isComplete: Boolean!) {
    updatetodosCollection(
      filter: { id: { eq: $id } }
      set: { is_complete: $isComplete }
    ) {
      records { id task is_complete }
    }
  }
`
```

## Common mistakes

- **Querying the GraphQL endpoint without the apikey header, getting a 401 Unauthorized error** — undefined Fix: Always include the apikey header with your SUPABASE_ANON_KEY in every GraphQL request, in addition to the Authorization header.
- **Expecting table names to map directly to GraphQL types without the Collection suffix** — undefined Fix: 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.
- **Not enabling RLS on tables, causing the GraphQL API to return empty results for all queries** — undefined Fix: 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.
- **Using the service role key in client-side Apollo configuration, bypassing all RLS policies** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-graphql
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-graphql
