Learn how to implement the User Data Override feature, allowing authorized users to override user values set by the authentication provider, enabling scenarios like account switching and role impersonation.
What You'll Accomplish
Section titled “What You'll Accomplish”By the end of this guide, you will:
- Enable the user data override feature
- Implement server-side logic for data handling
- Create custom UI for data selection
- Configure auto-complete functionality
- Understand use cases and security considerations
Prerequisites
Section titled “Prerequisites”- A running Pika installation
- Access to
pika-config.tsfor site configuration - Understanding of your authentication system
- Familiarity with Svelte for UI components
Understanding User Data Override
Section titled “Understanding User Data Override”The User Data Override feature allows authorized users to override values in ChatUser.customData set by the authentication provider. This is useful for internal users who need to act on behalf of different accounts, companies, or roles.
Use Cases
Section titled “Use Cases”- Customer Support: Agents acting on behalf of customer accounts
- Multi-tenant Applications: Users switching between company contexts
- Testing & QA: Testing chat behavior for different user profiles
- Account Management: Managers accessing different organizational contexts
How It Works
Section titled “How It Works”- User opens chat app or clicks override menu item
- Dialog appears with custom UI for data selection
- User selects override data (e.g., account, company)
- Server processes and stores override in session
- Overrides persist until logout or manual clearing
Step 1: Enable the Feature
Section titled “Step 1: Enable the Feature”Configure the user data override feature in your pika-config.ts.
Location: apps/pika-chat/pika-config.ts
export const pikaConfig: PikaConfig = { siteFeatures: { userDataOverrides: { enabled: true,
// Optional: Specify which user types can use this feature userTypesAllowed: ['internal-user'], // Defaults to ['internal-user']
// Optional: Customize UI text menuItemTitle: 'Switch Account Context', dialogTitle: 'Account Override', dialogDescription: 'Select the account context to use for this chat app.',
// Optional: Force users to provide overrides if missing required data promptUserIfAnyOfTheseCustomUserDataAttributesAreMissing: [ 'accountId', 'accountType' ] } }};Configuration Options
Section titled “Configuration Options”| Property | Type | Description |
|---|---|---|
enabled | boolean | Required. Enable the feature |
userTypesAllowed | string[] | User types that can use this feature |
menuItemTitle | string | Menu item text in chat interface |
dialogTitle | string | Title of the override dialog |
dialogDescription | string | Description shown in the dialog |
promptUserIfAnyOf... | string[] | Force override if these attributes missing |
Disable Per Chat App
Section titled “Disable Per Chat App”You can disable the feature for specific chat apps:
const chatApp: ChatApp = { chatAppId: 'customer-support', // ... other settings features: { userDataOverride: { featureId: 'userDataOverride', enabled: false } }};Step 2: Implement Server-side Logic
Section titled “Step 2: Implement Server-side Logic”Implement required methods in the custom data file.
Location: apps/pika-chat/src/routes/(auth)/api/user-data-override/custom-user-data.ts
Get Initial Data
Section titled “Get Initial Data”import type { AuthenticatedUser, ChatApp, RecordOrUndef} from 'pika-shared/types/chatbot/chatbot-types';
export async function getInitialDataForUserDataOverrideDialog( user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>, chatApp: ChatApp): Promise<unknown | undefined> { // Return existing override data if available if (user.overrideData && user.overrideData[chatApp.chatAppId]) { const overrideData = user.overrideData[chatApp.chatAppId];
// Transform stored data back to UI format return { accountId: overrideData.accountId, accountName: overrideData.accountName, accountType: overrideData.accountType }; }
return undefined;}Handle Auto-complete
Section titled “Handle Auto-complete”export async function getValuesForAutoComplete( componentName: string, valueProvidedByUser: string, user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>, chatApp: ChatApp): Promise<unknown[] | undefined> { if (componentName === 'accountSelector') { // If empty search, return first 20 accounts if (!valueProvidedByUser || valueProvidedByUser.trim() === '') { return getAccounts() .slice(0, 20) .map(account => ({ value: account.id, label: account.name, secondaryLabel: account.type })); }
// Return filtered results const searchTerm = valueProvidedByUser.toLowerCase(); return getAccounts() .filter(account => account.id.toLowerCase().includes(searchTerm) || account.name.toLowerCase().includes(searchTerm) ) .slice(0, 20) .map(account => ({ value: account.id, label: account.name, secondaryLabel: account.type })); }
return undefined;}
// Helper function - replace with your actual data sourcefunction getAccounts() { // In real implementation, fetch from your database/API return [ { id: 'acct-001', name: 'Acme Corp', type: 'enterprise' }, { id: 'acct-002', name: 'Beta Industries', type: 'standard' } // ... more accounts ];}Process Posted Data
Section titled “Process Posted Data”export async function userOverrideDataPostedFromDialog( user: AuthenticatedUser<RecordOrUndef, RecordOrUndef>, chatApp: ChatApp, overrideData: unknown | undefined): Promise<RecordOrUndef> { if (!overrideData) { return undefined; // Clear overrides }
const data = overrideData as any;
// Transform UI data to storage format // MUST return Record<string, string | undefined> return { accountId: data.accountId, accountName: data.accountName, accountType: data.accountType // Add any other fields your application needs };}Step 3: Create Custom UI Component
Section titled “Step 3: Create Custom UI Component”Create your UI component for the override dialog.
Location: apps/pika-chat/src/lib/client/features/chat/user-data-overrides/custom-data-overrides-ui.svelte
<script lang="ts"> import Combobox from '$ui/pika/combobox/combobox.svelte'; import type { UserOverrideDataCommand } from 'pika-shared/types/chatbot/chatbot-types';
// Required props interface interface Props { isValid: boolean | string; initialDataFromServer: unknown | undefined; disabled: boolean; getValuesForAutoComplete: ( componentName: string, valueProvidedByUser: string ) => Promise<void>; valuesForAutoComplete: Record<string, unknown[] | undefined>; userDataOverrideOperationInProgress: Record<UserOverrideDataCommand, boolean>; dataChanged: boolean; }
let { isValid = $bindable(), dataChanged = $bindable(), initialDataFromServer, getValuesForAutoComplete, valuesForAutoComplete, userDataOverrideOperationInProgress, disabled }: Props = $props();
// Component state let selectedAccount = $state(initialDataFromServer as Account | undefined); let loading = $derived(userDataOverrideOperationInProgress['getValuesForAutoComplete']);
const accountOptions = $derived.by(() => { return (valuesForAutoComplete?.['accountSelector'] ?? []) as Account[]; });
// Required methods export function reset() { selectedAccount = initialDataFromServer as Account | undefined; dataChanged = false; isValid = false; }
export async function getDataToPostToServer(): Promise<unknown | undefined> { return selectedAccount; }
// Event handlers function valueChanged(value: Account) { selected Account = value;
// Check if data has changed const initialAccount = initialDataFromServer as Account | undefined; const hasChanged = !areAccountsEqual(initialAccount, selectedAccount);
dataChanged = hasChanged; isValid = selectedAccount ? true : 'Please select an account'; }
async function onSearchValueChanged(value: string) { await getValuesForAutoComplete('accountSelector', value); }
function areAccountsEqual(a: Account | undefined, b: Account | undefined): boolean { if (!a && !b) return true; if (!a || !b) return false; return ( a.accountId === b.accountId && a.accountName === b.accountName && a.accountType === b.accountType ); }
interface Account { accountId: string; accountName: string; accountType: string; }</script>
<div class="space-y-4"> <div class="text-sm text-muted-foreground"> Select the account context for this chat session: </div>
<Combobox value={selectedAccount} mapping={{ value: (value) => value.accountId, label: (value) => value.accountName, secondaryLabel: (value) => value.accountType }} options={accountOptions} onValueChanged={valueChanged} {onSearchValueChanged} {loading} optionTypeName="account" optionTypeNamePlural="accounts" widthClasses="w-full" showValueInListEntries={true} minCharactersForSearch={1} {disabled} />
{#if typeof isValid === 'string'} <div class="text-sm text-red-500">{isValid}</div> {/if}</div>Required Component API
Section titled “Required Component API”Your UI component must implement:
Props:
isValid- Set totrue/falseor error message stringdataChanged- Bind to track if user modified datainitialDataFromServer- Data fromgetInitialDataForUserDataOverrideDialogdisabled- Disable inputs during operationsgetValuesForAutoComplete- Call for auto-completevaluesForAutoComplete- Auto-complete resultsuserDataOverrideOperationInProgress- Operation states
Methods:
reset()- Reset component to initial stategetDataToPostToServer()- Return data to save
Step 4: Configure Required Attributes
Section titled “Step 4: Configure Required Attributes”Force users to provide overrides if specific data is missing.
siteFeatures: { userDataOverrides: { enabled: true, // Use dot notation to check nested fields promptUserIfAnyOfTheseCustomUserDataAttributesAreMissing: [ 'accountId', // Checks user.customData.accountId 'accountType', // Checks user.customData.accountType 'company.name', // Checks user.customData.company.name 'permissions.level' // Checks user.customData.permissions.level ] }}When these attributes are missing, users are automatically prompted to provide overrides.
Testing Checklist
Section titled “Testing Checklist”Verify the feature works correctly:
Advanced Scenarios
Section titled “Advanced Scenarios”Multiple Auto-complete Components
Section titled “Multiple Auto-complete Components”Support multiple auto-complete inputs:
export async function getValuesForAutoComplete( componentName: string, valueProvidedByUser: string, user: AuthenticatedUser, chatApp: ChatApp): Promise<unknown[] | undefined> { switch (componentName) { case 'accountSelector': return getAccountOptions(valueProvidedByUser); case 'departmentSelector': return getDepartmentOptions(valueProvidedByUser); case 'regionSelector': return getRegionOptions(valueProvidedByUser); default: return undefined; }}External API Integration
Section titled “External API Integration”Fetch data from external APIs:
export async function getValuesForAutoComplete( componentName: string, valueProvidedByUser: string, user: AuthenticatedUser, chatApp: ChatApp): Promise<unknown[] | undefined> { if (componentName === 'customerSelector') { try { const response = await fetch( `/api/customers/search?q=${valueProvidedByUser}`, { headers: { Authorization: `Bearer ${getApiToken()}` } } );
const customers = await response.json(); return customers.map(customer => ({ value: customer.id, label: customer.name, secondaryLabel: customer.type })); } catch (error) { console.error('Failed to fetch customers:', error); return []; } }
return undefined;}AWS Permissions
Section titled “AWS Permissions”If calling AWS services, add permissions to the ECS task role:
Location: apps/pika-chat/infra/lib/stacks/custom-stack-defs.ts
export class PikaChatCustomStackDefs { public static addStackResourcesBeforeWeCreateThePikaChatConstruct( scope: PikaChatStack ): void { // Add API Gateway permissions scope.stack.webapp.taskRole.addToPolicy( new iam.PolicyStatement({ actions: ['execute-api:Invoke'], resources: [ 'arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/customers' ] }) );
// Add DynamoDB permissions scope.stack.webapp.taskRole.addToPolicy( new iam.PolicyStatement({ actions: ['dynamodb:Query', 'dynamodb:GetItem'], resources: [ 'arn:aws:dynamodb:us-east-1:123456789012:table/customers' ] }) ); }}Troubleshooting
Section titled “Troubleshooting”Override Dialog Not Appearing
Section titled “Override Dialog Not Appearing”- Check user type is in
userTypesAllowed - Verify feature enabled in
pika-config.ts - Ensure not in content admin mode
- Check browser console for errors
Auto-complete Not Working
Section titled “Auto-complete Not Working”- Verify
getValuesForAutoCompletereturns correct format - Check
componentNamematches in both server and UI - Ensure options array structure is correct
- Review CloudWatch logs for server errors
Data Not Persisting
Section titled “Data Not Persisting”- Confirm
userOverrideDataPostedFromDialogreturns proper format - Check returned data is
Record<string, string | undefined> - Verify cookies are being set correctly
- Review session management
Validation Errors
Section titled “Validation Errors”- Ensure UI component sets
isValidcorrectly - Check
isValidis boolean or string (not other types) - Verify required fields are validated
- Test edge cases (empty values, special characters)
Security Considerations
Section titled “Security Considerations”Access Control
Section titled “Access Control”- Only allow trusted user types
- Validate all user input on server
- Limit auto-complete results
- Implement rate limiting
Data Protection
Section titled “Data Protection”- Store override data in secure cookies
- Encrypt sensitive override data
- Audit override usage
- Clear overrides on logout
Compliance
Section titled “Compliance”- Document who can override data
- Log all override actions
- Implement approval workflows for production
- Ensure compliance with data regulations
Next Steps
Section titled “Next Steps”- Configure Chat App Access Control - Control app access
- Work with Entities - Entity-based access
- Integrate Your Authentication System - Custom auth
Related Documentation
Section titled “Related Documentation”- User Data Override Feature - Learn more
- Authentication Guide - Auth integration
- Customization - General customization