Uppy is an open source project by Transloadit
Transloadit Community Plan: free hosted Companion & tusd, unlimited uploading, and 5GB/mo encoding traffic!

Uppy 2.0.0: smaller, faster, modular plugins, Preact X, stricter types, and much more

Today, our tails are positively wagging with excitement about the release of Uppy 2.0! This latest version is on average 25% smaller and up to a thousand times faster, thanks to dropping support for IE11 and a lot of refactoring work. We’ve upgraded many dependencies, most notably Preact 8 to Preact X, greatly improved TypeScript support and screen reader accessibility, paid technical debt, and added support for multiple messages in the Informer plugin.

Chow down on all the juicy bits and pieces inside! And make sure to try Uppy live demo.

Uppy 2.0 cover banner

Uppy is a sleek, modular JavaScript file uploader that integrates seamlessly with any application. It is made for developers who want to provide their users with the ability to see image previews, edit metadata, pick large files directly from Dropbox, restore selected files when a tab was accidentally closed, or crop an image in-browser before sending.

A pup no more

Ever since the first introduction of Uppy five years ago (or 36 in dog-years), we’ve always referred to our project as ‘the next open source file uploader for web browsers’. The release of Uppy 1.0 a little over two years ago, however, soon led to a steep increase in adoption. Uppy now boasts over 24,000 stargazers on GitHub, making it the undisputed top dog in the world of file uploaders 🌍

With that in mind, we felt it was high time to give Uppy some more much-needed trimming. We want to take this opportunity to break with the past, to make the project leaner – and to pave the way for an even brighter future for Uppy!

Uppy 2.0 UI with files

Table of Contents

Highlights since 1.0

A lot of things have happened since we released Uppy 1.0 in April of 2019. In case you have missed some of our 1.x releases, here is a small overview. We have added:

Smaller bundles

With 2.0, following in the footsteps of Microsoft, we are dropping support for IE11. As a result, we are able to remove all built-in polyfills, and the new bundle size is 25% smaller! If you want your app to still support older browsers (such as IE11), you may need to add the following polyfills to your bundle:

If you’re using a bundler, you need import these before Uppy:

import 'core-js'
import 'whatwg-fetch'
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'
// Order matters here: AbortController needs fetch, which needs Promise (provided by core-js).

import 'md-gum-polyfill'
import ResizeObserver from 'resize-observer-polyfill'

window.ResizeObserver ??= ResizeObserver

export { default } from '@uppy/core'
export * from '@uppy/core'

If you’re using Uppy from a CDN, we now provide two bundles: one for up-to-date browsers that do not include polyfills and use modern syntax, and one for legacy browsers. When migrating, be mindful about the types of browsers you want to support:

<!-- Modern browsers (recommended) -->
<script src="https://releases.transloadit.com/uppy/v2.0.1/uppy.min.js"></script>

<!-- Legacy browsers (IE11+) -->
<script nomodule src="https://releases.transloadit.com/uppy/v2.0.1/uppy.legacy.min.js"></script>
<script type="module">import "https://releases.transloadit.com/uppy/v2.0.1/uppy.min.js";</script>

Please note that while you may be able to get 2.0 to work in IE11 this way, we do not officially support it anymore.


Uppy now loads faster thanks to the decreased bundle size. With Uppy 1.0, adding many files (hundreds or even thousands) used to take dozens of seconds. Uppy 2.0 does the same thing in mere milliseconds! So, at least for this specific use case, we feel comfortable in claiming that you may see your loading times go up to a thousand times faster.

This was made possible by avoiding having to re-render all the file components whenever something changes, using memoize and virtual-list (allowing us to only render what is actually visible on screen). In addition, multiple files are now added to state in one go via uppy.addFiles(Array) as opposed to before, when uppy.addFile(File) + uppy.setState were being called in a loop.

Before optimizations:

And after:

Preact X and upgraded dependencies

We’ve upgraded almost all of Uppy’s dependencies. This includes the migration to Preact X. All plugins that depend on Preact have been upgraded from 8.2.9 to the latest version 10.5.13. If you’d like your custom plugin to continue working with Uppy 2.0, it also needs to be using latest version of Preact.

Other notable upgrades include browserify to v10, typescript to v4.3, autoprefixer to v10, and lerna to v4.

Plugin is replaced with BasePlugin and UIPlugin

@uppy/core provided a Plugin class for creating plugins. This was used for any official plugin, but also for users who want to create their own custom plugin. However, Plugin always came bundled with Preact, even if the plugin itself didn’t add any UI elements.

As of Uppy 2.0.0, Plugin has been replaced with BasePlugin and UIPlugin. BasePlugin is the minimum you need to create a plugin and UIPlugin adds Preact for rendering user interfaces.

Interested in creating your own plugin? Check out the “writing plugins” guide.

Strict TypeScript types by default

Uppy used to have loose types by default and strict types as an opt-in. The default export was a function that returned the Uppy class, and the types came bundled with the default export (Uppy.SomeType).

import Uppy from '@uppy/core'
import Tus from '@uppy/tus'

const uppy = Uppy<Uppy.StrictTypes>()

uppy.use(Tus, {
  invalidOption: null, // this will make the compilation fail!

Uppy is now strictly typed by default and loose types have been removed. The default export is the Uppy class and not a function. This means you need to call Uppy with the new keyword when initializing it.

// ...

const uppy = new Uppy()

uppy.use(Tus, {
  invalidOption: null, // this will make the compilation fail!

Uppy types are now individual exports and should be imported separately.

import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core'

Event types

@uppy/core provides an .on method to listen to events. The types for these events were loose and allowed for invalid events to be passed, such as uppy.on('upload-errrOOOoooOOOOOrrrr').

Events have received a big update thanks to @Hawxy, making them more strict and accurate.

A breaking change was required to make this happen:

// Before:

type Meta = { myCustomMetadata: string }

// Invalid event
uppy.on<Meta>('upload-errrOOOoooOOOOOrrrr', () => {
  // ...

// After:

// Normal event signature
uppy.on('complete', (result) => {
  const successResults = result.successful

// Custom signature
type Meta = { myCustomMetadata: string }

// Notice how the custom type has now become the second argument
uppy.on<'complete', Meta>('complete', (result) => {
  // The passed type is now merged into the `meta` types.
  const meta = result.successful[0].meta.myCustomMetadata

Plugins that add their own events can merge with existing ones in @uppy/core with declare module '@uppy/core' { ... }. This is a TypeScript pattern called module augmentation. For instance, when using @uppy/dashboard:

uppy.on('dashboard:file-edit-state', (file) => {
  const fileName = file.name

Batch pre-signing URLs for AWS S3 Multipart

The @uppy/aws-s3-multipart plugin can be used to upload files directly to an S3 bucket using S3’s Multipart upload strategy. With this strategy, files are chopped up in parts of 5MB+ each, so they can be uploaded concurrently. It is also very reliable: if a single part fails to upload, only that 5MB chunk has to be retried.

However, in Uppy 1.0, every part had to make the trip to the server to generate a pre-signed URL. This meant that a 1GB file uploaded in 5MB chunks would require two hundred trips to the server.

As of Uppy 2.0.0, we now pre-sign URLs in batches. That same 1GB file now only takes fifty trips to the server (if the limit was four).

This is now the new default. Thanks to @martin-brennan for this contribution!

Do you care about reliable uploads? You could also consider @uppy/tus with a self-hosted or Transloadit Tus server. Tus can resume uploads, supports smaller chunks, and offers increased upload speeds.

And more

  • The .run method on the Uppy instance has been removed. This method was already obsolete and only logged a warning. As of this major version, it no longer exists.
  • @uppy/informer now supports showing multiple notifications at the same time. The notifications themselves have also been improved.
  • Improved screen reader accessibility for checkboxes and the ‘remove file’ button for @uppy/dashboard.
  • Sort files and folders alphabetically in the Google Drive provider.
  • Polished our code base with improved eslint rules, private field methods, and other modern JavaScript features that help us write more intention-revealing and safe code.
  • To make Uppy more friendly towards new contributors, we have renamed our master branch to main.

What future remains for 1.0?

Uppy 1.0 will continue to receive bug fixes for three more months (until ), security fixes for one more year (until ), but no more new features after today. Exceptions are unlikely, but can be made – to accommodate those with commercial support contracts, for example.

That’s it!

We hope you’ll waste no time in taking Uppy 2.0 out for a walk. When you do, please let us know what you thought of it on Reddit, HN, ProductHunt, or Twitter. We’re howling at the moon to hear from you!