Skip to content

Entity Management

Entity management provides the foundation for organizing users into logical groups representing organizational boundaries - companies, accounts, departments, or business units. This capability enables fine-grained access control, data filtering, and multi-tenant operation within a single Pika deployment.

Entity management gives you the infrastructure to organize and control access based on organizational membership:

Organizational Boundaries

Associate users with entities representing their companies, accounts, or departments for logical grouping and access control.

Entity-Based Access Control

Restrict chat app access and session visibility to specific entities, ensuring data isolation and appropriate access.

Administrative Tools

Manage entity associations, filter insights by entity, and configure entity-specific access rules through the admin interface.

Organizations deploying Pika often serve multiple companies, accounts, or departments that require data separation:

Software Vendors serving multiple customer companies need to ensure Company A cannot see Company B's chat sessions and data.

Enterprises with multiple business units need to control which employees access which internal tools and data.

Service Providers managing different client accounts need entity-based isolation for compliance and confidentiality.

While user types (internal/external) provide basic categorization, real-world applications need finer granularity:

  • User types tell you WHO users are (customers vs employees)
  • Entities tell you WHICH ORGANIZATION users belong to

This distinction enables sophisticated access patterns:

  • External users from Company A see only Company A's data
  • Internal users (support staff) can access any entity's data when authorized
  • Administrators manage entity-specific configurations

An entity represents an organizational unit that requires data isolation:

Customer Accounts:

Entity: "acct-001" (Acme Corp)
Entity: "acct-002" (Global Industries)
Entity: "acct-003" (Tech Solutions)

Business Units:

Entity: "sales" (Sales Department)
Entity: "engineering" (Engineering)
Entity: "support" (Customer Support)

Partner Organizations:

Entity: "partner-alpha" (Partner Alpha)
Entity: "partner-beta" (Partner Beta)

Users are linked to entities through their authentication data:

// User data from authentication provider
{
userId: "user123",
userType: "external-user",
customData: {
accountId: "acct-001", // Entity identifier
accountName: "Acme Corp", // Human-readable name
email: "user@acmecorp.com"
}
}

The accountId field (or whatever field you configure) becomes the entity identifier used throughout the platform.

Chat apps and sessions operate in one of two access modes:

Entity-Scoped Access:

  • Users only see content from their own entity
  • Perfect for customer-facing applications
  • Ensures complete data isolation between organizations

Global Access:

  • Content visible to any authorized user regardless of entity
  • Ideal for internal tools and public chat apps
  • Still respects user type and role restrictions

Sessions automatically respect entity boundaries:

// Customer from Acme Corp views sessions
GET /api/chat/sessions
// Returns only sessions where entity = "acct-001"
// Customer from Global Industries views sessions
GET /api/chat/sessions
// Returns only sessions where entity = "acct-002"

Filtering happens automatically based on the user's entity membership.

Restrict chat app access to specific entities:

{
chatAppId: 'customer-portal',
accessControl: {
externalUsers: {
exclusiveEntityAccess: {
enabled: true,
entityIds: ['acct-001', 'acct-002']
}
}
}
}

Result:

  • Only users from acct-001 or acct-002 can access this chat app
  • Users from other entities are denied access
  • Administrators can still access for support purposes

The admin interface provides entity-based filtering:

Session Insights:

  • Filter sessions by specific entity
  • See which entity each session belongs to
  • Analyze patterns within entities

Access Control Management:

  • Configure entity-specific access rules
  • Use entity autocomplete for easy selection
  • Manage which entities can access which apps

The platform provides entity lookup for administrative interfaces:

// Implement entity search for autocomplete
export async function getValuesForEntityAutoComplete(
searchTerm: string,
user: AuthenticatedUser
): Promise<SimpleOption[]> {
// Query your entity data source
const entities = await fetchEntitiesMatching(searchTerm);
return entities.map(entity => ({
value: entity.id, // Used in access control
label: entity.name // Displayed to admins
}));
}

This enables admins to easily search and select entities when configuring access controls.

The platform also needs to display entity names for already-selected entities:

// Implement entity lookup by IDs
export async function getValuesForEntityList(
entityIds: string[],
user: AuthenticatedUser
): Promise<SimpleOption[]> {
// Fetch entity details for the given IDs
const entities = await fetchEntitiesByIds(entityIds);
return entities.map(entity => ({
value: entity.id, // Entity identifier
label: entity.name // Display name
}));
}

This enables the admin interface to show human-readable names for entities that are already configured in access control rules.

The platform also needs to display entity names for already-selected entities:

// Implement entity lookup by IDs
export async function getValuesForEntityList(
entityIds: string[],
user: AuthenticatedUser
): Promise<SimpleOption[]> {
// Fetch entity details for the given IDs
const entities = await fetchEntitiesByIds(entityIds);
return entities.map(entity => ({
value: entity.id, // Entity identifier
label: entity.name // Display name
}));
}

This enables the admin interface to show human-readable names for entities that are already configured in access control rules.

In your pika-config.ts:

export const siteFeatures: SiteFeatures = {
entity: {
enabled: true,
attributeName: 'accountId', // Field in user.customData
searchPlaceholderText: 'Search for an account...',
displayNameSingular: 'Account',
displayNamePlural: 'Accounts',
tableColumnHeaderTitle: 'Account ID'
}
};

enabled: Whether entity feature is active site-wide

attributeName: Field name in user.customData containing entity identifier

  • Must match your authentication provider's data structure
  • Common values: accountId, companyId, departmentId, organizationId

searchPlaceholderText: Placeholder text for entity search inputs in admin interface

displayNameSingular: Singular form of entity name for UI display

  • Examples: "Account", "Company", "Department", "Organization"

displayNamePlural: Plural form for UI display

  • Examples: "Accounts", "Companies", "Departments", "Organizations"

tableColumnHeaderTitle: Header text for entity columns in tables

  • Examples: "Account ID", "Company", "Department"

Ensure your authentication provider populates the entity field:

// In your auth provider's authenticate() method
return {
authenticatedUser: {
userId: userData.id,
firstName: userData.firstName,
lastName: userData.lastName,
userType: userData.isEmployee ? 'internal-user' : 'external-user',
customData: {
accountId: userData.accountId, // MUST match entity.attributeName
accountName: userData.accountName,
email: userData.email,
department: userData.department
}
}
};

Critical: The field name in customData must match entity.attributeName in your configuration.

Scenario: Software vendor serving multiple customer companies

// Entity configuration
entity: {
enabled: true,
attributeName: 'companyId',
displayNameSingular: 'Company',
displayNamePlural: 'Companies'
}
// Customer-facing chat app
{
chatAppId: 'customer-support',
accessControl: {
externalUsers: {
// All customers can access
accessByUserTypes: ['external-user'],
// But only see their company's sessions
exclusiveEntityAccess: { enabled: false } // Global access
}
}
}
// Account-specific premium features
{
chatAppId: 'premium-analytics',
accessControl: {
externalUsers: {
// Only premium companies
exclusiveEntityAccess: {
enabled: true,
entityIds: ['company-001', 'company-005', 'company-012']
}
}
}
}

Result:

  • All customers access the support chat
  • Each company only sees their own sessions
  • Premium companies access advanced analytics
  • Support staff (internal users) see all companies' data

Scenario: Large enterprise with multiple departments

// Entity configuration
entity: {
enabled: true,
attributeName: 'departmentId',
displayNameSingular: 'Department',
displayNamePlural: 'Departments'
}
// Sales department tools
{
chatAppId: 'sales-assistant',
accessControl: {
internalUsers: {
exclusiveEntityAccess: {
enabled: true,
entityIds: ['sales']
}
}
}
}
// Engineering tools
{
chatAppId: 'code-assistant',
accessControl: {
internalUsers: {
exclusiveEntityAccess: {
enabled: true,
entityIds: ['engineering', 'qa']
}
}
}
}

Result:

  • Sales team accesses sales assistant only
  • Engineering and QA access code assistant
  • Each department sees only their sessions
  • Cross-department access requires explicit configuration

Scenario: Organization managing multiple partner companies

// Entity configuration
entity: {
enabled: true,
attributeName: 'partnerId',
displayNameSingular: 'Partner',
displayNamePlural: 'Partners'
}
// Partner portal
{
chatAppId: 'partner-portal',
accessControl: {
externalUsers: {
accessByUserTypes: ['external-user'],
// Each partner sees only their data
exclusiveEntityAccess: { enabled: false }
}
}
}
// Exclusive partner program
{
chatAppId: 'elite-partner-tools',
accessControl: {
externalUsers: {
exclusiveEntityAccess: {
enabled: true,
entityIds: ['partner-alpha', 'partner-gamma']
}
}
}
}

Result:

  • All partners access the general portal
  • Partner Alpha and Gamma access elite tools
  • Each partner's data remains isolated
  • Partner managers (internal) can view all partner data

Entity-based access control follows this precedence order:

  1. Disabled Override - If enabled: false, no access regardless of entity
  2. Exclusive User ID Control - Specific user IDs take precedence
  3. Exclusive Entity Control - Entity-based restrictions apply
  4. General Access Rules - Fall back to user type/role checking
// Example showing precedence
{
chatAppId: 'restricted-app',
accessControl: {
externalUsers: {
enabled: true, // Feature enabled
// Exclusive user list takes highest precedence
exclusiveUserIdAccess: {
enabled: true,
userIds: ['special-user-001']
},
// Entity restrictions apply if user not in exclusive list
exclusiveEntityAccess: {
enabled: true,
entityIds: ['acct-premium']
},
// General rules apply if no exclusive controls match
accessByUserTypes: ['external-user']
}
}
}

Data Isolation

Complete separation between entities ensures confidentiality and compliance with data protection regulations.

Flexible Architecture

Support both entity-scoped applications (customer portals) and global applications (internal tools) on the same platform.

Scalable Management

Add new entities without platform changes. Entity associations come from authentication provider.

Fine-Grained Control

Configure which entities can access which chat apps with entity-specific access rules.

Visibility & Insights

Filter session data by entity to analyze usage patterns, identify issues, and measure success per organization.

Easy Configuration

Entity autocomplete makes it simple to select entities when configuring access controls.

Relevant Content Only

Users see only sessions and data relevant to their organization, reducing clutter and confusion.

Privacy & Security

Automatic entity-based filtering ensures users cannot accidentally access other organizations' data.

Determine what "entity" means for your organization:

Questions to answer:

  • What organizational boundaries require data separation?
  • Where does entity data come from? (HR system, CRM, custom database)
  • What field name represents entities in your authentication system?
  • How do you want to refer to entities in the UI?

Example decisions:

// SaaS vendor
attributeName: 'companyId'
displayNameSingular: 'Company'
// Enterprise
attributeName: 'departmentId'
displayNameSingular: 'Department'
// Healthcare
attributeName: 'facilityId'
displayNameSingular: 'Facility'

Ensure entity field is populated in user data:

// Modify your authenticate() method
const userData = await fetchUserFromAuthSystem(credentials);
return {
authenticatedUser: {
// ... standard fields ...
customData: {
[entityAttributeName]: userData.entityId, // Key: matches config
entityName: userData.entityDisplayName,
// ... other custom data ...
}
}
};

Create entity lookup functions for the admin interface:

// In apps/pika-chat/src/routes/(auth)/api/site-admin/custom-data.ts
// Function 1: Search entities for autocomplete
export async function getValuesForEntityAutoComplete(
searchTerm: string,
user: AuthenticatedUser
): Promise<SimpleOption[]> {
// Query your entity data source
const entities = await yourEntityDatabase.search({
name: { contains: searchTerm },
limit: 20
});
return entities.map(entity => ({
value: entity.id, // Entity identifier
label: `${entity.name} (${entity.id})` // Display name
}));
}
// Function 2: Get entity details by IDs
export async function getValuesForEntityList(
entityIds: string[],
user: AuthenticatedUser
): Promise<SimpleOption[]> {
// Fetch entity details for the given IDs
const entities = await yourEntityDatabase.findByIds(entityIds);
return entities.map(entity => ({
value: entity.id, // Entity identifier
label: `${entity.name} (${entity.id})` // Display name
}));
}

Why both functions are needed:

  • getValuesForEntityAutoComplete: Used when admins search for entities to add to access control
  • getValuesForEntityList: Used to display names for entities already configured in access control

Add entity-based access control to chat apps:

{
chatAppId: 'my-chat-app',
accessControl: {
externalUsers: {
enabled: true,
accessByUserTypes: ['external-user'],
// Enable entity-based access control
exclusiveEntityAccess: {
enabled: true,
entityIds: ['entity-001', 'entity-002']
}
}
}
}

Verify entity-based access works correctly:

Test scenarios:

  1. User from Entity A cannot see Entity B's sessions
  2. Admin can filter insights by specific entity
  3. Entity autocomplete returns correct results
  4. Access control denies unauthorized entities

Grant entity access based on business rules:

function getAuthorizedEntities(user: AuthenticatedUser): string[] {
const entities: string[] = [];
// User's primary entity
if (user.customData.accountId) {
entities.push(user.customData.accountId);
}
// Parent organization entities (for enterprise hierarchy)
if (user.customData.parentOrgId) {
entities.push(user.customData.parentOrgId);
}
// Partner entities (for partner managers)
if (user.roles.includes('partner-manager')) {
entities.push(...user.customData.managedPartnerIds);
}
return entities;
}

Support entity hierarchies:

// Entity hierarchy example
const entityHierarchy = {
'parent-corp': {
children: ['subsidiary-a', 'subsidiary-b']
},
'subsidiary-a': {
parent: 'parent-corp',
children: ['division-1', 'division-2']
}
};
// Grant access to entity and all descendants
function getEntityAndDescendants(entityId: string): string[] {
const entities = [entityId];
const entity = entityHierarchy[entityId];
if (entity?.children) {
entity.children.forEach(child => {
entities.push(...getEntityAndDescendants(child));
});
}
return entities;
}

Enrich entity data with metadata:

// Store entity metadata for UI display
interface EntityMetadata {
id: string;
name: string;
tier: 'free' | 'pro' | 'enterprise';
features: string[];
contactEmail: string;
}
// Use metadata to customize chat app behavior
if (entityMetadata.tier === 'enterprise') {
enablePremiumFeatures();
}

The platform resolves entity identifiers from user data:

function getUserEntity(user: AuthenticatedUser, entityConfig: EntityFeature): string | undefined {
if (!entityConfig.enabled) {
return undefined;
}
const attributeName = entityConfig.attributeName;
return user.customData?.[attributeName];
}

Sessions are automatically filtered by entity:

// DynamoDB query with entity filter
const params = {
TableName: 'sessions',
FilterExpression: 'entityId = :entityId',
ExpressionAttributeValues: {
':entityId': userEntity
}
};

Entity-based access control is evaluated during authorization:

function hasEntityAccess(
user: AuthenticatedUser,
chatApp: ChatApp,
userEntity: string
): boolean {
const exclusiveAccess = chatApp.accessControl.exclusiveEntityAccess;
if (!exclusiveAccess?.enabled) {
return true; // No entity restrictions
}
return exclusiveAccess.entityIds.includes(userEntity);
}

Ready to implement entity management?

Implementation Guide

Step-by-step instructions for implementing entity management.

View How-To →