> Full Neon documentation index: https://neon.com/docs/llms.txt
# Building an Admin dashboard with Neon Auth
Learn how to create an internal admin dashboard for user management using Neon Auth's Admin APIs.
In a production application, internal tooling is often critical for operations and support teams. The Neon Auth [Admin plugin](https://neon.com/docs/auth/guides/plugins/admin) (powered by Better Auth) exposes powerful user management APIs directly through the SDK, allowing you to build these tools without writing complex backend logic.
This guide demonstrates how to build an **internal admin dashboard** using Neon Auth. You will create a React application that allows support staff to view registered users, ban abusive accounts, and securely impersonate users to reproduce bugs. It will cover the following features:
1. **A user table:** To list all registered users in your application.
2. **Moderation controls:** To ban and unban users instantly.
3. **Impersonation:** To log in as any user for troubleshooting purposes.
## Prerequisites
Before you begin, ensure you have the following:
- **Node.js:** Version `18` or later installed on your machine. You can download it from [nodejs.org](https://nodejs.org/).
- **Neon account:** A free Neon account. If you don't have one, sign up at [Neon](https://console.neon.tech/signup).
## Create a Neon project with Neon Auth
You'll need to create a Neon project and enable Neon Auth.
1. **Create a Neon project:** Navigate to [pg.new](https://pg.new) to create a new Neon project. Give your project a name, such as `admin-dashboard-demo`.
2. **Enable Neon Auth:**
- In your project's dashboard, go to the **Neon Auth** tab.
- Click on the **Enable Neon Auth** button to set up authentication for your project.
3. **Copy your Auth URL:**
Found on the **Auth** page (e.g., `https://ep-xxx.neon.tech/neondb/auth`).

## Create an Admin user
To use the Admin APIs, you must perform the operations as an authenticated user with the `admin` role. You cannot grant this role via the API initially; you must assign your first admin via the Neon Console.
1. **Create a user:** Go to your application URL or use the Neon Console to create an user (e.g., `admin@example.com`).
2. **Assign role:**
- In the Neon Console, go to **Auth** -> **Users**.
- Find your user, click the three-dot menu, and select **Make admin**.

Now you have an admin user to log in with and access the Admin APIs.
## Set up the React project
Create a new React project using Vite.
### Initialize the app
```bash
npm create vite@latest admin-dashboard -- --template react-ts
cd admin-dashboard && npm install
```
When prompted:
- Select "No" for "Use rolldown-vite (Experimental)?"
- Select "No" for "Install with npm and start now?"
You should see output similar to:
```bash
$ npm create vite@latest react-neon-todo -- --template react-ts
> npx
> "create-vite" react-neon-todo --template react-ts
│
◇ Use rolldown-vite (Experimental)?:
│ No
│
◇ Install with npm and start now?
│ No
│
◇ Scaffolding project in /home/user/react-neon-todo...
│
└ Done.
```
### Install dependencies
You will need the following packages for this project:
- **Neon SDK:** [`@neondatabase/neon-js`](https://www.npmjs.com/package/@neondatabase/neon-js) for interacting with Neon Auth and the Data API.
- **React Router:** [`react-router`](https://www.npmjs.com/package/react-router) for routing between pages.
```bash
npm install @neondatabase/neon-js react-router
```
### Setup Tailwind CSS
Install Tailwind CSS and the Vite plugin:
```bash
npm install tailwindcss @tailwindcss/vite
```
Add the `@tailwindcss/vite plugin` to your Vite configuration (`vite.config.ts`):
```javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
});
```
### Configure environment variables
Create a `.env` file in the root of your project and add the credentials you copied in [Step 1](https://neon.com/guides/admin-dashboard-neon-auth#create-a-neon-project-with-neon-auth):
```env
VITE_NEON_AUTH_URL="https://ep-xxx.neon.tech/neondb/auth"
```
## Configure Neon Auth client
### Initialize the Auth client
Create a client instance to interact with Neon Auth.
Create a file `src/auth.ts`. This file will export the `authClient` instance to be used throughout the app.
```typescript
import { createAuthClient } from '@neondatabase/neon-js/auth';
import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
export const authClient = createAuthClient(import.meta.env.VITE_NEON_AUTH_URL, {
adapter: BetterAuthReactAdapter(),
});
```
### Application entry point
Update `src/main.tsx` to wrap your app in the `NeonAuthUIProvider` and `BrowserRouter` to enable routing and authentication context. The `ImpersonationBanner` component is also included here to display when impersonating a user. The implementation part of this component is covered later in the guide.
```tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router';
import { NeonAuthUIProvider } from '@neondatabase/neon-js/auth/react/ui';
import App from './App.tsx';
import { authClient } from './auth.ts';
import './index.css';
import { ImpersonationBanner } from './components/ImpersonationBanner.tsx';
createRoot(document.getElementById('root')!).render(
);
```
### Create Auth and Account pages
Neon Auth provides pre‑built UI components for handling the complete flow of authentication, including Sign In, Sign Up, and Account management.
As outlined in the [Neon Auth React UI guide](https://neon.com/docs/auth/quick-start/react-router-components), you can use the `AuthView` and `AccountView` components to quickly set up these pages.
Create `src/pages/Auth.tsx`:
```tsx
import { AuthView } from '@neondatabase/neon-js/auth/react/ui';
import { useParams } from 'react-router';
export default function AuthPage() {
const { path } = useParams();
return (
);
}
```
### Update styles
Update `src/index.css` to include the Neon Auth Tailwind styles and set the minimal global styles.
```css
@import 'tailwindcss';
@import '@neondatabase/neon-js/ui/tailwind';
:root {
font-family: system-ui, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #0f172a;
background-color: #f3f4f6;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-height: 100vh;
background: #000000;
}
```
## Implement actions (Ban & Impersonate)
Create a component to render each user row with Ban and Impersonate actions required for the dashboard.
Create `src/components/UserRow.tsx` with the following code:
```tsx
import { useState } from 'react';
import { authClient } from '../auth';
import type { UserType } from './AdminDashboard';
export function UserRow({ user, refreshData }: { user: UserType; refreshData: () => void }) {
const [showBanModal, setShowBanModal] = useState(false);
const [banReason, setBanReason] = useState('');
const handleBanToggle = async () => {
if (user.banned) {
await authClient.admin.unbanUser({ userId: user.id });
refreshData();
} else {
setShowBanModal(true);
}
};
const handleConfirmBan = async () => {
await authClient.admin.banUser({
userId: user.id,
banReason: banReason || 'No reason provided',
});
setShowBanModal(false);
setBanReason('');
refreshData();
};
const handleImpersonate = async () => {
const { data } = await authClient.admin.impersonateUser({
userId: user.id,
});
if (data) {
window.location.href = '/';
}
};
return (
{user.name}
{user.email}
{user.role || 'user'}
{user.banned ? (
Banned
) : (
Active
)}
{showBanModal && (
Ban User: {user.name}
{[
'Violated terms of service',
'Free tier abuse',
'Spam or suspicious activity',
'Non payment of dues',
].map((reason) => (
))}
)}
);
}
```
The `UserRow` component includes the following functionality:
1. **State management**
- `showBanModal`: Controls whether the ban confirmation modal is visible.
- `banReason`: Stores the reason provided for banning a user.
2. `handleBanToggle()`
- If the user is already banned, calls `authClient.admin.unbanUser()` to unban them.
- If the user is not banned, opens the ban modal to collect a reason.
- Refreshes the user list after changes.
3. `handleConfirmBan()`
- Calls `authClient.admin.banUser()` with the user's ID and the selected or entered ban reason.
- Resets the modal state and refreshes the user list.
4. `handleImpersonate()`
- Calls `authClient.admin.impersonateUser()` with the user's ID.
- If successful, redirects the admin to the homepage (`'/'`) as the impersonated user.
5. **Row rendering**
- Displays user details: **Name, Email, Role, and Status (Active/Banned)**.
- Shows action buttons:
- **Impersonate**: Disabled if the user is banned.
- **Ban/Unban**: Toggles based on the user's current status.
6. **Ban modal**
- Appears when banning a user.
- Provides quick‑select ban reasons and a textarea for custom reasons.
This component ensures that admins can **manage user accounts directly from the dashboard**, with clear UI feedback for banning, unbanning, and impersonating users.
## Create the user list component
Create a file `src/components/AdminDashboard.tsx`. This component will fetch and display the list of users using the Admin API.
```tsx
import { useEffect, useState } from 'react';
import { authClient } from '../auth';
import { UserRow } from './UserRow';
import { RedirectToSignIn, SignedIn } from '@neondatabase/neon-js/auth/react/ui';
import type { User } from '@neondatabase/neon-js/auth/types';
export type UserType = User & { banned: boolean | null } & { role?: string | null };
export default function AdminDashboard() {
const [users, setUsers] = useState([]);
const [userDataLoading, setUserDataLoading] = useState(false);
const { data, isPending: isSessionDataLoading } = authClient.useSession();
const fetchUsers = async () => {
setUserDataLoading(true);
const { data, error } = await authClient.admin.listUsers({
query: { limit: 100, sortBy: 'createdAt', sortDirection: 'desc' },
});
if (data) {
setUsers(data.users);
} else {
console.error(error);
alert('Failed to fetch users.');
}
setUserDataLoading(false);
};
useEffect(() => {
if (data?.user?.role === 'admin') fetchUsers();
}, [data]);
if (userDataLoading || isSessionDataLoading) {
return (
);
}
```
The `AdminDashboard` component includes the following features:
1. **State management**
- `users`: Stores the list of users fetched from the backend.
- `userDataLoading`: Tracks whether user data is currently being loaded.
- `authClient.useSession()`: Provides session data and loading state for the authenticated user.
2. **`fetchUsers()`**
- Calls `authClient.admin.listUsers()` to retrieve up to 100 users, sorted by creation date (newest first).
> For detailed guidance on customizing query parameters, enabling partial searches by name or email, and implementing pagination for large user bases, see the [Admin API](https://neon.com/docs/auth/guides/plugins/admin) reference
- Updates the `users` state with the fetched data.
- Handles errors by logging them and showing an alert.
- Sets loading state before and after the request.
3. **`useEffect()`**
- Runs when session data changes.
- If the logged‑in user has the role `admin`, it triggers `fetchUsers()` to load user data.
4. **Loading state**
- If either session data or user data is still loading, displays a "Loading users…" message.
5. **Dashboard rendering**
- If the user has the `admin` role:
- Displays a **Support dashboard** header.
- Renders a table of users with columns for **User ID, Email, Role, Status, and Actions** (using the `UserRow` component).
- If the user is not an admin:
- Shows an **Access Denied** message.
6. **Authentication handling**
- Only renders the dashboard for signed‑in users by wrapping the content in ``.
- Uses `` to redirect unauthenticated users to the sign‑in page.
## Add an Impersonation banner
When impersonating a user, it is critical to have a way to return to your admin account. This component checks the session for the `impersonatedBy` field on the session object and displays a banner with a button to stop impersonation.
Create `src/components/ImpersonationBanner.tsx`:
```tsx
import { authClient } from '../auth';
export function ImpersonationBanner() {
const { data: session } = authClient.useSession();
// Only render if currently impersonating
if (!session?.session.impersonatedBy) return null;
const stopImpersonation = async () => {
await authClient.admin.stopImpersonating();
window.location.reload();
};
return (
👀 You are impersonating {session.user.email}
);
}
```
The `ImpersonationBanner` component includes the following features:
1. **Session handling**
- Uses `authClient.useSession()` to access the current session data.
- Checks if the session includes `impersonatedBy`.
- If not impersonating, the component returns `null` (renders nothing).
2. **`stopImpersonation()`**
- Calls `authClient.admin.stopImpersonating()` to end the impersonation session and revert to the admin account.
3. **Banner rendering**
- Displays a fixed banner at the top of the screen to display the user information being impersonated.
- Includes a **Return to Admin** button that triggers `stopImpersonation()`.
This component ensures admins have clear visibility when impersonating a user and provides a quick way to return to their own account.
## Complete the App component
Finally, update `src/App.tsx` to include routing and the main dashboard layout.
```tsx
import { RedirectToSignIn, SignedIn, UserButton } from '@neondatabase/neon-js/auth/react';
import AdminDashboard from './components/AdminDashboard';
import { Link, Route, Routes } from 'react-router';
import Auth from './pages/Auth';
import Account from './pages/Account';
import { authClient } from './auth';
const HomePage = () => {
const { data, isPending } = authClient.useSession();
const isImpersonating = data?.session?.impersonatedBy;
if (isPending) {
return (
You have permission to manage users and system settings.
Go to Admin Dashboard →
)}
);
};
export default function App() {
return (
} />
} />
} />
} />
);
}
```
This file defines the main **React application** with routing and a client dashboard that integrates authentication and admin access:
1. **`HomePage` component**
- **Session handling**
- Uses `authClient.useSession()` to fetch session data and loading state.
- **Header**
- Displays a "Client Dashboard" title.
- Includes a `UserButton` for account management (profile, sign out, etc.).
- **User and session details**
- Shows authentication status and user ID if logged in.
- Renders raw session data for debugging purposes.
- **Admin call‑to‑action**
- If the user's role is `admin`, shows an "Admin Access" card.
- Provides a link to the **Admin Dashboard** (`/admin`).
- **Authentication handling**
- Uses `` to render content only for authenticated users.
- Uses `` to redirect unauthenticated users to the sign‑in page.
2. **`App` component:**
The main application component that sets up routing using `react-router` with the following routes:
- `/` → `HomePage`
- `/admin` → `AdminDashboard`
- `/auth/:path` → `Auth` page
- `/account/:path` → `Account` page
This setup provides a **client dashboard** that shows session details, user status, and admin access, while routing users to authentication, account, and admin pages as needed.
## Run the application
1. **Start the development server:**
```bash
npm run dev
```
2. Open `http://localhost:5173` in your browser.
3. In a separate browser or incognito window, create some test user accounts by signing up.
4. In the original browser window, log in with the account you assigned the **admin** role to in [Step 2](https://neon.com/guides/admin-dashboard-neon-auth#create-an-admin-user).
5. You should now see the dashboard populated with user accounts.

6. Return to the admin dashboard, where you can list, ban, and impersonate users.
7. Try impersonating the user. The app will switch to their perspective, allowing you to debug issues they may encounter.

8. The **Go to Admin Dashboard** link is only visible to users with the `admin` role and provides quick access to the admin interface.

## Use cases for impersonation and admin tools
While this demo app simply shows the impersonated user's information and session details, in a production application impersonation and admin tools can be far more powerful and useful. They enable support teams, moderators, and operations staff to manage accounts effectively and resolve issues quickly. Common scenarios include:
- **Customer support & debugging:** Admins can impersonate a user to reproduce bugs, troubleshoot login issues, or verify account settings exactly as the user sees them. This eliminates guesswork when a user reports a problem that only occurs on their account.
- **Billing & subscriptions:** Support staff can impersonate a user to confirm subscription status, payment history, or upgrade/downgrade flows.
- **Feature access & permissions:** Admins can check whether a user has the correct role‑based permissions or feature entitlements, ensuring access policies are applied correctly.
- **Onboarding assistance:** Support teams can walk through the app as the user to confirm onboarding steps are completed properly.
- **Trust & safety:** Moderators can use the **Ban** functionality to revoke access for users posting spam or violating terms of service, preventing future logins.
- **Back‑office operations:** Operations managers can view user details, confirm email addresses, or audit user roles directly from a UI instead of running manual SQL queries.
## Source code
The complete source code for this example is available on GitHub.
- [Admin Dashboard Example](https://github.com/dhanushreddy291/neon-auth-admin-dashboard): Complete source code for the Admin Dashboard example.
## Resources
- [Neon Auth Admin API Reference](https://neon.com/docs/auth/guides/plugins/admin)
- [Neon Auth Overview](https://neon.com/docs/neon-auth/overview)
- [Neon Auth UI components](https://neon.com/docs/auth/reference/ui-components)
- [React with Neon Auth UI (UI Components)](https://neon.com/docs/auth/quick-start/react-router-components)
- [Neon Auth & Data API TypeScript SDKs](https://neon.com/docs/reference/javascript-sdk)