Node.js

The SDK is a plain Node library — scripts, workers, and small servers. It never runs in the browser; see Core concepts for how files become jobs and URLs.

One client, reused

Export a shared instance from tk.ts. Install and your first transform → Quickstart.

tk.ts
import { TransformKit } from '@transform-kit/sdk';

export const tk = new TransformKit({ apiKey: process.env.API_KEY! });

Batch a folder

Pass one entry per file. Use a lazy loader so bytes are read only when a slot opens — same pattern on Pipelines → Run a pipeline.

batch.ts
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import { tk } from './tk';

const dir = 'photos';
const names = (await readdir(dir)).filter((n) => /\.(png|jpe?g|webp|avif|tiff?)$/i.test(n));

const results = await tk
  .runQueue(
    names.map((name) => async () => ({
      bytes: await readFile(path.join(dir, name)),
      filename: name,
    })),
    'image',
  )
  .maxSize(2048)
  .convert({ format: 'webp', quality: 82 })
  .options({
    concurrency: 6,
    onProgress: (e) => console.log(e.stage, e.filename ?? e.index),
  });

for (const r of results) {
  if (r.ok) console.log(r.filename, '→', r.outputs[0]!.media.url);
  else console.error(r.filename, 'failed:', r.error);
}

Fan out with a pipeline

Linear chains use runQueue; one input → many outputs needs a graph. Define responsivePipeline on Pipelines, then:

fan-out.ts
import { readFile } from 'node:fs/promises';
import { tk } from './tk';
import { responsivePipeline } from './pipelines';

const [photo] = await tk.runPipeline(
  [{ bytes: await readFile('photo.png'), filename: 'photo.png' }],
  responsivePipeline,
);

if (!photo.ok) throw new Error(photo.error);

for (const out of photo.outputs) {
  console.log(out.output, out.media.url);
}

Bring your own storage

Override upload and delivery so bytes never touch TransformKit storage. The HTTP shape of input_url / output_targets HTTP → Bring your own storage.

  • .upload(fn) — you store the source and return a GET URL for the worker.
  • .deliver(fn) — you return a presigned PUT per output; the worker writes there.
byos.ts
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { readFile } from 'node:fs/promises';
import { tk } from './tk';

const r2 = new S3Client({
  region: 'auto',
  endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  },
});
const BUCKET = 'my-media';

const [photo] = await tk
  .runQueue([{ bytes: await readFile('photo.png'), filename: 'photo.png' }], 'image')
  .upload(async (file) => {
    const key = `inputs/${file.filename}`;
    await r2.send(new PutObjectCommand({ Bucket: BUCKET, Key: key, Body: file.bytes }));
    return getSignedUrl(r2, new GetObjectCommand({ Bucket: BUCKET, Key: key }), { expiresIn: 3600 });
  })
  .maxSize(2048)
  .convert({ format: 'webp', quality: 82 })
  .deliver(async (t) => {
    const key = `outputs/${t.output ?? t.outputId}-${t.index}.webp`;
    const putUrl = await getSignedUrl(
      r2,
      new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: 'image/webp' }),
      { expiresIn: 3600 },
    );
    const publicUrl = await getSignedUrl(
      r2,
      new GetObjectCommand({ Bucket: BUCKET, Key: key }),
      { expiresIn: 86400 },
    );
    return { putUrl, contentType: 'image/webp', publicUrl };
  });

console.log(photo.outputs[0]!.media.url);
Presigned URLs are short-lived. Dashboard retries reuse the same URLs — sign long enough for a retry window or treat BYOS as fire-once.

Local development

Point baseUrl at your local API and trust the portless CA so Node accepts .localhost TLS.

tk.ts
export const tk = new TransformKit({
  apiKey: process.env.API_KEY!,
  baseUrl: process.env.TK_API_PUBLIC_URL!,
});
export NODE_EXTRA_CA_CERTS=~/.portless/ca.pem

Next steps

  • Next.js — Server Actions and browser uploads.
  • HTTP & curl — raw endpoints behind the SDK.