Skip to content
Batchwork
Esc
navigateopen⌘Jpreview
On this page

Images

Generate and edit thousands of images in one batch with batch.images.create and batch.images.edit — the same job handle, base64 or hosted images per request.

Submit an image batch

batch.images() mirrors batch(): pass an image model and a list of prompts, and it returns the same BatchJob handle immediately. Each request produces one or more images, correlated by customId. It comes in two flavors — batch.images.create() (an alias of calling batch.images() directly) for generation, and batch.images.edit() for editing existing images.

import { batch } from "batchwork";
import { openai } from "@ai-sdk/openai";

const job = await batch.images({
  model: openai.image("gpt-image-2"),
  requests: [
    { customId: "a", prompt: "A red bicycle against a brick wall." },
    { customId: "b", prompt: "A watercolor painting of a sleeping cat." },
  ],
});

const results = await job.wait().then(() => job.collect());
for (const r of results) {
  for (const image of r.images ?? []) {
    // Inline base64 (`image.data` + `image.mediaType`), or a hosted
    // `image.url` for providers that return one (e.g. xAI batch).
    console.log(r.customId, image.mediaType ?? image.url);
  }
}

Everything on the job handle works unchanged — wait(), poll(), results(), collect(), and cancel() — as does rehydration with getBatch / getBatchResults / cancelBatch.

You can also pass a "provider/model" string (e.g. "openai/gpt-image-2"), though the model object is recommended.

Supported providers

Batch image generation is available for the providers whose batch API accepts an image-generation endpoint:

Provider Example model Notes
OpenAI gpt-image-2 /v1/images/generations batch endpoint. Inline base64.
Google Gemini gemini-3.1-flash-image Gemini image models via :batchGenerateContent. Inline base64.
xAI grok-imagine-image-quality /v1/images/generations. Signed URLs that expire ~1h.

For video generation, see batch.videos().

Editing images

batch.images.edit() applies a prompt to one or more source images per request. Batch bodies are JSON, so source images are passed as references — an already-uploaded provider file id (OpenAI Files API) or a hosted image URL — never raw uploads:

const job = await batch.images.edit({
  model: openai.image("gpt-image-2"),
  requests: [
    {
      customId: "a",
      prompt: "Make the bicycle blue.",
      images: [{ imageUrl: "https://example.com/bicycle.png" }],
      mask: { fileId: "file-abc123" }, // optional, OpenAI only
    },
  ],
});

Editing is supported for OpenAI (/v1/images/edits, file ids or URLs, optional mask, size/n) and xAI (/v1/images/edits, URL references only — masks, size, and file ids throw before any network request; use providerOptions.xai.aspect_ratio for dimensions). Other providers throw UnsupportedProviderError — Google’s Gemini batch has no edit endpoint.

Field Notes
prompt The text describing the edit.
images Source image references: { fileId } (OpenAI) or { imageUrl } (both; data URIs work on xAI).
mask Optional mask reference; transparent areas indicate where to edit. OpenAI only.
customId / n / size / providerOptions As in generation.

Edited images come back on result.images exactly like generation — inline base64 for OpenAI, signed ~1h URLs (and/or base64) for xAI. Source URLs must stay reachable by the provider while the batch processes.

Request shape

Each request is a text prompt plus an optional customId and a few generation knobs:

Field Notes
prompt The text describing the image to generate.
customId Correlates the result. Auto-generated as request-<index> if omitted; must be unique within a batch.
n Number of images to generate. Defaults to 1.
size Image size as "{width}x{height}" (e.g. OpenAI image models, "1024x1024").
aspectRatio Aspect ratio as "{width}:{height}" (e.g. Gemini image models, "16:9").
providerOptions Provider-specific options (e.g. { openai: { quality: "high" } }). See below.
seed Seed for deterministic generation, where the provider supports it.

limits and metadata work exactly as in batch().

Sizing, count, and provider options

size and aspectRatio are two ways providers express dimensions — OpenAI image models take a size like "1024x1024", while Gemini image models take an aspectRatio like "16:9". Pass whichever your model expects. Use n to request more than one image per prompt, and providerOptions (keyed by provider) for anything model-specific, the same shape the AI SDK’s generateImage uses:

// OpenAI — square 1024px, two variations, high quality
await batch.images({
  model: openai.image("gpt-image-2"),
  requests: [
    {
      customId: "a",
      prompt: "A red bicycle against a brick wall.",
      size: "1024x1024",
      n: 2,
      providerOptions: { openai: { quality: "high" } },
    },
  ],
});

// Google Gemini — widescreen aspect ratio
await batch.images({
  model: google.image("gemini-3.1-flash-image"),
  requests: [
    { customId: "a", prompt: "A sunlit forest path.", aspectRatio: "16:9" },
  ],
});

defaults are merged into every request (request-level values win), exactly as in batch() — handy for applying one size or providerOptions across the whole batch.

Results

Images reuse the normalized BatchResult. Generated images land on result.images, an array of { data?, mediaType?, url? }:

import { writeFile } from "node:fs/promises";

for await (const result of job.results()) {
  if (result.status === "succeeded") {
    for (const [i, image] of (result.images ?? []).entries()) {
      if (image.data) {
        // Inline base64 (OpenAI, Google) — no `data:` prefix.
        await writeFile(
          `${result.customId}-${i}.png`,
          Buffer.from(image.data, "base64")
        );
      } else if (image.url) {
        // Hosted URL (xAI batch) — fetch promptly; signed links expire ~1h.
        await fetch(image.url);
      }
    }
  } else if (result.status === "errored") {
    console.error(result.customId, result.error?.message);
  }
}

OpenAI and Google return inline base64 on image.data (with image.mediaType, e.g. "image/png"); xAI batch returns signed image.urls that expire ~1h after completion, so download them promptly. text and embedding are undefined for image batches.

usage is normalized to { inputTokens, totalTokens } where the provider reports it, billed at the batch rate (~50% off).

How it’s built

Like batch(), batch.images() derives each provider request body by running the AI SDK’s generateImage() through a capturing fetch that records the serialized body and aborts before any network call. Each request maps to a single image-generation call, correlated by customId.

Edit bodies are built directly instead — the AI SDK’s generateImage has no way to pass input images, so there is nothing to capture. Batchwork writes the JSON asset-reference shape each provider expects: { images: [{ file_id | image_url }], mask? } for OpenAI, { image: { url } } for xAI.

Was this page helpful?