Agent Native

neon-js-react

Configures the Neon JavaScript SDK with authentication and database access in React applications using Vite or Create React App.

by NeonOfficial publisherNot reviewed

Identity proven by GitHub's verified organisation badge (neondatabase)

Agents
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
License
Apache-2.0 (permissive)
Pin
029a336pinned Oct 9, 2026
Context cost
about 4,000 tokenswhen the skill loads
GitHub stars
18on neondatabase/neon-js
Scripts
None found

What it does

“Sets up the full Neon SDK with authentication AND database queries in React apps (Vite, CRA). Creates typed client, generates database types, and configures auth UI. Use for auth + database integration.”
The author's description, from SKILL.md

Generated summary from the skill's own text

Configures the Neon JavaScript SDK with authentication and database access in React applications using Vite or Create React App. Generates typed database clients, sets up auth UI components with optional social login, and enables typed queries to Neon's Data API.

When to use it

  • Building React apps needing both authentication and database access with Neon
  • Integrating BetterAuth or Supabase-style auth with typed database queries
  • Setting up auth UI with social providers and account management in React
  • Migrating to Neon from other backends while maintaining React patterns

When not to

  • Using Next.js (use neon-auth-nextjs instead)
  • Building apps without authentication requirements
  • Projects not using Vite or Create React App

Install

These commands install from the publisher's repository and need no account.

skills CLI

npx skills add neondatabase/neon-js --skill neon-js-react

Installs from the repository's default branch, which may have changed since the commit we checked.

GitHub CLI 2.90 or later

gh skill install neondatabase/neon-js neon-js-react@029a3367ee140d2c8987601b0e338000fe6db1e6

Installs the commit we checked (029a336).

Copy by hand

Download the folder at 029a336 and put it in .claude/skills/neon-js-react for Claude Code and in .agents/skills/neon-js-react for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.

Skill folder on GitHub at 029a336

Claude Code plugin

/plugin install neon@claude-plugins-official

The publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.

Copy for your agent

A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.

Prompt

Install the agent skill "neon-js-react" by Neon.

Source, pinned to commit 029a3367ee140d2c8987601b0e338000fe6db1e6:
https://github.com/neondatabase/neon-js/tree/029a3367ee140d2c8987601b0e338000fe6db1e6/skills/neon-js-react

1. Download that folder at exactly this commit. Do not use the default branch.
2. Copy every file unchanged, keeping the folder structure, into .claude/skills/neon-js-react/ for Claude Code and .agents/skills/neon-js-react/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist.
3. Do not edit the files, and do not run any script in the folder unless I ask you to.
4. When you are done, list the files you copied and where you put them.

Listing and checks: https://www.agentnative.dev/skills/neon/neon-js-react

SKILL.md at the pin

---
name: neon-js-react
description: Sets up the full Neon SDK with authentication AND database queries in React apps (Vite, CRA). Creates typed client, generates database types, and configures auth UI. Use for auth + database integration.
allowed-tools: ["Bash", "Write", "Read", "Edit", "Glob", "Grep"]
---

# Neon JS for React

Help developers set up @neondatabase/neon-js with authentication AND database queries in React applications (Vite, CRA, etc.).

## When to Use

Use this skill when:
- Setting up Neon Auth + Database in a React app (Vite, CRA, etc.)
- User needs both authentication AND database queries
- User mentions "neon-js", "neon auth + database", or "full neon SDK"
- User is NOT using Next.js (for Next.js, use `neon-auth-nextjs` as a starting point and add Data API configuration, or see `examples/nextjs-neon-auth/`)

## Critical Rules

1. **Adapter Factory Pattern**: Always call adapters with `()`
   ```typescript
   adapter: SupabaseAuthAdapter()  // Correct
   adapter: SupabaseAuthAdapter    // Wrong - missing ()
   ```

2. **React Adapter Import**: NOT exported from main - use subpath
   ```typescript
   import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
   ```

3. **Type Safety**: Always use Database generic for type-safe queries
   ```typescript
   const client = createClient<Database>({...});
   ```

4. **CSS Import**: Choose ONE - either `/ui/css` OR `/ui/tailwind`, never both

---

## Setup

### 1. Install
```bash
npm install @neondatabase/neon-js
```

### 2. Generate Database Types
```bash
npx neon-js gen-types --db-url "postgresql://user:pass@host:5432/db" --output src/database.types.ts
```

**CLI Options:**
```bash
npx neon-js gen-types --db-url <url> [options]

# Required
--db-url <url>              Database connection string

# Optional
--output, -o <path>         Output file (default: database.types.ts)
--schema, -s <name>         Schema to include (repeatable, default: public)
--postgrest-v9-compat       Disable one-to-one relationship detection
--query-timeout <duration>  Query timeout (e.g., 30s, 1m, default: 15s)
```

### 3. Create Client (`src/client.ts`)
```typescript
import { createClient } from '@neondatabase/neon-js';
import type { Database } from './database.types';

export const neonClient = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    // allowAnonymous: true, // Enable for RLS access without login
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});
```

### 4. Create Provider (`src/providers.tsx`)
```typescript
import { NeonAuthUIProvider } from '@neondatabase/neon-js/auth/react';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { neonClient } from './client';

// Import CSS (choose one)
import '@neondatabase/neon-js/ui/css';

export function Providers({ children }: { children: React.ReactNode }) {
  const navigate = useNavigate();

  return (
    <NeonAuthUIProvider
      authClient={neonClient.auth}
      navigate={navigate}
      redirectTo="/dashboard"
      Link={({href, children}) => <Link to={href}>{children}</Link>}
    >
      {children}
    </NeonAuthUIProvider>
  );
}
```

### 5. Wrap App (`src/main.tsx`)
```typescript
import { BrowserRouter } from 'react-router-dom';
import { Providers } from './providers';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <Providers>
      <App />
    </Providers>
  </BrowserRouter>
);
```

### 6. Environment Variables (`.env.local`)
```
VITE_NEON_AUTH_URL=https://your-auth.neon.tech
VITE_NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
```

---

## CSS & Styling

### Import Options

**Without Tailwind** (pre-built CSS bundle ~47KB):
```typescript
// In provider or main.tsx
import '@neondatabase/neon-js/ui/css';
```

**With Tailwind CSS v4**:
```css
@import 'tailwindcss';
@import '@neondatabase/neon-js/ui/tailwind';
```

**IMPORTANT**: Never import both - causes duplicate styles.

### Dark Mode

```typescript
<NeonAuthUIProvider
  defaultTheme="system" // 'light' | 'dark' | 'system'
  // ...
>
```

### Custom Theming

Override CSS variables in your stylesheet:
```css
:root {
  --primary: oklch(0.7 0.15 250);
  --primary-foreground: oklch(0.98 0 0);
  --background: oklch(1 0 0);
  --foreground: oklch(0.1 0 0);
  --card: oklch(1 0 0);
  --border: oklch(0.9 0 0);
  --radius: 0.5rem;
}

.dark {
  --background: oklch(0.15 0 0);
  --foreground: oklch(0.98 0 0);
}
```

---

## NeonAuthUIProvider Props

Full configuration:

```typescript
<NeonAuthUIProvider
  // Required
  authClient={neonClient.auth}  // Note: .auth property of neonClient

  // Navigation
  navigate={navigate}
  Link={({href, children}) => <Link to={href}>{children}</Link>}
  redirectTo="/dashboard"

  // Social/OAuth
  social={{
    providers: ['google'],
  }}

  // Feature Flags
  emailOTP={true}
  emailVerification={true}
  magicLink={false}
  multiSession={false}
  credentials={{ forgotPassword: true }}

  // Sign Up Fields
  signUp={{ fields: ['name'] }}

  // Account Fields
  account={{ fields: ['image', 'name', 'company'] }}

  // Organizations
  organization={{}}

  // Dark Mode
  defaultTheme="system"

  // Custom Labels
  localization={{
    SIGN_IN: 'Welcome Back',
    SIGN_UP: 'Create Account',
  }}
>
  {children}
</NeonAuthUIProvider>
```

---

## Database Queries

### Select

```typescript
// Basic select
const { data, error } = await neonClient
  .from('todos')
  .select('*');

// Select with filter
const { data, error } = await neonClient
  .from('todos')
  .select('*')
  .eq('user_id', userId)
  .order('created_at', { ascending: false });

// Select with relations
const { data, error } = await neonClient
  .from('posts')
  .select(`
    *,
    author:users(name, avatar),
    comments(id, content)
  `);

// Single row
const { data, error } = await neonClient
  .from('todos')
  .select('*')
  .eq('id', todoId)
  .single();
```

### Insert

```typescript
// Single insert
const { data, error } = await neonClient
  .from('todos')
  .insert({ title: 'New todo', user_id: userId })
  .select()
  .single();

// Bulk insert
const { data, error } = await neonClient
  .from('todos')
  .insert([
    { title: 'Todo 1', user_id: userId },
    { title: 'Todo 2', user_id: userId },
  ])
  .select();
```

### Update

```typescript
const { data, error } = await neonClient
  .from('todos')
  .update({ completed: true })
  .eq('id', todoId)
  .select()
  .single();
```

### Delete

```typescript
const { error } = await neonClient
  .from('todos')
  .delete()
  .eq('id', todoId);
```

### Upsert

```typescript
const { data, error } = await neonClient
  .from('profiles')
  .upsert({ user_id: userId, bio: 'Updated bio' })
  .select()
  .single();
```

### Filters

```typescript
// Equality
.eq('column', value)
.neq('column', value)

// Comparison
.gt('column', value)      // greater than
.gte('column', value)     // greater than or equal
.lt('column', value)      // less than
.lte('column', value)     // less than or equal

// Pattern matching
.like('column', '%pattern%')
.ilike('column', '%pattern%')  // case insensitive

// Arrays
.in('column', [1, 2, 3])
.contains('tags', ['javascript'])
.containedBy('tags', ['javascript', 'typescript'])

// Null
.is('column', null)
.not('column', 'is', null)

// Range
.range(0, 9)  // pagination
```

### Ordering & Pagination

```typescript
const { data, error } = await neonClient
  .from('posts')
  .select('*')
  .order('created_at', { ascending: false })
  .range(0, 9)  // First 10 items
  .limit(10);
```

---

## Auth Methods

### Default API (BetterAuth)

```typescript
// Sign up
await neonClient.auth.signUp.email({ email, password, name });

// Sign in
await neonClient.auth.signIn.email({ email, password });

// OAuth
await neonClient.auth.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});

// Get session
const session = await neonClient.auth.getSession();

// Sign out
await neonClient.auth.signOut();
```

### With SupabaseAuthAdapter

```typescript
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';

const neonClient = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    adapter: SupabaseAuthAdapter(),
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Supabase-style methods
await neonClient.auth.signUp({ email, password, options: { data: { name } } });
await neonClient.auth.signInWithPassword({ email, password });
await neonClient.auth.signInWithOAuth({ provider: 'google', options: { redirectTo } });
const { data: session } = await neonClient.auth.getSession();
await neonClient.auth.signOut();

// Event listener
neonClient.auth.onAuthStateChange((event, session) => {
  console.log(event); // 'SIGNED_IN', 'SIGNED_OUT', 'TOKEN_REFRESHED'
});
```

### With BetterAuthReactAdapter

```typescript
import { createClient } from '@neondatabase/neon-js';
import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';

const neonClient = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    adapter: BetterAuthReactAdapter(),
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Includes useSession() hook
const { data, isPending, error } = neonClient.auth.useSession();
```

---

## Session Hook

```typescript
function MyComponent() {
  const { data: session, isPending, error, refetch } = neonClient.auth.useSession();

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!session) return <div>Not signed in</div>;

  return (
    <div>
      <p>Hello, {session.user.name}</p>
      <p>Email: {session.user.email}</p>
    </div>
  );
}
```

**Session shape:**
```typescript
{
  user: {
    id: string;
    email: string;
    name: string;
    image?: string;
    emailVerified: boolean;
  };
  session: {
    id: string;
    token: string;
    expiresAt: Date;
  };
}
```

---

## UI Components

### AuthView - Main Auth Interface

```typescript
import { AuthView } from '@neondatabase/neon-js/auth/react';

// Route: /auth/:pathname
function AuthPage() {
  const { pathname } = useParams();
  return <AuthView pathname={pathname} />;
}
```

**Pathnames**: `sign-in`, `sign-up`, `forgot-password`, `reset-password`, `callback`, `sign-out`

### Conditional Rendering

```typescript
import {
  SignedIn,
  SignedOut,
  AuthLoading,
  RedirectToSignIn,
} from '@neondatabase/neon-js/auth/react';

function MyPage() {
  return (
    <>
      <AuthLoading>
        <LoadingSpinner />
      </AuthLoading>

      <SignedIn>
        <Dashboard />
      </SignedIn>

      <SignedOut>
        <LandingPage />
      </SignedOut>

      <RedirectToSignIn />
    </>
  );
}
```

### UserButton

```typescript
import { UserButton } from '@neondatabase/neon-js/auth/react';

function Header() {
  return (
    <header>
      <UserButton />
    </header>
  );
}
```

### Account Management

```typescript
import {
  AccountSettingsCards,
  SecuritySettingsCards,
  SessionsCard,
  ChangePasswordCard,
  ChangeEmailCard,
  DeleteAccountCard,
  ProvidersCard,
} from '@neondatabase/neon-js/auth/react';
```

### Organization Components

```typescript
import {
  OrganizationSwitcher,
  OrganizationSettingsCards,
  OrganizationMembersCard,
} from '@neondatabase/neon-js/auth/react';
```

---

## Social/OAuth Providers

### Configuration

```typescript
<NeonAuthUIProvider
  social={{
    providers: ['google'],
  }}
>
```

### Programmatic Sign-In

```typescript
await neonClient.auth.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});
```

### Supported Providers

`google`, `github`, `twitter`, `discord`, `apple`, `microsoft`, `facebook`, `linkedin`, `spotify`, `twitch`, `gitlab`, `bitbucket`

---

## Protected Routes

```typescript
// routes.tsx
import { Routes, Route } from 'react-router-dom';

export function AppRoutes() {
  return (
    <Routes>
      {/* Public */}
      <Route path="/" element={<HomePage />} />

      {/* Auth */}
      <Route path="/auth/:pathname" element={<AuthPage />} />

      {/* Protected */}
      <Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
      <Route path="/account/:view?" element={<ProtectedRoute><AccountPage /></ProtectedRoute>} />
    </Routes>
  );
}

// ProtectedRoute.tsx
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  return (
    <>
      <AuthLoading><LoadingSpinner /></AuthLoading>
      <RedirectToSignIn />
      <SignedIn>{children}</SignedIn>
    </>
  );
}
```

---

## Advanced Features

### Anonymous Access

Enable RLS-based data access for unauthenticated users:

```typescript
const neonClient = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    allowAnonymous: true,
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Queries work without sign-in (using anonymous JWT)
const { data } = await neonClient.from('public_posts').select('*');
```

### Get JWT Token

```typescript
const token = await neonClient.auth.getJWTToken();

// Use for external API calls
const response = await fetch('/api/external', {
  headers: { Authorization: `Bearer ${token}` },
});
```

### Identity Linking

```typescript
// List linked accounts
const { data } = await neonClient.auth.getUserIdentities();

// Link new provider
await neonClient.auth.linkIdentity({
  provider: 'github',
  options: { redirectTo: '/account/security' },
});

// Unlink provider
await neonClient.auth.unlinkIdentity({ identity_id: 'id' });
```

### Auth State Events (Supabase Adapter)

```typescript
const { data: { subscription } } = neonClient.auth.onAuthStateChange((event, session) => {
  switch (event) {
    case 'SIGNED_IN': /* ... */ break;
    case 'SIGNED_OUT': /* ... */ break;
    case 'TOKEN_REFRESHED': /* ... */ break;
    case 'USER_UPDATED': /* ... */ break;
  }
});

// Cleanup
subscription.unsubscribe();
```

### Cross-Tab Sync

Automatic via BroadcastChannel. Sign out in one tab signs out all tabs.

---

## Error Handling

### Query Errors

```typescript
const { data, error } = await neonClient.from('todos').select('*');

if (error) {
  console.error('Query failed:', error.message);
  return;
}

// Use data safely
console.log(data);
```

### Auth Errors

```typescript
const { error } = await neonClient.auth.signIn.email({ email, password });

if (error) {
  toast.error(error.message);
  return;
}
```

### Common Errors

| Error | Cause |
|-------|-------|
| `Invalid credentials` | Wrong email/password |
| `User already exists` | Email registered |
| `permission denied for table` | Missing RLS policy or GRANT |
| `JWT expired` | Token needs refresh |

---

## FAQ / Troubleshooting

### Anonymous access not working?

Grant permissions to the `anonymous` role in your database:

```sql
-- Grant SELECT on specific tables
GRANT SELECT ON public.posts TO anonymous;
GRANT SELECT ON public.products TO anonymous;

-- RLS policy for anonymous access
CREATE POLICY "Anyone can read published posts"
  ON public.posts FOR SELECT
  USING (published = true);
```

### "permission denied for table" error?

1. Check RLS is enabled: `ALTER TABLE posts ENABLE ROW LEVEL SECURITY;`
2. Create appropriate policies for authenticated users
3. Grant permissions: `GRANT SELECT, INSERT ON public.posts TO authenticated;`

### Database types out of date?

Regenerate types after schema changes:

```bash
npx neon-js gen-types --db-url "postgresql://..." --output src/database.types.ts
```

### OAuth not working in iframe?

OAuth automatically uses popup flow in iframes. Ensure popups aren't blocked.

### Session not persisting?

1. Cookies enabled?
2. Auth URL correct in `.env.local`?
3. Not in incognito with cookies blocked?

---

## Performance Notes

- **Session caching**: 60-second TTL
- **Request deduplication**: Concurrent calls share single request
- **Auto token injection**: JWT automatically added to all queries
- **Cross-tab sync**: <50ms via BroadcastChannel

From neondatabase/neon-js at 029a336, shown as plain text under its license: Apache-2.0 (permissive). The license text is in LICENSE.

View SKILL.md on GitHub at 029a336

Files

1 file, 16 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.

Skill folder on GitHub at 029a336
All 19 skills from Neon

More from Neon

  • neon

    Neon is a cloud backend platform providing Postgres, auth, object storage, serverless functions, and an AI gateway with branching, zero-scale compute, and…

    Claude Code, Codex, Cursor +4 · about 8.5K tokens · pinned 6 days ago

  • neon-ai-gateway

    The Neon AI Gateway provides a single API endpoint and credential to access frontier and open-source LLMs from multiple providers (OpenAI, Anthropic, Google…

    Claude Code, Codex, Cursor +4 · about 4.9K tokens · pinned 6 days ago

  • neon-auth

    This skill adds Better Auth-based login to new apps using Neon's managed authentication system, where users and sessions live in a Postgres schema.

    Claude Code, Codex, Cursor +4 · about 2.9K tokens · pinned 6 days ago

  • neon-functions

    Neon Functions deploys long-running Node.js HTTP handlers to your Neon database branch, injecting DATABASE_URL and hosting stateful workloads like AI agents…

    Claude Code, Codex, Cursor +4 · about 11.9K tokens · pinned 6 days ago

  • neon-object-storage

    Neon Object Storage provides S3-compatible file storage that branches with your Neon Postgres database, keeping files and database rows in sync across…

    Claude Code, Codex, Cursor +4 · about 3.3K tokens · pinned 6 days ago

  • neon-postgres

    This skill guides agents on working with Postgres databases on Neon, covering connection setup, pooled vs direct connections, schema migrations, branching…

    Claude Code, Codex, Cursor +4 · about 4K tokens · pinned 6 days ago

  • neon-auth-react

    Sets up Neon Auth (authentication without database) in React applications built with Vite or Create React App.

    by NeonOfficial publisher

    Claude Code, Codex, Cursor +4 · about 4.5K tokens · pinned 6 days ago

  • react-email

    React Email lets agents build HTML email templates as React components and render them to HTML or plain text.

    by ResendOfficial publisher

    Claude Code, Codex, Cursor +4 · about 3.4K tokens · pinned 6 days ago

  • apollo-client

    This skill guides building React applications with Apollo Client 4.x for managing GraphQL queries, mutations, and state.

    by Apollo GraphQLOfficial publisher

    Claude Code, Codex, Cursor +4 · about 1.7K tokens · pinned 6 days ago

  • stitch::code-to-design

    This skill converts existing frontend code (React, Vue, Angular, etc.) into a Stitch Design by extracting HTML, analyzing design systems, and uploading both…

    by GoogleOfficial publisher

    Claude Code, Codex, Cursor +4 · about 760 tokens · pinned 6 days ago

  • stitch::extract-design-md

    Extracts a comprehensive design system document (DESIGN.md) by analyzing frontend source code including component files, stylesheets, Tailwind configs, and…

    by GoogleOfficial publisher

    Claude Code, Codex, Cursor +4 · about 3.3K tokens · pinned 6 days ago

  • stitch::extract-static-html

    This skill extracts a self-contained static HTML file from a running web application or React components by inlining all CSS and images as base64 data.

    by GoogleOfficial publisher

    Contains scripts

    Claude Code, Codex, Cursor +3 · about 2.6K tokens · pinned 6 days ago

The skills listed here are third-party software, collected from public GitHub repositories as a catalog of recent skills and provided as is. Agent Native does not write or control them and is not responsible for any harm, loss or damage caused by installing or using them. Labels and checks describe what we saw at one commit and are not a guarantee. Read a skill's files before you run it. Terms of use