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.