Installation

Note:

This SDK is the client half of your integration. Feed groups, ranking, permissions, push setup and bulk imports are configured from your backend with a server-side SDK or the REST API, and the token your app connects with has to be signed there too. See the server-side overview for what belongs there, starting with tokens and authentication.

Build with Stream CLI and Agent Skills

The Stream CLI and Agent Skills provide AI coding agents, such as Claude Code, Cursor, and Codex, with the tools and knowledge necessary to build apps with Stream.

Install the CLI and add the skills to your project:

curl -fsSL https://getstream.io/cli.sh | bash
getstream skills stream-flutter

Once installed, invoke it from your agent:

/stream Add Stream Feeds to my Flutter app.

The /stream skill acts as a router, reading your request and dispatching it to the specialist skill. You can also invoke the /stream-flutter skill directly.

Stream Agent Skills can also be installed from skills.sh.

The Flutter SDK for Stream Feeds is distributed through pub.dev.

Currently we only have a package for the low level Feeds client: stream_feeds. Releases and changes are published on the GitHub releases page.

Adding the SDK to your project

Add the dependency to your pubspec.yaml:

pubspec.yaml
dependencies:
  stream_feeds: ^0.6.0

Or run flutter pub add in the terminal, which resolves the latest version for you and writes it to pubspec.yaml:

flutter pub add stream_feeds

Check the GitHub releases page for the newest version.

stream_feeds is a pure Dart package and requires Dart ^3.12.0, which ships with Flutter 3.44 and later.

Then import it:

import 'package:stream_feeds/stream_feeds.dart';

Creating the client

The client is what you connect with, and what every feed, activity and list is created from. It takes your API key, the user to connect as, and a token provider that supplies that user's JWT.

// Regular user: provide a JWT token (from your server).
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
);
await client.connect();

// Terminal, and what a real app calls when it is done with the client for good, on
// sign-out, say. Use `disconnect` to close the connection and keep the client.
await client.dispose();

dispose is terminal: a disposed client cannot reconnect, and connect throws a StateError afterwards. To close the connection but keep the client usable, with its subscriptions intact, call disconnect instead.

Refreshing an expiring token

A static token is fine while you are getting started, but tokens expire. A dynamic provider is asked for a new one whenever the SDK needs it, so an expired connection recovers on its own without your app doing anything.

// Dynamic token provider: fetches a new token from your server
// when the current one expires.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.dynamic((userId) async {
    // Fetch a fresh JWT for `userId` from your backend.
    final token = await fetchTokenFromYourServer(userId);
    return UserToken(token);
  }),
);
await client.connect();
await client.dispose();

Guest users

A guest user connects without a token of its own and gets the same read and write access, and the same real-time updates, as a regular user. Its id is assigned by the server during connect, so read it back from client.user rather than reusing the id you asked for.

// Guest user: the SDK obtains a temporary token during connect, so no
// tokenProvider is needed. The session is temporary and is not tied to a
// persistent account.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: User.guest('guest-${DateTime.now().millisecondsSinceEpoch}'),
);
await client.connect();

// The server assigns the guest its own id, so read it from `client.user`
// rather than reusing the id you asked for.
final feed = client.feed(group: 'user', id: client.user.id);
await feed.getOrCreate();

await client.dispose();

Anonymous users

An anonymous user has no token and opens no WebSocket. Use it to read public feeds. Because watching a feed requires a connection, pass watch: false.

// Anonymous user: no token of its own and no WebSocket connection. Use it to
// read public feeds. Calling connect() is not required, and opens no
// connection for an anonymous user.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User.anonymous(),
);

// Watching requires a connection, so ask for a feed that is not watched.
final feed = client.feedFromQuery(
  const FeedQuery(
    fid: FeedId(group: 'user', id: 'alice'),
    watch: false,
  ),
);
await feed.getOrCreate();

await client.dispose();

Connecting without a WebSocket

Pass connectWebSocket: false for a client that only makes requests. No events are emitted, and a watched query is rejected.

// Authenticate without opening a WebSocket, for a client that only makes
// requests. No events are emitted, and a watched query is rejected because
// watching requires a connection.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
);
await client.connect(connectWebSocket: false);

final feed = client.feedFromQuery(
  const FeedQuery(
    fid: FeedId(group: 'user', id: 'alice'),
    watch: false,
  ),
);
await feed.getOrCreate();

await client.dispose();

Custom headers

FeedsConfig.customHeaders are sent with every API request.

// Sent with every API request. The SDK's own headers win where they overlap,
// and none of this reaches the WebSocket.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    customHeaders: {'x-stream-ext': 'my-app/1.2.3'},
  ),
);
await client.connect();
Note:

The SDK's own headers take precedence where they overlap, and custom headers are not sent on the WebSocket connection.