MainSDK
Search⌘K
How-to

Process uploaded images and video

Turn an upload into image sizes, HLS video and audio in a background job, then serve a signed link to the result.

Building for 0.4

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

You will resize an uploaded photo in a background job and serve a signed link to a thumbnail. It takes about 15 minutes.

You need

  • A MainSDK app with files set up (see file uploads).
  • A file that is ready.

1. Choose a driver

main.yaml
media:
  driver: getqr

The local driver keeps the job and file flow working offline but does not transcode, so use it in tests.

2. Start a job

app/photos/thumb.ts
import { media } from "mainsdk/media";
import type { FileRef } from "mainsdk/files";

export async function thumbnails(photo: FileRef) {
  const job = await media.image(photo, { widths: [320, 640], formats: ["avif", "webp"] });
  return job.id;
}

The job is queued when the call returns, so the request never waits on the work. For video use media.video(file, { renditions: ["360p", "720p"], thumbnails: true }). Location data is stripped from every output.

3. Serve a result

app/api/photos/[id]/thumb/route.ts
import { auth } from "mainsdk";
import { media } from "mainsdk/media";

export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const session = await auth.require(req);
  const { id } = await params;
  const job = await media.get(id);
  const thumb = job.outputs.find((o) => o.kind === "image" && o.width === 320);
  if (!thumb) return new Response("not ready", { status: 404 });
  return Response.json({ url: await media.url(thumb, { for: { id: session.user } }) });
}

For an HLS video, media.playlist(job, output, { for }) returns a playlist whose paths are already signed, so a player makes no second request.

Check that it works

  • media.get(job.id) moves from queued to ready.
  • A user who may not read the source gets MAIN-MEDIA-0001.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet