0
# Plugin Architecture
1
2
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.
3
4
## Capabilities
5
6
### BasePlugin
7
8
Foundation class for all Uppy plugins providing core plugin functionality and lifecycle management.
9
10
```typescript { .api }
11
/**
12
* Base class for all Uppy plugins
13
* @template Options - Plugin configuration options type
14
* @template State - Plugin state type
15
* @template Events - Plugin events type
16
*/
17
abstract class BasePlugin<Options = {}, State = {}, Events = {}> {
18
/**
19
* Create plugin instance
20
* @param uppy - Uppy instance
21
* @param options - Plugin configuration options
22
*/
23
constructor(uppy: Uppy<any, any>, options?: Options);
24
25
/**
26
* Plugin identifier
27
*/
28
readonly id: string;
29
30
/**
31
* Plugin type identifier
32
*/
33
readonly type: string;
34
35
/**
36
* Reference to parent Uppy instance
37
*/
38
readonly uppy: Uppy<any, any>;
39
40
/**
41
* Plugin configuration options
42
*/
43
readonly opts: Options;
44
45
/**
46
* Install plugin - called when plugin is added to Uppy
47
*/
48
abstract install(): void;
49
50
/**
51
* Uninstall plugin - called when plugin is removed from Uppy
52
*/
53
abstract uninstall(): void;
54
55
/**
56
* Update plugin options
57
* @param newOptions - Partial options to merge
58
*/
59
protected setOptions(newOptions: Partial<Options>): void;
60
61
/**
62
* Get plugin-specific state
63
* @returns Current plugin state
64
*/
65
protected getPluginState(): State;
66
67
/**
68
* Update plugin-specific state
69
* @param patch - Partial state to merge
70
*/
71
protected setPluginState(patch: Partial<State>): void;
72
}
73
```
74
75
**Usage Example:**
76
77
```typescript
78
import { BasePlugin } from "uppy";
79
80
interface MyPluginOptions {
81
apiKey: string;
82
timeout?: number;
83
}
84
85
interface MyPluginState {
86
isConnected: boolean;
87
lastSync: number;
88
}
89
90
class MyPlugin extends BasePlugin<MyPluginOptions, MyPluginState> {
91
constructor(uppy, options) {
92
super(uppy, {
93
timeout: 5000,
94
...options
95
});
96
97
this.id = 'MyPlugin';
98
this.type = 'utility';
99
}
100
101
install() {
102
// Set initial state
103
this.setPluginState({
104
isConnected: false,
105
lastSync: 0
106
});
107
108
// Listen to Uppy events
109
this.uppy.on('file-added', this.handleFileAdded.bind(this));
110
}
111
112
uninstall() {
113
// Clean up event listeners
114
this.uppy.off('file-added', this.handleFileAdded.bind(this));
115
}
116
117
private handleFileAdded(file) {
118
console.log('File added:', file.name);
119
this.setPluginState({ lastSync: Date.now() });
120
}
121
}
122
123
// Use the plugin
124
const uppy = new Uppy();
125
uppy.use(MyPlugin, { apiKey: 'abc123' });
126
```
127
128
### UIPlugin
129
130
Extended base class for plugins that render user interface components, providing additional methods for DOM management.
131
132
```typescript { .api }
133
/**
134
* Base class for UI plugins that render components
135
* @template Options - Plugin configuration options type
136
* @template State - Plugin state type
137
*/
138
abstract class UIPlugin<Options = {}, State = {}> extends BasePlugin<Options, State> {
139
/**
140
* Create UI plugin instance
141
* @param uppy - Uppy instance
142
* @param options - Plugin configuration options
143
*/
144
constructor(uppy: Uppy<any, any>, options?: Options);
145
146
/**
147
* Render the plugin UI component
148
* @returns Preact component or element
149
*/
150
protected render(): ComponentChild;
151
152
/**
153
* Mount the plugin to a DOM target
154
* @param target - CSS selector or DOM element
155
* @param plugin - Plugin instance to mount
156
*/
157
protected mount(target: string | Element, plugin: UIPlugin<any, any>): void;
158
159
/**
160
* Unmount the plugin from DOM
161
*/
162
protected unmount(): void;
163
164
/**
165
* Force re-render of the plugin UI
166
*/
167
protected rerender(): void;
168
169
/**
170
* Get plugin DOM element
171
* @returns Plugin root element or null
172
*/
173
protected getElement(): Element | null;
174
}
175
```
176
177
**Usage Example:**
178
179
```typescript
180
import { UIPlugin } from "uppy";
181
import { h } from "preact";
182
183
interface FileCounterOptions {
184
target: string;
185
showTotal?: boolean;
186
}
187
188
class FileCounter extends UIPlugin<FileCounterOptions> {
189
constructor(uppy, options) {
190
super(uppy, {
191
showTotal: true,
192
...options
193
});
194
195
this.id = 'FileCounter';
196
this.type = 'ui';
197
}
198
199
install() {
200
const target = this.opts.target;
201
if (target) {
202
this.mount(target, this);
203
}
204
205
// Re-render on file changes
206
this.uppy.on('file-added', this.rerender.bind(this));
207
this.uppy.on('file-removed', this.rerender.bind(this));
208
}
209
210
uninstall() {
211
this.unmount();
212
this.uppy.off('file-added', this.rerender.bind(this));
213
this.uppy.off('file-removed', this.rerender.bind(this));
214
}
215
216
render() {
217
const files = this.uppy.getFiles();
218
const fileCount = files.length;
219
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
220
221
return h('div', { className: 'file-counter' }, [
222
h('span', null, `Files: ${fileCount}`),
223
this.opts.showTotal && h('span', null, ` (${this.formatBytes(totalSize)})`)
224
]);
225
}
226
227
private formatBytes(bytes) {
228
if (bytes === 0) return '0 Bytes';
229
const k = 1024;
230
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
231
const i = Math.floor(Math.log(bytes) / Math.log(k));
232
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
233
}
234
}
235
236
// Use the UI plugin
237
const uppy = new Uppy();
238
uppy.use(FileCounter, {
239
target: '#file-counter',
240
showTotal: true
241
});
242
```
243
244
### Plugin Lifecycle
245
246
Standard lifecycle methods and hooks for plugin development.
247
248
```typescript { .api }
249
/**
250
* Plugin lifecycle interface
251
*/
252
interface PluginLifecycle {
253
/**
254
* Called when plugin is added to Uppy instance
255
* Use for: event listeners, initial state, DOM setup
256
*/
257
install(): void;
258
259
/**
260
* Called when plugin is removed from Uppy instance
261
* Use for: cleanup, removing listeners, DOM cleanup
262
*/
263
uninstall(): void;
264
265
/**
266
* Called after all plugins are installed (optional)
267
* Use for: inter-plugin communication setup
268
*/
269
afterInstall?(): void;
270
271
/**
272
* Called before upload starts (optional)
273
* Use for: validation, file preprocessing
274
*/
275
prepareUpload?(): Promise<void>;
276
}
277
```
278
279
### Plugin State Management
280
281
Plugins maintain isolated state that integrates with Uppy's central state management.
282
283
```typescript { .api }
284
/**
285
* Plugin state management methods
286
*/
287
interface PluginStateManager<State> {
288
/**
289
* Get current plugin state
290
* @returns Plugin state object
291
*/
292
getPluginState(): State;
293
294
/**
295
* Update plugin state with patch
296
* @param patch - Partial state to merge
297
*/
298
setPluginState(patch: Partial<State>): void;
299
300
/**
301
* Reset plugin state to defaults
302
*/
303
resetPluginState(): void;
304
}
305
```
306
307
**Usage Example:**
308
309
```typescript
310
interface UploadStatsState {
311
uploadCount: number;
312
totalBytes: number;
313
averageSpeed: number;
314
}
315
316
class UploadStats extends BasePlugin<{}, UploadStatsState> {
317
install() {
318
// Initialize state
319
this.setPluginState({
320
uploadCount: 0,
321
totalBytes: 0,
322
averageSpeed: 0
323
});
324
325
this.uppy.on('upload-success', this.trackUpload.bind(this));
326
}
327
328
private trackUpload(file, response) {
329
const currentState = this.getPluginState();
330
const newSpeed = this.calculateSpeed(file.size, file.progress.uploadStarted);
331
332
this.setPluginState({
333
uploadCount: currentState.uploadCount + 1,
334
totalBytes: currentState.totalBytes + file.size,
335
averageSpeed: (currentState.averageSpeed + newSpeed) / 2
336
});
337
}
338
339
getStats() {
340
return this.getPluginState();
341
}
342
}
343
```
344
345
### Plugin Events
346
347
Plugins can emit and listen to custom events through the Uppy event system.
348
349
```typescript { .api }
350
/**
351
* Plugin event handling
352
*/
353
interface PluginEventEmitter {
354
/**
355
* Emit plugin-specific event
356
* @param event - Event name (should be prefixed with plugin ID)
357
* @param data - Event data
358
*/
359
emit(event: string, ...data: any[]): void;
360
361
/**
362
* Listen to Uppy or other plugin events
363
* @param event - Event name
364
* @param handler - Event handler function
365
*/
366
on(event: string, handler: (...args: any[]) => void): void;
367
368
/**
369
* Remove event listener
370
* @param event - Event name
371
* @param handler - Event handler to remove
372
*/
373
off(event: string, handler: (...args: any[]) => void): void;
374
}
375
```
376
377
**Usage Example:**
378
379
```typescript
380
class NotificationPlugin extends BasePlugin {
381
install() {
382
// Listen to upload events
383
this.uppy.on('upload-success', this.showSuccess.bind(this));
384
this.uppy.on('upload-error', this.showError.bind(this));
385
386
// Listen to custom plugin events
387
this.uppy.on('notification:show', this.displayNotification.bind(this));
388
}
389
390
private showSuccess(file) {
391
// Emit custom event
392
this.uppy.emit('notification:show', {
393
type: 'success',
394
message: `${file.name} uploaded successfully`,
395
timeout: 3000
396
});
397
}
398
399
private showError(file, error) {
400
this.uppy.emit('notification:show', {
401
type: 'error',
402
message: `Failed to upload ${file.name}: ${error.message}`,
403
timeout: 5000
404
});
405
}
406
407
private displayNotification(notification) {
408
// Display notification in UI
409
console.log(`[${notification.type.toUpperCase()}] ${notification.message}`);
410
411
if (notification.timeout) {
412
setTimeout(() => {
413
this.uppy.emit('notification:hide', notification);
414
}, notification.timeout);
415
}
416
}
417
}
418
```
419
420
### Plugin Options and Defaults
421
422
Standard pattern for handling plugin configuration with defaults.
423
424
```typescript { .api }
425
/**
426
* Plugin options management
427
*/
428
interface PluginOptionsHandler<Options> {
429
/**
430
* Default options for the plugin
431
*/
432
readonly defaultOptions: Options;
433
434
/**
435
* Update plugin options after instantiation
436
* @param newOptions - Partial options to merge
437
*/
438
setOptions(newOptions: Partial<Options>): void;
439
440
/**
441
* Get current plugin options
442
* @returns Current options object
443
*/
444
getOptions(): Options;
445
}
446
```
447
448
**Usage Example:**
449
450
```typescript
451
interface CompressionOptions {
452
quality: number;
453
maxWidth: number;
454
maxHeight: number;
455
mimeType: string;
456
}
457
458
class ImageCompression extends BasePlugin<CompressionOptions> {
459
constructor(uppy, options) {
460
const defaultOptions: CompressionOptions = {
461
quality: 0.8,
462
maxWidth: 1920,
463
maxHeight: 1080,
464
mimeType: 'image/jpeg'
465
};
466
467
super(uppy, {
468
...defaultOptions,
469
...options
470
});
471
}
472
473
// Update compression quality dynamically
474
setQuality(quality: number) {
475
this.setOptions({ quality });
476
}
477
478
install() {
479
this.uppy.on('preprocess-progress', this.compressImage.bind(this));
480
}
481
482
private compressImage(file) {
483
if (!file.type.startsWith('image/')) return;
484
485
const { quality, maxWidth, maxHeight, mimeType } = this.opts;
486
// Compression logic here...
487
}
488
}
489
```
490
491
## Core Plugin Types
492
493
```typescript { .api }
494
/**
495
* Plugin type categories
496
*/
497
type PluginType =
498
| 'ui' // User interface plugins (Dashboard, FileInput, etc.)
499
| 'source' // File source plugins (GoogleDrive, Webcam, etc.)
500
| 'uploader' // Upload handler plugins (Tus, XHRUpload, etc.)
501
| 'utility' // Utility plugins (ThumbnailGenerator, Form, etc.)
502
| 'custom'; // Custom plugin types
503
504
/**
505
* Plugin constructor interface
506
*/
507
interface PluginConstructor<Options = {}, State = {}> {
508
new (uppy: Uppy<any, any>, options?: Options): BasePlugin<Options, State>;
509
}
510
511
/**
512
* Plugin registration options
513
*/
514
interface PluginRegistration<Options> {
515
id: string;
516
type: PluginType;
517
options?: Options;
518
}
519
```