# Attachment Picker Modal

Customizing the Attachment Picker

### Introduction

The Attachment Picker is an inline panel that appears below the text input when a user taps the attachment button in [StreamMessageInput](/chat/docs/sdk/flutter/stream_chat_flutter/message_composer/stream_message_input/). It provides built-in picker options per platform:

- **Mobile** — tabbed interface with Gallery, Files, Camera, Video, and Poll tabs.
- **Web / Desktop** — a system list picker with Image, Video, File, and Poll options.

### Customizing Picker Options

Use `attachmentPickerOptionsBuilder` on `StreamMessageInput` to modify, filter, reorder, or extend the default options.

```dart
StreamMessageInput(
  attachmentPickerOptionsBuilder: (context, defaultOptions) {
    // Extend with a custom audio picker tab
    return [
      ...defaultOptions,
      TabbedAttachmentPickerOption(
        key: 'audio-picker',
        icon: Icons.audiotrack,
        supportedTypes: [AttachmentPickerType.audios],
        optionViewBuilder: (context, controller) {
          return AudioPicker(
            onAudioPicked: (audio) async {
              await controller.addAttachment(audio);
            },
          );
        },
      ),
    ];
  },
)
```

For custom attachment types that don't go through `addAttachment` (e.g. location, contact), use `controller.notifyCustomResult()` to signal completion. See [Custom Attachment Results](#custom-attachment-results) below.

**Filter out specific tabs:**

```dart
StreamMessageInput(
  attachmentPickerOptionsBuilder: (context, defaultOptions) {
    // Remove the poll tab
    return defaultOptions.where((o) => o.key != 'poll-creator').toList();
  },
)
```

**Reorder tabs:**

```dart
StreamMessageInput(
  attachmentPickerOptionsBuilder: (context, defaultOptions) {
    return defaultOptions.reversed.toList();
  },
)
```

#### Option Types

| Type                           | Description                                               |
| ------------------------------ | --------------------------------------------------------- |
| `TabbedAttachmentPickerOption` | Custom inline UI shown as a tab (gallery, custom pickers) |
| `SystemAttachmentPickerOption` | Launches a native system dialog (file browser, camera)    |

Both types take `icon: IconData` (not a Widget).

### Restricting Allowed Attachment Types

Use `allowedAttachmentPickerTypes` to limit which attachment types are available:

```dart
StreamMessageInput(
  allowedAttachmentPickerTypes: [
    AttachmentPickerType.images,
    AttachmentPickerType.videos,
  ],
)
```

Built-in types: `AttachmentPickerType.images`, `.videos`, `.audios`, `.files`, `.poll`.

### Using the System Picker (Web/Desktop Style on Mobile)

Set `useSystemAttachmentPicker: true` to force the system list picker on mobile:

```dart
StreamMessageInput(
  useSystemAttachmentPicker: true,
)
```

### Gallery Thumbnail Configuration

Thumbnail appearance in the gallery tab is configured via `GalleryPickerConfig`, which you pass when building a custom picker via `tabbedAttachmentPickerBuilder` or `systemAttachmentPickerBuilder`. The available fields are:

| Field                   | Default                   | Description                                     |
| ----------------------- | ------------------------- | ----------------------------------------------- |
| `mediaThumbnailSize`    | `ThumbnailSize(400, 400)` | Pixel dimensions of gallery thumbnails          |
| `mediaThumbnailFormat`  | `ThumbnailFormat.jpeg`    | `ThumbnailFormat.jpeg` or `ThumbnailFormat.png` |
| `mediaThumbnailQuality` | `100`                     | Quality value between 0–100                     |
| `mediaThumbnailScale`   | `1`                       | Logical-pixel scale factor                      |

### Typed Errors

Attachment errors are strongly typed. Catch them from `StreamAttachmentPickerController.addAttachment()`:

```dart
try {
  await controller.addAttachment(attachment);
} on AttachmentTooLargeError catch (e) {
  showError('File too large: ${e.fileSize} bytes (max ${e.maxSize} bytes)');
} on AttachmentLimitReachedError catch (e) {
  showError('Maximum ${e.maxCount} attachments allowed');
}
```

Surface these to the user via the `onError` parameter on `StreamMessageInput`:

```dart
StreamMessageInput(
  onError: (error, stackTrace) {
    if (error is AttachmentTooLargeError) {
      showSnackBar('File too large');
    } else if (error is AttachmentLimitReachedError) {
      showSnackBar('Too many attachments');
    }
  },
)
```

### Custom Attachment Results

For custom picker tabs that produce non-media results (e.g. location, contact), use `controller.notifyCustomResult()` instead of `addAttachment()`. This emits a `CustomAttachmentPickerResult` that `StreamMessageInput` forwards to `onAttachmentPickerResult`.

**Step 1 — Define your result type:**

```dart
class LocationPicked extends CustomAttachmentPickerResult {
  const LocationPicked({required this.location});
  final LatLng location;
}
```

**Step 2 — Emit the result from the picker tab:**

```dart
TabbedAttachmentPickerOption(
  key: 'location-picker',
  icon: Icons.location_on,
  supportedTypes: [CustomLocationPickerType()],
  optionViewBuilder: (context, controller) {
    return LocationPicker(
      onLocationPicked: (location) {
        if (location == null) return; // user cancelled — do nothing
        controller.notifyCustomResult(LocationPicked(location: location));
      },
    );
  },
)
```

Do **not** call `Navigator.pop()` from inside a picker tab — the picker is an inline widget, not a modal route, and popping would close the wrong page. `notifyCustomResult` is the correct signal.

**Step 3 — Handle the result in `onAttachmentPickerResult`:**

```dart
StreamMessageInput(
  onAttachmentPickerResult: (result) {
    if (result is LocationPicked) {
      sendLocationMessage(result.location);
      return true; // handled — StreamMessageInput will close the picker
    }
    return false; // not handled — let StreamMessageInput deal with it
  },
)
```

Return `true` to signal you handled the result (the picker closes automatically). Return `false` to let `StreamMessageInput` handle it.

`onAttachmentPickerResult` is only called for `CustomAttachmentPickerResult` subtypes. `AttachmentsPicked`, `PollCreated`, and `AttachmentPickerError` are handled internally by `StreamMessageInput`.

### StreamAttachmentPickerController

The inline picker tracks selected attachments through `StreamAttachmentPickerController`. Key methods available inside an `optionViewBuilder`:

| Method                         | Description                                                                  |
| ------------------------------ | ---------------------------------------------------------------------------- |
| `addAttachment(attachment)`    | Add a media attachment (image, video, file, audio)                           |
| `removeAttachment(attachment)` | Remove a previously added attachment                                         |
| `notifyCustomResult(result)`   | Emit a custom result (location, contact, etc.) to `onAttachmentPickerResult` |

`StreamMessageInput` manages its own internal controller automatically. You can configure limits via `StreamMessageInput` parameters:

```dart
StreamMessageInput(
  maxAttachmentSize: 10 * 1024 * 1024, // 10 MB
  attachmentLimit: 10,
)
```

You only need to create a `StreamAttachmentPickerController` manually if you are embedding `StreamTabbedAttachmentPicker` or `StreamSystemAttachmentPicker` directly outside of `StreamMessageInput`.


---

This page was last updated at 2026-04-23T18:43:03.508Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/v10/stream_chat_flutter/custom_widgets/customize_attachment_picker_modal/](https://getstream.io/chat/docs/sdk/flutter/v10/stream_chat_flutter/custom_widgets/customize_attachment_picker_modal/).