--- title: Building an Admin dashboard with Neon Auth subtitle: Learn how to create an internal admin dashboard for user management using Neon Auth's Admin APIs. author: dhanush-reddy enableTableOfContents: true createdAt: '2025-12-31T00:00:00.000Z' updatedOn: '2025-12-31T00:00:00.000Z' --- In a production application, internal tooling is often critical for operations and support teams. The Neon Auth [Admin plugin](/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`). ![Neon Auth URL](/docs/auth/neon-auth-base-url.png) ## 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**. ![Assign admin role in Neon Console](/docs/auth/make-admin.png) 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'; // [!code ++] export default defineConfig({ plugins: [ react(), tailwindcss(), // [!code ++] ], }); ``` ### Configure environment variables Create a `.env` file in the root of your project and add the credentials you copied in [Step 1](#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 shouldWrap 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 shouldWrap 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](/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 (
); } ``` Create `src/pages/Account.tsx`: ```tsx import { AccountView } from '@neondatabase/neon-js/auth/react/ui'; import { useParams } from 'react-router'; export default function AccountPage() { 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 shouldWrap 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) => ( ))}