Configuration
Every MediaModule option, the MediaService surface, and the injection tokens — in one place.
The complete configuration surface for @dudousxd/nestjs-media.
MediaModule.forRoot(options)
interface MediaModuleOptions {
// ── storage (layer 1) ──────────────────────────────────────────────
default: string; // name of the default disk
disks: Record<string, StorageDriver>; // named disks
// ── media-library (layer 2) — enabled when `store` is present ───────
store?: MediaStore;
collections?: MediaCollectionConfig[];
imageProcessor?: ImageProcessor;
// ── resumable uploads — enabled when `uploadSessions` is present ─────
uploadSessions?: UploadSessionStore;
uploadTmpPrefix?: string; // default '.uploads'
tus?: { // mounts the tus HTTP controller
disk: string;
basePath?: string; // default '/media/uploads'
maxSize?: number; // reject larger creations (bytes)
keyFor?: (filename: string, token: string, metadata: Record<string, string>) => string;
};
// ── direct (presigned multipart) uploads — needs an S3-capable disk ──
direct?: { // mounts the direct upload controller
disk: string;
basePath?: string;
partSize?: number; // default 8 MiB
};
// ── guarding the upload controllers ──────────────────────────────────
guards?: Type<CanActivate>[] | MediaUploadGuards; // plain array = uniform, object = per-surface
imports?: DynamicModule['imports']; // modules providing DI deps for `guards`
}| Option | Required | Purpose |
|---|---|---|
default | ✅ | Which disk media.disk() returns with no name |
disks | ✅ | The named disks (local, s3, custom, …) |
store | — | Enables media.library; omit for raw-storage-only |
collections | — | Per-collection rules + conversions |
imageProcessor | — | Required only if any collection declares conversions |
uploadSessions | — | Enables media.uploads (the resumable engine) |
uploadTmpPrefix | — | Where in-progress chunks are staged on the disk |
tus | — | Mounts MediaUploadController (needs uploadSessions) |
direct | — | Enables media.directUploads + mounts MediaDirectUploadController (needs a presign/multipart disk, e.g. S3) |
guards | — | Gate the upload controllers; plain array = uniform, { tus?, multipart?, direct? } = per-surface. Omitted → unauthenticated. |
imports | — | Modules providing DI deps for guards (or anything else you inject into them) |
MediaUploadGuards
Per-surface guard lists for the guards option — each key gates one controller; an omitted key leaves that controller unguarded (it does not fall back to another surface's guards). The plain-array form of guards gates all three uniformly instead.
interface MediaUploadGuards {
tus?: Type<CanActivate>[]; // MediaUploadController (tus session create/HEAD/PATCH)
multipart?: Type<CanActivate>[]; // MediaMultipartUploadController (part PUTs + /complete)
direct?: Type<CanActivate>[]; // MediaDirectUploadController (S3 presign endpoints)
}MediaModule.forRootAsync(options)
Same options, resolved through a factory with DI — except guards and mount, which are static fields on the config object itself, not part of what useFactory resolves (controllers, and the enhancers bound to them, are wired at module-build time, before any async factory runs):
interface MediaModuleAsyncOptions {
imports?: any[];
inject?: any[];
useFactory: (...args: any[]) => MediaModuleOptions | Promise<MediaModuleOptions>;
guards?: Type<CanActivate>[] | MediaUploadGuards; // static — see MediaModuleOptions.guards
mount?: { tus?: boolean; multipart?: boolean; direct?: boolean }; // static, default all true
}Use this whenever a disk or store needs an injected dependency (an S3Client, a DataSource, config service).
`mount` is forRootAsync-only
forRootAsync can't know at module-build time whether tus/direct will end up configured by useFactory, so by default it mounts all three controllers and each 501s if its feature is left unconfigured. Set the ones you never use to false in mount so they don't exist as dead, always-501 surface — e.g. mount: { direct: false }. forRoot doesn't need this: it already only mounts a controller when the matching option is present.
MediaCollectionConfig
interface MediaCollectionConfig {
name: string;
single?: boolean; // attaching replaces existing media
disk?: string; // override the target disk for this collection
acceptsMimeTypes?: string[]; // allow-list, else MimeNotAllowedError
conversions?: ConversionPreset[];
}
interface ConversionPreset {
name: string;
width?: number; height?: number;
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
format?: 'jpeg' | 'png' | 'webp' | 'avif';
quality?: number;
eager?: boolean; // generate on attach instead of on first access
}MediaService
class MediaService {
disk(name?: string): StorageDriver; // layer 1
get library(): MediaLibrary; // layer 2 — throws if no store configured
get attachments(): AttachmentManager; // attachment-as-column API — always available
get uploads(): ResumableUploadManager; // resumable proxy — throws if no uploadSessions configured
get directUploads(): DirectUploadManager; // direct multipart — throws if no direct configured
}Why the getters throw
library, uploads, and directUploads throw a clear, actionable error when their feature wasn't configured, rather than handing you undefined. The boundary is explicit: if you didn't pass a store, media.library tells you so by name.
MediaLibrary
attach(input: AttachInput): Promise<MediaRecord>;
ensureConversion(id: string, conversion: string): Promise<MediaRecord>;
list(ownerType: string, ownerId: string, collection?: string): Promise<MediaRecord[]>;
delete(id: string): Promise<void>;
url(id: string, conversion?: string): Promise<string>;
interface AttachInput {
ownerType: string; ownerId: string; collection: string;
fileName: string; mimeType: string; contents: Buffer | Readable;
size?: number; name?: string;
customProperties?: Record<string, unknown>;
disk?: string;
}Injection tokens
For advanced wiring, the module also exports tokens you can inject directly:
import { MEDIA_STORAGE, MEDIA_LIBRARY, MEDIA_ATTACHMENTS, MEDIA_UPLOADS, MEDIA_TUS, MEDIA_DIRECT } from '@dudousxd/nestjs-media';| Token | Resolves to |
|---|---|
MEDIA_STORAGE | StorageManager |
MEDIA_LIBRARY | MediaLibrary | null |
MEDIA_ATTACHMENTS | AttachmentManager |
MEDIA_UPLOADS | ResumableUploadManager | null |
MEDIA_TUS | TusUploadHandler | null |
MEDIA_DIRECT | DirectUploadManager | null |
Error codes
| Error | code |
|---|---|
FileNotFoundError | MEDIA_FILE_NOT_FOUND |
UnknownDiskError | MEDIA_UNKNOWN_DISK |
UnsupportedOperationError | MEDIA_UNSUPPORTED_OP |
MimeNotAllowedError | MEDIA_MIME_NOT_ALLOWED |
MediaNotFoundError | MEDIA_RECORD_NOT_FOUND |
ConversionNotDefinedError | MEDIA_CONVERSION_NOT_DEFINED |
ImageProcessorMissingError | MEDIA_IMAGE_PROCESSOR_MISSING |
UploadSessionNotFoundError | MEDIA_UPLOAD_SESSION_NOT_FOUND |
UploadOffsetConflictError | MEDIA_UPLOAD_OFFSET_CONFLICT |