Working with Chat Members
Overview
Live Pages can access information about all members in the current chat, including both human users and AI agents. This enables features like task assignment, creator tracking, and user-specific filtering.
Getting Chat Members
Use pt.getChatMembers() to retrieve all chat members:
const members = await pt.getChatMembers();
Response Structure
Each member object has a clear, consistent structure:
[
{
"id": 123, // Member ID (user_id or agent_id)
"type": "user", // "user" or "agent"
"name": "John Doe", // Display name
// User-specific fields (null for agents)
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"is_owner": true // Created the chat
},
{
"id": 456,
"type": "agent",
"name": "AI Assistant",
// These are null for agents
"first_name": null,
"last_name": null,
"email": null,
"is_owner": false
}
]
Field Reference
Field | Type | User | Agent | Description |
|---|
id | number | ✓ | ✓ | Member ID |
type | string | ✓ | ✓ | "user" or "agent" |
name | string | ✓ | ✓ | Display name (full name or agent name) |
first_name | string? | ✓ | null | First name |
last_name | string? | ✓ | null | Last name |
email | string? | ✓ | null | Email address |
is_owner | boolean | ✓ | ✓ | Created the chat |
Common Use Cases
1. Display All Members
// Get and display all members
const members = await pt.getChatMembers();
members.forEach(member => {
const icon = member.type === 'user' ? '👤' : '🤖';
console.log(`${icon} ${member.name}`);
});
// Filter by type
const users = members.filter(m => m.type === 'user');
const agents = members.filter(m => m.type === 'agent');
console.log(`Users: ${users.length}, Agents: ${agents.length}`);
2. Create Member List UI
async function renderMemberList() {
const members = await pt.getChatMembers();
const html = members.map(member => {
const icon = member.type === 'user' ? '👤' : '🤖';
const badge = member.is_owner ? '<span class="owner-badge">Owner</span>' : '';
const details = member.email ? `<small>${member.email}</small>` : '';
return `
<div class="member-card ${member.type}">
${icon} <strong>${member.name}</strong> ${badge}
${details}
</div>
`;
}).join('');
document.getElementById('memberList').innerHTML = html;
}
3. Get Current User
// Identify the current user (usually the owner)
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.type === 'user' && m.is_owner);
console.log(`Current user: ${currentUser.name}`);
console.log(`User ID: ${currentUser.id}`);
4. Create Assignment Dropdown
async function createAssigneeDropdown() {
const members = await pt.getChatMembers();
const users = members.filter(m => m.type === 'user');
const options = users
.map(m => `<option value="${m.id}">${m.name}</option>`)
.join('');
return `
<select id="assignee" class="px-3 py-2 border rounded">
<option value="">Unassigned</option>
${options}
</select>
`;
}
// Usage
document.getElementById('assigneeContainer').innerHTML = await createAssigneeDropdown();
Task Assignment Example
A complete example of task assignment with chat members:
<div class="container mx-auto p-6">
<h1 class="text-2xl font-bold mb-4">Team Tasks</h1>
<!-- Filter by Assignee -->
<div class="mb-4">
<label class="block text-sm font-medium mb-1">Filter by Assignee:</label>
<select id="assigneeFilter" onchange="filterTasks()" class="px-3 py-2 border rounded">
<option value="">All Tasks</option>
<option value="unassigned">Unassigned</option>
</select>
</div>
<!-- Add New Task -->
<div class="bg-white rounded-lg shadow p-4 mb-4">
<input
type="text"
id="taskInput"
placeholder="New task..."
class="w-full px-3 py-2 border rounded mb-2"
>
<div class="flex gap-2">
<select id="taskAssignee" class="flex-1 px-3 py-2 border rounded">
<option value="">Unassigned</option>
</select>
<button onclick="addTask()" class="bg-blue-500 text-white px-4 py-2 rounded">
Add Task
</button>
</div>
</div>
<!-- Task List -->
<div id="tasksList"></div>
</div>
<script>
let allMembers = [];
let allTasks = [];
// Initialize
async function init() {
// Load members first
allMembers = await pt.getChatMembers();
// Populate dropdowns
populateAssigneeDropdowns();
// Load tasks
await loadTasks();
}
function populateAssigneeDropdowns() {
const users = allMembers.filter(m => m.type === 'user');
const options = users
.map(m => `<option value="${m.id}">${m.name}</option>`)
.join('');
// Populate filter dropdown
const filterSelect = document.getElementById('assigneeFilter');
const existingOptions = filterSelect.innerHTML;
filterSelect.innerHTML = existingOptions + options;
// Populate assignment dropdown
document.getElementById('taskAssignee').innerHTML =
'<option value="">Unassigned</option>' + options;
}
async function loadTasks() {
const entities = await pt.list({
entityNames: ['task'],
filters: { completed: false }
});
allTasks = entities.filter(e => e.entity_name === 'task');
displayTasks(allTasks);
}
async function filterTasks() {
const assigneeId = document.getElementById('assigneeFilter').value;
if (assigneeId === '') {
// Show all tasks
displayTasks(allTasks);
} else if (assigneeId === 'unassigned') {
// Show unassigned tasks
const filtered = allTasks.filter(task => !task.data.assignee_id);
displayTasks(filtered);
} else {
// Show tasks for specific user
const filtered = allTasks.filter(task =>
task.data.assignee_id === parseInt(assigneeId)
);
displayTasks(filtered);
}
}
function displayTasks(tasks) {
const html = tasks.map(task => {
const assignee = allMembers.find(m => m.id === task.data.assignee_id);
const assigneeName = assignee ? assignee.name : 'Unassigned';
const creator = allMembers.find(m => m.id === task.creator_user_id);
const creatorName = creator ? creator.name : 'Unknown';
return `
<div class="bg-white rounded-lg shadow p-4 mb-2">
<div class="flex justify-between items-start">
<div>
<p class="font-medium">${task.data.text}</p>
<p class="text-sm text-gray-600">
Assigned to: ${assigneeName}
</p>
<p class="text-xs text-gray-400">
Created by: ${creatorName}
</p>
</div>
<div class="flex gap-2">
<button
onclick="reassignTask(${task.id})"
class="text-blue-500 text-sm"
>
Reassign
</button>
<button
onclick="deleteTask(${task.id})"
class="text-red-500 text-sm"
>
Delete
</button>
</div>
</div>
</div>
`;
}).join('');
document.getElementById('tasksList').innerHTML = html ||
'<p class="text-gray-500 text-center">No tasks found</p>';
}
async function addTask() {
const text = document.getElementById('taskInput').value.trim();
const assigneeId = document.getElementById('taskAssignee').value;
if (!text) return;
await pt.add('task', {
text: text,
completed: false,
assignee_id: assigneeId ? parseInt(assigneeId) : null
});
document.getElementById('taskInput').value = '';
await loadTasks();
}
async function reassignTask(taskId) {
const task = await pt.get(taskId);
const newAssigneeId = prompt('Enter new assignee ID (or leave empty for unassigned):');
await pt.edit(taskId, {
...task.data,
assignee_id: newAssigneeId ? parseInt(newAssigneeId) : null
});
await loadTasks();
}
async function deleteTask(taskId) {
if (confirm('Delete this task?')) {
await pt.delete(taskId);
await loadTasks();
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', init);
</script>
Filtering by Creator
Every entity has a creator_user_id field that tracks who created it. This is a column-level filter (not JSONB data), making it efficient:
Basic Creator Filtering
// Get tasks created by current user
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.type === 'user' && m.is_owner);
const myTasks = await pt.list({
entityNames: ['task'],
filters: {
creator_user_id: currentUser.id
}
});
Advanced Creator Filtering
// Get tasks created by multiple users
const teamIds = [123, 456, 789];
const teamTasks = await pt.list({
entityNames: ['task'],
filters: {
creator_user_id: { $in: teamIds }
}
});
// Get tasks NOT created by current user
const othersTasks = await pt.list({
entityNames: ['task'],
filters: {
creator_user_id: { $ne: currentUser.id }
}
});
// Combine creator filter with data filters
const myActiveTasks = await pt.list({
entityNames: ['task'],
filters: {
creator_user_id: currentUser.id, // Column-level filter
completed: false, // JSONB data filter
priority: { $in: ['high', 'medium'] }
}
});
"My Tasks" Toggle
let allMembers = [];
let currentUser = null;
let showOnlyMyTasks = false;
async function init() {
allMembers = await pt.getChatMembers();
currentUser = allMembers.find(m => m.type === 'user' && m.is_owner);
await loadTasks();
}
async function loadTasks() {
const filters = { completed: false };
if (showOnlyMyTasks && currentUser) {
filters.creator_user_id = currentUser.id;
}
const entities = await pt.list({
entityNames: ['task'],
filters: filters
});
displayTasks(entities.filter(e => e.entity_name === 'task'));
}
function toggleMyTasks() {
showOnlyMyTasks = !showOnlyMyTasks;
loadTasks();
}
Per-User Settings Example
Store personal preferences using creator_user_id:
// Save user-specific settings
async function saveUserSettings(settings) {
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.is_owner);
// Check if user already has settings
const existing = await pt.list({
entityNames: ['user_settings'],
filters: { creator_user_id: currentUser.id }
});
if (existing.length > 0) {
// Update existing settings
await pt.edit(existing[0].id, settings);
} else {
// Create new settings (creator_user_id is set automatically)
await pt.add('user_settings', settings);
}
}
// Load user-specific settings
async function loadUserSettings() {
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.is_owner);
const settings = await pt.list({
entityNames: ['user_settings'],
filters: { creator_user_id: currentUser.id }
});
return settings.length > 0 ? settings[0].data : getDefaultSettings();
}
function getDefaultSettings() {
return {
theme: 'light',
pageSize: 20,
defaultView: 'list'
};
}
Best Practices
1. Cache Members at Initialization
// ✅ GOOD: Load once at app start
let allMembers = [];
async function initApp() {
allMembers = await pt.getChatMembers();
await loadTasks();
}
function getMemberName(userId) {
const member = allMembers.find(m => m.id === userId);
return member ? member.name : 'Unknown';
}
// ❌ AVOID: Calling getChatMembers() repeatedly
async function displayTask(task) {
const members = await pt.getChatMembers(); // Called for every task!
const creator = members.find(m => m.id === task.creator_user_id);
return creator.name;
}
2. Handle Missing Members Gracefully
function getMemberName(userId) {
const member = allMembers.find(m => m.id === userId);
return member ? member.name : 'Unknown User';
}
function displayTaskCreator(task) {
const creator = allMembers.find(m => m.id === task.creator_user_id);
if (!creator) {
return '<span class="text-gray-400">Unknown</span>';
}
const icon = creator.type === 'user' ? '👤' : '🤖';
return `${icon} ${creator.name}`;
}
3. Separate Users and Agents
// Get only human users for assignment
const users = allMembers.filter(m => m.type === 'user');
// Get only agents
const agents = allMembers.filter(m => m.type === 'agent');
// Display differently
function renderMemberBadge(member) {
if (member.type === 'user') {
return `
<div class="user-badge">
👤 ${member.name}
${member.email ? `<small>${member.email}</small>` : ''}
</div>
`;
} else {
return `
<div class="agent-badge">
🤖 ${member.name}
</div>
`;
}
}
4. Use Meaningful Variable Names
// ✅ GOOD: Clear variable names
const chatMembers = await pt.getChatMembers();
const humanUsers = chatMembers.filter(m => m.type === 'user');
const aiAgents = chatMembers.filter(m => m.type === 'agent');
const chatOwner = chatMembers.find(m => m.is_owner);
// ❌ AVOID: Unclear names
const m = await pt.getChatMembers();
const u = m.filter(x => x.type === 'user');
Advanced Patterns
Team Dashboard
async function createTeamDashboard() {
const members = await pt.getChatMembers();
const users = members.filter(m => m.type === 'user');
// Get all tasks
const allTasks = await pt.list({
entityNames: ['task'],
filters: { completed: false }
});
// Calculate stats per user
const stats = users.map(user => {
const userTasks = allTasks.filter(t => t.data.assignee_id === user.id);
const createdTasks = allTasks.filter(t => t.creator_user_id === user.id);
return {
user: user,
assigned: userTasks.length,
created: createdTasks.length,
highPriority: userTasks.filter(t => t.data.priority === 'high').length
};
});
// Display dashboard
displayTeamStats(stats);
}
function displayTeamStats(stats) {
const html = stats.map(stat => `
<div class="bg-white rounded-lg shadow p-4">
<h3 class="font-bold">${stat.user.name}</h3>
<p>Assigned: ${stat.assigned}</p>
<p>Created: ${stat.created}</p>
<p>High Priority: ${stat.highPriority}</p>
</div>
`).join('');
document.getElementById('teamStats').innerHTML = html;
}
User Activity Log
async function getUserActivity(userId) {
// Get all entities created by user
const created = await pt.list({
entityNames: ['task', 'note', 'event'],
filters: { creator_user_id: userId },
limit: 100
});
// Get member info
const members = await pt.getChatMembers();
const user = members.find(m => m.id === userId);
return {
user: user,
activities: created.map(entity => ({
type: entity.entity_name,
created: entity.created_at,
data: entity.data
}))
};
}
22 October 2025