In this guide, you'll learn how to enable the Neon Data API for your database, create a table with Row-Level Security (RLS), and run your first query.

Before you begin

  • The Neon Data API is enabled at the branch level for a single database. Each branch has its own Data API configuration, so you must select the correct branch before enabling the API.
  • Neon Data API does not currently support projects with IP Allow or Private Networking enabled.
  1. Enable the Data API

    tip

    You can also enable the Data API programmatically using the Neon MCP Server. The provision_neon_data_api tool enables LLMs to provision HTTP-based Data API access for Neon databases with optional JWT authentication. See the Neon MCP Server documentation for more information.

    1. Navigate to the Data API page

    In the Neon Console, select your project and go to the Data API page in the sidebar.

    Data API page with enable button

    2. Configure Neon Auth (optional)

    The Use Neon Auth checkbox allows you to enable Neon Auth as your authentication provider for the Data API. When enabled, Neon Auth manages sign-up, login, and account access, issuing the JWTs required for API requests.

    If you prefer to use a different authentication provider (such as Auth0, Clerk, or Firebase Auth), leave this checkbox unchecked and configure your provider later. See Custom authentication providers for details.

    Authentication required

    All requests to the Data API require authentication with a valid JWT token.

    3. Configure schema access (optional)

    The Grant public schema access checkbox automatically applies database permissions so the authenticated role can read and write to tables in the public schema.

    View the GRANT statements applied
    -- Schema usage
    GRANT USAGE ON SCHEMA public TO authenticated;
    
    -- For existing tables
    GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA public TO authenticated;
    
    -- For future tables
    ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES TO authenticated;
    
    -- For sequences (for identity columns)
    GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;

    Enable this checkbox unless you need to manage permissions manually. If you leave it unchecked, see Access control for details on granting permissions yourself.

    4. Click Enable Data API

    Click Enable Data API to activate the Data API. Once enabled, you'll see the Data API page.

    Data API enabled view

    On the API tab, you'll see:

    • API URL: Your REST API endpoint for accessing your database
    • Refresh schema cache: A button to update the Data API when you make schema changes
    • Security section: Options to configure Neon Auth and enable Row-Level Security on your tables

    warning

    If you have tables without RLS enabled, you'll see a warning that authenticated users can view all rows in those tables. We'll show you how to add RLS in the next step.

    For advanced configuration options like custom authentication providers, exposed schemas, and CORS settings, see the Settings tab or refer to Managing the Data API.

    Next, you'll create a table with Row-Level Security (RLS) policies to define which rows users can access.

  2. Create a table with RLS

    The Data API interacts directly with your Postgres schema. Because the API is accessible over the internet, it's crucial to enforce security at the database level using PostgreSQL's Row-Level Security (RLS) features.

    In this example, we'll create a posts table where users can read published posts and manage their own posts securely. Choose the approach that matches how you manage your database schema:

    • SQL: Write SQL directly in the Neon SQL Editor or manage migrations manually. See our PostgreSQL RLS tutorial for more on RLS fundamentals.
    • Drizzle (crudPolicy): A high-level helper that generates all four CRUD policies (select, insert, update, delete) in one declaration. Best for simple cases where read and modify permissions follow the same pattern.
    • Drizzle (pgPolicy): Define individual policies for each operation. Use this when you need different logic for different operations (e.g., time-limited updates, different rules for INSERT vs UPDATE).

    For more on Drizzle RLS, see our Drizzle RLS guide.

    SQL
    Drizzle (crudPolicy)
    Drizzle (pgPolicy)
    -- This script creates a posts table, enables RLS, and defines four policies:
    -- one allows authenticated users to read published posts or their own posts,
    -- and the other three let users insert, update, and delete only their own posts.
    
    -- 1. Create the table
    CREATE TABLE posts (
      id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
      user_id text DEFAULT (auth.user_id()) NOT NULL,
      content text NOT NULL,
      is_published boolean DEFAULT false,
      "created_at" timestamp with time zone DEFAULT now() NOT NULL
    );
    
    -- 2. Enable RLS
    ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
    
    -- 3. Create Policy: Users can see all published posts and their own posts
    CREATE POLICY "Public read access" ON posts
      AS PERMISSIVE
      FOR SELECT TO authenticated
      USING (is_published OR (select auth.user_id() = "posts"."user_id"));
    
    -- 4. Create Policy: Users can insert their own posts
    CREATE POLICY "Users can insert their own posts" ON posts
      AS PERMISSIVE
      FOR INSERT TO "authenticated"
      WITH CHECK ((select auth.user_id() = "posts"."user_id"));
    
    -- 5. Create Policy: Users can update their own posts
    CREATE POLICY "Users can update their own posts" ON posts
      AS PERMISSIVE
      FOR UPDATE TO "authenticated"
      USING ((select auth.user_id() = "posts"."user_id"))
      WITH CHECK ((select auth.user_id() = "posts"."user_id"));
    
    CREATE POLICY "Users can delete their own posts" ON posts
      AS PERMISSIVE
      FOR DELETE TO "authenticated"
      USING ((select auth.user_id() = "posts"."user_id"));

    What is auth.user_id() and authUid()?

    auth.user_id() is a Data API helper that extracts the User ID from the JWT token for secure database permission enforcement. authUid() is a Drizzle ORM helper that simplifies using auth.user_id() in policies.

  3. Refresh schema cache

    The Data API caches your database schema for performance. When you modify your schema (adding tables, modifying columns, or changing structure, etc.), you need to refresh this cache for the changes to take effect.

    To refresh the cache, go to the Data API page in the Neon Console and click Refresh schema cache.

    Data API refresh schema cache button

  4. Connect and Query

    You can connect to the Data API using a client library or direct HTTP requests.

    Option 1: Using a client library

    Install a client library and run your first query. Choose the option that matches your authentication provider:

    Use @neondatabase/neon-js if you're using Neon Auth. This library handles token management automatically.

    1. Install

    npm install @neondatabase/neon-js

    2. Usage

    import { createClient } from '@neondatabase/neon-js';
    
    // Initialize with Neon Auth
    const client = createClient({
      auth: {
        url: process.env.NEON_AUTH_URL, // Your Neon Auth endpoint (from the Neon Console)
      },
      dataApi: {
        url: process.env.NEON_DATA_API_URL, // Your Data API endpoint (from the Neon Console)
      },
    });
    
    // Query - the JWT token is injected automatically when the user is signed in
    const { data, error } = await client
      .from('posts')
      .select('*')
      .eq('is_published', true)
      .order('created_at', { ascending: false });
    
    console.log(data);

    For detailed guidance on performing INSERT, UPDATE, DELETE, and advanced queries (filters, joins, stored procedures, etc.) in either case, refer to the Neon Javascript SDK documentation.

    Option 2: Direct HTTP requests

    Query the Data API directly using any HTTP client. Include the Authorization header with a valid JWT token from your authentication provider. The token must include a sub claim for RLS policies to work correctly.

    Where to get the JWT token:

    • Neon Auth (manual testing): Use the Auth API reference UI (navigate to your Auth URL with /reference appended, e.g., https://ep-example.neonauth.us-east-1.aws.neon.tech/neondb/auth/reference) to sign in and get a token. See Testing with Postman or cURL below.
    • Neon Auth (programmatic): Retrieve the token using client.auth.getSession() from the @neondatabase/neon-js library. See Get current session for details.
    • Other providers: Retrieve the token from your auth provider's SDK (e.g., getAccessToken() in Auth0, getToken() in Clerk).

    About the sub claim:

    For RLS policies to work correctly, the JWT token must include a sub (subject) claim, which contains the user's unique identifier. The Data API uses this claim to enforce Row-Level Security policies via the auth.user_id() function. Most authentication providers include this claim by default.

    Example: SELECT (GET)

    This request queries the posts table for all published posts, ordered by most recent first:

    curl -X GET 'https://your-data-api-endpoint/rest/v1/posts?is_published=eq.true&order=created_at.desc' \
      -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
      -H 'Content-Type: application/json'

    Example: INSERT (POST)

    This request inserts a new row into the posts table, setting the content column:

    curl -X POST 'https://your-data-api-endpoint/rest/v1/posts' \
      -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
      -H 'Content-Type: application/json' \
      -d '{"content": "Hello world"}'

    For UPDATE, DELETE, filtering, and other operations, see the PostgREST documentation.

Testing with Postman or cURL

If you're using Neon Auth and want to test the Data API without building an application first, you can use the Auth API reference UI to create test users and obtain JWT tokens.

Neon Auth only

This workflow applies when using Neon Auth as your authentication provider. If you're using a different provider, obtain JWT tokens through your provider's authentication flow.

  1. Open the Auth API reference: Navigate to your Auth URL with /reference appended (e.g., https://ep-example.neonauth.us-east-1.aws.neon.tech/neondb/auth/reference). This interactive UI lets you explore and test all auth endpoints. It's powered by Better Auth's OpenAPI plugin. You can find your Auth URL on the Auth page on the Configuration tab in the Neon Console.

  2. Create a test user: In the API reference, call POST /api/auth/sign-up/email with a JSON body:

    {
      "email": "test@example.com",
      "password": "your-password",
      "name": "Test User"
    }
  3. Or sign in with an existing user: Call POST /api/auth/sign-in/email with:

    {
      "email": "test@example.com",
      "password": "your-password"
    }
  4. Get the JWT token: Call GET /api/auth/get-session and copy the JWT from the Set-Auth-Jwt response header.

  5. Query the Data API: Use the JWT in your requests:

    curl -X GET 'https://your-data-api-endpoint/rest/v1/posts' \
      -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
      -H 'Content-Type: application/json'

Token expiration

JWTs expire after approximately 15 minutes. If you receive a "JWT token has expired" error, sign in again to get a fresh token.

Query patterns

The Data API supports full CRUD operations and advanced queries. Here's a quick reference of the most common methods available in the Neon TypeScript SDK:

CRUD operations

OperationMethodExampleSDK Reference
Select.select()client.from('posts').select('*')select
Insert.insert()client.from('posts').insert({ title: 'New post' })insert
Update.update()client.from('posts').update({ title: 'Updated' }).eq('id', 1)update
Delete.delete()client.from('posts').delete().eq('id', 1)delete
RPC.rpc()client.rpc('function_name', { param: 'value' })rpc

Filters

FilterDescriptionExample
.eq()Equals.eq('status', 'published')
.neq()Not equals.neq('status', 'draft')
.gt()Greater than.gt('price', 100)
.lt()Less than.lt('price', 50)
.gte()Greater than or equal.gte('quantity', 1)
.lte()Less than or equal.lte('quantity', 10)
.like()Pattern match (case-sensitive).like('title', '%hello%')
.ilike()Pattern match (case-insensitive).ilike('title', '%hello%')
.is()Is null / not null.is('deleted_at', null)
.in()Value in array.in('status', ['active', 'pending'])

Modifiers

ModifierDescriptionExample
.order()Sort results.order('created_at', { ascending: false })
.limit()Limit rows returned.limit(10)
.single()Return single row.select('*').eq('id', 1).single()

For the complete list of methods and detailed examples, see the Neon Auth & Data API TypeScript SDKs.

Next steps