GPT Image 2 (ChatGPT Images 2.0)
OpenAI's latest image model — the strongest model available for legible text inside images. Use this when the picture is the typography: chalkboards, store signs, hand-lettered posters, menu boards, magazine covers, packaging mockups, UI screenshots, book covers.
Two endpoints:
- Text-to-image (
openai/gpt-image-2) — generate from a prompt - Image edit (
openai/gpt-image-2/edit) — modify a reference image (with optional mask for region-specific edits)
| Field | Value |
|---|---|
| Model ID | openai/gpt-image-2 (text-to-image) · openai/gpt-image-2/edit (edit) |
| Provider | Fal AI |
| Method | Sync (POST → response with image URLs, no polling) |
| Type | Image generation + editing |
| API Key | .env → FAL_KEY |
| Docs | https://fal.ai/models/openai/gpt-image-2 · https://fal.ai/models/openai/gpt-image-2/edit |
Setup
- Get a Fal API key from https://fal.ai/dashboard/keys
- Add to your
.envfile (or wherever your project keeps secrets):FAL_KEY=your_key_here - That's it. Sync API — no SDK install required, just
curlorfetch.
How to call it
Auth header is Authorization: Key {key} — not Bearer. This applies to both endpoints.
Text-to-image — minimum request
bashcurl -s -X POST "https://fal.run/openai/gpt-image-2" \ -H "Authorization: Key $FAL_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "A vintage diner chalkboard reading TODAY SPECIAL — Lobster Roll $24"}'
Text-to-image — full request
json{ "prompt": "...", "image_size": "landscape_4_3", "quality": "medium", "num_images": 1, "output_format": "png" }
Image edit — minimum request
Pass one or more public image URLs as references. Prompt describes the edit.
bashcurl -s -X POST "https://fal.run/openai/gpt-image-2/edit" \ -H "Authorization: Key $FAL_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Same workers, same beam — but they are all on phones now. One taking a selfie, one annoyed on a call. Hard hats with AirPods.", "image_urls": ["https://v3b.fal.media/files/b/.../reference.png"] }'
Image edit — full request
json{ "prompt": "...", "image_urls": ["https://...", "https://..."], "image_size": "auto", "quality": "medium", "num_images": 1, "output_format": "png", "mask_url": "https://.../mask.png" }
image_size: "auto"infers dimensions from the reference image — usually what you want.mask_url(optional) is a black/white image marking the region to edit. White = edit, black = keep. Use for surgical edits ("only change the sky").
Response (both endpoints)
json{ "images": [ { "url": "https://v3b.fal.media/files/b/.../result.png", "content_type": "image/png", "file_name": "result.png" } ] }
Download images[i].url immediately — Fal URLs may expire.
Where do image_urls come from?
The reference image needs to be at a publicly accessible URL. Options:
- Fal's own storage — use the JS/Python client's upload helper (
fal.storage.upload(file)in JS,fal_client.upload_file(path)in Python). - Reuse a previous Fal output URL — outputs from any Fal model can be passed straight back as
image_urlsto chain edits. - Any public host — S3, GitHub raw, your own server, etc. Must be a direct image URL (not an HTML page).
If your project uses Kie AI, their file upload endpoint at https://kieai.redpandaai.co/api/file-stream-upload works fine and returns a URL you can pass to Fal — see the bash recipe below for an example.
Parameters
Text-to-image (openai/gpt-image-2)
| Param | Required | Options | Default |
|---|---|---|---|
prompt | Yes | string | — |
image_size | No | preset name OR {width, height} | "landscape_4_3" |
quality | No | "low", "medium", "high" | "medium" (Jay's default — high is overkill for most cases, ~3.5x more expensive) |
num_images | No | 1 – 4 | 1 |
output_format | No | "jpeg", "png", "webp" | "png" |
sync_mode | No | bool — return base64 data URI inline | false |
Image edit (openai/gpt-image-2/edit)
| Param | Required | Options | Default |
|---|---|---|---|
prompt | Yes | string — describe the edit | — |
image_urls | Yes | array of public image URLs | — |
image_size | No | preset, {width, height}, or "auto" | "auto" (infer from input) |
quality | No | "low", "medium", "high" | "medium" |
num_images | No | 1 – 4 | 1 |
output_format | No | "jpeg", "png", "webp" | "png" |
sync_mode | No | bool — return base64 data URI inline | false |
mask_url | No | URL of B/W mask (white = edit, black = keep) | — |
Image sizes
Preset names: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9.
Custom dimensions — pass an object:
json"image_size": { "width": 1536, "height": 1024 }
Constraints on custom sizes:
- Both dimensions must be multiples of 16
- Max edge: 3840px
- Aspect ratio: ≤ 3:1
- Total pixels between 655,360 and 8,294,400 (~640×1024 min, ~3840×2160 max)
Pricing
Token-based, billed via Fal:
| Token type | Input | Cached | Output |
|---|---|---|---|
| Text | $5 / 1M | $1.25 / 1M | $10 / 1M |
| Image | $8 / 1M | $2 / 1M | $30 / 1M |
Per-image rough cost (landscape_4_3 = 1536×1024):
low≈ $0.02 per imagemedium≈ $0.05 per image ← defaulthigh≈ $0.18 per image
quality is the biggest cost lever. Default to medium — that's the sweet spot for most work. Bump to high only when typography/detail really matters and the image is a final. low for cheap drafts. Check actual usage in the Fal dashboard.
When to use vs other models
| Scenario | Endpoint | Notes |
|---|---|---|
| Sign / chalkboard / poster with text | text-to-image | Best in class for typography |
| Magazine cover / book cover | text-to-image | |
| Menu board / packaging mockup | text-to-image | |
| UI screenshot mockup | text-to-image | |
| Modify an existing image ("change the sky", "swap the logo", "add text") | edit | Pass image as image_urls, describe the change |
| Surgical region edit ("only change this corner") | edit + mask_url | Mask defines the editable region |
| Iterative refinement | edit | Feed previous output URL back in |
| Photoreal portrait | other | Use Nano Banana / Flux |
| Style transfer with strong style preservation | other | GPT Image 2 edit reinterprets more than it preserves |
| Need < $0.02 per render | other | Switch model — even low quality here is comparable to other models' default |
Recipe — Node.js
One helper, both endpoints. Drop in a .js file and run with node. Forward slashes in paths work fine on Windows.
javascriptimport fs from 'node:fs'; import path from 'node:path'; const FAL_KEY = process.env.FAL_KEY || fs.readFileSync('.env', 'utf8').match(/FAL_KEY=(.+)/)[1].trim(); // Pass image_urls to use the edit endpoint; omit for text-to-image. async function gptImage2({ prompt, image_urls, mask_url, image_size, quality = 'medium', num_images = 1, output_format = 'png', outDir = './output', label = 'gpt2', }) { fs.mkdirSync(outDir, { recursive: true }); const isEdit = Array.isArray(image_urls) && image_urls.length > 0; const url = isEdit ? 'https://fal.run/openai/gpt-image-2/edit' : 'https://fal.run/openai/gpt-image-2'; const body = { prompt, quality, num_images, output_format }; if (isEdit) { body.image_urls = image_urls; body.image_size = image_size ?? 'auto'; if (mask_url) body.mask_url = mask_url; } else { body.image_size = image_size ?? 'landscape_4_3'; } const res = await fetch(url, { method: 'POST', headers: { 'Authorization': `Key ${FAL_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const json = await res.json(); if (!json.images?.length) throw new Error(`No images returned: ${JSON.stringify(json)}`); const ts = Date.now(); const saved = []; for (let i = 0; i < json.images.length; i++) { const suffix = json.images.length > 1 ? `_${i + 1}` : ''; const fname = `${label}_${ts}${suffix}.${output_format}`; const fp = path.join(outDir, fname); const buf = Buffer.from(await (await fetch(json.images[i].url)).arrayBuffer()); fs.writeFileSync(fp, buf); saved.push(fp); console.log(`Saved: ${fp}`); } return saved; } // Text-to-image await gptImage2({ prompt: 'A chalkboard sign on a brick wall, hand-lettered: "OPEN AT 7"', label: 'chalkboard', }); // Edit an existing image await gptImage2({ prompt: 'Same scene but everyone is on their phone now. One taking a selfie, one on a call.', image_urls: ['https://v3b.fal.media/files/b/.../original.png'], label: 'phones_edit', });
Recipe — Python
One helper, both endpoints. Pass image_urls for the edit variant; omit for text-to-image.
pythonimport os, time, requests, pathlib FAL_KEY = os.environ['FAL_KEY'] def gpt_image_2(prompt, image_urls=None, mask_url=None, image_size=None, quality='medium', num_images=1, output_format='png', out_dir='./output', label='gpt2'): pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True) is_edit = bool(image_urls) url = 'https://fal.run/openai/gpt-image-2/edit' if is_edit else 'https://fal.run/openai/gpt-image-2' body = {'prompt': prompt, 'quality': quality, 'num_images': num_images, 'output_format': output_format} if is_edit: body['image_urls'] = image_urls body['image_size'] = image_size or 'auto' if mask_url: body['mask_url'] = mask_url else: body['image_size'] = image_size or 'landscape_4_3' res = requests.post(url, headers={'Authorization': f'Key {FAL_KEY}', 'Content-Type': 'application/json'}, json=body) data = res.json() if not data.get('images'): raise RuntimeError(f'No images: {data}') ts = int(time.time() * 1000) saved = [] for i, img in enumerate(data['images']): suffix = f'_{i+1}' if len(data['images']) > 1 else '' fp = f'{out_dir}/{label}_{ts}{suffix}.{output_format}' with open(fp, 'wb') as f: f.write(requests.get(img['url']).content) saved.append(fp) print(f'Saved: {fp}') return saved # Text-to-image gpt_image_2(prompt='A chalkboard reading "OPEN AT 7"', label='chalkboard') # Edit gpt_image_2( prompt='Same scene but everyone is on their phone now', image_urls=['https://v3b.fal.media/files/b/.../original.png'], label='phones_edit', )
Recipe — bash (one-shot)
Use a temp JSON file to avoid shell-escape pain with multi-line prompts.
Text-to-image
bashcat > /tmp/gpt2_body.json << 'ENDJSON' { "prompt": "A vintage diner chalkboard, hand-lettered: TODAY SPECIAL — Lobster Roll $24", "image_size": "landscape_4_3", "quality": "medium", "num_images": 1, "output_format": "png" } ENDJSON curl -s -X POST "https://fal.run/openai/gpt-image-2" \ -H "Authorization: Key $FAL_KEY" \ -H "Content-Type: application/json" \ -d @/tmp/gpt2_body.json
Image edit
bashcat > /tmp/gpt2_edit_body.json << 'ENDJSON' { "prompt": "Same workers on the beam — but they're all on phones now. One taking a selfie.", "image_urls": ["https://v3b.fal.media/files/b/.../original.png"], "image_size": "auto", "quality": "medium", "num_images": 1, "output_format": "png" } ENDJSON curl -s -X POST "https://fal.run/openai/gpt-image-2/edit" \ -H "Authorization: Key $FAL_KEY" \ -H "Content-Type: application/json" \ -d @/tmp/gpt2_edit_body.json
Prompting tips
GPT Image 2 is unusually good at typography, but you have to tell it clearly:
- Structure complex prompts as JSON. For multi-element scenes (posters, infographics, multi-panel comics, app mockups), the model responds notably better to a JSON object than to flowing prose. Keys like
type,subject,style,background,header,layout,footergive the model an explicit slot for each element. Loose prose forces it to guess what's foreground vs background. The community-curated prompt library (see "Prompt examples library" below) is almost entirely structured JSON for this reason. - Parameterize with placeholders for reuse. Use
{argument name="quote" default="Stay hungry, stay foolish"}syntax to mark variables in your prompt. Originally a Raycast Snippets convention, widely adopted across the GPT Image 2 community. Lets one prompt template handle many variants without rewriting. - Quote the text:
hand-lettered text reads "OPEN AT 7"— quoting helps the model lock in the literal characters. - Specify the lettering style:
chalk lettering,serif sign painter,bold sans-serif neon,embossed metal type,cursive script. - Anchor the surface: chalkboard, brick wall, neon storefront, paper menu, vinyl record sleeve. The model uses the surface to inform the lettering style.
- Mention lighting:
warm tungsten,golden hour,cool fluorescent— affects mood + readability. - Layout hints work:
the title stacked over three lines,with a small drawing below,centered. - For multi-line text: spell out line breaks.
Line 1: "ESPRESSO". Line 2: "$4.50".Don't trust newlines in the prompt to translate.
For the edit endpoint
- Describe the change, not the whole scene. "Same workers, same beam, same lunch boxes — but everyone is on their phone now" works better than re-describing the original from scratch.
- Anchor to what stays the same. The model preserves more when you explicitly call out what shouldn't change.
- Use
mask_urlfor surgical edits. Without a mask, the model may reinterpret the whole image. With a mask, it constrains the change to the white region. - Chain edits. Pass the previous output URL back into a new edit call. Good for iterative refinement.
What it's bad at:
- Long paragraphs of text — keep it to a few words/lines.
- Faces with expressions tied to specific real people — generic figures fine, named likenesses iffy.
- Strict style preservation in edits — it tends to reinterpret. For tight style transfer use a different model.
Transparent backgrounds — not supported
Fal's GPT Image 2 endpoints do not support true transparency. Tested 2026-04-22:
- No
backgroundparameter (OpenAI's direct API hasbackground: "transparent"forgpt-image-1— Fal's wrapper for GPT Image 2 does not expose it). - Passing
background: "transparent"anyway → silently ignored, output is RGB PNG with no alpha channel. - Prompting for "transparent background" → model paints a fake checkerboard pattern in the pixels. Looks transparent; isn't. The PNG is still RGB.
If you need a transparent PNG, chain a background remover:
fal-ai/imageutils/rembg— fast, cheap, works on most subjects.fal-ai/birefnet— higher quality, slower.- Pass the GPT Image 2 output URL straight in as
image_url.
Or use Nano Banana Pro / other models for the generation step if clean alpha matters.
Prompt examples library
For style/category inspiration, the awesome-gpt-image-2 community library has 700+ curated prompts (CC BY 4.0, attribution required). Use it whenever a user asks "how would I prompt this?" for a recognizable style or use-case.
Two ways in:
- Browse the gallery (humans): youmind.com/gpt-image-2-prompts — masonry grid, full-text search, deep-linkable categories.
- Grep the README (agents): raw.githubusercontent.com/YouMind-OpenLab/awesome-gpt-image-2/main/README.md — single markdown file, ~350KB, all prompts in one place. Predictable structure: each prompt is
### No. N: Category - Title→ badges →#### 📖 Description→#### 📝 Prompt(a fenced code block, usually JSON) →#### 📌 Details(author/source/date).
Category slugs (use as ?categories={slug} on youmind.com, or as a grep target in the README):
- Use cases:
profile-avatar,social-media-post,infographic-edu-visual,youtube-thumbnail,comic-storyboard,product-marketing,ecommerce-main-image,game-asset,poster-flyer,app-web-design - Styles:
photography,cinematic-film-still,anime-manga,illustration,sketch-line-art,comic-graphic-novel,3d-render,chibi-q-style,isometric,pixel-art,oil-painting,watercolor,ink-chinese-style,retro-vintage,cyberpunk-sci-fi,minimalism - Subjects:
portrait-selfie,influencer-model,character,group-couple,product,food-drink,fashion-item,animal-creature,vehicle,architecture-interior,landscape-nature,cityscape-street,diagram-chart,text-typography,abstract-background
Recipe — fetch + grep examples for a category:
bash# Pull the README once, save locally curl -s https://raw.githubusercontent.com/YouMind-OpenLab/awesome-gpt-image-2/main/README.md -o /tmp/gpt2_prompts.md # Find all entries matching a category (case-insensitive) grep -n "^### No\." /tmp/gpt2_prompts.md | grep -i "youtube thumbnail" # Read a specific prompt block by its line range (use line numbers from the grep above) sed -n '2722,2830p' /tmp/gpt2_prompts.md
Or, in one shot via WebFetch (for agents): fetch the raw README and ask for the N best examples in category X — the file's predictable headings make extraction reliable.
Attribution. When reusing a prompt verbatim or near-verbatim in published work, credit YouMind OpenLab + the original author (the 📌 Details section names the author and source). Repo: github.com/YouMind-OpenLab/awesome-gpt-image-2.
Notes / gotchas
- Sync API — single POST call returns the image URL. No polling, no taskId.
- Auth header is
Key, notBearer— Fal-specific. - Up to 4 images per call via
num_images— useful for picking from variants. Cost scales linearly. - Custom dimensions: multiples of 16, total pixels in [655K, 8.3M], aspect ratio ≤ 3:1, max edge 3840px.
- Two endpoints:
/openai/gpt-image-2(text-to-image) vs/openai/gpt-image-2/edit(withimage_urlsand optionalmask_url). Pick by what you have as input. - Edit endpoint defaults
image_sizetoauto— uses the input image's dimensions. Override only if you need a different output shape. - Reference images must be public URLs — upload to Fal storage, reuse a previous Fal output URL, or host elsewhere (S3, GitHub raw).
- Fal URLs expire — download immediately.
sync_mode: truereturns a base64 data URI inline (skips request history). Useful for piping into another tool without disk I/O. Default isfalse.
Links
- Text-to-image: https://fal.ai/models/openai/gpt-image-2
- Image edit: https://fal.ai/models/openai/gpt-image-2/edit
- Fal API docs (T2I): https://fal.ai/models/openai/gpt-image-2/api
- Fal API docs (Edit): https://fal.ai/models/openai/gpt-image-2/edit/api
- Fal pricing: https://fal.ai/pricing
- Fal dashboard (usage / keys): https://fal.ai/dashboard
- Prompt library (gallery): https://youmind.com/gpt-image-2-prompts
- Prompt library (repo, CC BY 4.0): https://github.com/YouMind-OpenLab/awesome-gpt-image-2


