> Markdown version of [File Attachments](https://vaadin.com/docs/next/flow/ai-support/file-attachments). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# File Attachments

Enable file uploads by passing an [`UploadManager`](https://vaadin.com/docs/next/components/upload.md), [`Upload`](https://vaadin.com/docs/next/components/upload.md), or any custom `AIFileReceiver` implementation to the builder via `withFileReceiver()`. Uploaded files are sent to the LLM as attachments with the next prompt.

```java
var uploadManager = new UploadManager(this);
var uploadButton = new UploadButton(uploadManager);

var orchestrator = AIOrchestrator
        .builder(provider, systemPrompt)
        .withMessageList(messageList)
        .withInput(messageInput)
        .withFileReceiver(uploadManager)
        .build();
```

> **Note: Upload Handler Conflict**
>
> When using `UploadManager` or `Upload`, the component must not already have an upload handler set, and an `Upload` must not have a receiver either. The orchestrator installs its own in-memory handler. If one is already set, `withFileReceiver()` throws an `IllegalArgumentException`. This restriction does not apply to custom `AIFileReceiver` implementations.

> **Note: Supported Attachment Types**
>
> The following MIME type categories are supported: images (`image/`**), text (`text/`**), PDF (`application/pdf` or `application/x-pdf`), audio (`audio/`**), and video (`video/`**). An attachment with any other MIME type still appears in the Message List, but the built-in providers leave its content out of the request, so the LLM never sees it. To send such a file anyway, convert it to a supported format with a [request interceptor](https://vaadin.com/docs/next/flow/ai-support/request-interception.md).

> **Note: Duplicate File Names**
>
> Uploading a file whose name matches one that is still pending, that is, uploaded but not yet sent with a prompt, throws an `IllegalArgumentException`. Removing the earlier file from the upload list frees the name, and once a prompt has consumed the pending files, the name can be used again.

## <a id="persisting-attachments"></a>Persisting Attachments

The orchestrator passes attachments to the LLM with each request but does not store them afterward. To save attachments to your own storage and look them up later — for example when the user clicks one in the message list — use `withRequestListener()` (see [Request & Response Listeners](https://vaadin.com/docs/next/flow/ai-support/listeners.md)) and `withAttachmentClickListener()`:

```java
var orchestrator = AIOrchestrator
        .builder(provider, systemPrompt)
        .withMessageList(messageList)
        .withInput(messageInput)
        .withFileReceiver(uploadManager)
        .withRequestListener(event -> {
            // Store attachments keyed by message ID
            saveAttachments(event.getMessageId(),
                    event.getAttachments());
        })
        .withAttachmentClickListener(event -> {
            // Retrieve and display the attachment
            showAttachment(event.getMessageId(),
                    event.getAttachmentIndex());
        })
        .build();
```

`saveAttachments` and `showAttachment` are placeholders for application code. The former persists the `AIAttachment` data, while the latter loads it back and displays it.

## <a id="programmatic-attachments"></a>Programmatic Attachments (since V25.2)

Use `prompt(message, attachments)` to send a prompt and attachments from application code — a file the user dropped on the form, a PDF the application generated, or content fetched from a service. No chat UI is needed.

```java
var attachment = new AIAttachment("receipt.png", "image/png", receiptBytes);
orchestrator.prompt("Fill the form from this receipt",
        List.of(attachment));
```

`AIAttachment` is a record with `name()`, `mimeType()`, and `data()` (raw bytes). All three are required.

### <a id="from-a-vaadin-upload"></a>From a Vaadin Upload

`UploadHandler.inMemory()` is the simplest way to receive a file as a byte array. The callback runs on the UI thread, so `prompt(…​)` can be called directly:

```java
var handler = UploadHandler.inMemory((metadata, data) -> {
    var attachment = new AIAttachment(metadata.fileName(),
            metadata.contentType(), data);
    orchestrator.prompt("Fill the form from this receipt",
            List.of(attachment));
});
var upload = new Upload(handler);
```

The same handler can be passed to `UploadManager`.

> **Note: Mixing With Chat Attachments**
>
> `prompt(message, attachments)` sends only the supplied list. Anything already queued in a configured `AIFileReceiver` stays there for the next `prompt(message)` call or chat submit — the two paths do not share state.
