CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-uppy

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.

Pending
Overview
Eval results
Files

plugin-architecture.mddocs/

Plugin Architecture

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.

Capabilities

BasePlugin

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' });

UIPlugin

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 
});

Plugin Lifecycle

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>;
}

Plugin State Management

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();
  }
}

Plugin Events

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);
    }
  }
}

Plugin Options and Defaults

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...
  }
}

Core Plugin Types

/**
 * 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

docs

cloud-providers.md

core-uppy.md

file-sources.md

index.md

plugin-architecture.md

typescript-support.md

ui-plugins.md

upload-handlers.md

utility-plugins.md

tile.json