Chatpack
Reference

Packages

Every @chatpack/* package, what it exports, and when you need it.

All packages are MIT-licensed and published to npm. Everything is 0.x until the public API stabilizes - expect minor breaking changes before 1.0.

The current package set covers direct and group conversations, invite links and join requests, public channels with a browsable directory, text messages, permissions, read state and unread counts, SSE with gap-fill, memory and Drizzle storage, Redis event fan-out, typing/presence/receipts, reactions, quote-replies, validated mentions, message forwarding, participant-scoped search, the opt-in message thread API, post-persistence mutation hook, the browser client with React hooks and polling fallback, Filepack-backed file attachments, and project setup and complete starter generation with @chatpack/cli init, plus reusable React UI blocks from @chatpack/ui.

The Chatpack app includes a thread inbox and optional email and browser push alerts. Those alerts are application code, not core package APIs. Multi-node presence is available with redisPresenceStore() and the Redis transport.

@chatpack/core

The engine: domain logic, permissions, HTTP handler, SSE, plugins, telemetry. The only package every integration needs.

Main exports:

ExportWhat it is
chatpack(options)The single factory - returns { api, handler, transport, telemetry, options }
ChatpackErrorDomain error with a stable code
pairKeyFor(a, b)Deterministic DM pair key (internal detail, exported for adapters/tests)
inProcessTransport()The default single-node transport
isEphemeralEvent(event)Type guard for TransportEvent
isMessageEvent(event)Type guard for the message half of TransportEvent - TransportEvent has four members (ChatEvent, ReactionEvent, ConversationEvent, EphemeralEvent), so "not ephemeral" no longer means "a message"
isReactionEvent(event)Type guard for reaction.added / reaction.removed
isConversationEvent(e)Type guard for the membership events participant.added / participant.removed / conversation.updated
TypesChatpackOptions, ChatpackUser, AuthHook, PermissionHooks, StorageAdapter, InviteStorage, ChannelStorage, Transport, ChatpackPlugin, Conversation, ConversationType, Participant, ParticipantRole, Message, MessageWithDetails, Reaction, ReactionSummary, MessageRole, ConversationInvite, InvitePreview, JoinRequest, JoinRequestStatus, ChannelVisibility, ChannelJoinPolicy, ChannelPreview, TelemetryPayload, ...

Subpath @chatpack/core/plugins:

ExportWhat it is
typing()Typing indicators - POST /conversations/:id/typing → typing.started / typing.stopped
presence(options?)Online/offline - SSE connection is the heartbeat; offlineDelayMs default 5000
receipts()Delivered/read ticks - hooks into send + mark-read

@chatpack/client

The framework-agnostic browser client for the public REST and SSE contract. Authentication remains owned by the application.

import { createChatClient } from "@chatpack/client";
import { typingClient, presenceClient, receiptsClient } from "@chatpack/client/plugins";

export const chatClient = createChatClient({
  credentials: "include",
  plugins: [typingClient(), presenceClient(), receiptsClient()],
});

const result = await chatClient.conversations.list();

@chatpack/client/react adds useConversations, useConversation, useMessages, useMessageSearch, useRealtimeStatus, and first-party plugin hooks. It uses useSyncExternalStore and has no state-library dependency. Invite, join-request, and channel actions are imperative client methods; they do not add new React cache queries.

ExportWhat it is
createChatClient(options?)One per-application client instance
conversations / messagesTyped REST namespaces with { data, error } results
conversations.createGroup etc.The five group mutations (create, add/remove participant, set role, rename) - membership events update the cache
invitesCreate, list, revoke, preview, and accept invite links
joinRequestsCreate, list, approve, and deny join requests
channelsList public-channel previews and join channels
messages.react / unreactIdempotent reaction writes; return the message's complete reaction set
messages.forwardCopy a message into another conversation (toConversationId); resolves with the copy
messages.searchParticipant-scoped, whole-token, relevance-ranked message search
realtimeOne lazy EventSource with durable deduplication
realtime configmode: "auto" | "sse" | "poll" - polling fallback where SSE can't work
isReactionChatEvent(event)Narrowing helper for reaction.added / reaction.removed
isConversationChatEvent(event)Narrowing helper for participant.added / participant.removed / conversation.updated
@chatpack/client/pluginsTyping, presence, and receipts client adapters

@chatpack/adapter-memory

In-memory storage - zero-setup demos and fast, deterministic tests. The reference StorageAdapter implementation - all twenty-one required methods, including the two that mentions added (setMessageMentions, listMentionsByMessageIds) - plus all four optional capabilities (search, the invites namespace behind invite links and join requests, the channels namespace behind the public directory, and the moderation namespace).

ExportWhat it is
memoryAdapter()Create an in-memory storage adapter

→ Memory adapter

@chatpack/adapter-drizzle

Drizzle ORM (Postgres) storage - real persistence for production. Peer dependency on drizzle-orm.

ExportWhat it is
drizzleAdapter(db)Create the adapter from any Drizzle Postgres instance
chatpackSchemaDrizzle table objects (for your drizzle-kit flow)
conversations, conversationParticipants, messages, messageReactions, messageMentions, messageSearchTokens, conversationInvites, joinRequests, userBlocks, conversationMutes, moderationReports, userBansThe individual tables
migrationSqlIdempotent DDL as one string (no Drizzle needed to run it)
migrationStatementsThe same DDL split for one-statement-per-call drivers
backfillMessageSearchTokens(db)Index existing message bodies for search after an upgrade

→ Drizzle / Postgres

@chatpack/adapter-prisma

Prisma ORM 7 PostgreSQL storage. Server-only. Pass a consumer-generated Prisma client; the package does not bundle one.

ExportWhat it is
prismaAdapter(client)Create the adapter from the caller-owned Prisma client
backfillMessageSearchTokens(client)Rebuild search tokens after import
prisma/schema.prismaPrisma models to copy into the application schema
prisma/migrations/0001_chatpack/migration.sqlPostgreSQL migration asset

Verified: Prisma 7.10.0, @prisma/adapter-pg 7.10.0, PostgreSQL 16. Prisma 8 and other providers/drivers are not claimed compatible.

→ Prisma / Postgres

@chatpack/adapter-sqlite

Drizzle ORM SQLite storage for durable local and single-node deployments. Requires drizzle-orm, better-sqlite3, and Node.js 22 or newer.

ExportWhat it is
sqliteAdapter(db)Create the adapter from a Drizzle better-sqlite3 database
chatpackSchemaDrizzle table objects
migrationSql / migrationStatementsIdempotent SQLite DDL
backfillMessageSearchTokens(db)Rebuild search tokens after importing existing messages

→ SQLite

@chatpack/adapter-mysql

Server-side MySQL 8 storage through Drizzle's mysql2 driver. Requires a transaction-capable mysql2 connection or pool.

ExportWhat it is
mysqlAdapter(db)Create the adapter from a Drizzle MySQL instance
chatpackSchema and table exportsDrizzle schema objects
migrationSql / migrationStatementsMySQL 8 DDL
backfillMessageSearchTokens(db)Rebuild search tokens after importing messages

→ MySQL

@chatpack/adapter-turso

Drizzle ORM storage backed by Turso/libSQL. It provides the full persistent adapter contract, optional channels, invite links, join requests, and moderation storage.

ExportWhat it is
tursoAdapter(db)Create the adapter from a Drizzle libSQL instance
chatpackSchema and table exportsDrizzle schema objects
migrationSqlIdempotent DDL as one string
migrationStatementsThe same DDL split for one-statement-per-call drivers
backfillMessageSearchTokens(db)Index existing message bodies for search after an upgrade

→ Turso / libSQL

@chatpack/adapter-supabase

Server-side Supabase/Postgres storage. Pass an already-created privileged Supabase client; apply its RLS-safe migration before use.

ExportWhat it is
supabaseAdapter(client, options?)Create the adapter from a server-side Supabase client
SupabaseAdapterOptionsOptional generated-id prefix configuration

→ Custom adapter and Supabase guidance

@chatpack/next

Next.js App Router integration - sugar for mounting the handler.

ExportWhat it is
toNextRouteHandlers(chat, options?)Returns { GET, POST, PATCH, DELETE, PUT } for a catch-all route file

→ Next.js guide

@chatpack/cli

The setup CLI for detecting a project and generating a safe Chatpack integration.

npx @chatpack/cli init

The command supports Next.js App Router, Hono, and Express. In an empty repository it creates a complete Next.js chat application with Better Auth, Auth.js, or Auth0, or a fail-closed Hono/Express backend. In an existing project it generates focused integration wiring. It never provisions accounts, writes secrets, runs migrations, or deploys.

The Next.js UI is reviewed application-owned source. It is not a reusable @chatpack/ui package, so Chatpack remains headless at the package boundary.

@chatpack/transport-redis

Redis pub/sub Transport for multi-node SSE fan-out. Needed once you run more than one app server: the default transport fans out inside a single process, so a message sent on node B never reaches a stream held by node A.

import { redisTransport } from "@chatpack/transport-redis";
import { Redis } from "ioredis";

const transport = redisTransport({
  publisher: new Redis(process.env.REDIS_URL!),
  subscriber: new Redis(process.env.REDIS_URL!),
});
ExportWhat it is
redisTransport(options)A Transport plus nodeId and close()
DEFAULT_CHANNEL"chatpack:events"
TypesRedisTransportOptions, RedisPublisher, RedisSubscriber, ...

Bring your own client (ioredis or node-redis v4+) - Chatpack has no Redis dependency. Two separate connections are required, since a client in subscriber mode cannot PUBLISH.

→ Multi-node fan-out

@chatpack/file

Filepack-backed message attachments. Mounts Filepack's upload/download routes below the Chatpack handler (default /api/chat/files) and validates every attachment reference against the conversation before a message persists. Chatpack stores only stable references ({ filepack: { version: 1, attachments: [{ id, name, contentType, size }] } }) in message metadata - never bytes, signed URLs, or object keys.

ExportWhat it is
createFileAttachmentPlugin(opts)The plugin: nested Filepack routes + blocking message hook
createFileAttachmentMessageHookJust the beforeMessageSend validation hook
createFileAttachmentMetadataBuild the metadata namespace from ready Filepack files
parseFileAttachmentMetadataValidate the namespace in arbitrary message metadata
@chatpack/file/clientBrowser wrapper over @filepack/client (uploads, resume, media)

Requires a Filepack instance (@filepack/core + a storage provider) and an authorizeUpload host policy. Remember to also export PUT from the catch-all route.

Two things worth knowing before you build an attachment UI:

  • A message still needs a body. Attachments live in metadata and never substitute for the text, so an image-only send is 400 INVALID_INPUT - synthesize a body (the file name works) rather than sending "".
  • Pass controlFetch to the browser client while @filepack/client is at or below 0.1.1: it holds globalThis.fetch unbound and calls it as a method, which Chrome rejects. The failure looks like a network error but no request is ever sent. controlFetch: (input, init) => fetch(input, init) fixes it.
import { createChatpackFileClient } from "@chatpack/file/client";

const files = createChatpackFileClient({
  basePath: "/api/chat/files",
  controlFetch: (input, init) => fetch(input, init),
});

llms.txt in every tarball

Every published package ships the repo's llms.txt - the single-fetch integration guide for AI builders and coding agents - at its root: node_modules/@chatpack/core/llms.txt.

Versioning

Releases are managed with Changesets; every package keeps a CHANGELOG.md. Follow releases on GitHub or via npm view @chatpack/core dist-tags.

On this page