> Markdown version of [Modular Upload](https://vaadin.com/docs/next/components/upload/modular-upload). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Modular Upload (since V25.1)

The standard [Upload](https://vaadin.com/docs/next/components/upload.md) component bundles a button, drop zone, file list, and upload management into a single component. `UploadManager` decouples these concerns into independent, composable pieces. Use it when you need to customize the upload layout, place the file list in a different location, omit UI components you don’t need, or integrate upload functionality into a custom component such as a chat input, form field, or toolbar.

- `UploadManager`

  Non-visual. Handles file validation, upload queue management, and server communication. The three UI components below connect to it.

- `UploadButton`

  A button that opens the file picker dialog. Automatically disables when the maximum file count is reached.

- `UploadDropZone`

  A drag-and-drop target area that can wrap other content.

- `UploadFileList`

  Displays files with progress indicators, status messages, and action buttons (retry, abort, remove).

Each component can be placed anywhere in your layout, independently of one another. Multiple components of the same type can also connect to a single manager — for example, two separate upload buttons in different parts of the UI. They all link to the same `UploadManager` instance, which coordinates their behavior.

## <a id="basic-usage"></a>Basic Usage

Create an `UploadManager` instance and pass it to the UI components. The components automatically synchronize with the manager’s state.

**Lit** — `upload-manager-basic.ts`

```typescript
private readonly manager = new UploadManager({
  target: '/api/fileupload',
  maxFiles: 5,
  maxFileSize: 10 * 1024 * 1024, // 10 MB
  accept: 'image/*,application/pdf',
});

protected override render() {
  return html`
    <vaadin-vertical-layout theme="spacing">
      <vaadin-upload-button .manager="${this.manager}"> Select Files </vaadin-upload-button>
      <vaadin-upload-file-list
        .manager="${this.manager}"
        style="width: 100%"
      ></vaadin-upload-file-list>
    </vaadin-vertical-layout>
  `;
}
```

**Flow** — `UploadManagerBasic.java`

```java
// Create the upload handler
var handler = UploadHandler.inMemory((metadata, data) -> {
    // Process the uploaded file
    String fileName = metadata.fileName();
    String mimeType = metadata.contentType();
    // ...
});

// Create the manager with constraints
var manager = new UploadManager(this, handler);
manager.setMaxFiles(5);
manager.setMaxFileSize(10L * 1024 * 1024); // 10 MB
manager.setAcceptedMimeTypes("image/*", "application/pdf");

// Create the UI components
var uploadButton = new UploadButton("Select Files", manager);

var fileList = new UploadFileList(manager);
fileList.setWidthFull();

// Layout the components
var layout = new VerticalLayout(uploadButton, fileList);
layout.setPadding(false);
layout.setSpacing(true);
```

**React** — `upload-manager-basic.tsx`

```tsx
// Create the upload manager
const manager = React.useMemo(
  () =>
    new UploadManager({
      target: '/api/fileupload',
      maxFiles: 5,
      maxFileSize: 10 * 1024 * 1024, // 10 MB
      accept: 'image/*,application/pdf',
    }),
  []
);

return (
  <VerticalLayout theme="spacing">
    <UploadButton manager={manager}>Select Files</UploadButton>
    <UploadFileList manager={manager} style={{ width: '100%' }} />
  </VerticalLayout>
);
```

The `UploadManager` is created with an upload handler that processes the files. The `UploadButton` and `UploadFileList` components are linked to the manager and can be placed anywhere in the layout.

## <a id="owner-component-lifecycle"></a>Owner Component Lifecycle

The `UploadManager` requires an owner component, typically the view or layout containing the upload UI. The manager’s lifecycle is tied to the owner’s lifecycle:

- When the owner is detached from the UI or disabled, uploads stop working

- Events fired by the manager use the owner as their source component

```java
// The view itself is typically used as the owner
public class MyView extends VerticalLayout {
    public MyView() {
        var manager = new UploadManager(this, handler);
        // ...
    }
}
```

This design ensures that the upload infrastructure is automatically cleaned up when the view is removed, preventing resource leaks.

## <a id="using-a-drop-zone"></a>Using a Drop Zone

The `UploadDropZone` component provides drag-and-drop functionality. It can wrap other content, allowing users to drop files onto a larger area.

**Lit** — `upload-manager-drop-zone.ts`

```typescript
private readonly manager = new UploadManager({
  target: '/api/fileupload',
  maxFiles: 10,
});

protected override render() {
  return html`
    <vaadin-vertical-layout theme="spacing" style="width: 100%">
      <vaadin-upload-drop-zone
        .manager="${this.manager}"
        style="border: 1px dashed var(--vaadin-border-color-secondary); border-radius: var(--vaadin-radius-l); padding: var(--vaadin-padding-l); width: 100%; box-sizing: border-box"
      >
        <vaadin-horizontal-layout
          theme="spacing"
          style="align-items: center; justify-content: center"
        >
          <vaadin-icon icon="vaadin:upload"></vaadin-icon>
          <span>Drop files here or</span>
          <vaadin-upload-button .manager="${this.manager}">Browse</vaadin-upload-button>
        </vaadin-horizontal-layout>
      </vaadin-upload-drop-zone>
      <vaadin-upload-file-list
        .manager="${this.manager}"
        style="width: 100%"
      ></vaadin-upload-file-list>
    </vaadin-vertical-layout>
  `;
}
```

**Flow** — `UploadManagerDropZone.java`

```java
// Create the upload handler
var handler = UploadHandler.inMemory((metadata, data) -> {
    // Process the uploaded file
});

// Create the manager
var manager = new UploadManager(this, handler);
manager.setMaxFiles(10);

// Create the upload button
var uploadButton = new UploadButton("Browse", manager);

// Create drop zone content
var icon = VaadinIcon.UPLOAD.create();
var label = new Span("Drop files here or");
var dropContent = new HorizontalLayout(icon, label, uploadButton);
dropContent.setAlignItems(FlexComponent.Alignment.CENTER);
dropContent
        .setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
dropContent.setSpacing(true);

// Create the drop zone with the content inside
var dropZone = new UploadDropZone(dropContent, manager);
dropZone.setWidthFull();
dropZone.getStyle().set("border",
        "1px dashed var(--vaadin-border-color-secondary)");
dropZone.getStyle().set("border-radius", "var(--vaadin-radius-l)");
dropZone.getStyle().set("padding", "var(--vaadin-padding-l)");
dropZone.getStyle().set("box-sizing", "border-box");

// Create the file list
var fileList = new UploadFileList(manager);
fileList.setWidthFull();

// Layout the components
var layout = new VerticalLayout(dropZone, fileList);
layout.setPadding(false);
layout.setSpacing(true);
```

**React** — `upload-manager-drop-zone.tsx`

```tsx
// Create the upload manager
const manager = React.useMemo(
  () =>
    new UploadManager({
      target: '/api/fileupload',
      maxFiles: 10,
    }),
  []
);

return (
  <VerticalLayout theme="spacing" style={{ width: '100%' }}>
    <UploadDropZone
      manager={manager}
      style={{
        border: '1px dashed var(--vaadin-border-color-secondary)',
        borderRadius: 'var(--vaadin-radius-l)',
        padding: 'var(--vaadin-padding-l)',
        width: '100%',
        boxSizing: 'border-box',
      }}
    >
      <HorizontalLayout
        theme="spacing"
        style={{ alignItems: 'center', justifyContent: 'center' }}
      >
        <Icon icon="vaadin:upload" />
        <span>Drop files here or</span>
        <UploadButton manager={manager}>Browse</UploadButton>
      </HorizontalLayout>
    </UploadDropZone>
    <UploadFileList manager={manager} style={{ width: '100%' }} />
  </VerticalLayout>
);
```

The drop zone applies a `dragover` attribute when files are dragged over it, which can be styled using CSS.

## <a id="thumbnails"></a>Thumbnails

The `UploadFileList` supports a thumbnails theme variant that displays uploaded files as a compact grid instead of a vertical list. Image files are shown with a thumbnail preview, while other file types display a file icon.

**Lit** — `upload-manager-thumbnails.ts`

```typescript
private readonly manager = new UploadManager({
  target: '/api/fileupload',
});

protected override render() {
  return html`
    <vaadin-vertical-layout theme="spacing">
      <vaadin-upload-button .manager="${this.manager}"> Select Files </vaadin-upload-button>
      <vaadin-upload-file-list
        .manager="${this.manager}"
        theme="thumbnails"
        style="width: 100%"
      ></vaadin-upload-file-list>
    </vaadin-vertical-layout>
  `;
}
```

**Flow** — `UploadManagerThumbnails.java`

```java
var handler = UploadHandler.inMemory((metadata, data) -> {
    // Process the uploaded file
});

var manager = new UploadManager(this, handler);

var uploadButton = new UploadButton("Select Files", manager);

var fileList = new UploadFileList(manager);
fileList.setWidthFull();
fileList.addThemeVariants(UploadFileListVariant.THUMBNAILS);

var layout = new VerticalLayout(uploadButton, fileList);
layout.setPadding(false);
layout.setSpacing(true);
```

**React** — `upload-manager-thumbnails.tsx`

```tsx
const manager = React.useMemo(
  () =>
    new UploadManager({
      target: '/api/fileupload',
    }),
  []
);

return (
  <VerticalLayout theme="spacing">
    <UploadButton manager={manager}>Select Files</UploadButton>
    <UploadFileList manager={manager} theme="thumbnails" style={{ width: '100%' }} />
  </VerticalLayout>
);
```

## <a id="configuration"></a>Configuration

The `UploadManager` accepts an [upload handler](https://vaadin.com/docs/next/components/upload/file-handling.md) for processing files, and shares most of its configuration API with the standard [Upload](https://vaadin.com/docs/next/components/upload.md) component — including [upload restrictions](https://vaadin.com/docs/next/components/upload.md#upload-restrictions) (file count, size, and format), [auto-upload](https://vaadin.com/docs/next/components/upload.md#auto-upload), and [event listeners](https://vaadin.com/docs/next/components/upload.md#listeners).

The following sections describe where `UploadManager` differs from `Upload`.

### <a id="file-type-restrictions"></a>File Type Restrictions

Unlike `Upload`, which uses a single `setAcceptedFileTypes()` method for both MIME types and extensions as a client-side hint, `UploadManager` provides two separate methods:

```java
// Accept by MIME type (wildcards supported)
manager.setAcceptedMimeTypes("image/*", "application/pdf");

// Accept by file extension (must start with a dot)
manager.setAcceptedFileExtensions(".pdf", ".docx", ".xlsx");
```

When both are configured, a file must match at least one MIME type and at least one file extension. These restrictions are enforced both on the client side (to filter the file picker) and on the server side (to reject non-matching uploads). This is an improvement over `Upload`, which only validates file types on the client.

### <a id="event-differences"></a>Event Differences

The `FileRejectedEvent` in `UploadManager` carries a typed `FileRejectionReason` enum (`TOO_MANY_FILES`, `FILE_TOO_LARGE`, `INCORRECT_FILE_TYPE`) instead of a raw error string, making it easier to handle specific rejection causes programmatically.

Upload progress events aren’t available as component event listeners. Use `TransferProgressListener` on the [upload handler](https://vaadin.com/docs/next/components/upload/file-handling.md) instead.

## <a id="disabling-uploads"></a>Disabling Uploads

> **Warning:** Disabling UI components (`UploadButton`, `UploadDropZone`) only affects the user interface. A malicious client can still initiate uploads by sending requests directly to the server. To securely prevent uploads, you must disable the `UploadManager` itself.

Use `setEnabled()` on the manager to securely control whether uploads are allowed. Disabling the manager prevents the server from accepting upload requests, regardless of the state of UI components. You can also disable individual UI components for visual feedback, but this should always be combined with disabling the manager for security.

## <a id="internationalization"></a>Internationalization

The `UploadFileList` component supports internationalization (i18n) for its labels and messages:

```java
var i18n = new UploadFileListI18N()
    .setFile(new UploadFileListI18N.File()
        .setRetry("Try again")
        .setStart("Upload")
        .setRemove("Delete"))
    .setError(new UploadFileListI18N.Error()
        .setTooManyFiles("Too many files")
        .setFileIsTooBig("File is too large")
        .setIncorrectFileType("Invalid file type"));

fileList.setI18n(i18n);
```

## <a id="related-components"></a>Related Components

| Component                                                               | Usage Recommendation                                                         |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| [Upload](https://vaadin.com/docs/next/components/upload.md)             | The default Upload component with built-in drop zone, button, and file list. |
| [Progress Bar](https://vaadin.com/docs/next/components/progress-bar.md) | Component for showing task completion progress.                              |

`UPLOAD-MANAGER-MODULAR-UPLOAD`
