Plugins
Extend FluxMedia with custom functionality using the plugin system.
Installation
pnpm add @fluxmedia/plugins
# For image optimization / metadata extraction:
pnpm add sharp
Using Plugins
import { MediaUploader } from '@fluxmedia/core';
import {
createFileValidationPlugin,
createAnalyticsPlugin
} from '@fluxmedia/plugins';
const uploader = new MediaUploader(provider);
// Add plugins
await uploader.use(createFileValidationPlugin({
maxSize: 10 * 1024 * 1024,
allowedTypes: ['image/*']
}));
await uploader.use(createAnalyticsPlugin({
logLevel: 'info'
}));
Available Plugins
File Validation
Validates files before upload.
import { createFileValidationPlugin } from '@fluxmedia/plugins';
const plugin = createFileValidationPlugin({
allowedTypes: ['image/*', 'video/mp4'],
maxSize: 10 * 1024 * 1024, // 10MB
blockedExtensions: ['.exe', '.bat'],
useMagicBytes: true,
});
Image Optimization (Server-side)
Optimizes images using sharp before upload.
import { createImageOptimizationPlugin } from '@fluxmedia/plugins';
const plugin = createImageOptimizationPlugin({
maxWidth: 2000,
maxHeight: 2000,
quality: 0.85,
format: 'webp',
});
Metadata Extraction
Extracts EXIF data, dimensions, and file hashes. Requires sharp as a peer dependency for image metadata.
import { createMetadataExtractionPlugin } from '@fluxmedia/plugins';
const plugin = createMetadataExtractionPlugin({
extractExif: true, // EXIF data (orientation, camera, GPS)
extractDimensions: true, // Image width/height
hashFile: true, // Generate file hash
hashAlgorithm: 'md5', // 'md5' or 'sha256'
});
Extracted metadata is added to options.metadata.extracted:
interface ExtractedMetadata {
dimensions?: { width: number; height: number };
exif?: {
make?: string;
model?: string;
orientation?: number;
dateTime?: string;
exposureTime?: number;
fNumber?: number;
iso?: number;
focalLength?: number;
gps?: { latitude?: number[]; longitude?: number[] };
};
hash?: string;
hashAlgorithm?: string;
}
This plugin is marked as optional: true — extraction failures won't block uploads.
Analytics
Logs and tracks upload operations with strongly-typed events. Environment-aware with configurable log levels.
import { createAnalyticsPlugin, type TrackFunction } from '@fluxmedia/plugins';
const plugin = createAnalyticsPlugin({
environment: 'production', // 'development' | 'production' | 'test' | 'all'
logLevel: 'info', // 'none' | 'error' | 'warn' | 'info' | 'debug'
trackPerformance: true, // Track upload duration and speed
console: true, // Log to console
// Typed track function — TypeScript verifies event/data match
track: (event, data) => {
// event: 'media.upload.started' | 'media.upload.completed' | 'media.delete.completed' | 'media.error'
myAnalytics.track(event, data);
},
// Individual callbacks
onUploadStart: (file, options) => { /* ... */ },
onUploadComplete: (result, durationMs) => { /* ... */ },
onUploadError: (error, file) => { /* ... */ },
onDelete: (id) => { /* ... */ },
});
Event Types: media.upload.started, media.upload.completed, media.delete.completed, media.error
Performance metadata is automatically added to upload results when trackPerformance: true:
result.metadata.performance = {
duration: 1234, // ms
uploadSpeed: 524288, // bytes/sec
};
This plugin is marked as optional: true — analytics failures won't block uploads.
Retry
Provides automatic retry with exponential backoff. Automatically resumes multipart uploads from PartialUploadError context.
import { createRetryPlugin, withRetry } from '@fluxmedia/plugins';
// 1. Add retry plugin to enrich upload options with retry metadata
await uploader.use(createRetryPlugin({
maxRetries: 3,
retryDelay: 1000,
exponentialBackoff: true,
retryableErrors: [
MediaErrorCode.NETWORK_ERROR,
MediaErrorCode.RATE_LIMITED,
MediaErrorCode.UPLOAD_FAILED,
],
onRetry: (attempt, error, delay) => {
console.log(`Retry ${attempt} after ${delay}ms`);
},
shouldRetry: (error, attempt) => {
// Custom retry logic
return attempt < 5;
},
}));
// 2. Use withRetry for automatic retry logic with PartialUploadError resume
const result = await withRetry(
(resumeContext) => uploader.upload(file, {
metadata: resumeContext ? { _resumeFrom: resumeContext } : {},
}),
{ maxRetries: 3, exponentialBackoff: true }
);
Key features:
- Exponential backoff: delays double each retry (1s → 2s → 4s → ...)
PartialUploadErrorawareness:withRetrycaptures the upload context and passes it to the next attempt, enabling multipart upload resumption- Custom
shouldRetryfunction for fine-grained control getRetryConfig(options)to read retry metadata from upload options
Creating Custom Plugins
Using createPlugin Helper
import { createPlugin } from '@fluxmedia/core';
const myPlugin = createPlugin('my-plugin', {
beforeUpload: async (file, options) => {
console.log('Uploading:', file);
return { file, options };
},
afterUpload: async (result) => {
console.log('Uploaded:', result.url);
return result;
},
onError: async (error, context) => {
console.error(`Failed in ${context.phase} phase:`, error.message);
},
}, { optional: true }); // Optional plugins degrade gracefully
Manual Plugin Definition
import type { FluxMediaPlugin } from '@fluxmedia/core';
const watermarkPlugin: FluxMediaPlugin = {
name: 'watermark',
version: '1.0.0',
optional: false, // Errors will propagate (default behavior)
async init() {
// Called when registered via uploader.use()
console.log('Watermark plugin initialized');
},
async destroy() {
// Called when unregistered via uploader.plugins.unregister()
console.log('Watermark plugin destroyed');
},
hooks: {
beforeUpload: async (file, options) => {
// Add watermark to image...
return { file: watermarkedFile, options };
},
},
};
Plugin Lifecycle
Registration & Initialization
When a plugin is registered with uploader.use():
- If a plugin with the same name exists, it is unregistered first (calling
destroy()) - The new plugin's
init()method is called (if defined) - The plugin is stored in the plugin manager
Optional Plugins
Plugins with optional: true enable graceful degradation:
const plugin = createPlugin('non-critical', {
afterUpload: async (result) => {
await sendToAnalytics(result); // If this throws...
return result;
},
}, { optional: true }); // ...the upload still succeeds
When an optional plugin's hook throws:
- The error is caught silently
- The upload pipeline continues
- Useful for analytics, logging, metadata extraction
Plugin Hooks
Hooks execute in registration order:
- beforeUpload - Modify file/options before upload. Return
{ file, options }to modify, orvoidto pass through. - afterUpload - Modify result after upload. Must return
UploadResult. - onError - Handle upload errors. Receives
phase('before','upload', or'after') and optionaluploadResult. - beforeDelete - Modify ID before deletion. Return new ID or
void. - afterDelete - Run code after deletion.
- beforeGetUrl - Modify URL generation parameters.
Plugin Manager API
// Check if plugin exists
uploader.plugins.has('logger');
// Get a plugin
const plugin = uploader.plugins.get('logger');
// Unregister (calls destroy())
await uploader.plugins.unregister('logger');
// Get all plugins
const allPlugins = uploader.plugins.getAll();
// Clear all (calls destroy() on each)
await uploader.plugins.clear();
Plugin Override Behavior
When registering plugins with the same name, the last one wins:
// First version
await uploader.use(createPlugin('analytics', {
afterUpload: async (result) => {
sendToAnalytics('v1', result);
return result;
},
}));
// Override with new version — previous version's destroy() is called
await uploader.use(createPlugin('analytics', {
afterUpload: async (result) => {
sendToAnalytics('v2', result); // This one runs
return result;
},
}));