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:
useSubmitImageJobsubmits a generation request and returns ajobId.useSubmitImageEditJobsubmits an edit request and returns ajobId.useImageJobStatuspolls 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, default1): 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, ornullto disable polling.options(object): Optional polling configuration.
Status Options
pollInterval(number, default10000): Polling interval in milliseconds; values below5000are clamped.enabled(boolean, defaulttrue): 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", ornull): Current job status.images(array | null): Ordered completed image objects containing a permanently hostedurl.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
jobIduntil the job completes or fails so polling survives reloads. - Treat
job.statusas the source of truth for loading UI. - Use
onCompleteandonErrorto copy terminal state and clear thejobId. - Persist completed hosted
urlvalues for display and reuse. - Choose an aspect ratio that matches the final UI or export format.
- Use
numImagesto create variations from a single prompt. - Use reference images with
useSubmitImageJobfor subject or style consistency across new images. - Use
useSubmitImageEditJobwhen the source image itself should remain the base.