Skip to main content

Channels & Workspaces

Learn how to create and manage channels and workspaces for team collaboration in ChatSDK 2.0.

Overview

ChatSDK supports a Slack-style hierarchy:

Workspace (Organization)
└── Channels (Rooms/Conversations)
└── Messages
└── Threads

Use Cases:

  • Team Messaging - Slack/Teams clone with workspaces and channels
  • Customer Support - Separate workspace per customer account
  • Gaming - Workspace per game server, channels per room
  • Healthcare - Workspace per hospital, channels per department

Workspace Basics

Create Workspace

import { ChatSDK } from '@chatsdk/core';

const sdk = await ChatSDK.connect({
apiUrl: 'https://api.yourdomain.com',
userId: 'user-123',
});

// Create workspace
const workspace = await sdk.createWorkspace({
name: 'Acme Inc',
slug: 'acme', // URL-friendly identifier
description: 'Company workspace',
avatar: 'https://acme.com/logo.png',
settings: {
publicSignup: false,
inviteOnly: true,
},
});

console.log('Workspace ID:', workspace.id); // 'ws-abc123'

List Workspaces

// Get all workspaces for current user
const workspaces = await sdk.listWorkspaces();

workspaces.forEach((ws) => {
console.log(`${ws.name} (${ws.memberCount} members)`);
});

Get Workspace Details

const workspace = await sdk.getWorkspace({ workspaceId: 'ws-abc123' });

console.log(workspace);
// {
// id: 'ws-abc123',
// name: 'Acme Inc',
// slug: 'acme',
// memberCount: 42,
// channelCount: 15,
// createdAt: '2024-01-09T...',
// }

Update Workspace

await sdk.updateWorkspace({
workspaceId: 'ws-abc123',
name: 'Acme Corporation',
description: 'Updated description',
settings: {
publicSignup: true,
},
});

Delete Workspace

await sdk.deleteWorkspace({ workspaceId: 'ws-abc123' });
// ⚠️ This deletes all channels and messages!

Channel Basics

Channel Types

ChatSDK supports 3 channel types:

TypeDescriptionUse Case
messagingDirect message (1-on-1)User to user chat
groupSmall private group#project-team, #customer-support
teamTeam/public channel#general, #engineering

Create Channel

// Direct message (1-on-1)
const dm = await sdk.createChannel({
type: 'messaging',
memberIds: ['other-user-789'], // Just the other person's ID
});

// Group channel
const projectTeam = await sdk.createChannel({
type: 'group',
name: 'Project Team',
memberIds: ['user-123', 'user-456', 'user-789'],
description: 'Private project discussion',
});

// Team channel
const general = await sdk.createChannel({
workspaceId: 'ws-abc123',
name: 'general',
type: 'team',
description: 'General discussion',
memberIds: ['user-123', 'user-456'], // Initial members
});

List Channels

// All channels in workspace
const allChannels = await sdk.listChannels({
workspaceId: 'ws-abc123',
});

// Only public channels
const publicChannels = await sdk.listChannels({
workspaceId: 'ws-abc123',
type: 'public',
});

// Channels I'm a member of
const myChannels = await sdk.listMyChannels({
workspaceId: 'ws-abc123',
});

Get Channel Details

const channel = await sdk.getChannel({ channelId: 'ch-xyz789' });

console.log(channel);
// {
// id: 'ch-xyz789',
// name: 'general',
// type: 'public',
// memberCount: 42,
// unreadCount: 5,
// lastMessage: { ... },
// createdAt: '2024-01-09T...',
// }

Update Channel

await sdk.updateChannel({
channelId: 'ch-xyz789',
name: 'general-chat',
topic: 'Updated topic',
description: 'Updated description',
});

Archive Channel

// Archive (soft delete)
await sdk.archiveChannel({ channelId: 'ch-xyz789' });

// Unarchive
await sdk.unarchiveChannel({ channelId: 'ch-xyz789' });

// Permanently delete
await sdk.deleteChannel({ channelId: 'ch-xyz789' });
// ⚠️ This deletes all messages!

Channel Membership

Join Channel

// Join public channel
await sdk.joinChannel({ channelId: 'ch-xyz789' });

// Auto-join on message send
await sdk.sendMessage({
channelId: 'ch-xyz789',
text: 'Hello!',
autoJoin: true, // Join automatically if not a member
});

Leave Channel

await sdk.leaveChannel({ channelId: 'ch-xyz789' });

Invite Members

// Invite single user
await sdk.inviteToChannel({
channelId: 'ch-xyz789',
userId: 'user-456',
});

// Invite multiple users
await sdk.inviteToChannel({
channelId: 'ch-xyz789',
userIds: ['user-456', 'user-789', 'user-012'],
});

Remove Members

await sdk.removeFromChannel({
channelId: 'ch-xyz789',
userId: 'user-456',
});

List Members

const members = await sdk.listChannelMembers({
channelId: 'ch-xyz789',
limit: 50,
offset: 0,
});

members.forEach((member) => {
console.log(`${member.name} - ${member.role}`);
});

Channel Roles & Permissions

Assign Role

// Make user a moderator
await sdk.assignChannelRole({
channelId: 'ch-xyz789',
userId: 'user-456',
role: 'moderator',
});

// Available roles: 'owner', 'admin', 'moderator', 'member'

Check Permissions

const canDelete = await sdk.checkChannelPermission({
channelId: 'ch-xyz789',
userId: 'user-456',
permission: 'messages.delete',
});

if (canDelete) {
await sdk.deleteMessage({ messageId: 'msg-123' });
}

Custom Permissions

await sdk.updateChannelPermissions({
channelId: 'ch-xyz789',
permissions: {
'messages.send': ['member', 'moderator', 'admin', 'owner'],
'messages.delete': ['moderator', 'admin', 'owner'],
'channels.invite': ['admin', 'owner'],
'channels.settings': ['owner'],
},
});

Real-Time Channel Events

Subscribe to Channel

// Subscribe to new messages in channel
sdk.subscribeToChannel({ channelId: 'ch-xyz789' });

// Listen for new messages
sdk.on('message.new', (message) => {
if (message.channelId === 'ch-xyz789') {
console.log('New message in general:', message.text);
}
});

// Unsubscribe
sdk.unsubscribeFromChannel({ channelId: 'ch-xyz789' });

Channel Events

// Member joined
sdk.on('channel.member_joined', ({ channelId, userId, userName }) => {
console.log(`${userName} joined #${channelId}`);
});

// Member left
sdk.on('channel.member_left', ({ channelId, userId, userName }) => {
console.log(`${userName} left #${channelId}`);
});

// Channel updated
sdk.on('channel.updated', ({ channelId, changes }) => {
console.log('Channel updated:', changes);
});

// Typing indicator
sdk.on('typing.start', ({ channelId, userId, userName }) => {
console.log(`${userName} is typing in #${channelId}...`);
});

React Integration

Channel List Component

import { useChannels } from '@chatsdk/react';

function ChannelList({ workspaceId }) {
const { channels, loading, error } = useChannels({ workspaceId });

if (loading) return <div>Loading channels...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<div className="channel-list">
<h2>Channels</h2>
{channels.map((channel) => (
<ChannelItem
key={channel.id}
channel={channel}
onClick={() => selectChannel(channel.id)}
/>
))}
</div>
);
}

function ChannelItem({ channel, onClick }) {
return (
<div onClick={onClick} className="channel-item">
<span className="channel-icon">
{channel.type === 'public' ? '#' : '🔒'}
</span>
<span className="channel-name">{channel.name}</span>
{channel.unreadCount > 0 && (
<span className="unread-badge">{channel.unreadCount}</span>
)}
</div>
);
}

Create Channel Form

import { useState } from 'react';
import { useCreateChannel } from '@chatsdk/react';

function CreateChannelForm({ workspaceId, onCreated }) {
const [name, setName] = useState('');
const [type, setType] = useState('public');
const { createChannel, loading } = useCreateChannel();

const handleSubmit = async (e) => {
e.preventDefault();

const channel = await createChannel({
workspaceId,
name,
type,
description: '',
});

onCreated(channel);
setName('');
};

return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Channel name"
required
/>

<select value={type} onChange={(e) => setType(e.target.value)}>
<option value="public">Public Channel</option>
<option value="private">Private Channel</option>
</select>

<button type="submit" disabled={loading || !name.trim()}>
Create Channel
</button>
</form>
);
}

React Native Integration

import { useChannels, useCreateChannel } from '@chatsdk/react-native';
import { FlatList, TouchableOpacity, Text, View } from 'react-native';

function ChannelListScreen({ workspaceId, navigation }) {
const { channels, loading, refresh } = useChannels({ workspaceId });

const handleSelectChannel = (channel) => {
navigation.navigate('Chat', { channelId: channel.id });
};

return (
<FlatList
data={channels}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => handleSelectChannel(item)}>
<View style={styles.channelItem}>
<Text style={styles.channelName}>
{item.type === 'public' ? '#' : '🔒'} {item.name}
</Text>
{item.unreadCount > 0 && (
<View style={styles.unreadBadge}>
<Text style={styles.unreadText}>{item.unreadCount}</Text>
</View>
)}
</View>
</TouchableOpacity>
)}
keyExtractor={(item) => item.id}
refreshing={loading}
onRefresh={refresh}
/>
);
}

Best Practices

1. Channel Naming

// ✅ Good: lowercase, hyphenated
'general', 'random', 'engineering-team', 'product-feedback'

// ❌ Bad: spaces, special chars, uppercase
'General Discussion!', 'Engineering Team', 'product_feedback'

2. Default Channels

// Create default channels when workspace is created
const workspace = await sdk.createWorkspace({ name: 'Acme Inc' });

// Auto-create #general
await sdk.createChannel({
workspaceId: workspace.id,
name: 'general',
type: 'public',
description: 'General discussion',
isDefault: true, // Users auto-join on workspace join
});

// Auto-create #random
await sdk.createChannel({
workspaceId: workspace.id,
name: 'random',
type: 'public',
description: 'Random conversations',
isDefault: true,
});

3. Channel Pagination

// Load channels in batches for better performance
const loadChannels = async (workspaceId) => {
let allChannels = [];
let offset = 0;
const limit = 50;

while (true) {
const batch = await sdk.listChannels({
workspaceId,
limit,
offset,
});

allChannels = [...allChannels, ...batch];

if (batch.length < limit) break; // No more channels
offset += limit;
}

return allChannels;
};

4. Unread Count Management

// Mark channel as read when user opens it
const handleOpenChannel = async (channelId) => {
await sdk.markChannelAsRead({ channelId });

// Unread count updates automatically via WebSocket
};

5. Channel Archiving

// Archive old channels instead of deleting
const archiveInactiveChannels = async (workspaceId) => {
const channels = await sdk.listChannels({ workspaceId });

for (const channel of channels) {
const lastActivity = new Date(channel.lastMessageAt);
const daysSinceActivity = (Date.now() - lastActivity) / (1000 * 60 * 60 * 24);

if (daysSinceActivity > 90) {
await sdk.archiveChannel({ channelId: channel.id });
console.log(`Archived inactive channel: ${channel.name}`);
}
}
};

Advanced Features

Channel Categories

// Group channels into categories (like Discord)
await sdk.createChannelCategory({
workspaceId: 'ws-abc123',
name: 'Engineering',
position: 1,
});

await sdk.updateChannel({
channelId: 'ch-xyz789',
categoryId: 'cat-123',
});

Channel Templates

// Create channel from template
await sdk.createChannelFromTemplate({
workspaceId: 'ws-abc123',
templateId: 'support-channel',
name: 'customer-acme-corp',
variables: {
customerName: 'Acme Corp',
assignedAgent: 'user-456',
},
});

Bulk Operations

// Bulk invite users to multiple channels
await sdk.bulkInviteToChannels({
userId: 'user-123',
channelIds: ['ch-abc', 'ch-def', 'ch-ghi'],
});

// Bulk archive channels
await sdk.bulkArchiveChannels({
channelIds: ['ch-old1', 'ch-old2', 'ch-old3'],
});

Troubleshooting

Channel not appearing in list:

  • Check user is a member: await sdk.isMemberOf({ channelId })
  • Verify channel type (private channels need explicit membership)
  • Check workspace ID matches

Can't send messages to channel:

  • Join channel first: await sdk.joinChannel({ channelId })
  • Check permissions: await sdk.checkChannelPermission(...)
  • Verify channel is not archived

Unread count not updating:

  • Ensure WebSocket is connected: sdk.getConnectionState()
  • Subscribe to channel: sdk.subscribeToChannel({ channelId })
  • Check browser console for errors

Next Steps


Need help? Join our Discord community or check the API Reference.