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

@bunny.net/upload-react

v0.0.2

Published

React hooks and drop-in component for [bunny-upload](../../README.md).

Readme

@bunny.net/upload-react

React hooks and drop-in component for bunny-upload.

Install

npm install @bunny.net/upload-react @bunny.net/upload-handler @bunny.net/upload-next

Drop-in component

"use client";

import { BunnyUpload } from "@bunny.net/upload-react";

export default function Page() {
  return (
    <BunnyUpload
      accept={["image/*"]}
      maxSize="10mb"
      maxFiles={5}
      onComplete={(files) => console.log("Uploaded:", files)}
    />
  );
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | endpoint | string | "/.bunny/upload" | Server upload endpoint | | accept | string[] | — | Allowed MIME types (e.g. ["image/*"]) | | maxSize | string \| number | — | Max file size (e.g. "10mb") | | maxFiles | number | — | Max number of files | | autoUpload | boolean | true | Upload immediately when files are added | | onComplete | (files: UploadResult[]) => void | — | Called when all uploads finish | | onError | (error: Error, file?: FileState) => void | — | Called when an upload fails | | className | string | — | CSS class for the container |

Headless hook

For full control over the UI:

"use client";

import { useBunnyUpload } from "@bunny.net/upload-react";

export default function CustomUploader() {
  const { files, addFiles, removeFile, upload, reset, isUploading } = useBunnyUpload({
    accept: ["image/*"],
    maxSize: "10mb",
    maxFiles: 5,
    onComplete: (results) => console.log(results),
  });

  return (
    <div>
      <input type="file" multiple onChange={(e) => addFiles(e.target.files!)} />
      {files.map((file) => (
        <div key={file.id}>
          {file.name} — {file.status} ({file.progress}%)
          <button onClick={() => removeFile(file.id)}>Remove</button>
        </div>
      ))}
      <button onClick={upload} disabled={isUploading}>Upload</button>
    </div>
  );
}

Shared defaults

Use configureBunnyUpload to set defaults across your app without React context:

import { configureBunnyUpload } from "@bunny.net/upload-react";

const { BunnyUpload, useBunnyUpload } = configureBunnyUpload({
  accept: ["image/*"],
  maxSize: "10mb",
  maxFiles: 5,
});

// Use these pre-configured versions anywhere
export { BunnyUpload, useBunnyUpload };

File Manager

Browse, select, and manage files in your Bunny Storage zone. Three levels of control — just like uploads.

FileManagerWidget

A ready-to-use dialog with grid view, breadcrumbs, and selection.

"use client";

import { FileManagerWidget } from "@bunny.net/upload-react";

// Multi-select: pick files, confirm, get URLs
<FileManagerWidget
  accept={["jpg", "png", "webp"]}
  onSelect={(entries, urls) => console.log("Selected:", urls)}
  trigger={({ open }) => <button onClick={open}>Pick images</button>}
/>

// Single select: click a file and it's immediately selected — no confirm step
<FileManagerWidget
  allowMultiple={false}
  onSelect={(entries, urls) => setImage(urls[0])}
  trigger={({ open }) => <button onClick={open}>Pick image</button>}
/>

// Pre-select previously chosen files when the dialog reopens
const [selected, setSelected] = useState<string[]>([]);

<FileManagerWidget
  value={selected}
  onSelect={(entries, urls) => {
    setSelected(entries.map(e => e.path + e.objectName));
  }}
/>

// Custom footer actions when files are selected
<FileManagerWidget
  allowMultiple={false}
  renderActions={({ selected, urls, actions, executeAction }) => (
    <>
      <button onClick={() => navigator.clipboard.writeText(urls[0])}>Copy URL</button>
      <button onClick={() => insertImage(urls[0])}>Insert</button>
      {/* Or render registered actions dynamically */}
      {actions.map(a => (
        <button key={a.id} onClick={() => executeAction(a.id)}>{a.label}</button>
      ))}
    </>
  )}
/>

// Per-entry actions on each item in the grid/list
import { copyUrlAction, downloadAction } from "@bunny.net/file-manager-core/actions";

<FileManagerWidget
  actions={[copyUrlAction, downloadAction]}
  renderEntryActions={({ entry, url, actions, executeAction }) => (
    <>
      {actions.map(a => (
        <button key={a.id} onClick={() => executeAction(a.id)}>{a.label}</button>
      ))}
    </>
  )}
/>

Props

| Prop | Type | Default | Description | |---|---|---|---| | endpoint | string | "/.bunny/files" | Server file manager endpoint | | cdnBase | string | — | CDN base URL (auto-detected from server if not set) | | initialPath | string | "/" | Starting directory | | actions | FileManagerAction[] | — | Custom actions registered on the file manager | | allowMultiple | boolean | true | Allow selecting multiple files. When false with onSelect, clicking a file immediately fires the callback and closes the dialog. | | accept | string[] | — | Filter by file extension (e.g. ["jpg", "png"]) | | value | string[] | — | Pre-select entries matching these paths (entry.path + entry.objectName). When the dialog opens, matching files appear checked. | | onSelect | (entries, urls) => void | — | Called when user confirms selection (or immediately on click when allowMultiple={false}) | | renderEntryActions | (props) => ReactNode | — | Custom actions rendered on each entry in the grid/list | | renderActions | (props) => ReactNode | — | Custom actions in the footer when files are selected | | trigger | (props: { open }) => ReactNode | — | Custom trigger element | | label | string | "Browse files" | Label for default trigger button | | view | "grid" \| "list" | "grid" | View mode |

The renderEntryActions callback receives { entry: StorageEntry, url: string, actions: FileManagerAction[], executeAction: (id) => Promise<void> }. The actions array is filtered to those applicable for that single entry (respects target and isApplicable).

The renderActions callback receives { selected: StorageEntry[], urls: string[], actions: FileManagerAction[], executeAction: (id) => Promise<void>, deselectAll: () => void }. The actions array is filtered for the current selection count.

When allowMultiple={false} is combined with renderActions (instead of onSelect), the instant-select behavior is disabled — the user clicks to select, then uses your custom action buttons.

FileManager (render props)

Full control over the UI. The component provides all state and methods via render props.

"use client";

import { FileManager } from "@bunny.net/upload-react";

<FileManager>
  {({ entries, selected, selectedUrls, navigate, toggleSelect, entryUrl, breadcrumbs, cdnUrl }) => (
    <div>
      {breadcrumbs.map((c) => (
        <button key={c.path} onClick={() => navigate(c.path)}>{c.label}</button>
      ))}

      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))", gap: 12 }}>
        {entries.map(entry => (
          <div
            key={entry.guid}
            onClick={() =>
              entry.isDirectory
                ? navigate(entry.path + entry.objectName + "/")
                : toggleSelect(entry.guid)
            }
          >
            {!entry.isDirectory && /\.(jpg|png|webp)$/i.test(entry.objectName) && (
              <img src={entryUrl(entry)} alt={entry.objectName} />
            )}
            <span>{entry.objectName}</span>
          </div>
        ))}
      </div>

      {selected.length > 0 && (
        <button onClick={() => console.log(selectedUrls)}>
          Use {selected.length} file(s)
        </button>
      )}
    </div>
  )}
</FileManager>

Render props

| Prop | Type | Description | |---|---|---| | entries | StorageEntry[] | Files and folders in current directory | | currentPath | string | Current directory path | | status | "idle" \| "loading" \| "error" | Loading state | | error | string | Error message when status is "error" | | selected | StorageEntry[] | Currently selected entries | | selectedUrls | string[] | CDN URLs for selected files | | breadcrumbs | { label, path }[] | Navigation breadcrumbs | | navigate | (path: string) => void | Navigate to a directory | | goUp | () => void | Go to parent directory | | refresh | () => void | Refresh current directory | | toggleSelect | (guid: string) => void | Toggle selection | | selectAll / deselectAll | () => void | Select/deselect all | | entryUrl | (entry) => string | Get CDN URL for an entry | | cdnUrl | (path: string) => string | Get CDN URL for a path | | createFolder | (name: string) => void | Create a folder | | deleteEntry | (path: string) => void | Delete a file or folder | | importFromUrl | (url, filename?) => void | Import file from URL |

useFileManager (hook)

Maximum control — just state and methods, zero UI.

"use client";

import { useFileManager } from "@bunny.net/upload-react";

export default function CustomFileBrowser() {
  const {
    entries, currentPath, status, error, selected, breadcrumbs,
    navigate, goUp, refresh, toggleSelect, selectAll, deselectAll,
    createFolder, deleteEntry, deleteSelected, importFromUrl,
    cdnUrl, downloadUrl, getActions, executeAction,
  } = useFileManager({
    endpoint: "/.bunny/files",
    initialPath: "/",
  });

  return (
    <div>
      <button onClick={goUp} disabled={currentPath === "/"}>Back</button>
      <button onClick={refresh}>Refresh</button>

      {entries.map(entry => (
        <div key={entry.guid}>
          <input
            type="checkbox"
            checked={selected.some(s => s.guid === entry.guid)}
            onChange={() => toggleSelect(entry.guid)}
          />
          {entry.isDirectory ? (
            <a onClick={() => navigate(entry.path + entry.objectName + "/")}>
              {entry.objectName}/
            </a>
          ) : (
            <span>{entry.objectName}</span>
          )}
        </div>
      ))}

      {selected.length > 0 && (
        <button onClick={deleteSelected}>
          Delete {selected.length} file(s)
        </button>
      )}
    </div>
  );
}

Options

| Option | Type | Default | Description | |---|---|---|---| | endpoint | string | "/.bunny/files" | Server file manager endpoint | | cdnBase | string | — | CDN base URL | | initialPath | string | "/" | Starting directory | | actions | FileManagerAction[] | — | Custom actions |

Server setup

You'll need a server route to handle uploads. See the handler docs for full options.

// app/.bunny/upload/route.ts
import { serveBunnyUpload } from "@bunny.net/upload-next";
import { createBunnyUploadHandler } from "@bunny.net/upload-handler";

export const { POST } = serveBunnyUpload(
  createBunnyUploadHandler({
    restrictions: { maxFileSize: "10mb", allowedTypes: ["image/*"], maxFiles: 5 },
    getPath: (file) => `/uploads/${Date.now()}-${file.name}`,
  })
);

File manager handler

// app/.bunny/files/route.ts
import { serveBunnyFileManager } from "@bunny.net/upload-next";
import { createFileManagerHandler } from "@bunny.net/file-manager-handler";

export const { GET, POST, DELETE } = serveBunnyFileManager(
  createFileManagerHandler()
);