File upload

Drag-and-drop uploader with validation for type, size, count, and image dimensions.

Pass `sources` to render the Uppy Dashboard with an import-from row. Device and Screencast work with no Companion; Url, Dropbox, and Google Drive route through a Companion server at `companionUrl`.

Loading uploader…
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/file-upload-v2.json
Usage
The CLI install pulls every file the demo needs (component, registry deps, Uppy plugins, and the S3 + Dropbox upload routes). The component renders its own UI with Tailwind — there's no global CSS to wire up. Set `SQUAD_API_KEY` and it works.
$Terminal
npm install @uppy/core @uppy/react @uppy/aws-s3 \
  @uppy/xhr-upload @uppy/thumbnail-generator
TSImport
import { FileUploadV2 } from "@/components/blocks/file-upload-v2";
TSExample
"use client";

import { FileUploadV2 } from "@/components/blocks/file-upload-v2";

// Single component, every "variant" driven by props:
//   label / caption / error / restrictionText / showUploadedUrls
//   acceptedFileTypes / maxFileSize / maxFiles / maxTotalFileSize / allowMultiple
//   imageValidateSizeMin/MaxWidth/Height
//   destination ("s3" | "dropbox") / dropboxFolder
export function Example() {
  return (
    <FileUploadV2
      label="Files"
      allowMultiple
      maxFiles={2}
      maxFileSize="10MB"
      acceptedFileTypes={["image/*"]}
      restrictionText="Images only"
      showUploadedUrls
    />
  );
}
Post-install wiring
Things the registry can't do for you (the env var). Skip it and you'll get a 500 from `/api/s3-presign` (or `/api/dropbox-upload`) on the first upload.
What `shadcn add file-upload-v2.json` drops in for you:
- src/components/blocks/file-upload-v2.tsx           (the Uppy component)
- src/components/ui/{alert,label,field-hint}.tsx     (registry deps)
- src/app/api/s3-presign/route.ts                    (S3 presign proxy)
- src/app/api/dropbox-upload/route.ts                (Dropbox destination proxy)
- .env.example                                       (the env vars the routes read)
- npm: @uppy/core, @uppy/react, @uppy/aws-s3,
       @uppy/xhr-upload, @uppy/thumbnail-generator, lucide-react

No global CSS wiring needed — the Uppy block renders its
own item cards with Tailwind, so there's no stylesheet to import.

What you still have to wire up by hand:
1. Copy `.env.example` → `.env.local` and set `SQUAD_API_KEY`. Both the S3
   presign route and the Dropbox route read it. Without it, uploads 500.

2. Brand-new project? Install the theme first (tokens, `cn`, shared deps):
     npx shadcn@latest add https://sdk-components.thesqd.com/r/theme.json
Styling
Nothing to wire up. the Uppy block renders its own dropzone and item cards with Tailwind + your theme tokens — there's no third-party stylesheet to import into globals.css, and no @import-order pitfalls.
Composition
Anatomy of the uploader.
FileUploadV2                        (Uppy engine — one flat component)
├── @uppy/core                    (reactive state: files, progress, restrictions)
├── @uppy/aws-s3                  (destination="s3": presign → PUT to Wasabi)
├── @uppy/xhr-upload              (destination="dropbox": POST → /api/dropbox-upload)
├── @uppy/thumbnail-generator     (image previews when allowImagePreview)
└── restrictions                  (maxFileSize / maxNumberOfFiles / maxTotalFileSize / allowedFileTypes)

Item cards render from useUppyState — no DOM scraping for progress. Image
dimension bounds are checked in a file-added handler (Uppy has no built-in
dimension restriction). Pair with the relevant route handler:
  • S3:      src/app/api/s3-presign/route.ts      → api.thesqd.com /v1/s3/get-presigned-upload-url
  • Dropbox: src/app/api/dropbox-upload/route.ts  → api.thesqd.com /v1/dropbox/upload-files
Import sources (Dashboard)v2
Pass `sources` to render the Uppy Dashboard with an import-from row: My Device, Screencast, Url, Google Drive, Dropbox. `device` and `screenCapture` need no server; `url`, `dropbox`, and `googleDrive` fetch through an `@uppy/companion` server you set via `companionUrl` (server-side OAuth, so they work on any host — no browser origin whitelisting). Uploads still go to your chosen `destination`.

Pass `sources` to render the Uppy Dashboard with an import-from row. Device and Screencast work with no Companion; Url, Dropbox, and Google Drive route through a Companion server at `companionUrl`.

Loading uploader…
API Reference
Most-used props. The wrapper props are the same on either engine; engine-specific props are flagged in the table.

Wrapper — label / hint / error

PropTypeDefaultDescription
labelstringField label rendered above the uploader.
captionReactNodeHelper copy shown directly under the label via FieldLabel.
errorboolean | stringStatic error state. `true` applies the red panel only; a string also renders the inline message (`text-xs mt-1.5 text-red-500`) below the uploader. Overridden by runtime validation messages.
showNetworkStatusbooleanfalseRender a live network-quality + estimated-time-left readout under the uploader while files are in flight (e.g. "Network good · 42.5% · ~1m 20s left"). Upload rate is sampled over a fixed window and EMA-smoothed; a stall reads as "Reconnecting…" and going offline shows "Offline — will resume".
progressDecimalsnumber0Decimal places for the overall percent in the network readout (`0` → "42%", `1` → "42.5%"). Only applies with `showNetworkStatus`.
onwarning(err, file?, status?) => voidValidation warning callback (e.g. file count exceeded). (v1 engine) Displays `err.body ?? err.main` as an amber-600 message below the uploader. Clears on a successful `onaddfile`.
onerror(err, file?, status?) => voidUpload error callback (e.g. server upload failure). (v1 engine) Displays `err.body ?? err.main` as a red-500 message below the uploader. Clears on a successful `onaddfile`.

Core

PropTypeDefaultDescription
namestringForm field name applied to the upload request body.
allowMultiplebooleanfalseAccept more than one file in a single picker.
instantUploadbooleantrueUpload as soon as the user adds a file.
serverFileUploadServerConfig["server"]Custom upload pipeline. Most apps implement `process` and pass the response body to `load()` so it lands as the file's serverId.
creditsbooleantrueShow the uploader branding in the corner. (v1 only.)
destination"s3" | "dropbox""s3"v2 (Uppy) only. Where uploads land: `s3` presigns + PUTs to Wasabi; `dropbox` POSTs each file to the Squad API Dropbox endpoint via `/api/dropbox-upload`. Read once at mount.
dropboxFolderstring"/Squad SDK Uploads"v2 (Uppy) only. Dropbox destination folder when `destination="dropbox"`; the file name is appended.
sources("device" | "url" | "screenCapture" | "googleDrive" | "googleDrivePicker" | "dropbox")[]v2 (Uppy) only. Import sources. Non-empty renders the Uppy Dashboard with an import-from row. `device`/`screenCapture` need no server; `url`/`dropbox`/`googleDrive` route through `companionUrl` (server OAuth, works on any host). `googleDrivePicker` is the client-side Picker — slicker, but Google requires a public-TLD origin so it can't run on `*.localhost` dev hosts.
companionUrlstringv2 (Uppy) only. Base URL of your `@uppy/companion` server. Required for the `url`, `dropbox`, and `googleDrive` sources.
googleClientId / googleApiKey / googleAppIdstringv2 (Uppy) only. Google Cloud OAuth client ID / API key / project number — only for the client-side `googleDrivePicker` source. The Companion-backed `googleDrive` source needs none of these.

Validation

PropTypeDefaultDescription
maxFilesnumberCap how many files can be selected.
maxFileSizestringPer-file size limit, e.g. "10MB".
maxTotalFileSizestringCombined size cap across every selected file.
acceptedFileTypesstring[]Whitelist of MIME types, e.g. ["image/*", "application/pdf"].
allowImagePreviewbooleanRender image thumbnails for image files and lay items out in a 2-column grid. Defaults to true; set false to stack items full-width.
imageValidateSizeMinWidth / MinHeight / MaxWidth / MaxHeightnumberImage dimension bounds (min/max width & height).