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

@unciatech/file-manager

v0.0.32

Published

Modern file manager component for React and Next.js

Readme

🗂️ File Manager

A robust, production-ready React / Next.js file management system designed to mirror the capabilities of professional asset managers (like Google Drive, macOS Finder, or Strapi Media Library).

It supports deep folder nesting, drag-and-drop file uploads, metadata management for various file types (Images, Videos, Audio, Documents), unified grid layouts, and fully optimized loading states.

🌟 Key Features

  • Dual Operating Modes: Use it as a standalone full-page media library or instantiate it as a picker modal for form inputs.
  • Unified Grid View: Beautiful, responsive layout that intelligently renders thumbnails, icons, and metadata based on the file's MIME type.
  • Nested Folder Structure: Infinite folder depth with smooth virtualized/paginated fetching.
  • Provider Agnostic: Built on an IFileManagerProvider interface. You can easily hot-swap the mock data provider for a real backend (Node.js, Supabase, Strapi, etc.).
  • Bulk Actions: Select multiple files/folders at once to bulk move or bulk delete.
  • Optimistic UI Updates: Instant visual feedback when renaming folders or updating file descriptions, with silent background synchronization.
  • Graceful Error Handling: Resilient <FileManagerErrorBoundary> that captures catastrophic failures and allows users to hard-reload safely without app crashes.

🛠️ Tech Stack

  • Framework: Next.js / React
  • Styling: Tailwind CSS
  • Icons: Custom SVG Icons
  • Notifications: Sonner

[!WARNING] This library is currently in BETA. Please report any bugs or feature requests on the GitHub issues page.

🚀 How to Install and Use in Your Project

If you want to integrate this File Manager into your own Next.js or React application, follow this step-by-step guide.

Step 1: Install the Package

Install the library via NPM:

npm install @unciatech/file-manager

(Optional) ⚡ Magic Quick Start Scaffolding Instead of setting everything up manually, our init script can spawn a brand new full-stack application instantly:

npx @unciatech/file-manager init my-media-app

It will ask if you want Next.js or Vite (React), install Tailwind, install the package, and set everything up including styles!

(CRITICAL) Import the styles: The init script includes this automatically, but if you are installing manually, add this import to your root layout / entry file:

import '@unciatech/file-manager/styles';

(CRITICAL) Configure Tailwind CSS: Because this library uses Tailwind CSS, you MUST tell your Tailwind compiler to scan the library components for utility classes, otherwise it will render with zero styles!

For Tailwind v3 (tailwind.config.ts):

import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    // Your existing paths...
    "./node_modules/@unciatech/file-manager/dist/**/*.js",
    "./node_modules/@unciatech/file-manager/dist/**/*.mjs",
  ],
  // ...
};
export default config;

For Tailwind v4 (globals.css):

@import "tailwindcss";
@source "../node_modules/@unciatech/file-manager/dist";

Step 2: Create your Custom API Provider

The file manager is completely agnostic to your backend database. You simply need to create a class that implements the IFileManagerProvider interface.

Here is an example of what your custom provider might look like, making real API calls to your backend using fetch:

// lib/my-api-provider.ts
import { 
  IFileManagerProvider, 
  FolderId, 
  FileUploadInput 
} from "@/types/file-manager"; // Or "@unciatech/file-manager" for external users

export class MyCustomApiProvider implements IFileManagerProvider {
  private baseUrl = "https://api.mybackend.com/v1";

  // Example 1: Fetching folders from your Real API
  async getFolders(folderId: FolderId, page = 1, limit = 24) {
    const parentQuery = folderId ? `&parentId=${folderId}` : "&isRoot=true";
    
    // Simulate real API Call
    const res = await fetch(`${this.baseUrl}/folders?page=${page}&limit=${limit}${parentQuery}`);
    
    if (!res.ok) throw new Error("Failed to fetch folders");
    
    const data = await res.json();
    
    return {
      folders: data.folders, // Array of Folder objects matching our interface
      pagination: data.pagination // { currentPage, totalPages, totalFiles, filesPerPage }
    };
  }

  // Example 2: Uploading files via multipart/form-data
  async uploadFiles(filesInput: FileUploadInput[], folderId?: FolderId) {
    const formData = new FormData();
    if (folderId) formData.append("folderId", String(folderId));
    
    filesInput.forEach(({ file }) => {
      formData.append("files", file);
    });

    const res = await fetch(`${this.baseUrl}/upload`, {
      method: 'POST',
      body: formData
    });

    return res.json(); // Returns array of FileMetaData
  }

  // ... Implement the remaining interface methods (getFiles, renameFolder, bulkMove, etc.)
}

💡 Pro Tip - The Mock Provider: If you are just prototyping and don't have a backend ready yet, you can skip Step 2 entirely! We included a fully functional MockProvider that fakes network latency and stores data in memory. Just import it and use it right away to see the UI in action.

// app/media/page.tsx import { FileManagerProvider } from "@/context/file-manager-context"; import { FileManager } from "@/components/file-manager"; import { MockProvider } from "@/providers/mock-provider";

// OR import your real provider import { MyCustomApiProvider } from "@/lib/my-api-provider";

export default function MediaLibraryPage() { // Instantiate the provider (Real or Mock) const apiProvider = new MockProvider();

return ( <FileManagerProvider mode="page" selectionMode="multiple" allowedFileTypes={["images", "videos", "audios", "files"]} provider={apiProvider} > ); }


---

## 💾 Database Schema Design

Because this application relies heavily on tree structures (Folders inside Folders) and varied JSON metadata (Video durations vs Document page counts), using a relational database with JSONB support (like PostgreSQL) is highly recommended. 

Below are production-ready schema examples using **Prisma** and **Drizzle ORM**.

### Option A: Prisma Schema (PostgreSQL/MySQL)

```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // or "mysql"
  url      = env("DATABASE_URL")
}

model Folder {
  id          Int       @id @default(autoincrement())
  name        String
  
  // Self-referencing relationship for infinite folder depth
  parentId    Int?      
  parent      Folder?   @relation("FolderHierarchy", fields: [parentId], references: [id])
  children    Folder[]  @relation("FolderHierarchy")
  
  // Cached counts for fast UI rendering
  folderCount Int       @default(0)
  fileCount   Int       @default(0)
  
  // Path optimization
  pathId      Int       @default(0)
  path        String?   // e.g. "/1/5/12"
  
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  
  files       File[]

  @@index([parentId])
}

model File {
  id              Int       @id @default(autoincrement())
  name            String
  
  // Foreign Key to Folder
  folderId        Int?
  folder          Folder?   @relation(fields: [folderId], references: [id], onDelete: Cascade)
  folderPath      String?
  
  // Core Asset Data
  size            Int
  url             String
  previewUrl      String?   // Lightweight thumbnail URL
  mime            String    // e.g. "image/jpeg", "video/mp4"
  ext             String?
  hash            String?
  
  // Common Media Details
  alternativeText String?
  caption         String?
  width           Int?
  height          Int?
  
  // JSONB storage for flexible metadata 
  // (e.g., formats: { thumbnail: {...}, small: {...} })
  formats         Json?     
  // (e.g., metaData: { duration: 120, bitrate: 320, pageCount: 15 })
  metaData        Json?     
  
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt

  @@index([folderId])
}

Option B: Drizzle ORM Schema (PostgreSQL)

If you prefer a lighter, TypeScript-first approach using Drizzle:

import { pgTable, serial, varchar, integer, timestamp, jsonb, text } from "drizzle-orm/pg-core";

export const folders = pgTable("folders", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  
  // Nullable parentId allows root-level folders
  parentId: integer("parent_id"), // Needs recursive FK setup in relations
  
  folderCount: integer("folder_count").default(0),
  fileCount: integer("file_count").default(0),
  
  pathId: integer("path_id").default(0),
  path: varchar("path", { length: 255 }),
  
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

export const files = pgTable("files", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  
  folderId: integer("folder_id").references(() => folders.id, { onDelete: "cascade" }),
  folderPath: varchar("folder_path", { length: 255 }),
  
  size: integer("size").notNull(),
  url: text("url").notNull(),
  previewUrl: text("preview_url"),
  mime: varchar("mime", { length: 100 }).notNull(),
  ext: varchar("ext", { length: 20 }),
  hash: varchar("hash", { length: 255 }),
  
  alternativeText: text("alternative_text"),
  caption: text("caption"),
  width: integer("width"),
  height: integer("height"),
  
  // JSONB is perfect for storing our dynamic metadata & responsive formats
  formats: jsonb("formats"),
  metaData: jsonb("meta_data"), 
  
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});