npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nestjs-dash/media

v1.1.0

Published

Polymorphic media library plugin for NestJS Dash (Spatie-style media collections on top of @nestjs-dash/storage)

Readme

@nestjs-dash/media

A polymorphic media library plugin for NestJS Dash — Spatie-laravel-medialibrary-style media collections (attach many files to any model, grouped into named collections like avatar or gallery) built on top of @nestjs-dash/storage's disks.

Docs: Media library.

When you need this

Only if you turn on media.enabled — most apps can use @nestjs-dash/storage's upload fields directly without this plugin. Reach for @nestjs-dash/media when a record needs multiple attached files organized into named collections, rather than a single upload field per column.

Install

pnpm add @nestjs-dash/media

Requires @nestjs-dash/storage to be configured (this package stores files through it), a TypeORM dataSource, and typeorm (media records persist to a TypeORM table). List MediaEntity in your TypeORM entities: [...] explicitly.

Usage

media (and storage) are top-level AdminModule options — siblings of panel:

import { MediaEntity } from '@nestjs-dash/media';
import { AdminModule } from '@nestjs-dash/nestjs';
import { DataSource } from 'typeorm';

TypeOrmModule.forRootAsync({
  useFactory: () => ({
    entities: [, /* your entities */ MediaEntity],
  }),
});

AdminModule.forRootAsync({
  inject: [DataSource],
  useFactory: (dataSource) => ({
    dataSource,
    media: { enabled: true },
    storage: {
      defaultDisk: 'local',
      disks: { local: { driver: 'local', root: './storage' } },
    },
    panel: {
      // ...
    },
  }),
});
import { MediaService } from '@nestjs-dash/media';
import { StorageService } from '@nestjs-dash/storage';

@Injectable()
class ProductPhotosService {
  constructor(
    private readonly media: MediaService,
    private readonly storage: StorageService,
  ) {}

  async attachPhoto(product: ProductEntity, file: Express.Multer.File) {
    const stored = await this.storage.put(file.buffer, {
      directory: 'gallery',
      fileName: file.originalname,
      contentType: file.mimetype,
    });
    await this.media.addFromUpload({ modelType: 'Product', modelId: product.id }, 'gallery', {
      path: stored.path,
      disk: stored.disk,
      fileName: file.originalname,
      mimeType: file.mimetype,
      size: stored.size,
    });
  }
}

Field helpers: .collection('gallery').multiple() on ImageUpload / FileUpload (vs plain .disk() for a single column path).

Configuration

| Option | Where | Description | | --- | --- | --- | | media.enabled | AdminModule options | Must be true; otherwise reads return [] and remove() is a no-op. | | dataSource | AdminModule options | TypeORM DataSource with MediaEntity registered (table nestjs_dash_media). | | storage.disks / storage.defaultDisk | AdminModule options | Disks media may be stored on. A media disk must name one of these. |

API (MediaService)

| Method | Notes | | --- | --- | | getMedia(owner, collection) / getFirst(owner, collection) | Rows ordered by orderColumn, then id. | | addFromUpload(owner, collection, upload) | Appends at the end of the collection. Throws BadRequestException if upload.disk is not a configured disk or upload.path is unsafe (see below). | | remove(mediaId, owner?) | Returns true if deleted. When owner ({ modelType, modelId, collection? }) is given, the row must belong to it or nothing happens (false). Always pass it for request-driven deletes. | | clearCollection(owner, collection) | Deletes every row (and file) in the collection. | | reorder(owner, collection, ids) | Listed ids first, in that order; unlisted items keep their relative order after them. Ids from other owners are ignored. | | syncCollection(owner, collection, items, defaults?) | Validates all new items first, saves kept/new items, then deletes rows missing from items. Ids from other owners are ignored. | | toClientItems(owner, collection) | Serialised items with URLs; an item whose disk can no longer be resolved gets url: ''. | | assertSafeMediaPath(path) | Exported helper; throws BadRequestException for unsafe keys. |

Deleting a row removes the database row first and then the stored file (best-effort, logged on failure). The file is kept if another media row still references the same disk + path.

Security considerations

  • Ownership checks. Always derive owner from the route (resource slug + record id) after authorizing access to that record, and pass it to remove(mediaId, owner). Calling remove(mediaId) without an owner deletes any media row by id and must only be used from trusted server code.
  • Client-supplied paths. addFromUpload / syncCollection reject unconfigured disks and paths that are empty, absolute (/x, C:/x), contain backslashes, NUL bytes, or . / .. segments. This prevents traversal but does not prove the caller uploaded the file: a client could still reference another existing key on the same disk. Prefer passing the path returned by your own StorageService.put() call, or restrict accepted keys to a per-upload directory/token on the server.
  • Accept lists are server-side. accept/MIME hints on upload fields only filter the browser's file picker. Validate MIME type and extension on the server (in the upload endpoint) before storing; never trust mimeType sent by the client.
  • Size limits. This package does not enforce file sizes; the size value it stores is informational. Enforce limits in the upload endpoint (for example multer limits.fileSize) and at your reverse proxy.
  • Serve private disks through signed URLs (visibility: 'private') rather than the public file route.

License

MIT