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

file-validation.mddocs/

0

# File Validation and Restrictions

1

2

Uppy provides comprehensive file validation through a configurable restrictions system that supports both individual file constraints and aggregate restrictions across all files.

3

4

## Restrictions Interface

5

6

```typescript { .api }

7

interface Restrictions {

8

maxFileSize: number | null; // Maximum individual file size in bytes

9

minFileSize: number | null; // Minimum individual file size in bytes

10

maxTotalFileSize: number | null; // Maximum total size of all files

11

maxNumberOfFiles: number | null; // Maximum number of files

12

minNumberOfFiles: number | null; // Minimum number of files

13

allowedFileTypes: string[] | null; // Allowed MIME types/extensions

14

requiredMetaFields: string[]; // Required metadata fields

15

}

16

```

17

18

### Default Values

19

20

All restriction values default to `null` (no restriction) except:

21

- `requiredMetaFields`: defaults to empty array `[]`

22

23

## Validation Methods

24

25

### Individual File Validation

26

27

```typescript { .api }

28

validateSingleFile(file: ValidateableFile<M, B>): string | null;

29

```

30

31

Validates a single file against individual restrictions (size, type, etc.).

32

33

**Parameters:**

34

- `file`: File to validate

35

36

**Returns:** Error message string if validation fails, `null` if valid

37

38

### Aggregate Validation

39

40

```typescript { .api }

41

validateAggregateRestrictions(files: ValidateableFile<M, B>[]): string | null;

42

```

43

44

Validates aggregate restrictions across all files (total size, file count).

45

46

**Parameters:**

47

- `files`: Array of all files to validate

48

49

**Returns:** Error message string if validation fails, `null` if valid

50

51

### Complete Validation

52

53

```typescript { .api }

54

validateRestrictions(

55

file: ValidateableFile<M, B>,

56

files?: ValidateableFile<M, B>[]

57

): RestrictionError<M, B> | null;

58

```

59

60

Performs both individual and aggregate validation.

61

62

**Parameters:**

63

- `file`: File to validate

64

- `files`: All files for aggregate validation (optional)

65

66

**Returns:** `RestrictionError` object if validation fails, `null` if valid

67

68

## ValidateableFile Type

69

70

```typescript { .api }

71

type ValidateableFile<M extends Meta, B extends Body> = Pick<

72

UppyFile<M, B>,

73

'type' | 'extension' | 'size' | 'name'

74

> & {

75

isGhost?: boolean; // Optional ghost file marker

76

};

77

```

78

79

Minimal file interface required for validation. Both `UppyFile` and `CompanionFile` objects can be validated.

80

81

## RestrictionError Class

82

83

```typescript { .api }

84

class RestrictionError<M extends Meta, B extends Body> extends Error {

85

isUserFacing: boolean; // Whether error should be shown to user

86

file?: UppyFile<M, B>; // Associated file (if applicable)

87

isRestriction: true; // Marker property for error type identification

88

89

constructor(

90

message: string,

91

opts?: {

92

isUserFacing?: boolean;

93

file?: UppyFile<M, B>;

94

}

95

);

96

}

97

```

98

99

**Properties:**

100

- `isUserFacing`: Defaults to `true`, indicates if error should be displayed to user

101

- `file`: Optional file reference for file-specific errors

102

- `isRestriction`: Always `true`, used to identify restriction errors

103

104

## File Type Validation

105

106

### MIME Type Patterns

107

108

```typescript

109

// Exact MIME types

110

allowedFileTypes: ['image/jpeg', 'image/png', 'text/plain']

111

112

// Wildcard patterns

113

allowedFileTypes: ['image/*', 'video/*', 'audio/*']

114

115

// Mixed patterns

116

allowedFileTypes: ['image/*', 'application/pdf', 'text/plain']

117

```

118

119

### File Extension Patterns

120

121

```typescript

122

// File extensions

123

allowedFileTypes: ['.jpg', '.jpeg', '.png', '.gif']

124

125

// Mixed MIME types and extensions

126

allowedFileTypes: ['image/*', '.pdf', '.doc', '.docx']

127

```

128

129

### Complex Type Restrictions

130

131

```typescript

132

// Multiple specific types

133

allowedFileTypes: [

134

'image/jpeg',

135

'image/png',

136

'image/gif',

137

'application/pdf',

138

'text/plain',

139

'text/csv'

140

]

141

142

// Category-based with exceptions

143

allowedFileTypes: ['image/*', 'video/*', 'application/pdf']

144

```

145

146

## Size Restrictions

147

148

### Individual File Size

149

150

```typescript

151

// Maximum 5MB per file

152

maxFileSize: 5 * 1024 * 1024

153

154

// Minimum 1KB per file

155

minFileSize: 1024

156

157

// Both min and max

158

restrictions: {

159

minFileSize: 1024, // 1KB minimum

160

maxFileSize: 10485760 // 10MB maximum

161

}

162

```

163

164

### Total Size Restrictions

165

166

```typescript

167

// Maximum 50MB total across all files

168

maxTotalFileSize: 50 * 1024 * 1024

169

170

// Combine with individual limits

171

restrictions: {

172

maxFileSize: 5 * 1024 * 1024, // 5MB per file

173

maxTotalFileSize: 25 * 1024 * 1024 // 25MB total

174

}

175

```

176

177

## File Count Restrictions

178

179

```typescript

180

// Maximum 10 files

181

maxNumberOfFiles: 10

182

183

// Minimum 2 files required

184

minNumberOfFiles: 2

185

186

// Range restrictions

187

restrictions: {

188

minNumberOfFiles: 1,

189

maxNumberOfFiles: 5

190

}

191

```

192

193

## Required Metadata Fields

194

195

```typescript

196

// Require specific metadata fields

197

requiredMetaFields: ['name', 'description', 'category']

198

199

// Usage with custom metadata

200

const uppy = new Uppy({

201

restrictions: {

202

requiredMetaFields: ['author', 'tags']

203

},

204

meta: {

205

author: '', // Default values

206

tags: ''

207

}

208

});

209

210

// Files must have these metadata fields set

211

uppy.setFileMeta(fileId, {

212

author: 'John Doe',

213

tags: 'important,work'

214

});

215

```

216

217

## Validation Examples

218

219

### Basic Restrictions Setup

220

221

```typescript

222

import Uppy from '@uppy/core';

223

224

const uppy = new Uppy({

225

restrictions: {

226

maxFileSize: 2 * 1024 * 1024, // 2MB per file

227

maxNumberOfFiles: 3, // Maximum 3 files

228

allowedFileTypes: ['image/*'], // Images only

229

requiredMetaFields: ['caption'] // Require caption

230

}

231

});

232

233

// Listen for validation failures

234

uppy.on('restriction-failed', (file, error) => {

235

console.log('Validation failed:', error.message);

236

237

// Handle specific error types

238

if (error.message.includes('size')) {

239

showError('File is too large. Maximum size is 2MB.');

240

} else if (error.message.includes('type')) {

241

showError('Only image files are allowed.');

242

} else if (error.message.includes('meta')) {

243

showError('Please provide a caption for your image.');

244

}

245

});

246

```

247

248

### Custom Validation Logic

249

250

```typescript

251

const uppy = new Uppy({

252

restrictions: {

253

maxFileSize: 5 * 1024 * 1024,

254

allowedFileTypes: ['image/*', 'application/pdf']

255

},

256

onBeforeFileAdded: (currentFile, files) => {

257

// Custom validation beyond standard restrictions

258

259

// Check filename pattern

260

if (!currentFile.name.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9]+$/)) {

261

uppy.info('Filename can only contain letters, numbers, hyphens, and underscores', 'error');

262

return false;

263

}

264

265

// Check for duplicates

266

const isDuplicate = Object.values(files).some(

267

file => file.name === currentFile.name

268

);

269

if (isDuplicate) {

270

uppy.info(`File "${currentFile.name}" is already added`, 'error');

271

return false;

272

}

273

274

// Custom business logic

275

if (currentFile.type.startsWith('image/') && currentFile.size < 10000) {

276

uppy.info('Image files must be at least 10KB', 'error');

277

return false;

278

}

279

280

return true;

281

}

282

});

283

```

284

285

### Dynamic Restrictions

286

287

```typescript

288

class DynamicUploadRestrictions {

289

private uppy: Uppy;

290

private userPlan: 'free' | 'pro' | 'enterprise';

291

292

constructor(userPlan: 'free' | 'pro' | 'enterprise') {

293

this.userPlan = userPlan;

294

this.uppy = new Uppy();

295

this.updateRestrictions();

296

}

297

298

updateRestrictions() {

299

const restrictions = this.getRestrictionsForPlan(this.userPlan);

300

this.uppy.setOptions({ restrictions });

301

}

302

303

getRestrictionsForPlan(plan: string): Partial<Restrictions> {

304

switch (plan) {

305

case 'free':

306

return {

307

maxFileSize: 1024 * 1024, // 1MB

308

maxNumberOfFiles: 3,

309

maxTotalFileSize: 5 * 1024 * 1024, // 5MB

310

allowedFileTypes: ['image/*']

311

};

312

313

case 'pro':

314

return {

315

maxFileSize: 10 * 1024 * 1024, // 10MB

316

maxNumberOfFiles: 20,

317

maxTotalFileSize: 100 * 1024 * 1024, // 100MB

318

allowedFileTypes: ['image/*', 'video/*', 'application/pdf']

319

};

320

321

case 'enterprise':

322

return {

323

maxFileSize: 100 * 1024 * 1024, // 100MB

324

maxNumberOfFiles: null, // Unlimited

325

maxTotalFileSize: null, // Unlimited

326

allowedFileTypes: null // All types

327

};

328

329

default:

330

return {};

331

}

332

}

333

334

upgradeUser(newPlan: 'free' | 'pro' | 'enterprise') {

335

this.userPlan = newPlan;

336

this.updateRestrictions();

337

338

// Revalidate existing files

339

const files = this.uppy.getFiles();

340

files.forEach(file => {

341

const error = this.uppy.validateRestrictions(file, files);

342

if (error) {

343

this.uppy.emit('restriction-failed', file, error);

344

}

345

});

346

}

347

}

348

```

349

350

### File Type Detection

351

352

```typescript

353

// Enhanced file type validation

354

const uppy = new Uppy({

355

restrictions: {

356

allowedFileTypes: ['image/*']

357

},

358

onBeforeFileAdded: (currentFile, files) => {

359

// Validate actual file content, not just extension

360

return new Promise((resolve) => {

361

if (currentFile.type.startsWith('image/')) {

362

// For images, verify it's actually an image

363

const img = new Image();

364

img.onload = () => resolve(true);

365

img.onerror = () => {

366

uppy.info('File appears corrupted or is not a valid image', 'error');

367

resolve(false);

368

};

369

img.src = URL.createObjectURL(currentFile.data);

370

} else {

371

resolve(true);

372

}

373

});

374

}

375

});

376

```

377

378

### Validation with User Feedback

379

380

```typescript

381

const uppy = new Uppy({

382

restrictions: {

383

maxFileSize: 5 * 1024 * 1024,

384

maxNumberOfFiles: 10,

385

allowedFileTypes: ['image/*', 'video/*', 'application/pdf']

386

}

387

});

388

389

uppy.on('restriction-failed', (file, error) => {

390

const fileName = file?.name || 'Unknown file';

391

392

// Provide detailed, user-friendly error messages

393

if (error.message.includes('size')) {

394

const maxSize = formatBytes(5 * 1024 * 1024);

395

const fileSize = formatBytes(file?.size || 0);

396

showDetailedError(

397

'File Too Large',

398

`"${fileName}" (${fileSize}) exceeds the maximum allowed size of ${maxSize}.`,

399

'Please choose a smaller file or compress the current file.'

400

);

401

} else if (error.message.includes('type')) {

402

showDetailedError(

403

'File Type Not Allowed',

404

`"${fileName}" is not an allowed file type.`,

405

'Please select an image, video, or PDF file.'

406

);

407

} else if (error.message.includes('number')) {

408

showDetailedError(

409

'Too Many Files',

410

'You can upload a maximum of 10 files at once.',

411

'Please remove some files and try again.'

412

);

413

}

414

});

415

416

function formatBytes(bytes: number): string {

417

if (bytes === 0) return '0 Bytes';

418

const k = 1024;

419

const sizes = ['Bytes', 'KB', 'MB', 'GB'];

420

const i = Math.floor(Math.log(bytes) / Math.log(k));

421

return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];

422

}

423

424

function showDetailedError(title: string, message: string, suggestion: string) {

425

// Display rich error message to user

426

const errorDialog = document.createElement('div');

427

errorDialog.innerHTML = `

428

<div class="error-dialog">

429

<h3>${title}</h3>

430

<p><strong>${message}</strong></p>

431

<p><em>${suggestion}</em></p>

432

<button onclick="this.parentElement.remove()">OK</button>

433

</div>

434

`;

435

document.body.appendChild(errorDialog);

436

}

437

```

438

439

### Batch Validation

440

441

```typescript

442

// Pre-upload validation of entire batch

443

uppy.on('upload', (files) => {

444

const fileArray = Object.values(files);

445

446

// Validate aggregate restrictions manually

447

const totalSize = fileArray.reduce((sum, file) => sum + (file.size || 0), 0);

448

const maxTotal = 50 * 1024 * 1024; // 50MB

449

450

if (totalSize > maxTotal) {

451

uppy.info(

452

`Total file size (${formatBytes(totalSize)}) exceeds limit (${formatBytes(maxTotal)})`,

453

'error'

454

);

455

return false; // Cancel upload

456

}

457

458

// Check for required metadata on all files

459

const missingMeta = fileArray.filter(file =>

460

!file.meta.description || file.meta.description.trim() === ''

461

);

462

463

if (missingMeta.length > 0) {

464

uppy.info(

465

`${missingMeta.length} file(s) missing required description`,

466

'error'

467

);

468

return false;

469

}

470

471

return true;

472

});

473

```

474

475

## Validation Best Practices

476

477

### User Experience

478

- Provide clear, actionable error messages

479

- Show file size limits in human-readable format

480

- Validate files immediately when added, not just before upload

481

- Allow users to fix validation errors easily

482

483

### Performance

484

- Use client-side validation to reduce server load

485

- Implement progressive validation (quick checks first)

486

- Cache validation results when possible

487

- Avoid expensive validation operations in tight loops

488

489

### Security

490

- Always validate file types by content, not just extension

491

- Implement server-side validation as well as client-side

492

- Be cautious with file metadata validation

493

- Consider implementing virus scanning for uploaded files

494

495

### Accessibility

496

- Ensure error messages are accessible to screen readers

497

- Provide multiple ways to understand restrictions (text, icons, examples)

498

- Make restriction information discoverable before file selection