Files
stuffbox-sdk/README.md
T
cyph3rasi 9e1ed2c9cb Publish the unified MIT-licensed Stuffbox SDK source
Hop-State: A_06FPAF0M83E7T0JJ7EP2WQ0
Hop-Proposal: R_06FPAF04S0D4G8TA8TNK3C0
Hop-Task: T_06FPADTV2YD4PD5GMTK3150
Hop-Attempt: AT_06FPADTV2ZYKEY8GH3JTK98
2026-07-15 03:04:24 -07:00

4.4 KiB

@stuffbox/sdk

The official TypeScript SDK for connecting applications to Stuffbox, a user-owned media library. The package includes a framework-neutral client, shared API types, PKCE helpers, typed errors, and optional React upload helpers.

Install

npm install @stuffbox/sdk

The core entry point has no runtime dependencies. If you use @stuffbox/sdk/react, install React 18.2 or React 19 in your application.

Connect a user

Run the connection flow on your server. Access and refresh tokens must not be embedded in browser JavaScript.

import {
  StuffboxClient,
  createPkcePair,
  generateState,
} from "@stuffbox/sdk";

const stuffbox = new StuffboxClient({ baseUrl: "https://stuffbox.xyz" });
const pkce = await createPkcePair();
const state = generateState();

const connection = await stuffbox.createConnectionRequest({
  selfHosted: true,
  callbackUrl: "https://yourapp.example/api/stuffbox/callback",
  codeChallenge: pkce.challenge,
  scopes: ["assets:read"],
  state,
});

// Persist all four values before redirecting the user's browser.
await savePendingConnection({
  clientId: connection.clientId,
  callbackUrl: connection.callbackUrl,
  codeVerifier: pkce.verifier,
  state,
});

redirect(connection.authorizationUrl);

After the exact callback, compare the returned state with the persisted value and exchange the one-time code:

const pending = await loadPendingConnection();

const tokens = await stuffbox.exchangeAuthorizationCode({
  clientId: pending.clientId,
  code: callbackCode,
  codeVerifier: pending.codeVerifier,
  redirectUri: pending.callbackUrl,
});

Hosted applications register once in the Stuffbox developer dashboard and send their assigned clientId instead of selfHosted: true. A first-time self-hosted installation receives a stable public client ID automatically; persist it for later connections.

Read a user's media library

const media = new StuffboxClient({
  baseUrl: "https://stuffbox.xyz",
  accessToken: () => loadCurrentAccessToken(),
});

let cursor: string | undefined;
do {
  const page = await media.listAssets({
    limit: 50,
    ...(cursor ? { cursor } : {}),
  });
  addToMediaPicker(page.items);
  cursor = page.nextCursor;
} while (cursor);

Request only the permissions your feature uses: assets:read, assets:write, and assets:delete.

React uploads

The optional React entry point exports useStuffboxUpload, uploadFile, and their types:

import { useStuffboxUpload, type UploadTransport } from "@stuffbox/sdk/react";

const transport: UploadTransport = {
  async createUpload(input, options) {
    const response = await fetch("/api/stuffbox/uploads", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(input),
      signal: options?.signal,
    });
    if (!response.ok) throw new Error("Could not create upload");
    return response.json();
  },
  async completeUpload(uploadId, input, options) {
    const response = await fetch(`/api/stuffbox/uploads/${uploadId}/complete`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(input ?? {}),
      signal: options?.signal,
    });
    if (!response.ok) throw new Error("Could not complete upload");
    return response.json();
  },
};

export function UploadButton() {
  const { state, upload, abort } = useStuffboxUpload(transport);

  return (
    <div>
      <input
        type="file"
        onChange={(event) => {
          const file = event.currentTarget.files?.[0];
          if (file) void upload(file);
        }}
      />
      {state.status === "uploading" && (
        <button type="button" onClick={abort}>
          Cancel {Math.round(state.progress.percent)}%
        </button>
      )}
    </div>
  );
}

The browser should call same-origin endpoints owned by your application. Those endpoints call Stuffbox with the user's access token and return only the short-lived upload session or completed asset. File bytes then travel directly from the browser to object storage.

Documentation

License

MIT © Gnosys Labs. The Stuffbox service implementation is proprietary and is not included in this package or its public source repository.