# How to Use Claude Code for Image Generation: Skill, MCP or SVG

> Claude Code outputs no pixels itself. Render diagrams as SVG, or call an image model from a skill script or an MCP server. Gemini images start at $0.0336 each.

- URL: https://blog.laozhang.ai/en/posts/claude-code-image-generation
- Published: 2026-10-02
- Updated: 2026-10-02
- Author: LaoZhang AI Team (https://blog.laozhang.ai/en/about)
- Category: Claude Code
- Tags: Claude Code, Image Generation, Claude Code Skills, MCP, OpenAI API, Gemini API

---
Claude Code doesn't produce pixels. Every image that appears in a Claude Code session comes from one of two places: code that Claude writes and runs, such as an SVG rendered to PNG, or an external image model that Claude reaches through a script or an MCP server. Using Claude Code for image generation means choosing one of those and wiring it up once.

The choice follows the image. Diagrams, charts and covers with text come out sharper from code, and they cost nothing per image. Photo-like and illustrated images need an image model, which means an API key and a bill. Inside the model route, a skill with a small script saves files straight into your repository and is easy to share with a team. An MCP server takes one command to add, but the file lands outside the project folder.

## Can Claude Code generate images? Not by itself

No Claude model outputs images, on any plan. Anthropic's help center says "Claude doesn't generate photos or illustrations the way image-generation tools do," and the [vision documentation](https://platform.claude.com/docs/en/build-with-claude/vision) describes Claude as "an image understanding model only." Reading an image and making one are different abilities. Claude Code has the first and lacks the second.

What Claude Code adds is a shell. It can write a file, run a command and look at the result, so it can drive anything that does make images. Anthropic ships no image-model connector or skill of its own. The image-related skills in the `anthropics/skills` repository, `canvas-design`, `algorithmic-art` and `slack-gif-creator`, all render with code and call no image model. The image connectors in Anthropic's directory, such as Adobe, Canva and Hugging Face, are built by those partners.

For what the chat apps can and can't do, see [Can Claude Generate Images? No, But Here's What It Can Do](https://blog.laozhang.ai/en/posts/can-claude-generate-images).

## Pick a route by image type: SVG for diagrams, a model for photos

Start from what the image has to show, then check which access you can actually get.

| You need | Route | What you need first | Cost per image |
| --- | --- | --- | --- |
| Diagram, chart, cover with exact text | Claude writes SVG, a renderer makes PNG or WebP | `rsvg-convert` and `cwebp` installed | None beyond the Claude Code session |
| Photo-like or illustrated image, saved into the repo, shared with a team | A skill with a script that calls an image API | An API key with image access | Billed by the image API |
| Photo-like image, one person, fastest setup | An MCP server from Hugging Face, fal.ai or Replicate | An account with that provider | A daily GPU-time quota on Hugging Face, pay per run on fal.ai |

![Route picker for Claude Code images: diagrams go to SVG, repo-saved photos to a skill script, quick solo setups to an MCP server, with daily allowances as the fallback](https://blog.laozhang.ai/posts/en/claude-code-image-generation/img/image-route-picker.webp)

Three conditions move you from one row to another:

- **You can't get API access.** OpenAI may require organization verification with a government ID before GPT Image models work. Gemini image models have no free tier, so billing must be enabled. If neither is possible, the Hugging Face MCP server or Cloudflare Workers AI gives you a small daily allowance.
- **The image contains text or exact layout.** Use the SVG route. Text in SVG is real text, so a label is spelled the way you typed it.
- **Several people use the same repository.** A project skill in `.claude/skills/` travels with the repo. An MCP server added with the default local scope stays on your machine.

## Render diagrams, charts and covers as SVG with no image model

For anything made of shapes and text, ask Claude Code to write an SVG file and render it. Two commands do the conversion:

```bash
rsvg-convert -w 2000 -h 1125 diagram.svg -o diagram.png
cwebp -q 85 diagram.png -o diagram.webp
```

On macOS, `brew install librsvg webp` provides both tools. With `rsvg-convert` 2.61.3, those two commands turned a 684-byte SVG into a 2000×1125 PNG and a 9 KB WebP.

The images on this blog are made this way, inside Claude Code and without an image model. Claude writes the SVG, `rsvg-convert` renders it at an exact size, and `cwebp` converts it. Covers are 2400×1350 and body images are 2000×1125. The 18 WebP files for one article published on October 2, 2026 were each between 136 KB and 192 KB. In a separate test, a 911-byte hand-written SVG rendered to a 1600×600 PNG in 135 ms.

A prompt that works for this route names the size, the content and the output path:

```text
Write assets/architecture.svg with viewBox 0 0 2000 1125: three boxes
(Browser, API, Postgres) connected left to right, labels in 48px sans-serif.
Render it to assets/architecture.webp with rsvg-convert and cwebp,
then open the PNG and fix any overlapping text.
```

The limit is firm. This route can't create a new photograph or a painted illustration. If you ask for "a photo of a desk at sunset," you get vector shapes arranged to suggest a desk.

## Add an image skill: SKILL.md plus a 66-line script

A skill is the route to choose when you want model-generated images written into the project with a path you control. It is a folder with a `SKILL.md` file and a script. Put it in `.claude/skills/image/` to share it through the repository, or in `~/.claude/skills/image/` to keep it personal.

```text
.claude/skills/image/
├── SKILL.md
└── scripts/
    └── generate.py
```

### The script: one POST to the OpenAI Images API

This script uses only the Python standard library. It sends one request to `POST /v1/images/generations`, decodes the base64 image in `data[0].b64_json` and writes it to the path you give.

```python
#!/usr/bin/env python3
"""Generate one image with the OpenAI Images API and save it to disk.

Standard library only. Reads the key from OPENAI_API_KEY; never prints it.
"""
import argparse
import base64
import json
import os
import pathlib
import sys
import urllib.error
import urllib.request

MODELS = ("gpt-image-2.5-flare", "gpt-image-2.5-sunburst")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--prompt", required=True)
    parser.add_argument("--out", required=True, help="output file, e.g. assets/hero.png")
    parser.add_argument("--model", default="gpt-image-2.5-flare", choices=MODELS)
    parser.add_argument("--size", default="1536x1024")
    parser.add_argument("--quality", default="low")
    args = parser.parse_args()

    key = os.environ.get("OPENAI_API_KEY")
    if not key:
        print("OPENAI_API_KEY is not set. Export it in the shell before starting claude.", file=sys.stderr)
        return 2

    out = pathlib.Path(args.out)
    if out.exists():
        print(f"{out} already exists. Choose another --out so nothing is overwritten.", file=sys.stderr)
        return 2

    base = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1").rstrip("/")
    body = json.dumps({
        "model": args.model,
        "prompt": args.prompt,
        "size": args.size,
        "quality": args.quality,
    }).encode()
    request = urllib.request.Request(
        f"{base}/images/generations",
        data=body,
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(request, timeout=300) as response:
            payload = json.load(response)
    except urllib.error.HTTPError as error:
        print(f"HTTP {error.code}: {error.read().decode(errors='replace')[:600]}", file=sys.stderr)
        return 1
    except urllib.error.URLError as error:
        print(f"Request failed: {error.reason}", file=sys.stderr)
        return 1

    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_bytes(base64.b64decode(payload["data"][0]["b64_json"]))
    print(json.dumps({"saved": str(out), "bytes": out.stat().st_size, "usage": payload.get("usage")}))
    return 0


if __name__ == "__main__":
    sys.exit(main())
```

The script has been run only against its failure paths, on October 2, 2026 with Python 3.12.1 on macOS. No image was generated with a real key, so the success path, the shape of `usage` and the latency are unverified. The request format follows [OpenAI's image generation guide](https://developers.openai.com/api/docs/guides/image-generation). The four checks behaved like this:

| Condition | Result |
| --- | --- |
| `OPENAI_API_KEY` not set | Message on stderr, exit code 2, no network call |
| A fake key | The request reached api.openai.com and returned HTTP 401 with `invalid_api_key`; exit code 1, no file written |
| `--model dall-e-3` | argparse rejected it with "invalid choice" |
| `--out` points at an existing file | Message on stderr, exit code 2, no network call |

The defaults are chosen to keep a first run cheap: `gpt-image-2.5-flare` is OpenAI's fast everyday model, and `low` is the lowest of the documented quality values (`low`, `medium`, `high`, `xhigh`, `max`, `auto`). OpenAI recommends the sizes `1024x1024`, `1536x1024` and `1024x1536`. Complex prompts can take up to about 2 minutes, which is why the timeout is 300 seconds.

### The SKILL.md that tells Claude when to run it

```markdown
---
name: image
description: Generate a photo-like or illustrated raster image with the OpenAI Images API and save it into the project. Use for hero images, illustrations and icons. Do not use for diagrams or charts.
allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/generate.py *)
---

Generate one image per request:

python3 ${CLAUDE_SKILL_DIR}/scripts/generate.py --prompt "PROMPT" --out assets/NAME.png

- Pick a new file name under assets/. The script refuses to overwrite.
- Keep the defaults unless the user asks for another size, quality or model.
- After it prints the saved path, open the file with Read and describe what you see.
- Report the usage object from the output. Never run the script more than once without asking.
```

This file follows the documented frontmatter and the documented `${CLAUDE_SKILL_DIR}` pattern, its `allowed-tools` line adapts the documentation's own example to this script, and the skill was not invoked in a live session for this article.

Claude uses `description` to decide when the skill applies, and you can also call it by typing `/image`. Claude Code expands `${CLAUDE_SKILL_DIR}` both in the body and in `allowed-tools` Bash rules, which is the pattern the [skills documentation](https://code.claude.com/docs/en/skills) shows for bundled scripts. A new or edited skill takes effect in the running session. You need `/reload-skills` only if the top-level skills directory didn't exist when the session started.

### First run: export the key, then ask for one image

Claude Code reads shell environment variables when `claude` starts. Export the key first, and restart `claude` after you change it.

```bash
export OPENAI_API_KEY="sk-..."
claude
```

Then ask for an image:

```text
/image a hero image for the pricing page: soft morning light on a tidy desk
with a laptop, shallow depth of field. Save it as assets/hero.png
```

A successful run prints one JSON line with `saved`, `bytes` and `usage`, and the file exists at the path you named. If you get HTTP 401, the key is wrong or wasn't exported before `claude` started. If the response says your organization must be verified, complete OpenAI's [API Organization Verification](https://help.openai.com/en/articles/10910291-api-organization-verification). It needs a physical government-issued ID from a supported country, and one person can verify only one organization.

### Swap the provider: Gemini or Cloudflare Workers AI

The skill structure stays the same when you change the API. Only the script changes.

For Google's models, the current image IDs are `gemini-3.1-flash-lite-image`, `gemini-3.1-flash-image` and `gemini-3-pro-image`. `gemini-2.5-flash-image` shuts down on October 2, 2026, so a script written for it stops working. The complete setup is in [Nano Banana in Claude Code: Skill or MCP Setup That Still Works](https://blog.laozhang.ai/en/posts/nano-banana-claude-code).

Cloudflare Workers AI needs an account ID and an API token, and it includes a daily allowance. Its documented call for FLUX.1 schnell is:

```bash
curl https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/run/@cf/black-forest-labs/flux-1-schnell \
  -X POST \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -d '{ "prompt": "cyberpunk cat" }'
```

The model returns the image as base64 JPEG, so the script decodes it the same way. This command comes from [Cloudflare's model page](https://developers.cloudflare.com/workers-ai/models/flux-1-schnell/) and hasn't been run here.

## Use an MCP server instead: Hugging Face, fal.ai, Replicate

An MCP server gives Claude image tools without any script in your repository. Each of these providers publishes its own Claude Code command. The commands below are quoted from the vendors' documentation and haven't been executed here.

| Provider | Command | How you pay |
| --- | --- | --- |
| Hugging Face | `claude mcp add hf-mcp-server -t http "https://huggingface.co/mcp?login"` | ZeroGPU daily quota: 5 minutes of GPU time on a free account, 40 minutes on PRO |
| fal.ai | `claude mcp add --transport http fal-ai https://mcp.fal.ai/mcp --header "Authorization: Bearer YOUR_FAL_KEY"` | The server is free; you pay for each model run |
| Replicate | `claude mcp add replicate https://mcp.replicate.com/sse --transport sse --scope user` | Through your Replicate account; model prices are listed per model on Replicate |

After adding a server, start `claude`, run `/mcp` to authenticate, and confirm the status with `claude mcp list`. Three details differ between providers:

- **Hugging Face** generates images through Spaces that you add at huggingface.co/settings/mcp. Its examples include FLUX and Qwen image Spaces. The quotes around the URL keep zsh from treating the `?` as a wildcard.
- **fal.ai**'s command writes your key into the Claude Code configuration in clear text. Its server includes a `get_pricing` tool, so you can ask Claude for a model's price before running it.
- **Replicate**'s command uses the SSE transport, which Claude Code's documentation marks as deprecated.

### Where MCP images are saved: the tool-results directory

When an MCP tool returns a PNG, JPEG, GIF or WebP, Claude sees it inline and Claude Code saves the original bytes to a file in the session's `tool-results` directory under `~/.claude/projects/`. This requires Claude Code v2.1.283 or later, according to the [MCP documentation](https://code.claude.com/docs/en/mcp). The file is not in your project, so ask Claude to copy it to the path you want.

![Skill script versus MCP server: what runs, where the image file lands and how each route is paid for](https://blog.laozhang.ai/posts/en/claude-code-image-generation/img/skill-vs-mcp-file-location.webp)

Image results also count against the MCP output limit, which defaults to 25,000 tokens. If a large image is rejected, raising `MAX_MCP_OUTPUT_TOKENS` is the only fix the documentation offers. Whether a given server returns the image itself or a URL to it depends on that server.

Connectors from claude.ai, including the Hugging Face connector in Anthropic's directory, appear in Claude Code only when you are logged in with a claude.ai account. They don't appear when you authenticate with `ANTHROPIC_API_KEY`.

## What each image costs: $0.0336 to $0.24 on Gemini, tokens on OpenAI

Gemini is the only one of these with a published price per image. OpenAI bills by token, and Cloudflare and Hugging Face meter a daily allowance. All figures are from the providers' rate cards as of October 2, 2026.

| Provider and model | Unit | Price or allowance |
| --- | --- | --- |
| Gemini `gemini-3.1-flash-lite-image` | per 1K image | $0.0336 |
| Gemini `gemini-3.1-flash-image` | per image | $0.045 at 0.5K, $0.067 at 1K, $0.101 at 2K, $0.151 at 4K |
| Gemini `gemini-3-pro-image` | per image | $0.134 at 1K or 2K, $0.24 at 4K |
| OpenAI `gpt-image-2.5-flare` and `gpt-image-2.5-sunburst` | per 1M tokens | $30 image output, $8 image input, $5 text input |
| Cloudflare `flux-1-schnell` | Neurons | 10,000 per day included, then $0.011 per 1,000 |
| Hugging Face ZeroGPU | GPU minutes per day | 2 unauthenticated, 5 free account, 40 PRO |

Gemini has no free tier for any of its three image models. Iteration is what drives the bill: 20 attempts at a 1K hero image on `gemini-3.1-flash-image` cost 20 × $0.067 = $1.34.

OpenAI publishes no fixed price per image. The cost of a call is its image output tokens × $30 ÷ 1,000,000, plus the input tokens, and the token count is in the `usage` object of each response. That is why the script prints `usage`. Tier 1 accounts are also limited to 5 images per minute. For worked numbers, see [GPT Image 2.5 Sunburst Pricing: Official Costs vs $0.03 per Call](https://blog.laozhang.ai/en/posts/gpt-image-2-5-api-pricing).

Cloudflare's allowance can be turned into an image count. FLUX.1 schnell costs 4.80 Neurons per 512×512 tile plus 9.60 per step, with 4 steps by default. A 1024×1024 image is 4 tiles, so one image is 4 × 4.80 + 4 × 9.60 = 57.6 Neurons, and 10,000 ÷ 57.6 ≈ 173 images per day. That estimate assumes nothing else on the account uses the allowance. The allowance resets at 00:00 UTC, and requests over the limit fail unless the account is on Workers Paid.

Hugging Face's quota is time, not images. How many images fit in 5 minutes depends on the Space you call, and the quota resets 24 hours after first use.

If you want a fixed price per call, or you can't complete OpenAI's verification or enable Gemini billing, a third-party API service is another option. laozhang.ai, the service run by this site, documents OpenAI-compatible image endpoints with `gpt-image-2.5-flare-vip` and `gpt-image-2.5-sunburst-vip` at $0.03 per call as of September 12, 2026, and Gemini image models from $0.025 to $0.09 per call as of September 24, 2026. It is not OpenAI or Google, so their terms and data commitments don't apply. To use it with the script above you would add those model IDs to `MODELS` and set `OPENAI_BASE_URL` to `https://api2.laozhang.ai/v1`. That combination is untested.

## Let Claude check the result with Read, then fix the prompt

Claude Code can look at the image it just produced. The Read tool returns PNG, JPG and similar files as visual content, which makes a generate, look, fix loop possible in one session:

```text
Open assets/hero.png. Is the laptop screen blank, and is there any garbled text?
If so, change the prompt and generate assets/hero-2.png.
```

Two limits apply. Claude Code scales and recompresses large images before the model sees them, so Claude isn't looking at the full-resolution file. When detail matters, the [tools reference](https://code.claude.com/docs/en/tools-reference) suggests cropping the region first. And on the model routes every "fix" is another paid call, so put a number on it: "at most two more attempts."

For the SVG route the loop is free and more precise. Claude edits coordinates in the file and renders again.

## Permissions and keys: every auto-approved run is a paid call

The safe default is to approve each generation yourself. Two mechanisms remove the prompt, and they differ in scope.

`allowed-tools` in `SKILL.md` grants permission only for the turn that invokes the skill. The grant clears when you send your next message. One request can still trigger several runs, which is why the skill body above tells Claude to ask before running again.

A standing allow rule lasts across sessions. Choosing "Yes, and don't ask again" on a Bash prompt saves a rule to `.claude/settings.local.json`, in a form like `Bash(python3 .claude/skills/image/scripts/generate.py *)`. From then on Claude can call the image API without asking, in any session in that repository. Add such a rule only if you have a spending limit set on the provider's side.

Three more precautions:

- **Review skills before you run Claude Code in someone else's repository.** Workspace trust doesn't gate a project skill's `allowed-tools`, so a checked-in skill can pre-approve its own commands.
- **Keep the key out of the conversation.** Export it in the shell rather than pasting it into a prompt. If it lives in a `.env` file, deny reads of that file:

```json
{
  "permissions": {
    "deny": ["Read(./.env)"]
  }
}
```

- **Prefer variable expansion for MCP keys.** `claude mcp add --env KEY=value` writes the literal value into the configuration. A project `.mcp.json` supports `${VAR}` expansion, so the file you commit carries no secret.

## Failures and fixes: 401, verification, retired model IDs

Most failures point to a specific fix, and a few mean the route is wrong for you. The 401 row is what the script returned with a fake key. The other rows follow from the limits each provider documents.

| What you see | What it means | What to do |
| --- | --- | --- |
| HTTP 401 `invalid_api_key` | Wrong key, or exported after `claude` started | Export the key and restart `claude` |
| OpenAI asks for organization verification | GPT Image models are gated for your organization | Verify, or move to Gemini, Cloudflare or Hugging Face |
| A Gemini call fails on `gemini-2.5-flash-image` or a preview ID | The ID is shut down | Change to a current ID such as `gemini-3.1-flash-image` |
| Gemini rejects an image call on a free project | Image models have no free tier | Enable billing |
| The MCP image isn't in your project | It was saved under `~/.claude/projects/` | Ask Claude to copy it; update to v2.1.283 or later if no file is saved |
| An MCP image result is rejected as too large | It exceeds the output token limit | Raise `MAX_MCP_OUTPUT_TOKENS`, or use a script |
| Cloudflare requests start failing during the day | The 10,000 Neurons are used up | Wait for 00:00 UTC or move to Workers Paid |
| Text in the image is misspelled | Image models draw text as pixels | Render the text with the SVG route |

## Questions about image generation in Claude Code

### Is there a free way to generate images in Claude Code?

There are daily allowances, not unlimited free generation. Hugging Face ZeroGPU gives a free account 5 minutes of GPU time per day through its MCP server. Cloudflare Workers AI includes 10,000 Neurons per day, which works out to about 173 FLUX.1 schnell images at 1024×1024 and 4 steps. Gemini and OpenAI image models require billing. The SVG route costs nothing per image but can't make photos.

### Is a skill or an MCP server better for image generation in Claude Code?

A skill is better when the image must land in the repository at a path you choose, when teammates need the same setup, or when you want the key to stay in an environment variable. An MCP server is better when one person wants to try several models quickly and doesn't mind copying files out of `~/.claude/projects/`. If you are still deciding which skills belong in your setup, see [Best Claude Code Skills to Install First (2026)](https://blog.laozhang.ai/en/posts/claude-code-best-skills).

### Why can't Claude make images like ChatGPT?

Claude models take images as input and return text. Anthropic's documentation states that Claude "cannot generate, produce, edit, manipulate, or create images." ChatGPT pairs its language model with an image model. In Claude Code you build that pairing yourself, with a script or an MCP server.

### Does Vercel AI Gateway give free image generation in Claude Code?

Not in a way you can rely on. Vercel AI Gateway does support image generation, including an OpenAI-compatible endpoint at `https://ai-gateway.vercel.sh/v1/images/generations`. Its free tier covers only some models at lower rate limits, and the monthly free allowance stops applying once you buy credits. Vercel's pricing page doesn't state that any image model is included in the free tier.
