# Package exports

URL: https://better-helpdesk.com/docs/reference/exports/

Each entry point of the better-helpdesk package and what it exports.



The `better-helpdesk` package has eight entry points. Related guide: [Call the helpdesk from your own code](/docs/app/own-code).

| Entry point                                             | Runs on           | Exports                                                                |
| ------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------- |
| [`better-helpdesk`](#better-helpdesk)                   | Server            | `buildHelpdesk`, `postgresAdapter`, identity tokens, references, types |
| [`better-helpdesk/adapters`](#better-helpdeskadapters)  | Server            | The database adapters, the adapter contract and `migrate`              |
| [`better-helpdesk/testing`](#better-helpdesktesting)    | Tests             | `runAdapterTests`, the checks an adapter of your own must pass         |
| [`better-helpdesk/widget`](#better-helpdeskwidget)      | Browser           | The widget and the support page, for React and as custom elements      |
| [`better-helpdesk/admin`](#better-helpdeskadmin)        | Browser           | The agent UI                                                           |
| [`better-helpdesk/rich`](#better-helpdeskrich)          | Server or browser | The message format: parser and renderer                                |
| [`better-helpdesk/next`](#better-helpdesknext)          | Build             | `withHelpdesk` for `next.config`                                       |
| [`better-helpdesk/widget.js`](#better-helpdeskwidgetjs) | Browser           | The standalone widget script                                           |

The package is ESM only and ships its type declarations. Its peer dependencies are `react` and `react-dom` (React 19) and the driver of the host's database, which only a host on that database installs: `pg` for Postgres, `mysql2` 3.24 or newer for MySQL, `tedious` 20 and `tarn` 3 for SQL Server, and none for SQLite; it requires Node.js 22.19 or newer.

## better-helpdesk [#better-helpdesk]

| Export                                                                                             | Kind     | What it is                                                                                                                                                                                                                 |
| -------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `buildHelpdesk(config)`                                                                            | function | Builds the helpdesk from a [`HelpdeskConfig`](/docs/reference/configuration). Throws on [invalid configuration](/docs/reference/configuration#startup-checks).                                                             |
| `postgresAdapter({ pool })`                                                                        | function | The `DatabaseAdapter` over a `pg.Pool`, for the `db` option.                                                                                                                                                               |
| `mysqlAdapter({ pool })`                                                                           | function | The `DatabaseAdapter` over the host's mysql2 pool, MySQL 8.4 or newer. The pool must be created with `timezone: 'Z'`; the adapter refuses one that converts times in another zone.                                         |
| `mssqlAdapter(config)`                                                                             | function | The `DatabaseAdapter` over SQL Server 2022 or newer. `config` is what Kysely's `MssqlDialect` takes: the host's `tedious` and `tarn` modules and its connection.                                                           |
| `sqliteAdapter({ database })`                                                                      | function | The `DatabaseAdapter` over a SQLite database the host opened, from `node:sqlite` or `better-sqlite3`. It turns foreign keys on and refuses to start if they stay off.                                                      |
| `kyselyAdapter({ dialect, family })`                                                               | function | A built-in adapter over any Kysely dialect of a family Better Helpdesk knows. `postgresAdapter` is this with Kysely's `PostgresDialect`.                                                                                   |
| `migrate(adapter)`                                                                                 | function | Applies the migrations through a built-in adapter, as [`better-helpdesk-migrate`](/docs/operations/cli#better-helpdesk-migrate) does; returns the names of the ones it applied.                                            |
| `createAdapter(input)`                                                                             | function | Turns an `AdapterInput`, the contract a custom adapter implements, into a `DatabaseAdapter`: it sets ids and defaults and converts values to and from what the input supports.                                             |
| `memoryRealtime()`                                                                                 | function | A `RealtimeAdapter` for the [`realtime`](/docs/reference/configuration#conversations) option that reaches the event streams of one process.                                                                                |
| `postgresRealtime({ pool, retryMs? })`                                                             | function | A `RealtimeAdapter` over Postgres `LISTEN` and `NOTIFY` on the host's `pg.Pool`, so a stream on one process hears a change made on another. It holds one pooled connection while anyone is subscribed.                     |
| `createStore(adapter, options?)`                                                                   | function | The `HelpdeskStore` over an adapter, for code that runs without `buildHelpdesk`. Without `options.slaClock`, the targets `buildHelpdesk` derives from `sla`, the store starts no deadlines.                                |
| `exportConversations(store, { prefix, since? })`                                                   | function | An async iterator over every conversation in the [export format](/docs/operations/cli#better-helpdesk-export), as `better-helpdesk-export` writes it.                                                                      |
| `importConversations(store, lines, options?)`                                                      | function | Reads lines of that format, as `better-helpdesk-import conversations` does; returns the same report as the other imports.                                                                                                  |
| `helpdeskModel`                                                                                    | object   | Every table and field, with its kind, whether it may be null, its default and its references.                                                                                                                              |
| `signIdentityToken(payload, secret, options?)`                                                     | function | Signs an identity token.                                                                                                                                                                                                   |
| `verifyIdentityToken(token, secret)`                                                               | function | Returns the `Identity` a token vouches for, with `isAgent: false`, or `null` when it is forged, malformed or expired.                                                                                                      |
| `formatReference(prefix, number)`                                                                  | function | `formatReference('ACME', 1042)` is `ACME-1042`.                                                                                                                                                                            |
| `parseReference(prefix, value)`                                                                    | function | The number in a reference such as `ACME-1042`, matched case-insensitively, or `undefined`.                                                                                                                                 |
| `ipBucket(ip)`                                                                                     | function | The key the rate limits count a client address under: an IPv4 address as it is, an IPv6 address by its `/64`, since one host owns the whole block. For host code that limits or groups visitors the way the helpdesk does. |
| `HelpdeskError`                                                                                    | class    | An error with an HTTP `status` and, for a bulk change, the failed `ids`. The handler answers it with that status.                                                                                                          |
| `BuiltHelpdesk`                                                                                    | type     | What `buildHelpdesk` returns.                                                                                                                                                                                              |
| `Conversation`, `Message`                                                                          | type     | Rows of [`conversation`](/docs/database/tables#conversation) and [`message`](/docs/database/tables#message).                                                                                                               |
| `HelpdeskStore`                                                                                    | type     | The query layer, `buildHelpdesk(...).store`.                                                                                                                                                                               |
| `DatabaseAdapter`, `AdapterInput`, `Capabilities`, `Where`, `OrderBy`, `FindQuery`, `Notification` | type     | The adapter contract.                                                                                                                                                                                                      |
| `HelpdeskEvent`                                                                                    | type     | See [Events](/docs/app/events).                                                                                                                                                                                            |
| `SlaClock`, `SlaClockFor`, `SlaTargetName`                                                         | type     | The targets a conversation runs under, as `createStore` takes them.                                                                                                                                                        |
| `RealtimeAdapter`, `RealtimeChange`                                                                | type     | What the `realtime` option takes, and the change it carries.                                                                                                                                                               |
| `IdentityClaims`                                                                                   | type     | The claims of an identity token.                                                                                                                                                                                           |

Every type in the configuration module is exported as well: `HelpdeskConfig`, `InboxConfig`, `BusinessHours`, `Identity`, `HelpdeskUser`, `HelpdeskOrg`, `HostLink`, `LinkContact`, `LinkCompany`, `HelpdeskEmail`, `StorageAdapter`, `AiAdapter`, `CaptchaAdapter`, `AiFeature`, `AiSettings`, `HelpResult`, `InboundMessage`, `CustomFieldDef`, `Locale`, `Translated`, `Priority`, `SlaConfig`, `SlaPolicy`, `SlaTarget`, `SlaDuration`, `TierInput` and `ResolvedConfig`. The default lists, such as the statuses and the default types, are not exported.

### What buildHelpdesk returns [#what-buildhelpdesk-returns]

<TypeTable
  type="{
  handler: {
    type: '(request: Request) => Promise<Response>',
    description: <>Serves every <a href=&#x22;/docs/reference/routes&#x22;>route</a> under <code>basePath</code>.</>,
  },
  config: {
    type: 'ResolvedConfig',
    description: 'The configuration with its defaults filled in.',
  },
  store: {
    type: 'HelpdeskStore',
    description: <>The queries the routes run. Writes through it raise no events. See <a href=&#x22;#helpdeskstore&#x22;>HelpdeskStore</a>.</>,
  },
  reference: {
    type: '(conversation: { number: number }) => string',
    description: &#x22;The conversation's reference, such as ACME-1042.&#x22;,
  },
  createConversation: {
    type: '(request: Request, input: unknown) => Promise<{ conversation: Conversation | null; visitorToken?: string }>',
    description: <>Opens a conversation as <code>POST widget/conversations</code> does, for the customer the request identifies, and raises <code>conversation.created</code>. <code>input</code> takes <a href=&#x22;/docs/reference/routes#opening-a-conversation&#x22;>that route's body</a>; where the route would answer an error, it throws: a <code>HelpdeskError</code> with that status, or a Zod error for an invalid body. The conversation's channel is <code>api</code>.</>,
  },
  openConversation: {
    type: '(input: { agentUserId: string; … }) => Promise<{ conversation: Conversation; message: Message }>',
    description: <>Opens a conversation the team starts, in the name of the agent whose host user id is <code>agentUserId</code>, as <code>POST agent/conversations</code> does with <a href=&#x22;/docs/reference/routes#opening-a-conversation-as-an-agent&#x22;>that route's body</a>. Throws a <code>HelpdeskError</code> with status 400 for an unknown agent.</>,
  },
  addNote: {
    type: '(conversationId: string, body: string, options?: { agentUserId?: string; internal?: boolean; notify?: string[] }) => Promise<Message>',
    description: <>Writes an internal note, or a public message when <code>internal</code> is <code>false</code>, from the agent named or from the system. Raises <code>message.created</code> and reaches open agent UIs; a public message is emailed to the customer, and from an agent it assigns an unassigned conversation to them. For what your own systems learn, such as an incident acknowledged or an issue closed.</>,
  },
  changeConversation: {
    type: '(conversationId: string, patch: unknown, options?: { agentUserId?: string }) => Promise<Conversation | null>',
    description: <>Changes a conversation as <code>PATCH agent/conversations/{'{id}'}</code> does, with the same body and checks, in the name of the agent given or of nobody. Writes the timeline and raises <code>conversation.updated</code>.</>,
  },
  runJobs: {
    type: '(options?: { budgetMs?: number }) => Promise<JobsReport>',
    description: <>Runs scheduled work and due jobs. See <a href=&#x22;/docs/reference/routes#jobs&#x22;>Jobs</a>.</>,
  },
  track: {
    type: '(input: { externalUserId?: string; externalOrgId?: string; event: string; props?: Record<string, unknown> }) => Promise<boolean>',
    description: <>Puts a host event on a timeline as an <code>activity</code> of kind <code>event</code>. With <code>externalUserId</code>, on the contact that holds that host identity, and on its company; with only <code>externalOrgId</code>, on that organization's company, created when missing. Returns <code>false</code> and records nothing when no contact holds the user id, or neither id is given.</>,
  },
  refreshTiers: {
    type: '(target: { orgId?: string; email?: string }) => Promise<number>',
    description: <>Asks <code>resolveTier</code> again for the open conversations of the organization <code>orgId</code>, or of the person at <code>email</code>, and moves each to the tier it gives now, with fresh deadlines. A tier an agent chose by hand stands. Returns how many changed. See <a href=&#x22;/docs/inbox/sla#when-a-plan-changes&#x22;>When a plan changes</a>.</>,
  },
  removeAgent: {
    type: '(externalUserId: string) => Promise<void>',
    description: <>Removes an agent from the team and unassigns their conversations, as <code>DELETE agent/agents/{'{id}'}</code> does. Their next request to an agent route with <code>isAgent: true</code> adds them back.</>,
  },
  deleteContact: {
    type: '(contactId: string) => Promise<{ conversations: number }>',
    description: &#x22;Deletes a contact by its helpdesk id, with their conversations, what they wrote in other conversations, and their files. Returns the number of conversations deleted.&#x22;,
  },
  deleteCompany: {
    type: '(externalOrgId: string) => Promise<{ conversations: number }>',
    description: &#x22;Deletes a host organization's company with its conversations and files. Its contacts who have no conversation outside the company are deleted too, with what they wrote elsewhere; the others stay, without a company. Returns the number of conversations deleted, 0 when no company has that id.&#x22;,
  },
}"
/>

`JobsReport` stands for `{ woken, reminders, retention, jobs, failed, errors }`, described under [Jobs](/docs/reference/routes#jobs); it is not an exported name. The object also carries the internal functions the handler is built from, which are not listed here.

### HelpdeskStore [#helpdeskstore]

`helpdesk.store` holds the queries the routes run, over the `helpdesk` schema. These are the ones the [guides](/docs/app/own-code) use. Rows use the column names of [Database](/docs/database/tables) in camel case.

<TypeTable
  type="{
  adapter: {
    type: 'DatabaseAdapter',
    description: <>The database adapter the store runs on, the one passed as <code>db</code>.</>,
  },
  findContactByIdentity: {
    type: &#x22;(channel: 'host' | 'email' | 'visitor' | 'github', externalId: string, options: { verifiedOnly: boolean }) => Promise<Contact | null>&#x22;,
    description: <>The contact that holds an <a href=&#x22;/docs/database/tables#identity&#x22;>identity</a>, a verified one first.</>,
  },
  findContactByEmail: {
    type: '(email: string) => Promise<Contact | null>',
    description: 'The oldest contact with exactly this address. Addresses are stored lower-cased.',
  },
  updateContact: {
    type: '(id: string, patch: Partial<Contact>) => Promise<Contact | null>',
    description: 'Writes the fields given.',
  },
  listContacts: {
    type: '(filter: { query?: string; leadStage?: string; companyId?: string; limit?: number }) => Promise<Contact[]>',
    description: <>Contacts, most recently active first, at most <code>limit</code> (100 by default). <code>query</code> matches name or email. Each row also carries <code>conversationCount</code>, <code>lastMessageAt</code>, <code>isTeam</code> and <code>firstContext</code>.</>,
  },
  listAgents: {
    type: '() => Promise<Agent[]>',
    description: 'The team, by name, without removed agents.',
  },
  listInbox: {
    type: '(filter: InboxFilter) => Promise<{ conversation: Conversation; contact: Contact }[]>',
    description: <>The agent inbox, longest waiting first, at most <code>limit</code> (200 by default). The filter takes <code>inbox</code>, <code>status</code> (also <code>snoozed</code>, <code>rated-good</code> and <code>rated-bad</code>), <code>assigneeId</code> (<code>null</code> for unassigned), <code>contactId</code>, <code>companyId</code>, <code>tag</code>, <code>tier</code>, <code>channel</code>, <code>query</code>, <code>priority: 'high'</code>, <code>since</code> (a <code>Date</code>: resolved, rated, or for any other status opened, resolved or rated since then), <code>sort</code> (<code>waiting</code>, <code>priority</code> or <code>sla</code>) and <code>limit</code>. Blocked contacts' conversations are left out unless <code>contactId</code> is given.</>,
  },
  updateConversation: {
    type: '(id: string, patch: Partial<Conversation>) => Promise<Conversation | null>',
    description: 'Writes the fields given, without a timeline row or an event.',
  },
  listActivities: {
    type: '(filter: { contactId?: string; companyId?: string; dealId?: string }) => Promise<{ activity: Activity; agentName: string | null }[]>',
    description: 'Activities on any of the given records, newest first, at most 200.',
  },
}"
/>

`Contact`, `Agent` and `Activity` are the stored rows; only `Conversation` and `Message` are exported as type names.

### signIdentityToken [#signidentitytoken]

```ts
signIdentityToken(
  payload: Omit<IdentityClaims, 'exp'>,
  secret: string,
  options?: { expiresInSeconds?: number } // default 3600
): string
```

The claims of the token:

<TypeTable
  type="{
  sub: { type: 'string', required: true, description: &#x22;The host's user id, 1 to 200 characters.&#x22; },
  exp: { type: 'number', required: true, description: <>Seconds since the epoch. <code>signIdentityToken</code> sets it.</> },
  email: { type: 'string', description: 'An email address, up to 320 characters.' },
  email_verified: { type: 'boolean', default: 'false', description: <>Becomes <code>emailVerified</code>.</> },
  name: { type: 'string', description: 'Up to 200 characters.' },
  locale: { type: 'string', description: 'Up to 35 characters.' },
  orgs: { type: '{ id: string; name?: string }[]', default: '[]', description: 'Up to 100; each id 1 to 200 characters, each name up to 200.' },
}"
/>

The token is an HS256 JWT, so any JWT library that signs the same claims produces a valid one.

## better-helpdesk/adapters [#better-helpdeskadapters]

What a host needs to pick or write a database adapter, without the rest of the server: `postgresAdapter`, `mysqlAdapter`, `mssqlAdapter`, `sqliteAdapter`, `kyselyAdapter`, `createAdapter`, `migrate` and `helpdeskModel`, with the contract's types, and the realtime adapters `memoryRealtime` and `postgresRealtime`. The same names are exported from `better-helpdesk`. None of them imports a database driver: the host passes its own connection.

## better-helpdesk/testing [#better-helpdesktesting]

`runAdapterTests({ name?, adapter, reset })` registers vitest tests that hold an adapter to the contract the store relies on: every kind of value read back as written, NULL compared as SQL does, exact text unless `insensitive`, search characters taken literally, NULLs ordered where asked, times and numbers compared and ordered by value, paging and selected fields, conditions through another table, counted updates and in-place increments, one winner of simultaneous compare-and-swap updates, a row read for update held until its transaction ends, whole transactions, a repeated primary key refused, and the partial verified-identity key held. `adapter` returns the adapter (an `AdapterInput` or a `DatabaseAdapter`) over a database that has the tables of `helpdeskModel`; `reset` empties them before each test. `vitest` 4 or 5 is an optional peer dependency, needed only here. `examples/adapters/database-memory.ts`, an adapter that keeps everything in memory, passes it, and the built-in adapters run it in the integration suite.

## better-helpdesk/widget [#better-helpdeskwidget]

`HelpdeskWidget`, `HelpdeskConversations`, `defineHelpdeskWidget` and `defineHelpdeskConversations`. See [Widget](/docs/widget/reference).

## better-helpdesk/admin [#better-helpdeskadmin]

`HelpdeskAdmin`, `HELPDESK_SECTIONS` and `HELPDESK_CHANGED`. See [Agent UI](/docs/inbox/agent-ui).

## better-helpdesk/rich [#better-helpdeskrich]

The message format that messages and canned replies are written in, and a renderer for it. The module has no hooks and no `'use client'`, so it renders on the server as well, for example in an email template.

The format is plain text with a small set of Markdown: `**bold**`, `_italic_`, `++underline++`, `[label](url)`, bare `http` and `https` links, lists whose lines start with `-`, `*` or `•`, or with a number and `.` or `)`, blank lines between paragraphs, and code blocks between lines of three or more backticks.

| Export            | Signature                                                                                                                                                      | What it does                                                                                                                                                                                                               |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RichText`        | `(props: { text: string; compact?: boolean; hosts?: boolean; code?: (text: string) => ReactNode; reference?: (reference: string) => ReactNode }) => ReactNode` | Renders a message. `compact` renders inline only, with lists as `•` lines. `hosts` shows where each labeled link goes. `code` renders code blocks. `reference` renders each conversation reference outside links and code. |
| `parseRich`       | `(text: string) => Block[]`                                                                                                                                    | Parses a message into paragraphs, lists and code blocks.                                                                                                                                                                   |
| `parseInline`     | `(text: string) => Inline[]`                                                                                                                                   | Parses one line into text, bold, italic, underline and link runs.                                                                                                                                                          |
| `plainText`       | `(text: string) => string`                                                                                                                                     | The words without the markers.                                                                                                                                                                                             |
| `safeHref`        | `(href: string) => string \| null`                                                                                                                             | The target when it starts with `http://`, `https://` or `mailto:`, otherwise `null`.                                                                                                                                       |
| `unlabelLinks`    | `(text: string) => string`                                                                                                                                     | Rewrites `[label](url)` as `label (url)`.                                                                                                                                                                                  |
| `fence`           | `(text: string) => string`                                                                                                                                     | Wraps text in a code block whose backtick lines are longer than any run inside it.                                                                                                                                         |
| `Block`, `Inline` | type                                                                                                                                                           | The parsed shapes.                                                                                                                                                                                                         |

## better-helpdesk/next [#better-helpdesknext]

```js title="next.config.mjs"
import { withHelpdesk } from 'better-helpdesk/next';

export default withHelpdesk({ trailingSlash: true });
```

`withHelpdesk(nextConfig = {})` returns the config with `better-helpdesk` added to `transpilePackages`, keeping any packages already listed.

## better-helpdesk/widget.js [#better-helpdeskwidgetjs]

The minified standalone widget, the file `dist/widget.js`, for a page without React. See [Script tag](/docs/widget/reference#script-tag).

`better-helpdesk/package.json` is exported too.
