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