Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more.
—
Uppy's plugin architecture provides a modular system for extending functionality through base classes and standardized interfaces. Plugins can add UI components, file sources, upload handlers, or utility functions to the core Uppy instance.
Foundation class for all Uppy plugins providing core plugin functionality and lifecycle management.
/**
* Base class for all Uppy plugins
* @template Options - Plugin configuration options type
* @template State - Plugin state type
* @template Events - Plugin events type
*/
abstract class BasePlugin<Options = {}, State = {}, Events = {}> {
/**
* Create plugin instance
* @param uppy - Uppy instance
* @param options - Plugin configuration options
*/
constructor(uppy: Uppy<any, any>, options?: Options);
/**
* Plugin identifier
*/
readonly id: string;
/**
* Plugin type identifier
*/
readonly type: string;
/**
* Reference to parent Uppy instance
*/
readonly uppy: Uppy<any, any>;
/**
* Plugin configuration options
*/
readonly opts: Options;
/**
* Install plugin - called when plugin is added to Uppy
*/
abstract install(): void;
/**
* Uninstall plugin - called when plugin is removed from Uppy
*/
abstract uninstall(): void;
/**
* Update plugin options
* @param newOptions - Partial options to merge
*/
protected setOptions(newOptions: Partial<Options>): void;
/**
* Get plugin-specific state
* @returns Current plugin state
*/
protected getPluginState(): State;
/**
* Update plugin-specific state
* @param patch - Partial state to merge
*/
protected setPluginState(patch: Partial<State>): void;
}Usage Example:
import { BasePlugin } from "uppy";
interface MyPluginOptions {
apiKey: string;
timeout?: number;
}
interface MyPluginState {
isConnected: boolean;
lastSync: number;
}
class MyPlugin extends BasePlugin<MyPluginOptions, MyPluginState> {
constructor(uppy, options) {
super(uppy, {
timeout: 5000,
...options
});
this.id = 'MyPlugin';
this.type = 'utility';
}
install() {
// Set initial state
this.setPluginState({
isConnected: false,
lastSync: 0
});
// Listen to Uppy events
this.uppy.on('file-added', this.handleFileAdded.bind(this));
}
uninstall() {
// Clean up event listeners
this.uppy.off('file-added', this.handleFileAdded.bind(this));
}
private handleFileAdded(file) {
console.log('File added:', file.name);
this.setPluginState({ lastSync: Date.now() });
}
}
// Use the plugin
const uppy = new Uppy();
uppy.use(MyPlugin, { apiKey: 'abc123' });Extended base class for plugins that render user interface components, providing additional methods for DOM management.
/**
* Base class for UI plugins that render components
* @template Options - Plugin configuration options type
* @template State - Plugin state type
*/
abstract class UIPlugin<Options = {}, State = {}> extends BasePlugin<Options, State> {
/**
* Create UI plugin instance
* @param uppy - Uppy instance
* @param options - Plugin configuration options
*/
constructor(uppy: Uppy<any, any>, options?: Options);
/**
* Render the plugin UI component
* @returns Preact component or element
*/
protected render(): ComponentChild;
/**
* Mount the plugin to a DOM target
* @param target - CSS selector or DOM element
* @param plugin - Plugin instance to mount
*/
protected mount(target: string | Element, plugin: UIPlugin<any, any>): void;
/**
* Unmount the plugin from DOM
*/
protected unmount(): void;
/**
* Force re-render of the plugin UI
*/
protected rerender(): void;
/**
* Get plugin DOM element
* @returns Plugin root element or null
*/
protected getElement(): Element | null;
}Usage Example:
import { UIPlugin } from "uppy";
import { h } from "preact";
interface FileCounterOptions {
target: string;
showTotal?: boolean;
}
class FileCounter extends UIPlugin<FileCounterOptions> {
constructor(uppy, options) {
super(uppy, {
showTotal: true,
...options
});
this.id = 'FileCounter';
this.type = 'ui';
}
install() {
const target = this.opts.target;
if (target) {
this.mount(target, this);
}
// Re-render on file changes
this.uppy.on('file-added', this.rerender.bind(this));
this.uppy.on('file-removed', this.rerender.bind(this));
}
uninstall() {
this.unmount();
this.uppy.off('file-added', this.rerender.bind(this));
this.uppy.off('file-removed', this.rerender.bind(this));
}
render() {
const files = this.uppy.getFiles();
const fileCount = files.length;
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
return h('div', { className: 'file-counter' }, [
h('span', null, `Files: ${fileCount}`),
this.opts.showTotal && h('span', null, ` (${this.formatBytes(totalSize)})`)
]);
}
private formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// Use the UI plugin
const uppy = new Uppy();
uppy.use(FileCounter, {
target: '#file-counter',
showTotal: true
});Standard lifecycle methods and hooks for plugin development.
/**
* Plugin lifecycle interface
*/
interface PluginLifecycle {
/**
* Called when plugin is added to Uppy instance
* Use for: event listeners, initial state, DOM setup
*/
install(): void;
/**
* Called when plugin is removed from Uppy instance
* Use for: cleanup, removing listeners, DOM cleanup
*/
uninstall(): void;
/**
* Called after all plugins are installed (optional)
* Use for: inter-plugin communication setup
*/
afterInstall?(): void;
/**
* Called before upload starts (optional)
* Use for: validation, file preprocessing
*/
prepareUpload?(): Promise<void>;
}Plugins maintain isolated state that integrates with Uppy's central state management.
/**
* Plugin state management methods
*/
interface PluginStateManager<State> {
/**
* Get current plugin state
* @returns Plugin state object
*/
getPluginState(): State;
/**
* Update plugin state with patch
* @param patch - Partial state to merge
*/
setPluginState(patch: Partial<State>): void;
/**
* Reset plugin state to defaults
*/
resetPluginState(): void;
}Usage Example:
interface UploadStatsState {
uploadCount: number;
totalBytes: number;
averageSpeed: number;
}
class UploadStats extends BasePlugin<{}, UploadStatsState> {
install() {
// Initialize state
this.setPluginState({
uploadCount: 0,
totalBytes: 0,
averageSpeed: 0
});
this.uppy.on('upload-success', this.trackUpload.bind(this));
}
private trackUpload(file, response) {
const currentState = this.getPluginState();
const newSpeed = this.calculateSpeed(file.size, file.progress.uploadStarted);
this.setPluginState({
uploadCount: currentState.uploadCount + 1,
totalBytes: currentState.totalBytes + file.size,
averageSpeed: (currentState.averageSpeed + newSpeed) / 2
});
}
getStats() {
return this.getPluginState();
}
}Plugins can emit and listen to custom events through the Uppy event system.
/**
* Plugin event handling
*/
interface PluginEventEmitter {
/**
* Emit plugin-specific event
* @param event - Event name (should be prefixed with plugin ID)
* @param data - Event data
*/
emit(event: string, ...data: any[]): void;
/**
* Listen to Uppy or other plugin events
* @param event - Event name
* @param handler - Event handler function
*/
on(event: string, handler: (...args: any[]) => void): void;
/**
* Remove event listener
* @param event - Event name
* @param handler - Event handler to remove
*/
off(event: string, handler: (...args: any[]) => void): void;
}Usage Example:
class NotificationPlugin extends BasePlugin {
install() {
// Listen to upload events
this.uppy.on('upload-success', this.showSuccess.bind(this));
this.uppy.on('upload-error', this.showError.bind(this));
// Listen to custom plugin events
this.uppy.on('notification:show', this.displayNotification.bind(this));
}
private showSuccess(file) {
// Emit custom event
this.uppy.emit('notification:show', {
type: 'success',
message: `${file.name} uploaded successfully`,
timeout: 3000
});
}
private showError(file, error) {
this.uppy.emit('notification:show', {
type: 'error',
message: `Failed to upload ${file.name}: ${error.message}`,
timeout: 5000
});
}
private displayNotification(notification) {
// Display notification in UI
console.log(`[${notification.type.toUpperCase()}] ${notification.message}`);
if (notification.timeout) {
setTimeout(() => {
this.uppy.emit('notification:hide', notification);
}, notification.timeout);
}
}
}Standard pattern for handling plugin configuration with defaults.
/**
* Plugin options management
*/
interface PluginOptionsHandler<Options> {
/**
* Default options for the plugin
*/
readonly defaultOptions: Options;
/**
* Update plugin options after instantiation
* @param newOptions - Partial options to merge
*/
setOptions(newOptions: Partial<Options>): void;
/**
* Get current plugin options
* @returns Current options object
*/
getOptions(): Options;
}Usage Example:
interface CompressionOptions {
quality: number;
maxWidth: number;
maxHeight: number;
mimeType: string;
}
class ImageCompression extends BasePlugin<CompressionOptions> {
constructor(uppy, options) {
const defaultOptions: CompressionOptions = {
quality: 0.8,
maxWidth: 1920,
maxHeight: 1080,
mimeType: 'image/jpeg'
};
super(uppy, {
...defaultOptions,
...options
});
}
// Update compression quality dynamically
setQuality(quality: number) {
this.setOptions({ quality });
}
install() {
this.uppy.on('preprocess-progress', this.compressImage.bind(this));
}
private compressImage(file) {
if (!file.type.startsWith('image/')) return;
const { quality, maxWidth, maxHeight, mimeType } = this.opts;
// Compression logic here...
}
}/**
* Plugin type categories
*/
type PluginType =
| 'ui' // User interface plugins (Dashboard, FileInput, etc.)
| 'source' // File source plugins (GoogleDrive, Webcam, etc.)
| 'uploader' // Upload handler plugins (Tus, XHRUpload, etc.)
| 'utility' // Utility plugins (ThumbnailGenerator, Form, etc.)
| 'custom'; // Custom plugin types
/**
* Plugin constructor interface
*/
interface PluginConstructor<Options = {}, State = {}> {
new (uppy: Uppy<any, any>, options?: Options): BasePlugin<Options, State>;
}
/**
* Plugin registration options
*/
interface PluginRegistration<Options> {
id: string;
type: PluginType;
options?: Options;
}Install with Tessl CLI
npx tessl i tessl/npm-uppy