or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

event-management.mdfile-validation.mdindex.mdplugin-development.mdtype-system.mduppy-class.md

plugin-development.mddocs/

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