Documentation menu

Error Monitoring

upup exposes structured errors at every layer — client uploads, pipeline processing, cloud-drive operations, and server-side handlers. This guide shows how to wire them into Sentry (or any error tracker) so upload failures surface in your dashboard instead of disappearing silently.

This is opt-in. upup never phones home or sends telemetry. You add the listeners you want, in your code, reporting to your Sentry project.

Error surfaces at a glance

SurfaceEvent / APIPayload
Upload failureupload-error event{ error: Error, file?: UploadFile }
Pipeline steppipeline-error event{ scope, name, message }
Cloud drive<provider>:error event{ error: Error, action: string }
React/framework proponError callbackerrorMessage: string
Server handlerThrown ErrorStandard Node.js error

Every error thrown by upup carries a code string from a fixed taxonomy (UpupErrorCode) and a retryable boolean.

Client-side: React

The onError prop (component surface)

The UpupUploader component reports upload failures through its onError prop as a message string — this is the supported wiring for the shipped component today:

tsx
import { UpupUploader } from '@upupjs/react'
import * as Sentry from '@sentry/react'

;<UpupUploader
    onError={message => {
        Sentry.captureMessage(message, { level: 'error' })
    }}
    // ...your other props
/>

This fires for upload-level errors only (not pipeline or drive errors), and it carries the message string, not the Error object.

Known limitation

The component does not currently expose its internal core — UploaderRef exposes useUpload() only — so the rich upload-error payload (the full Error with code/retryable, plus the failing file) is not reachable from the component API. A ref-level core accessor is on the roadmap. The event examples below apply when you drive @upupjs/core yourself (headless or custom-UI integrations).

Full-fidelity events (headless @upupjs/core)

On the headless core, the upload-error event gives you the richest context — the full Error object plus the file that failed:

ts
import { UpupCore, UpupError } from '@upupjs/core'
import * as Sentry from '@sentry/browser'

const core = new UpupCore({
    /* ...your options */
})

const off = core.on('upload-error', ({ error, file }) => {
    const upup = error instanceof UpupError ? error : undefined
    Sentry.captureException(error, {
        tags: {
            upup_code: upup?.code,
            upup_retryable: String(upup?.retryable ?? false),
        },
        contexts: {
            upload: {
                fileName: file?.name,
                fileSize: file?.size,
                fileType: file?.type,
            },
        },
    })
})

Pipeline errors

The HEIC-conversion step emits pipeline-error for non-fatal diagnostics — the upload may still succeed with the original file:

ts
core.on('pipeline-error', ({ scope, name, message }) => {
    Sentry.captureMessage(`Pipeline: ${scope}/${name} — ${message}`, {
        level: 'warning',
        tags: { upup_pipeline_scope: scope, upup_pipeline_step: name },
    })
})

Cloud-drive errors

Each drive provider emits namespaced errors. Listen per-provider or catch all four:

ts
const providers = ['google-drive', 'one-drive', 'dropbox', 'box'] as const

for (const p of providers) {
    core.on(`${p}:error`, ({ error, action }) => {
        Sentry.captureException(error, {
            tags: { upup_drive: p, upup_drive_action: action },
        })
    })
}

Server-side: Next.js / Express / Hono

Server handler errors are standard Error objects. If your server framework is already instrumented with Sentry, handler errors are captured automatically — no extra upup-specific wiring needed.

For example, with @sentry/nextjs configured in your Next.js app, a failed presign or multipart call surfaces as a caught exception in your route.

If you instrument manually:

ts
import { createUpupHandler } from '@upupjs/server'
import * as Sentry from '@sentry/node'

const handle = createUpupHandler({
    /* storage, uploadTokenSecret, ... */
})

export async function handler(req: Request): Promise<Response> {
    try {
        return await handle(req)
    } catch (err) {
        Sentry.captureException(err)
        return new Response(JSON.stringify({ error: 'Internal error' }), {
            status: 500,
        })
    }
}

Error taxonomy

Every UpupError carries a code from the UpupErrorCode enum. Use these codes as Sentry tags to filter and alert:

CodeMeaning
AUTH_EXPIREDOAuth token expired mid-session
AUTH_DENIEDServer rejected the upload token
FILE_TOO_LARGEFile exceeds maxFileSize
TYPE_MISMATCHFile type not in allowedFileTypes
LIMIT_EXCEEDEDmaxFiles reached
UPLOAD_FAILEDS3/storage PUT failed
UPLOAD_ABORTEDUser or code called abort()
PRESIGN_FAILEDPresign endpoint returned an error
CORS_ERRORBrowser blocked the S3 request
NETWORK_ERRORFetch failed (offline, DNS, etc.)
HEIC_CONVERSION_FAILEDHEIC → JPEG pipeline step failed
PIPELINE_STEP_FAILEDGeneric pipeline step failure

The retryable flag tells you whether the error is transient. A Sentry alert rule on upup_retryable: false surfaces permanent failures that need human attention.

What upup guarantees

The codebase enforces these invariants (verified by lint rules and tests):

  • No silent catches. Every catch block either re-throws, emits an error event, or has a lint-mandated justification comment.
  • No floating promises in TypeScript sources. Async failures propagate to an error surface (linting inside .vue/.svelte templates is a tracked phase-2).
  • One upload-failure channel. All upload errors route through upload-error; there is no second event to miss.