# @alsocoder/apna-upload

> Provider-free React file upload with drag-and-drop, progress, cancel, retry, and validation.

Latest version **0.1.1** (published 2026-09-24) · MIT license · 0 weekly downloads

## Install

```sh
npm install @alsocoder/apna-upload
pnpm add @alsocoder/apna-upload
yarn add @alsocoder/apna-upload
bun add @alsocoder/apna-upload
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.1 |
| Published | 2026-09-24 |
| First published | 2026-08-04 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 0 |
| Author | Also Coder |
| Maintainers | alsocoder-org |
| Keywords | react, react-component, file-upload, drag-and-drop, dropzone, ui-component, css-variables, typescript, apna-upload, alsocoder |

## Links

- npm: https://www.npmjs.com/package/@alsocoder/apna-upload
- Repository: https://github.com/alsocoders/apna-upload
- Homepage: https://github.com/alsocoders/apna-upload#readme
- Issues: https://github.com/alsocoders/apna-upload/issues
- npm.io page: https://npm.io/package/@alsocoder/apna-upload

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 0.1.1 (latest) — 2026-09-24
- 0.1.0 — 2026-08-04

## README

# @alsocoder/apna-upload

Provider-free React file upload component with drag-and-drop, progress, cancel, retry, and validation.

## Install

```bash
npm install @alsocoder/apna-upload
```

Peer dependencies: `react`, `react-dom`.

Optional: `pdfjs-dist` for PDF first-page thumbnails (`enablePdfPreview`).

## Setup

```tsx
import "@alsocoder/apna-upload/styles.css"
```

## Basic usage

```tsx
import { ApnaUpload } from "@alsocoder/apna-upload"

async function uploadFile(file: File, { signal, onProgress }) {
  const body = new FormData()
  body.append("file", file)

  const response = await fetch("/api/upload", {
    method: "POST",
    body,
    signal,
  })

  if (!response.ok) {
    throw new Error("Upload failed")
  }

  onProgress(100)
  return response.json()
}

export function ResumeField() {
  const [resume, setResume] = useState("")

  return (
    <ApnaUpload
      label="Resume"
      accept=".pdf"
      upload={uploadFile}
      transformResult={(result) => result.file.path}
      value={resume}
      onChange={setResume}
    />
  )
}
```

## Multiple files

```tsx
<ApnaUpload
  multiple
  maxFiles={5}
  accept="image/*"
  upload={uploadFile}
  value={images}
  onChange={setImages}
/>
```

## Result modes

| `resultMode` | `onChange` value |
|--------------|------------------|
| `"url"` (default) | `string` or `string[]` (via `transformResult`) |
| `"file"` | `File` or `File[]` |

## Hooks and callbacks

| Prop | Purpose |
|------|---------|
| `beforeUpload` | Transform or reject a file before upload (`null` = reject). Use for crop, compression, watermark, EXIF fix. |
| `transformResult` | Map API response to field value. Default extracts `{ url }` or a string. |
| `onUploading` | Called during upload with `(file, progress)` — use for analytics or custom progress UI |
| `onUploaded` | Called per file after successful upload |
| `onRemoved` | Called when user removes a completed file |
| `onError` | Per-file error callback (wire your own toast) |

## Validation

Built-in rules: `accept`, `maxSize`, `maxFiles`.

For custom rules, use `validate`:

```tsx
<ApnaUpload
  accept="image/*"
  validate={(file) => {
    if (!file.name.endsWith(".png")) {
      return "Only PNG allowed"
    }
    return true
  }}
  upload={uploadFile}
/>
```

Return `true` to allow, or a string error message to reject. Supports async validators.

## Disabled and read-only

| Prop | Behavior |
|------|----------|
| `disabled` | No browse, drop, paste, cancel, retry, or remove |
| `readOnly` | Files visible but no interactions (same as disabled for actions) |

```tsx
<ApnaUpload disabled upload={uploadFile} />
<ApnaUpload readOnly value={url} upload={uploadFile} />
```

## Custom preview

Override the default thumbnail with `renderPreview`:

```tsx
<ApnaUpload
  renderPreview={(file, item) => (
    <div className="my-card">
      <strong>{file.name}</strong>
      <span>{item.progress}%</span>
    </div>
  )}
  upload={uploadFile}
/>
```

## Headless hook

Use `useApnaUpload()` for custom UI — same engine as the component:

```tsx
import { useApnaUpload } from "@alsocoder/apna-upload"

const { items, enqueueFiles, cancelItem, retryItem, removeItem } = useApnaUpload({
  upload: uploadFile,
  validate: (file) => file.size < 5_000_000 || "Max 5 MB",
  onUploading: (file, progress) => track("upload", { file: file.name, progress }),
})
```

## Architecture

```
ApnaUpload (UI)
├── Upload Engine   — queue, progress, cancel, retry
├── Validation      — accept, maxSize, maxFiles, validate()
├── Preview         — image/PDF thumbnails, renderPreview
├── Drag & Drop     — dropzone
├── Paste           — clipboard files
├── Reorder         — drag sort (multiple)
└── UI              — dropzone + file list
```

`useApnaUpload()` exposes the engine layer without UI.

## Features

| Feature | Status |
|---------|--------|
| Drag & drop | Yes |
| Browse | Yes |
| Multiple | Yes |
| Preview (image) | Yes |
| PDF preview | Yes (`enablePdfPreview` + optional `pdfjs-dist`) |
| Progress | Yes |
| Retry | Yes |
| Cancel | Yes |
| `beforeUpload` | Yes |
| `onUploading` | Yes |
| `validate` (custom) | Yes |
| `renderPreview` | Yes |
| `disabled` / `readOnly` | Yes |
| `onUploaded` / `onRemoved` | Yes |
| `useApnaUpload()` hook | Yes |
| Max files / max size / accept | Yes |
| `resultMode` url \| file | Yes |
| AbortController | Yes |
| Paste upload (Ctrl+V) | Yes (`enablePaste`, default on) |
| Reorder (drag) | Yes (`enableReorder` + `multiple`) |
| Chunk upload | Adapter contract — see below |

## Chunk upload (adapter contract)

For large files, your `upload` function may receive optional chunk metadata:

```ts
type ApnaUploadFn = (
  file: File,
  ctx: {
    signal: AbortSignal
    onProgress: (percent: number) => void
    chunk?: { index: number; total: number; slice: Blob }
  }
) => Promise<unknown>
```

Implement multipart/resumable logic in your adapter — the component does not ship S3/Cloudinary adapters.

## Custom upload adapters

Implement any backend in the `upload` function:

- S3 presigned URL
- Cloudinary unsigned upload
- Firebase Storage
- Supabase storage
- Your REST API

Return any JSON shape and use `transformResult` to map it to your form value.

## License

MIT

---
_Source: https://npm.io/package/@alsocoder/apna-upload · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
