AI Image Reference

Use async image jobs to generate new images from prompts or edit existing images. Completed jobs return permanently hosted PNG files and can resume polling after a page reload.

Job-Based Image Generation

Image generation uses three hooks:

  • useSubmitImageJob submits a generation request and returns a jobId.
  • useSubmitImageEditJob submits an edit request and returns a jobId.
  • useImageJobStatus polls either job until it completes or fails.
import {
  useImageJobStatus,
  useSubmitImageEditJob,
  useSubmitImageJob,
} from '@/hooks/use-ai'

Persist the returned jobId with usePersistentItem, then pass it to useImageJobStatus. The status hook treats the server-side job record as the source of truth, so polling can continue after a reload.

useSubmitImageJob

Hook Options

  • onError ((error: Error) => void): Optional error callback.
  • modelProvider (string): Optional model provider.
  • modelTier ("lite", "standard", or "advanced"): Optional model tier.

Returns

  • submitImage (function): Submit an image generation job.
  • isSubmitting (boolean): Whether submission is in progress.
  • error (Error | null): Current submission error.
  • clearError (() => void): Clear the current error.

submitImage Input

  • prompt (string, required): Description of the image.
  • aspectRatio ("1:1", "16:9", or "9:16"): Output aspect ratio.
  • numImages (number, default 1): Number of images from 1 through 4.
  • quality ("low", "medium", or "high"): Optional quality tier. Omit it unless the user requests it.
  • seed (number): Optional integer seed for more reproducible results.
  • images (FileList, File[], or an array of file or URL objects): Optional references for style or subject consistency.
  • model (string): Model ID for the request.

Submit a new image generation job:

const { submitImage, isSubmitting, error, clearError } = useSubmitImageJob()

const result = await submitImage({
  prompt: 'A cinematic product photograph of a ceramic teapot',
  aspectRatio: '1:1',
  numImages: 4,
  model: modelId,
})

if (result) setJobId(result.jobId)

Reference images can guide style or subject consistency, but the output remains a new creative asset.

useSubmitImageEditJob

Hook Options

  • onError ((error: Error) => void): Optional error callback.
  • modelProvider (string): Optional model provider.
  • modelTier ("lite", "standard", or "advanced"): Optional model tier.

Returns

  • submitImageEdit (function): Submit an image editing job.
  • isSubmitting (boolean): Whether submission is in progress.
  • error (Error | null): Current submission error.
  • clearError (() => void): Clear the current error.

submitImageEdit Input

  • prompt (string, required): Description of the requested change.
  • image (File, { file: File }, or { url: string }, required): Source image to edit.
  • aspectRatio ("1:1", "16:9", or "9:16"): Output aspect ratio.
  • quality ("low", "medium", or "high"): Optional quality tier. Omit it unless the user requests it.
  • seed (number): Optional integer seed for more reproducible results.
  • model (string): Model ID for the request.

Submit a job that transforms one source image:

const { submitImageEdit, isSubmitting, error, clearError } =
  useSubmitImageEditJob()

const result = await submitImageEdit({
  prompt:
    'Replace the cloudy sky with a warm sunset. Keep the subject and foreground unchanged.',
  image: { url: sourceImageUrl },
})

if (result) setJobId(result.jobId)

useImageJobStatus

Arguments

  • jobId (string | null): Job to poll, or null to disable polling.
  • options (object): Optional polling configuration.

Status Options

  • pollInterval (number, default 10000): Polling interval in milliseconds; values below 5000 are clamped.
  • enabled (boolean, default true): Whether polling is enabled.
  • onComplete (function): Called once with { images } when valid hosted images are available.
  • onError ((error: string) => void): Called once when the job fails or completes without valid images.

Returns

  • status ("pending", "processing", "completed", "failed", or null): Current job status.
  • images (array | null): Ordered completed image objects containing a permanently hosted url.
  • error (string | null): Current job error.
  • createdAt (string | null): Job creation timestamp.
  • completedAt (string | null): Job completion timestamp.
  • refetch (() => void): Trigger an immediate status fetch.

Poll a generation or edit job:

const job = useImageJobStatus(jobId, {
  onComplete: ({ images }) => {
    setImageUrls(images.map((image) => image.url))
    setJobId(null)
  },
  onError: (message) => {
    setJobError(message)
    setJobId(null)
  },
})

Persist and Poll Example

import { useImageJobStatus, useSubmitImageJob } from '@/hooks/use-ai'
import { usePersistentItem } from '@/hooks/use-persistent-item'

export function ImageGenerator() {
  const [prompt, setPrompt] = React.useState('')
  const [jobId, setJobId] = usePersistentItem<string | null>('imageJobId', null)
  const [imageUrls, setImageUrls] = usePersistentItem<string[]>('imageUrls', [])
  const [jobError, setJobError] = React.useState<string | null>(null)
  const { submitImage, isSubmitting, error: submitError } = useSubmitImageJob()

  const job = useImageJobStatus(jobId, {
    onComplete: ({ images }) => {
      setImageUrls(images.map((image) => image.url))
      setJobId(null)
    },
    onError: (message) => {
      setJobError(message)
      setJobId(null)
    },
  })

  const isGenerating = job.status === 'pending' || job.status === 'processing'

  const handleGenerate = async () => {
    if (!prompt.trim()) return
    setJobError(null)

    const result = await submitImage({
      prompt: prompt.trim(),
      aspectRatio: '1:1',
    })

    if (result) setJobId(result.jobId)
  }

  const errorMessage = submitError?.message || jobError || job.error

  return (
    <div>
      <input
        value={prompt}
        onChange={(event) => setPrompt(event.target.value)}
        placeholder="Describe the image you want..."
      />
      <button onClick={handleGenerate} disabled={isSubmitting || isGenerating}>
        {isSubmitting
          ? 'Submitting...'
          : isGenerating
            ? 'Generating...'
            : 'Generate image'}
      </button>
      {errorMessage ? <p className="text-destructive">{errorMessage}</p> : null}
      {imageUrls.map((url) => (
        <img
          key={url}
          src={url}
          alt="Generated"
          className="w-full rounded-lg"
        />
      ))}
    </div>
  )
}

Prompting

Image prompts work best when they describe the scene instead of listing keywords. Include:

  • Subject: The main object, person, animal, or scenery.
  • Setting: Where the scene takes place.
  • Style: Photorealistic, watercolor, flat vector, sticker, 3D render, or another creative direction.
  • Lighting and mood: Golden hour, dramatic studio light, cool moonlight, soft ambient light.
  • Composition: Close-up, wide shot, aerial view, negative space, or camera angle.
  • Details and textures: Fine details that make the output specific.

For edits, say exactly what should change and what should stay the same. For targeted edits, name the object precisely: "Change only the blue sofa to a vintage brown leather chesterfield sofa. Keep the rest of the room exactly as it is."

Model Selection

When no model is specified, the platform chooses an efficient default image model for the user's plan. To expose model choice, pass a model ID and build selectors from listImageModels():

import { listImageModels, useSubmitImageJob } from '@/hooks/use-ai'

const models = listImageModels()
const [modelId, setModelId] = React.useState<string | undefined>(undefined)
const { submitImage } = useSubmitImageJob()

const result = await submitImage({
  prompt: 'A sunset over the ocean',
  model: modelId,
})

if (result) setJobId(result.jobId)

listImageModels() returns model objects with id, displayName, provider, tier, description, and capabilities. Image model capabilities include:

  • generation: Supports creating images from a prompt.
  • editing: Supports editing or remixing existing images.

All models are available on every plan; more capable models cost more credits. Deprecated models automatically fall back to a recommended replacement.

Best Practices

  • Persist the jobId until the job completes or fails so polling survives reloads.
  • Treat job.status as the source of truth for loading UI.
  • Use onComplete and onError to copy terminal state and clear the jobId.
  • Persist completed hosted url values for display and reuse.
  • Choose an aspect ratio that matches the final UI or export format.
  • Use numImages to create variations from a single prompt.
  • Use reference images with useSubmitImageJob for subject or style consistency across new images.
  • Use useSubmitImageEditJob when the source image itself should remain the base.