From install to your first answer.
Seven steps in an existing Next.js app. You need Node 20, PostgreSQL 14, React 19 and Next.js 15 or newer.
Install the package
react and react-dom are peer dependencies that a Next.js app already has.
terminalnpm install better-helpdesk pgCreate the schema
The CLI opens one connection, creates the helpdesk schema and its tables, and exits, so it fits into a release step next to your own migrations.
terminalHELPDESK_DATABASE_URL=postgres://… npx better-helpdesk-migrateLet Next.js compile the package
Both UIs request paths with a trailing slash. Without it, every call pays a redirect first, and a widget on another origin fails its CORS preflight.
next.config.mjsimport { withHelpdesk } from 'better-helpdesk/next'; export default withHelpdesk({ trailingSlash: true });Build the helpdesk and mount the handler
identify runs on every request. Return null for an anonymous visitor. adminUrl must be the URL your team opens: the handler refuses mutations from any other origin.
lib/helpdesk.tsimport { buildHelpdesk, postgresAdapter } from 'better-helpdesk'; import pg from 'pg'; const pool = new pg.Pool({ connectionString: process.env.HELPDESK_DATABASE_URL }); export const helpdesk = buildHelpdesk({ db: postgresAdapter({ pool }), referencePrefix: 'ACME', adminUrl: 'https://app.example.com/helpdesk/', inboxes: { support: { receipt: true } }, identify: async request => { const session = await getSession(request); // however your app does it if (!session) return null; // an anonymous visitor return { user: { id: session.user.id, email: session.user.email, emailVerified: true, name: session.user.name }, orgs: session.orgs.map(org => ({ id: org.id, name: org.name })), isAgent: session.user.role === 'support', }; }, });Mount the route handler
One handler serves the widget, the agent UI, inbound mail and jobs under your basePath.
app/api/helpdesk/[...slug]/route.tsimport { helpdesk } from '@/lib/helpdesk'; const handle = (request: Request) => helpdesk.handler(request); export { handle as GET, handle as POST, handle as PATCH, handle as DELETE, handle as OPTIONS };Render the agent UI
Put the page behind your own agent check as well. The API refuses anyone who is not an agent either way.
app/helpdesk/[[...slug]]/page.tsximport { HelpdeskAdmin } from 'better-helpdesk/admin'; export default function Page() { return <HelpdeskAdmin api="/api/helpdesk" basePath="/helpdesk" locale="en" />; }Add the widget
Send a message from the widget, open /helpdesk as an agent and answer it. That is the whole loop.
app/layout.tsximport { HelpdeskWidget } from 'better-helpdesk/widget'; export function Layout({ children }) { return ( <> {children} <HelpdeskWidget inbox="support" locale="en" /> </> ); }
Next: the guides.
Email in and out, jobs and retention, storage, AI, theming and signed-in users on another origin are in the README.