# Creating Channels

This recipe shows how to build a user search screen that lets the current user select one or more members and create a new channel with them.

### Implementation

```dart
class NewChannelPage extends StatefulWidget {
  const NewChannelPage({super.key});

  @override
  State<NewChannelPage> createState() => _NewChannelPageState();
}

class _NewChannelPageState extends State<NewChannelPage> {
  final _searchController = TextEditingController();
  List<User> _searchResults = [];
  final List<User> _selectedUsers = [];
  bool _isLoading = false;

  Future<void> _searchUsers(String query) async {
    if (query.isEmpty) {
      setState(() => _searchResults = []);
      return;
    }
    setState(() => _isLoading = true);
    final client = StreamChat.of(context).client;
    final response = await client.queryUsers(
      filter: Filter.and([
        Filter.autoComplete('name', query),
        Filter.notEqual('id', client.state.currentUser!.id),
      ]),
      sort: [SortOption<User>.asc('name')],
    );
    setState(() {
      _searchResults = response.users;
      _isLoading = false;
    });
  }

  Future<void> _createChannel() async {
    if (_selectedUsers.isEmpty) return;
    final client = StreamChat.of(context).client;

    final memberIds = [
      client.state.currentUser!.id,
      ..._selectedUsers.map((u) => u.id),
    ];

    // Use messaging type for direct messages or group chats
    final channel = client.channel(
      'messaging',
      extraData: {
        'members': memberIds,
        if (_selectedUsers.length > 1)
          'name': _selectedUsers.map((u) => u.name).join(', '),
      },
    );

    await channel.watch();

    if (!mounted) return;
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (_) => StreamChannel(
          channel: channel,
          child: const ChannelPage(),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('New Message'),
        actions: [
          TextButton(
            onPressed: _selectedUsers.isNotEmpty ? _createChannel : null,
            child: const Text('Create'),
          ),
        ],
      ),
      body: Column(
        children: [
          if (_selectedUsers.isNotEmpty)
            SizedBox(
              height: 56,
              child: ListView(
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.symmetric(horizontal: 8),
                children: _selectedUsers
                    .map(
                      (user) => Padding(
                        padding: const EdgeInsets.only(right: 8),
                        child: Chip(
                          label: Text(user.name ?? user.id),
                          onDeleted: () => setState(
                            () => _selectedUsers.remove(user),
                          ),
                        ),
                      ),
                    )
                    .toList(),
              ),
            ),
          Padding(
            padding: const EdgeInsets.all(8),
            child: TextField(
              controller: _searchController,
              onChanged: _searchUsers,
              decoration: const InputDecoration(
                prefixIcon: Icon(Icons.search),
                hintText: 'Search by name...',
              ),
            ),
          ),
          if (_isLoading)
            const Center(child: CircularProgressIndicator())
          else
            Expanded(
              child: ListView.builder(
                itemCount: _searchResults.length,
                itemBuilder: (context, index) {
                  final user = _searchResults[index];
                  final isSelected = _selectedUsers.contains(user);
                  return ListTile(
                    leading: StreamUserAvatar(user: user),
                    title: Text(user.name ?? user.id),
                    trailing: isSelected
                        ? const Icon(Icons.check_circle, color: Colors.blue)
                        : null,
                    onTap: () => setState(() {
                      if (isSelected) {
                        _selectedUsers.remove(user);
                      } else {
                        _selectedUsers.add(user);
                      }
                    }),
                  );
                },
              ),
            ),
        ],
      ),
    );
  }
}
```

### Channel types

| Type         | Use case                          |
| ------------ | --------------------------------- |
| `messaging`  | Direct messages and group chats   |
| `livestream` | Livestream chat with many viewers |
| `team`       | Team collaboration channels       |
| `commerce`   | Customer support                  |

Choose the channel type that matches the built-in permission set closest to your requirements, then fine-tune in the [Stream Dashboard](https://dashboard.getstream.io).

### Watching vs. querying an existing channel

`channel.watch()` creates the channel if it does not exist, or returns the existing channel. To look up an existing channel without creating it, use `client.queryChannels` with a filter.


---

This page was last updated at 2026-06-09T15:44:04.024Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/cookbook/creating-channels/](https://getstream.io/chat/docs/sdk/flutter/cookbook/creating-channels/).