Processing & scanning

Reflect server-side processing and malware scans in the UI.

"use client"

import {
  type ProcessContext,
  type UploadItem,
  UploadError,
  s3Adapter,
} from "@uploadcn/core"

import { demoTransport, simulateBackendStep } from "@/examples/_demo"
import {
  Upload,
  UploadDropzone,
  UploadDropzoneDescription,
  UploadDropzoneTitle,
  UploadQueue,
} from "@/components/ui/upload"

const adapter = s3Adapter({
  endpoint: "/api/upload",
  transport: demoTransport, // docs only, see examples/_demo.ts
})

/**
 * The browser can't scan files for malware, your backend does. `process`
 * runs after the upload and reflects the backend's verdict:
 * uploading → processing → scanning → success | rejected.
 */
async function scan(
  item: UploadItem,
  { setStatus, signal }: ProcessContext<unknown>
) {
  setStatus("scanning")
  // Your backend scans the file; here a stand-in answers.
  const verdict = await simulateBackendStep(item.name, "scan", signal)
  if (verdict === "infected") {
    throw new UploadError("Malware detected, the file was quarantined", {
      code: "rejected",
    })
  }
}

export default function ScanStatesExample() {
  return (
    <Upload adapter={adapter} process={scan}>
      <UploadDropzone>
        <UploadDropzoneTitle>
          Upload, then wait for the scan
        </UploadDropzoneTitle>
        <UploadDropzoneDescription>
          Files named like “eicar.txt” or “virus.pdf” are flagged by the demo
          scanner
        </UploadDropzoneDescription>
      </UploadDropzone>
      <UploadQueue />
    </Upload>
  )
}

A browser can't scan files for malware, and UploadCN doesn't pretend to. What it gives you is states your backend controls.

Looking for built-in scanning?

createUploadRoute can scan every upload with ClamAV, VirusTotal or your own service before it completes. See Virus scanning. This page covers driving the states yourself, for background jobs and webhooks. For text recognition, see OCR.

  1. Uploading
  2. Processing
  3. Scanning
  4. Success
  • ScanningRejected, the scanner found a problem

With process

process runs after the adapter stored the file. Move between states with setStatus; throw an UploadError with code "rejected" to reject.

import { UploadError } from "@uploadcn/core"

<Upload
  adapter={adapter}
  process={async (item, { setStatus, signal, result }) => {
    setStatus("scanning")
    const verdict = await waitForScan(result.key, { signal }) // poll, SSE, or websocket
    if (verdict === "infected") {
      throw new UploadError("Malware detected", { code: "rejected" })
    }
    return { ...result, scanned: true } // becomes item.result
  }}
/>

From outside (webhooks, realtime)

When results arrive through another channel, drive the item directly:

uploader.update(id, { status: "scanning" })
uploader.update(id, { status: "success", result })
uploader.update(id, { status: "rejected", error: "Malware detected" })

Server side

For inline scanning, pass scan to createUploadRoute (Virus scanning). For background scanning, kick it off in onUploadComplete (enqueue a job, or rely on your storage provider's scanning integration) and expose the verdict to the client. Until a file is approved, keep it out of public access, for example, upload to a quarantine prefix and copy approved files to their final location.

On this page