CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-uppy--core

Core module for the 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-development.mddocs/

Plugin Development

Uppy's plugin system allows extending functionality through BasePlugin and UIPlugin classes. Plugins can add new upload sources, processing capabilities, UI components, or modify upload behavior.

BasePlugin Class

Base class for all Uppy plugins, providing core functionality without DOM rendering capabilities.

Constructor

constructor(uppy: Uppy<M, B>, opts?: Opts)

Parameters:

  • uppy: Uppy instance the plugin will be attached to
  • opts: Plugin-specific configuration options

Core Properties

uppy: Uppy<M, B>;                    // Uppy instance reference
opts: Opts;                          // Plugin options
id: string;                          // Plugin identifier
type: string;                        // Plugin type
VERSION: string;                     // Plugin version
defaultLocale: OptionalPluralizeLocale; // Default locale
i18n: I18n;                         // Translation function
i18nArray: Translator['translateArray']; // Array translation

State Management

getPluginState(): PluginState;
setPluginState(update?: Partial<PluginState>): void;

Usage:

  • getPluginState(): Returns plugin's slice of global state
  • setPluginState(): Updates plugin state and triggers UI re-render

Options Management

setOptions(newOpts: Partial<Opts>): void;

Updates plugin options and triggers localization re-initialization.

Internationalization

i18nInit(): void;

Initializes translation functions with locale hierarchy: default locale → Uppy locale → plugin locale.

Lifecycle Hooks

install(): void;
uninstall(): void;
update(state: Partial<State<M, B>>): void;
afterUpdate(): void;

Hook Descriptions:

  • install(): Called when plugin is added to Uppy instance
  • uninstall(): Called when plugin is removed from Uppy instance
  • update(state): Called on every state change with state patch
  • afterUpdate(): Called after all updates complete (debounced)

Plugin Target Management

addTarget(plugin: UnknownPlugin<M, B>): HTMLElement | null;

Override this method to integrate with other plugins that provide UI targets.

UIPlugin Class

Extended plugin class with Preact rendering capabilities for user interface components.

Constructor

constructor(uppy: Uppy<M, B>, opts?: UIPluginOptions)

Inherits all BasePlugin functionality with additional UI-specific options.

Additional Properties

el: HTMLElement | null;              // DOM element reference
isTargetDOMEl: boolean;             // Whether target is DOM element
parent: unknown;                    // Parent component reference

UI Rendering

abstract render(): ComponentChild;

Must be implemented by subclasses to define the plugin's UI structure using Preact components.

Mount and Unmount

mount(target: PluginTarget, plugin: UnknownPlugin<M, B>): void;
unmount(): void;

Parameters:

  • target: DOM selector, element, or plugin to mount to
  • plugin: Plugin instance requesting the mount

Target Override

addTarget(plugin: UnknownPlugin<M, B>): HTMLElement | null;

Returns DOM element where other plugins can mount their UI.

Plugin Options Types

Base Plugin Options

interface PluginOpts {
  locale?: OptionalPluralizeLocale;
  id?: string;
}

UI Plugin Options

interface UIPluginOptions extends PluginOpts {
  target?: PluginTarget;
}

type PluginTarget = 
  | string                    // CSS selector
  | Element                   // DOM element
  | UnknownPlugin<any, any>   // Another plugin

Advanced Plugin Options

type DefinePluginOpts<
  Opts,
  AlwaysDefinedKeys extends keyof OnlyOptionals<Opts>
> = Opts & Required<Pick<Opts, AlwaysDefinedKeys>>;

Use DefinePluginOpts to mark certain optional properties as required for your plugin.

Plugin State Types

type UnknownPlugin<
  M extends Meta,
  B extends Body,
  PluginState extends Record<string, unknown> = Record<string, unknown>
> = BasePlugin<any, M, B, PluginState>;

Plugin Development Examples

Basic Non-UI Plugin

import { BasePlugin, type PluginOpts } from '@uppy/core';

interface MyPluginOptions extends PluginOpts {
  apiKey?: string;
  timeout?: number;
}

interface MyPluginState {
  isProcessing: boolean;
  lastResult?: any;
}

class MyPlugin extends BasePlugin<MyPluginOptions, Meta, Body, MyPluginState> {
  static VERSION = '1.0.0';
  
  type = 'myPlugin';
  id = 'MyPlugin';
  
  defaultOptions: Partial<MyPluginOptions> = {
    timeout: 30000
  };

  constructor(uppy, opts) {
    super(uppy, { ...this.defaultOptions, ...opts });
    this.id = this.opts.id || this.id;
  }

  install() {
    // Setup event listeners
    this.uppy.on('file-added', this.handleFileAdded);
  }

  uninstall() {
    // Cleanup
    this.uppy.off('file-added', this.handleFileAdded);
  }

  handleFileAdded = (file) => {
    this.setPluginState({ isProcessing: true });
    
    // Process file
    this.processFile(file).then(result => {
      this.setPluginState({ 
        isProcessing: false,
        lastResult: result 
      });
    });
  };

  async processFile(file) {
    // Custom processing logic
    return new Promise(resolve => {
      setTimeout(() => resolve({ processed: true }), 1000);
    });
  }
}

export default MyPlugin;

UI Plugin with Preact

import { UIPlugin, type UIPluginOptions } from '@uppy/core';
import { h } from 'preact';

interface MyUIPluginOptions extends UIPluginOptions {
  buttonText?: string;
}

class MyUIPlugin extends UIPlugin<MyUIPluginOptions, Meta, Body> {
  static VERSION = '1.0.0';
  
  type = 'myUIPlugin';
  id = 'MyUIPlugin';
  
  defaultOptions: Partial<MyUIPluginOptions> = {
    buttonText: 'Process Files'
  };

  constructor(uppy, opts) {
    super(uppy, { ...this.defaultOptions, ...opts });
    this.id = this.opts.id || this.id;
  }

  render() {
    const { buttonText } = this.opts;
    const files = this.uppy.getFiles();
    
    return h('div', { className: 'my-plugin' }, [
      h('h3', null, 'My Custom Plugin'),
      h('p', null, `Files: ${files.length}`),
      h('button', {
        onClick: this.handleButtonClick,
        disabled: files.length === 0
      }, buttonText)
    ]);
  }

  install() {
    const { target } = this.opts;
    if (target) {
      this.mount(target, this);
    }
  }

  uninstall() {
    this.unmount();
  }

  handleButtonClick = () => {
    const files = this.uppy.getFiles();
    console.log('Processing files:', files);
    
    // Custom processing logic
    files.forEach(file => {
      this.uppy.setFileState(file.id, {
        meta: { ...file.meta, processed: true }
      });
    });
  };
}

export default MyUIPlugin;

Provider Plugin Pattern

import { BasePlugin } from '@uppy/core';

interface ProviderPluginOptions extends PluginOpts {
  companionUrl: string;
  companionHeaders?: Record<string, string>;
}

class MyProviderPlugin extends BasePlugin<ProviderPluginOptions, Meta, Body> {
  static VERSION = '1.0.0';
  
  type = 'provider';
  id = 'MyProvider';

  constructor(uppy, opts) {
    super(uppy, opts);
    this.requests = new Map();
  }

  install() {
    this.uppy.addUploader(this.uploadFiles);
  }

  uninstall() {
    this.uppy.removeUploader(this.uploadFiles);
    // Cancel any ongoing requests
    this.requests.forEach(request => request.abort());
    this.requests.clear();
  }

  uploadFiles = async (fileIDs, uploadID) => {
    const files = fileIDs.map(id => this.uppy.getFile(id));
    
    for (const file of files) {
      try {
        await this.uploadFile(file, uploadID);
      } catch (error) {
        this.uppy.emit('upload-error', file, error);
      }
    }
  };

  async uploadFile(file, uploadID) {
    const { companionUrl, companionHeaders } = this.opts;
    
    // Implementation would interact with Companion server
    const response = await fetch(`${companionUrl}/upload`, {
      method: 'POST',
      headers: companionHeaders,
      body: file.data
    });

    if (!response.ok) {
      throw new Error(`Upload failed: ${response.statusText}`);
    }

    const result = await response.json();
    this.uppy.emit('upload-success', file, { body: result });
  }
}

export default MyProviderPlugin;

Plugin Usage

import Uppy from '@uppy/core';
import MyPlugin from './MyPlugin';
import MyUIPlugin from './MyUIPlugin';

const uppy = new Uppy()
  .use(MyPlugin, {
    apiKey: 'your-api-key',
    timeout: 60000
  })
  .use(MyUIPlugin, {
    target: '#my-plugin-container',
    buttonText: 'Custom Process'
  });

// Access plugin instances
const myPlugin = uppy.getPlugin('MyPlugin');
const myUIPlugin = uppy.getPlugin('MyUIPlugin');

// Plugin state access
console.log(myPlugin.getPluginState());

Plugin Best Practices

State Management

  • Use getPluginState() and setPluginState() for plugin-specific state
  • Keep state minimal and focused on UI needs
  • Trigger state updates when UI needs to refresh

Event Handling

  • Always clean up event listeners in uninstall()
  • Use arrow functions for event handlers to maintain this context
  • Emit custom events for plugin-specific actions

Error Handling

  • Catch and handle all async operations
  • Use uppy.info() for user-facing error messages
  • Emit appropriate Uppy events for upload errors

Internationalization

  • Provide defaultLocale with all translatable strings
  • Use this.i18n() for single strings, this.i18nArray() for arrays
  • Support pluralization where appropriate

Performance

  • Use afterUpdate() for expensive operations that don't need immediate execution
  • Debounce frequent updates to avoid excessive re-renders
  • Clean up resources in uninstall() to prevent memory leaks

Install with Tessl CLI

npx tessl i tessl/npm-uppy--core

docs

event-management.md

file-validation.md

index.md

plugin-development.md

type-system.md

uppy-class.md

tile.json