Extension points are exported functions in src/lib/custom/ that you can override to add deployment-specific behavior. Each has a no-op default — Pika works correctly without any overrides.
All files under src/lib/custom/ are protected from pika sync, so your overrides survive framework updates.
Available Extension Points
Section titled “Available Extension Points”| File | Export | Default | Purpose |
|---|---|---|---|
site-admin.ts | isUserAllowedAdminAccess(user) | delegates to isUserSiteAdmin() | Gate admin routes on custom criteria |
additional-session-sources.ts | getAdditionalSessionSources(user, chatAppId) | [] | Add 0..N custom session sources to the sidebar |
session-read-only.ts | isSessionReadOnly(session, user) | false | Mark additional session types as read-only |
request-user-id-resolver.ts | resolveRequestUserId(requestedUserId, sessionUserId, ctx) | undefined | Override the user id used by message routes |
session-entity-extraction.ts | getSessionEntityValue(session) | session.entityId | Extract entity/account ID from a session |
session-account-context.ts | transformSessionAccountContext(session, user) | session unchanged | Backfill missing account context before sessions are returned |
server-hooks.ts | transformCustomUserData(data, ctx) | data unchanged | Transform customUserData before it reaches the converse Lambda |
server-hooks.ts | onAuthProviderCallback(event, provider) | no-op | Run logic on OAuth provider callbacks |
server-hooks.ts | onBeforeAuth(event, pathName, user) | { clearSession: false } | Clear the session conditionally before auth proceeds |
chat-user-auth.ts | shouldBypassChatUserRoleMerge(user) | false | Use token roles as source-of-truth; skip DDB role merge |
How to Override
Section titled “How to Override”- Open the relevant file in
src/lib/custom/. - Replace the default function body with your implementation.
- The framework calls your override automatically.
Example — require AzureAD for admin access:
import { isUserSiteAdmin } from '$lib/server/utils';import type { AuthenticatedUser, RecordOrUndef } from 'pika-shared/types/chatbot/chatbot-types';
export async function isUserAllowedAdminAccess( user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>): Promise<boolean> { return isUserSiteAdmin(user) && user.authData?.provider === 'azuread';}Hook Reference
Section titled “Hook Reference”isUserAllowedAdminAccess — site-admin.ts
Section titled “isUserAllowedAdminAccess — site-admin.ts”Controls which users can access the site admin panel and session insights.
export async function isUserAllowedAdminAccess( user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>): Promise<boolean>The default delegates to the built-in isUserSiteAdmin(user) check. Override to add additional criteria such as provider checks, group membership, or external permission lookups.
getAdditionalSessionSources — additional-session-sources.ts
Section titled “getAdditionalSessionSources — additional-session-sources.ts”Adds 0..N custom session sources to the sidebar. Each source has its own loader, optional sidebar slots, and optional per-source read-only predicate. Pika iterates the returned array in declared order and renders one Sidebar.Group per source.
export async function getAdditionalSessionSources( user: ChatUser<RecordOrUndef>, chatAppId: string): Promise<SessionSource[]>
export interface SessionSource { /** Stable identifier for this source. Used as a Svelte key and for diagnostics. */ id: string; /** Header label rendered above this source's session list. */ label?: string; /** * Loads sessions for this source. Called client-side once per chat-app init, * in parallel with other sources via Promise.allSettled. Capture user/chatAppId * from the enclosing getAdditionalSessionSources closure when constructing this * descriptor — the framework does not re-pass them. * * Return [] for "applicable but empty." If the source is not applicable to this * user, omit it from the getAdditionalSessionSources return array entirely. */ load(): Promise<ChatSession<RecordOrUndef>[]>; /** Per-source read-only predicate. OR-ed with the top-level isSessionReadOnly hook. */ isReadOnly?: (session: ChatSession<RecordOrUndef>) => boolean; /** * Optional sidebar slots: * - `trigger` renders before load completes (e.g. a "Load sessions" button) * - `header` renders inside Sidebar.GroupLabel above the loaded session list */ sidebarSlot?: { header?: Component<Record<string, never>>; trigger?: Component<Record<string, never>>; };}Rendering semantics: the sidebar renders each source's group through three states:
- Loading: if
sidebarSlot.triggeris set, render it; otherwise render a default loading row. - Loaded: render
sidebarSlot.header(if set) insideSidebar.GroupLabel, then the session list (or "No sessions found." when empty). - Errored: render
sidebarSlot.header(if set) and an inline error row. The group does not collapse; sibling sources are unaffected.
Sources load via Promise.allSettled, so one failing source never breaks the others. The default returns [] (no additional sources).
isSessionReadOnly — session-read-only.ts
Section titled “isSessionReadOnly — session-read-only.ts”Marks additional session types as read-only in the chat UI. Applies cross-cutting rules — for per-source rules, prefer SessionSource.isReadOnly.
export function isSessionReadOnly( session: ChatSession<RecordOrUndef> | undefined, user: ChatUser<RecordOrUndef> | undefined): booleanThe framework already marks shared sessions as read-only. Override this hook to add further conditions — for example, marking sessions belonging to a different effective user as read-only. The result is OR-ed with the framework's shared-session predicate and with each SessionSource.isReadOnly. The default returns false.
resolveRequestUserId — request-user-id-resolver.ts
Section titled “resolveRequestUserId — request-user-id-resolver.ts”Server-side hook called on message routes before the effective user id is used. Override to honor an alternate user id source (e.g. a separate cookie for legacy actions).
export async function resolveRequestUserId( requestedUserId: string, sessionUserId: string, context: RequestUserIdResolverContext): Promise<string | undefined>
export interface RequestUserIdResolverContext { request: Request; cookies: Cookies; stage: string; chatAppId?: string;}Return a non-undefined string to override the user id used for the request; return undefined (the default) to leave requestedUserId unchanged. Scope is limited to the message routes (api/message/+server.ts and api/message/[chatAppId]/[sessionId]/+server.ts).
Consumers that need a cookie name should define their own constant — the framework no longer exports one.
getSessionEntityValue — session-entity-extraction.ts
Section titled “getSessionEntityValue — session-entity-extraction.ts”Extracts the entity or account ID from a session for display and analytics.
export function getSessionEntityValue( session: ChatSession<RecordOrUndef>): string | undefinedOverride when your deployment stores the relevant account context in a field other than session.entityId, or when you need to normalize/transform the raw value. The default returns session.entityId || undefined.
transformSessionAccountContext — session-account-context.ts
Section titled “transformSessionAccountContext — session-account-context.ts”Backfills missing account context onto sessions before they are returned to the client.
export function transformSessionAccountContext( session: ChatSession<RecordOrUndef>, user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>): ChatSession<RecordOrUndef>Called on every session returned from the sessions API and the site-admin search endpoint. Override to copy account data from user onto sessions that were created before account context was stored on sessions. Return a new object rather than mutating the input. The default returns the session unchanged.
onBeforeAuth — server-hooks.ts
Section titled “onBeforeAuth — server-hooks.ts”Called on every request after the AUTH_USER cookie is deserialized but before the ChatUser refresh and auth-provider validation steps run.
export async function onBeforeAuth( event: RequestEvent, pathName: string, user: AuthenticatedUser<RecordOrUndef, RecordOrUndef> | undefined): Promise<{ clearSession: boolean }>Return { clearSession: true } to atomically clear all cookies and drop the current session — the request continues as unauthenticated, triggering a fresh auth flow. Use this to discard stale sessions based on path, request headers, or cookie state without editing the synced hooks.server.ts. The default returns { clearSession: false }.
shouldBypassChatUserRoleMerge — chat-user-auth.ts
Section titled “shouldBypassChatUserRoleMerge — chat-user-auth.ts”Controls whether the framework uses roles from the authentication token instead of merging with the stored ChatUser record in the database.
export function shouldBypassChatUserRoleMerge( user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>): booleanWhen this returns true the framework will:
- Treat roles as unchanged during periodic refreshes (no cookie re-serialization for role drift)
- Use token roles instead of database roles when building the merged user object
- Skip
mergeAuthenticatedUserWithExistingChatUseron initial login - Create the ChatUser record if it is missing during a refresh rather than forcing re-authentication
This is useful when the identity provider is the authoritative source for roles and the database should not override them. The default returns false.
transformCustomUserData — server-hooks.ts
Section titled “transformCustomUserData — server-hooks.ts”Transforms customUserData server-side before it is encoded into the message JWT and sent to the converse Lambda.
See Server Hooks for full documentation.
onAuthProviderCallback — server-hooks.ts
Section titled “onAuthProviderCallback — server-hooks.ts”Called from hooks.server.ts whenever a request arrives at an OAuth provider callback path (/auth/callback/<provider>).
export async function onAuthProviderCallback( event: RequestEvent, provider: string): Promise<void>The provider parameter is the path segment after /auth/callback/ (e.g., "azuread", "okta"). The hook fires before the route is resolved, so cookies set here are visible to the downstream route handler.
Use this hook to run per-provider post-callback logic — such as clearing legacy session cookies or triggering a token exchange — without editing the synced hooks.server.ts. The default is a no-op.
Migration from v0.26.0
Section titled “Migration from v0.26.0”The v0.26.0 → v0.27.0 release replaces five ai-bot-specific hooks with three generic seams. The pika migrate v0.26.0-v0.27.0 CLI subcommand removes the orphan hook files from your consumer tree (see Migration Guides for the full procedure).
| v0.26.0 hook | v0.27.0 replacement | Migration |
|---|---|---|
loadLegacyChatsIfNeeded(user, chatAppId) → LegacySessionsResult | getAdditionalSessionSources(user, chatAppId) → SessionSource[] returning a single descriptor | Move loader body into source.load(); if loaded: false was the signal, omit the source from the array instead. |
getLegacyChatsSectionHeader() → Component? | SessionSource.sidebarSlot.header | Move component reference onto the source descriptor. |
getLegacyChatsSectionTrigger() → Component? | SessionSource.sidebarSlot.trigger | Same. |
isCurrentSessionReadOnly(session) | isSessionReadOnly(session, user) | Add user to the signature. session-read-only.ts is sync-protected — pika sync does not overwrite it and pika migrate does not touch it. You must hand-edit the file. For per-source rules, prefer SessionSource.isReadOnly instead. |
validateLegacyUserIdIfNeeded(effectiveUserId, sessionUserId, ctx) | resolveRequestUserId(requestedUserId, sessionUserId, ctx) | Argument order changed. The framework call site swapped from (user.userId, params.userId, ctx) to (params.userId, user.userId, ctx) — arg1 is now the request-supplied (attacker-influenceable) id, not the session id. Re-read your override end-to-end; do not just rename the parameter. |
LEGACY_ACTION_USER_ID_COOKIE constant | Removed | Define your own constant in consumer code. |
Type LegacySessionsResult | Removed | No replacement; not needed under the new shape. |
Client-side hook signatures use ChatUser<U> (not AuthenticatedUser<T, U>) so server-only fields like authData are not leaked across the public hook surface. Server-side hooks (e.g. resolveRequestUserId's context) continue to use the server-side types where appropriate.
Signature Stability
Section titled “Signature Stability”test/custom/contract.test.ts contains compile-time type assertions and runtime smoke tests for every hook. If a Pika upgrade accidentally changes a hook's signature, this test fails before the breakage reaches your deployment.
Run the tests with:
pnpm test --filter pika-chat -- --testPathPattern=contractRelated Documentation
Section titled “Related Documentation”- Server Hooks — Full guide for
transformCustomUserData - Client Lifecycle Hooks — Client-side init and polling hooks
- UI Theming — Custom theme configuration