Aviary
Reference

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`
}
OptionRequiredPurpose
defaultWhich disk media.disk() returns with no name
disksThe named disks (local, s3, custom, …)
storeEnables media.library; omit for raw-storage-only
collectionsPer-collection rules + conversions
imageProcessorRequired only if any collection declares conversions
uploadSessionsEnables media.uploads (the resumable engine)
uploadTmpPrefixWhere in-progress chunks are staged on the disk
tusMounts MediaUploadController (needs uploadSessions)
directEnables media.directUploads + mounts MediaDirectUploadController (needs a presign/multipart disk, e.g. S3)
guardsGate the upload controllers; plain array = uniform, { tus?, multipart?, direct? } = per-surface. Omitted → unauthenticated.
importsModules 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';
TokenResolves to
MEDIA_STORAGEStorageManager
MEDIA_LIBRARYMediaLibrary | null
MEDIA_ATTACHMENTSAttachmentManager
MEDIA_UPLOADSResumableUploadManager | null
MEDIA_TUSTusUploadHandler | null
MEDIA_DIRECTDirectUploadManager | null

Error codes

Errorcode
FileNotFoundErrorMEDIA_FILE_NOT_FOUND
UnknownDiskErrorMEDIA_UNKNOWN_DISK
UnsupportedOperationErrorMEDIA_UNSUPPORTED_OP
MimeNotAllowedErrorMEDIA_MIME_NOT_ALLOWED
MediaNotFoundErrorMEDIA_RECORD_NOT_FOUND
ConversionNotDefinedErrorMEDIA_CONVERSION_NOT_DEFINED
ImageProcessorMissingErrorMEDIA_IMAGE_PROCESSOR_MISSING
UploadSessionNotFoundErrorMEDIA_UPLOAD_SESSION_NOT_FOUND
UploadOffsetConflictErrorMEDIA_UPLOAD_OFFSET_CONFLICT

On this page