0
# Plugin Development
1
2
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.
3
4
## BasePlugin Class
5
6
Base class for all Uppy plugins, providing core functionality without DOM rendering capabilities.
7
8
### Constructor
9
10
```typescript { .api }
11
constructor(uppy: Uppy<M, B>, opts?: Opts)
12
```
13
14
**Parameters:**
15
- `uppy`: Uppy instance the plugin will be attached to
16
- `opts`: Plugin-specific configuration options
17
18
### Core Properties
19
20
```typescript { .api }
21
uppy: Uppy<M, B>; // Uppy instance reference
22
opts: Opts; // Plugin options
23
id: string; // Plugin identifier
24
type: string; // Plugin type
25
VERSION: string; // Plugin version
26
defaultLocale: OptionalPluralizeLocale; // Default locale
27
i18n: I18n; // Translation function
28
i18nArray: Translator['translateArray']; // Array translation
29
```
30
31
### State Management
32
33
```typescript { .api }
34
getPluginState(): PluginState;
35
setPluginState(update?: Partial<PluginState>): void;
36
```
37
38
**Usage:**
39
- `getPluginState()`: Returns plugin's slice of global state
40
- `setPluginState()`: Updates plugin state and triggers UI re-render
41
42
### Options Management
43
44
```typescript { .api }
45
setOptions(newOpts: Partial<Opts>): void;
46
```
47
48
Updates plugin options and triggers localization re-initialization.
49
50
### Internationalization
51
52
```typescript { .api }
53
i18nInit(): void;
54
```
55
56
Initializes translation functions with locale hierarchy: default locale → Uppy locale → plugin locale.
57
58
### Lifecycle Hooks
59
60
```typescript { .api }
61
install(): void;
62
uninstall(): void;
63
update(state: Partial<State<M, B>>): void;
64
afterUpdate(): void;
65
```
66
67
**Hook Descriptions:**
68
- `install()`: Called when plugin is added to Uppy instance
69
- `uninstall()`: Called when plugin is removed from Uppy instance
70
- `update(state)`: Called on every state change with state patch
71
- `afterUpdate()`: Called after all updates complete (debounced)
72
73
### Plugin Target Management
74
75
```typescript { .api }
76
addTarget(plugin: UnknownPlugin<M, B>): HTMLElement | null;
77
```
78
79
Override this method to integrate with other plugins that provide UI targets.
80
81
## UIPlugin Class
82
83
Extended plugin class with Preact rendering capabilities for user interface components.
84
85
### Constructor
86
87
```typescript { .api }
88
constructor(uppy: Uppy<M, B>, opts?: UIPluginOptions)
89
```
90
91
Inherits all BasePlugin functionality with additional UI-specific options.
92
93
### Additional Properties
94
95
```typescript { .api }
96
el: HTMLElement | null; // DOM element reference
97
isTargetDOMEl: boolean; // Whether target is DOM element
98
parent: unknown; // Parent component reference
99
```
100
101
### UI Rendering
102
103
```typescript { .api }
104
abstract render(): ComponentChild;
105
```
106
107
**Must be implemented** by subclasses to define the plugin's UI structure using Preact components.
108
109
### Mount and Unmount
110
111
```typescript { .api }
112
mount(target: PluginTarget, plugin: UnknownPlugin<M, B>): void;
113
unmount(): void;
114
```
115
116
**Parameters:**
117
- `target`: DOM selector, element, or plugin to mount to
118
- `plugin`: Plugin instance requesting the mount
119
120
### Target Override
121
122
```typescript { .api }
123
addTarget(plugin: UnknownPlugin<M, B>): HTMLElement | null;
124
```
125
126
Returns DOM element where other plugins can mount their UI.
127
128
## Plugin Options Types
129
130
### Base Plugin Options
131
132
```typescript { .api }
133
interface PluginOpts {
134
locale?: OptionalPluralizeLocale;
135
id?: string;
136
}
137
```
138
139
### UI Plugin Options
140
141
```typescript { .api }
142
interface UIPluginOptions extends PluginOpts {
143
target?: PluginTarget;
144
}
145
146
type PluginTarget =
147
| string // CSS selector
148
| Element // DOM element
149
| UnknownPlugin<any, any> // Another plugin
150
```
151
152
### Advanced Plugin Options
153
154
```typescript { .api }
155
type DefinePluginOpts<
156
Opts,
157
AlwaysDefinedKeys extends keyof OnlyOptionals<Opts>
158
> = Opts & Required<Pick<Opts, AlwaysDefinedKeys>>;
159
```
160
161
Use `DefinePluginOpts` to mark certain optional properties as required for your plugin.
162
163
## Plugin State Types
164
165
```typescript { .api }
166
type UnknownPlugin<
167
M extends Meta,
168
B extends Body,
169
PluginState extends Record<string, unknown> = Record<string, unknown>
170
> = BasePlugin<any, M, B, PluginState>;
171
```
172
173
## Plugin Development Examples
174
175
### Basic Non-UI Plugin
176
177
```typescript
178
import { BasePlugin, type PluginOpts } from '@uppy/core';
179
180
interface MyPluginOptions extends PluginOpts {
181
apiKey?: string;
182
timeout?: number;
183
}
184
185
interface MyPluginState {
186
isProcessing: boolean;
187
lastResult?: any;
188
}
189
190
class MyPlugin extends BasePlugin<MyPluginOptions, Meta, Body, MyPluginState> {
191
static VERSION = '1.0.0';
192
193
type = 'myPlugin';
194
id = 'MyPlugin';
195
196
defaultOptions: Partial<MyPluginOptions> = {
197
timeout: 30000
198
};
199
200
constructor(uppy, opts) {
201
super(uppy, { ...this.defaultOptions, ...opts });
202
this.id = this.opts.id || this.id;
203
}
204
205
install() {
206
// Setup event listeners
207
this.uppy.on('file-added', this.handleFileAdded);
208
}
209
210
uninstall() {
211
// Cleanup
212
this.uppy.off('file-added', this.handleFileAdded);
213
}
214
215
handleFileAdded = (file) => {
216
this.setPluginState({ isProcessing: true });
217
218
// Process file
219
this.processFile(file).then(result => {
220
this.setPluginState({
221
isProcessing: false,
222
lastResult: result
223
});
224
});
225
};
226
227
async processFile(file) {
228
// Custom processing logic
229
return new Promise(resolve => {
230
setTimeout(() => resolve({ processed: true }), 1000);
231
});
232
}
233
}
234
235
export default MyPlugin;
236
```
237
238
### UI Plugin with Preact
239
240
```typescript
241
import { UIPlugin, type UIPluginOptions } from '@uppy/core';
242
import { h } from 'preact';
243
244
interface MyUIPluginOptions extends UIPluginOptions {
245
buttonText?: string;
246
}
247
248
class MyUIPlugin extends UIPlugin<MyUIPluginOptions, Meta, Body> {
249
static VERSION = '1.0.0';
250
251
type = 'myUIPlugin';
252
id = 'MyUIPlugin';
253
254
defaultOptions: Partial<MyUIPluginOptions> = {
255
buttonText: 'Process Files'
256
};
257
258
constructor(uppy, opts) {
259
super(uppy, { ...this.defaultOptions, ...opts });
260
this.id = this.opts.id || this.id;
261
}
262
263
render() {
264
const { buttonText } = this.opts;
265
const files = this.uppy.getFiles();
266
267
return h('div', { className: 'my-plugin' }, [
268
h('h3', null, 'My Custom Plugin'),
269
h('p', null, `Files: ${files.length}`),
270
h('button', {
271
onClick: this.handleButtonClick,
272
disabled: files.length === 0
273
}, buttonText)
274
]);
275
}
276
277
install() {
278
const { target } = this.opts;
279
if (target) {
280
this.mount(target, this);
281
}
282
}
283
284
uninstall() {
285
this.unmount();
286
}
287
288
handleButtonClick = () => {
289
const files = this.uppy.getFiles();
290
console.log('Processing files:', files);
291
292
// Custom processing logic
293
files.forEach(file => {
294
this.uppy.setFileState(file.id, {
295
meta: { ...file.meta, processed: true }
296
});
297
});
298
};
299
}
300
301
export default MyUIPlugin;
302
```
303
304
### Provider Plugin Pattern
305
306
```typescript
307
import { BasePlugin } from '@uppy/core';
308
309
interface ProviderPluginOptions extends PluginOpts {
310
companionUrl: string;
311
companionHeaders?: Record<string, string>;
312
}
313
314
class MyProviderPlugin extends BasePlugin<ProviderPluginOptions, Meta, Body> {
315
static VERSION = '1.0.0';
316
317
type = 'provider';
318
id = 'MyProvider';
319
320
constructor(uppy, opts) {
321
super(uppy, opts);
322
this.requests = new Map();
323
}
324
325
install() {
326
this.uppy.addUploader(this.uploadFiles);
327
}
328
329
uninstall() {
330
this.uppy.removeUploader(this.uploadFiles);
331
// Cancel any ongoing requests
332
this.requests.forEach(request => request.abort());
333
this.requests.clear();
334
}
335
336
uploadFiles = async (fileIDs, uploadID) => {
337
const files = fileIDs.map(id => this.uppy.getFile(id));
338
339
for (const file of files) {
340
try {
341
await this.uploadFile(file, uploadID);
342
} catch (error) {
343
this.uppy.emit('upload-error', file, error);
344
}
345
}
346
};
347
348
async uploadFile(file, uploadID) {
349
const { companionUrl, companionHeaders } = this.opts;
350
351
// Implementation would interact with Companion server
352
const response = await fetch(`${companionUrl}/upload`, {
353
method: 'POST',
354
headers: companionHeaders,
355
body: file.data
356
});
357
358
if (!response.ok) {
359
throw new Error(`Upload failed: ${response.statusText}`);
360
}
361
362
const result = await response.json();
363
this.uppy.emit('upload-success', file, { body: result });
364
}
365
}
366
367
export default MyProviderPlugin;
368
```
369
370
### Plugin Usage
371
372
```typescript
373
import Uppy from '@uppy/core';
374
import MyPlugin from './MyPlugin';
375
import MyUIPlugin from './MyUIPlugin';
376
377
const uppy = new Uppy()
378
.use(MyPlugin, {
379
apiKey: 'your-api-key',
380
timeout: 60000
381
})
382
.use(MyUIPlugin, {
383
target: '#my-plugin-container',
384
buttonText: 'Custom Process'
385
});
386
387
// Access plugin instances
388
const myPlugin = uppy.getPlugin('MyPlugin');
389
const myUIPlugin = uppy.getPlugin('MyUIPlugin');
390
391
// Plugin state access
392
console.log(myPlugin.getPluginState());
393
```
394
395
## Plugin Best Practices
396
397
### State Management
398
- Use `getPluginState()` and `setPluginState()` for plugin-specific state
399
- Keep state minimal and focused on UI needs
400
- Trigger state updates when UI needs to refresh
401
402
### Event Handling
403
- Always clean up event listeners in `uninstall()`
404
- Use arrow functions for event handlers to maintain `this` context
405
- Emit custom events for plugin-specific actions
406
407
### Error Handling
408
- Catch and handle all async operations
409
- Use `uppy.info()` for user-facing error messages
410
- Emit appropriate Uppy events for upload errors
411
412
### Internationalization
413
- Provide `defaultLocale` with all translatable strings
414
- Use `this.i18n()` for single strings, `this.i18nArray()` for arrays
415
- Support pluralization where appropriate
416
417
### Performance
418
- Use `afterUpdate()` for expensive operations that don't need immediate execution
419
- Debounce frequent updates to avoid excessive re-renders
420
- Clean up resources in `uninstall()` to prevent memory leaks