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

remult-media

v0.2.0

Published

Upload and manage media files in Remult applications

Readme

Remult Media

Media upload and management module for Remult applications. Supports local filesystem and S3-compatible cloud storage with path-based access control.

Features

  • File uploads to local filesystem or S3-compatible storage
  • Framework-specific UI components (Svelte, React, Vue)
  • Path-based access control (public, authenticated, owner-only)
  • Support for custom permission functions
  • Public and private file visibility with signed URLs
  • Custom metadata support with JSON storage and querying
  • Configurable file key generation (UUID or original filename)
  • Date-based and user-based path prefixes
  • Uses Flydrive as the storage abstraction layer

Installation

npm install remult-media

Quick Start

1. Server Setup

// src/server/api.ts
import { remultApi } from 'remult/remult-express'; // or your framework adapter
import { media } from 'remult-media/server';

export const api = remultApi({
  modules: [media()]
});

Zero config uses default uploads path with public read/insert access.

2. Use Components

Svelte:

<script>
  import { UploadInput, UploadBrowser } from 'remult-media/svelte';
  import 'remult-media/styles.css';
</script>

<UploadInput />
<UploadBrowser />

React:

import { UploadInput, UploadBrowser } from 'remult-media/react';
import 'remult-media/styles.css';

function App() {
  return (
    <>
      <UploadInput />
      <UploadBrowser />
    </>
  );
}

Vue:

<script setup>
import { UploadInput, UploadBrowser } from 'remult-media/vue';
import 'remult-media/styles.css';
</script>

<template>
  <UploadInput />
  <UploadBrowser />
</template>

Configuration

Path Configuration

Each path can be configured with permissions, prefixes, storage, and key style:

media({
  paths: {
    uploads: {
      read: true,              // Permission: true, false, 'authenticated', 'owner', or function
      insert: 'authenticated',
      update: 'owner',
      delete: 'owner',
      prefix: 'YYYY/MM/DD',    // Date pattern or function
      storage: 'fs',           // Storage service name
      keyStyle: 'id'           // 'id' (default), 'fileName', or custom function
    }
  }
});

Key Style Options

The keyStyle option controls how file keys are generated:

  • 'id' (default): Uses UUID for filenames (e.g., uploads/2024/01/a1b2c3d4.jpg)
  • 'fileName': Keeps original filename, sanitized for storage (e.g., uploads/2024/01/photo.jpg)
  • Custom function: Full control over key generation
media({
  paths: {
    documents: {
      keyStyle: 'fileName',  // Keep original filenames
      read: true,
      insert: true
    },
    avatars: {
      keyStyle: 'id',  // Use UUIDs (default)
      read: true,
      insert: 'authenticated'
    },
    custom: {
      // Custom key generation
      keyStyle: ({ fileName, extension, user }) => {
        return `${user?.id || 'anon'}-${Date.now()}.${extension}`;
      },
      read: true,
      insert: 'authenticated'
    }
  }
});

Multiple Paths

media({
  paths: {
    public: {
      read: true,
      insert: true
    },
    'user-files': {
      read: 'owner',
      insert: 'authenticated',
      delete: 'owner',
      prefix: ({ user }) => user?.id || 'anonymous'
    }
  }
});

Storage Configuration

import { media, fsStorage, s3Storage } from 'remult-media/server';

media({
  paths: {
    public: { read: true, insert: true, storage: 'fs' },
    avatars: { read: true, insert: 'authenticated', storage: 's3' }
  },
  storage: {
    default: 'fs',
    services: {
      fs: fsStorage({
        location: './static/media',
        urlPrefix: '/media'
      }),
      s3: s3Storage({
        region: 'us-east-1',
        credentials: {
          accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
        },
        bucket: 'my-app-avatars',
        visibility: 'public'
      })
    }
  }
});

Custom Metadata

Files can have custom metadata attached as JSON, which can be queried using Remult's filter syntax.

Uploading with Metadata

Svelte:

<UploadInput
  path="uploads"
  meta={{ category: 'avatar', userId: '123' }}
/>

React:

<UploadInput
  path="uploads"
  meta={{ category: 'avatar', userId: '123' }}
/>

Vue:

<UploadInput
  path="uploads"
  :meta="{ category: 'avatar', userId: '123' }"
/>

Filtering by Metadata

Use the where prop to filter files by metadata:

Svelte:

<UploadBrowser
  path="uploads"
  where={{ meta: { category: 'avatar' } }}
/>

<MediaPicker
  path="uploads"
  where={{ meta: { category: 'avatar' } }}
  meta={{ category: 'avatar' }}
/>

React:

<UploadBrowser
  path="uploads"
  where={{ meta: { category: 'avatar' } }}
/>

// Or use the hook directly
const { media, upload } = useMedia({
  path: 'uploads',
  where: { meta: { category: 'avatar' } },
  meta: { category: 'avatar' }
});

Vue:

<UploadBrowser
  path="uploads"
  :where="{ meta: { category: 'avatar' } }"
/>

Querying Metadata with Remult

import { repo } from 'remult';
import { MediaWithUrl } from 'remult-media';

// Find all avatars
const avatars = await repo(MediaWithUrl).find({
  where: { meta: { category: 'avatar' } }
});

// Find files by user
const userFiles = await repo(MediaWithUrl).find({
  where: { meta: { userId: '123' } }
});

Exports

The package provides subpath exports for each framework:

| Import | Description | |--------|-------------| | remult-media | Core entities and types (Media, MediaWithUrl) | | remult-media/server | Server module (media, fsStorage, s3Storage) | | remult-media/svelte | Svelte 5 components | | remult-media/react | React components and hooks | | remult-media/vue | Vue 3 components | | remult-media/styles.css | Component styles |

Components

All framework packages export the same components:

| Component | Description | |-----------|-------------| | UploadInput | File input with drag-and-drop support. Props: path, display, meta, onUploaded | | UploadBrowser | Gallery view of uploaded files. Props: path, display, where | | MediaUpload | Combined upload input and browser | | MediaPicker | Modal picker for selecting uploaded media. Props: path, where, meta, value, onchange | | FilePreview | Single file preview component |

React Hook

import { useMedia } from 'remult-media/react';

function MyComponent() {
  const { media, loading, upload, deleteMedia, refresh } = useMedia({
    path: 'uploads',
    where: { meta: { category: 'photos' } },
    meta: { category: 'photos' }
  });

  const handleUpload = async (file: File) => {
    // Uses default meta from options
    await upload(file);

    // Or override with custom meta
    await upload(file, { category: 'special' });
  };

  return (
    <div>
      {media.map(m => (
        <div key={m.id}>
          <img src={m.url} alt={m.name} />
          <button onClick={() => deleteMedia(m.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

Development

# Install dependencies
pnpm install

# Build the library
pnpm build

# Run Svelte demo
pnpm dev:svelte

# Run React demo
pnpm dev:react

# Run Vue demo
pnpm dev:vue

# Type check
pnpm check

Requirements

  • Remult 3.3.1+
  • Svelte 5.0.0+ (for Svelte components)
  • React 18.0.0+ (for React components)
  • Vue 3.0.0+ (for Vue components)

License

MIT