> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Embeddable Assets upload widget

> Install `@stackshift-cloud/assets-widget` for React or vanilla JavaScript with constrained backend-issued capabilities, resumable chunks, camera and URL input, cropping, previews, progress, retry, and cancellation.

<Tip>
  **Live.** This area is documented as current, user-reliable behavior.
</Tip>

## Goal

Embed a production upload experience without placing a permanent or publishable StackShift credential in browser code.

## Prerequisites

* Node.js 20 or newer for package builds
* `@stackshift-cloud/assets-widget` 1.0.0 and `@stackshift-cloud/sdk` 1.1.0 or newer
* A trusted backend with a StackShift token carrying `assets:write`
* An existing Assets bucket and an HTTPS browser origin

## Workflow

<Steps>
  <Step>
    Create a host-backend endpoint authenticated by your application session.
  </Step>

  <Step>
    For each file, have that endpoint create one short-lived upload capability with a server-chosen bucket, fixed key or prefix, MIME allowlist, byte limit, exact origin, and expiry.
  </Step>

  <Step>
    Render the React or vanilla widget and provide an asynchronous `getUploadCapability` callback that calls only your host endpoint.
  </Step>

  <Step>
    Subscribe to progress, completion, and error events and store the returned Asset ID in your application data.
  </Step>

  <Step>
    Destroy a vanilla widget when its containing view is removed. The React wrapper handles teardown during unmount.
  </Step>
</Steps>

## Install and import

The package publishes isolated `ss-assets-widget` CSS, ESM and CommonJS builds, source maps, TypeScript declarations, and separate vanilla and React entry points. Import the stylesheet exactly once in the application shell.

```bash theme={null}
npm install @stackshift-cloud/assets-widget @stackshift-cloud/sdk
```

## Issue capabilities on your backend

Authenticate the caller with your own application session and derive authorization from server-side identity. Do not accept the bucket, allowed origin, key prefix, byte limit, or MIME policy directly from an untrusted browser body. Return only the one-time capability token and expiry.

```ts Backend route theme={null}
import { randomUUID } from 'node:crypto'

export async function POST(request: Request) {
  const user = await requireApplicationSession(request)
  const input = await request.json() as { fileName: string; mimeType: string; size: number }
  if (!['image/jpeg', 'image/png', 'application/pdf'].includes(input.mimeType)) {
    return new Response('Unsupported file type', { status: 415 })
  }
  const extension = safeExtension(input.fileName)
  const created = await stackshift.assets.uploadCapabilities.create({
    bucket: 'customer-uploads',
    fixed_key: `users/${user.id}/${randomUUID()}${extension}`,
    allowed_mime_types: ['image/jpeg', 'image/png', 'application/pdf'],
    allowed_origins: ['https://app.example.com'],
    max_bytes: Math.min(25 * 1024 * 1024, Math.max(1, input.size)),
    allow_remote_url: false,
    expires_in: '10m',
  })
  return Response.json({
    token: created.token,
    expiresAt: created.capability.expires_at,
  })
}
```

## Capability boundary

* Creation accepts `bucket`, optional `key_prefix` or `fixed_key`, `allowed_mime_types`, one or more exact `allowed_origins`, `max_bytes`, `allow_remote_url`, and `expires_in`.
* Expiry must be between one second and one hour. Origins must be HTTPS origins without a path, query, fragment, or embedded credentials.
* The opaque token is shown only when created. The server stores its SHA-256 hash and binds the capability to one tenant, user, and upload session.
* Widget routes accept the capability bearer token, not a general API key. They can create/read/complete/cancel only the bound session, upload its parts, or perform an explicitly allowed remote-URL ingestion.
* Part requests include `X-Content-SHA256`. Completion revokes the capability; cancellation cancels the session and revokes it.

## React integration

```tsx theme={null}
import { StackshiftAssetsWidget } from '@stackshift-cloud/assets-widget/react'
import '@stackshift-cloud/assets-widget/style.css'

export function AssetUpload() {
  return (
    <StackshiftAssetsWidget
      accept="image/*,application/pdf"
      allowCamera
      allowRemoteUrl={false}
      crop={{ enabled: true, aspectRatio: 1, outputType: 'image/webp' }}
      key={(file) => file.name}
      getUploadCapability={async (context) => {
        const response = await fetch('/api/assets/upload-capability', {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(context),
        })
        if (!response.ok) throw new Error('Upload authorization failed')
        return response.json()
      }}
      onProgress={(item) => updateProgress(item.id, item.progress)}
      onComplete={(asset) => saveAssetId(asset.id)}
      onError={(error, item) => reportUploadError(item.id, error)}
    />
  )
}
```

## Vanilla JavaScript integration

```ts theme={null}
import { createAssetsWidget } from '@stackshift-cloud/assets-widget'
import '@stackshift-cloud/assets-widget/style.css'

const container = document.querySelector<HTMLElement>('#asset-upload')!
const widget = createAssetsWidget(container, {
  getUploadCapability: authorizeUpload,
  accept: 'image/*,application/pdf',
  maxFiles: 10,
  queueConcurrency: 2,
  chunkConcurrency: 4,
  retryAttempts: 4,
  metadata: (file) => ({ client_name: file.name }),
  onComplete: (asset) => saveAssetId(asset.id),
})

// Call this when the containing view is removed.
widget.destroy()
```

## Inputs, crop metadata, resume, and accessibility

* Local picker and drag/drop respect `accept`; `maxFiles` defaults to 20. Camera capture uses the environment-facing mobile file input and falls back to the device file picker.
* Remote URL controls appear only when `allowRemoteUrl` is true. The backend capability must separately set `allow_remote_url: true`; source redirects and private/reserved networks are rejected.
* Cropping supports JPEG, PNG, or WebP output, quality, aspect ratio, keyboard-operable numeric geometry, and “Use original.” The uploaded asset metadata receives `stackshift_crop` with source dimensions and pixel `x`, `y`, `width`, and `height`.
* Queue concurrency defaults to 2 and is bounded to 1–6. Chunk concurrency defaults to 4 and is bounded to 1–8. Chunk and completion attempts default to 4 and are bounded to 1–8 with exponential backoff.
* IndexedDB stores the file fingerprint and session state. Reload reconciliation asks the server for received parts and re-hashes matching local parts before skipping them. Expired, mismatched, or invalid sessions are discarded safely.
* The widget supplies keyboard activation, labeled controls, focus restoration after queue rendering, native progress semantics, polite live announcements, responsive styles, and reduced-motion rules.

## Expected result

<Check>
  Browser files upload directly through the capability-only widget routes, resume safely after reload, and complete as normal StackShift Assets.
</Check>

## Common failures

<Warning>
  * The backend returns a capability created for a different HTTPS origin, key prefix, MIME type, or maximum size.
  * A capability is reused after it has already been bound to one session, completed, canceled, revoked, or expired.
  * Remote URL ingestion is requested without `allow_remote_url`, or the remote host redirects or resolves to a private address.
  * Browser storage is blocked; uploads still run, but resume state falls back to memory and will not survive a reload.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Direct browser uploads" href="/assets/direct-browser-uploads">
    Upload browser files directly with short-lived sessions, per-part checksums, progress callbacks, retries, resume, and cancellation.
  </Card>

  <Card title="Assets platform API, SDK, CLI, and operations" href="/assets/platform-api-cli-and-operations">
    A complete interface and recovery reference for storage connections, S2, imports, relocation, OCR, browser capabilities, reports, scopes, events, billing, and durable worker behavior.
  </Card>

  <Card title="Storage connections, BYOB, and StackShift S2" href="/assets/storage-connections-and-byob">
    Configure AWS S3, Cloudflare R2, Wasabi, MinIO, generic S3, or native StackShift S2 without exposing customer storage credentials or changing delivery URLs.
  </Card>
</CardGroup>
