Flag of Ukraine   We stand with the brave people of Ukraine. Stop the war. Find out how you can help.

Custom Stores

This section concerns storing the internal application state, if you work with React/Redux, for example. If none of this rings a bell, you can safely skip this section.

By default, Uppy stores its internal state in an object.

If your app uses a state management library such as Redux, it can be useful to have Uppy store its state there instead—that way, you could write custom uploader UI components in the same way as the other components in the application.

Uppy comes with two state management solutions (stores):

  • @uppy/store-default, a basic object-based store.
  • @uppy/store-redux, a store that uses a key in a Redux store.

You can also use a third-party store:

Using stores

To use a store, pass an instance to the store option in the Uppy constructor:

import DefaultStore from '@uppy/store-default'

const uppy = new Uppy({
  store: new DefaultStore(),


Uppy uses the DefaultStore…by default! You do not need to do anything to use it.
It does not take any options.


The ReduxStore stores Uppy state on a key in an existing Redux store.
The ReduxStore dispatches uppy/STATE_UPDATE actions to update state.
When the state in Redux changes, it notifies Uppy.
This way, you get most of the benefits of Redux, including support for the Redux Devtools and time traveling!

Here is how you can integrate Uppy’s ReduxStore:

import Uppy from '@uppy/core'
import * as ReduxStore from '@uppy/store-redux'
import * as Redux from 'redux'

function createStore (reducers = {}) {
  const reducer = Redux.combineReducers({ ...reducers, uppy: ReduxStore.reducer })
  return Redux.createStore(reducer)

const store = new ReduxStore.ReduxStore({ store: createStore() })
const uppy = new Uppy({ store })


Pass a Redux store instance, from Redux.createStore.
This instance should have the Uppy reducer mounted somewhere already.


By default, the ReduxStore assumes Uppy state is stored on a state.uppy[id] key.
id is randomly generated by the store constructor, but can be specified by passing an id option if it should be predictable.

  id: 'avatarUpload',


If you’d rather not store the Uppy state under the state.uppy key at all, use the selector option to the ReduxStore constructor to tell it where to find state instead:

const uppy = new Uppy({
  store: ReduxStore({
    id: 'avatarUpload',
    selector: state => state.pages.profile.uppy.avatarUpload,

Note that when specifying a custom selector, you must also specify a custom store ID. The store id tells the reducer in which property it should put Uppy’s state. The selector must then take the state from that property. In the example, we set the ID to avatarUpload and take the state from the [reducer mount path].avatarUpload.

If your app uses reselect, its selectors work well with this!

Implementing Stores

An Uppy store is an object with three methods.

  • getState() - Return the current state object.

  • setState(patch) - Merge the object patch into the current state.

  • subscribe(listener) - Call listener whenever the state changes.
    listener is a function that should receive three parameters:
    (prevState, nextState, patch)

    The subscribe() method should return a function that “unsubscribes” (removes) the listener.

The default store implementation, for example, looks a bit like this:

function createDefaultStore () {
  let state = {}
  const listeners = new Set()

  return {
    getState: () => state,
    setState: (patch) => {
      const prevState = state
      const nextState = { ...prevState, ...patch }

      state = nextState

      listeners.forEach((listener) => {
        listener(prevState, nextState, patch)
    subscribe: (listener) => {
      return () => listeners.remove(listener)

A pattern like this, where users can pass options via a function call if necessary, is recommended.

See the @uppy/store-default package for more inspiration.