← Files InsForgeARCHIVED FILE
skills/insforge/ai/image-generation.md
3.95 KB · Oct 3, 2026 · 06:30 UTC
# Image Generation
Use OpenRouter image-capable chat models for image generation or image editing,
then store durable outputs in InsForge Storage.
Official OpenRouter references:
- [Image generation](https://openrouter.ai/docs/guides/overview/multimodal/image-generation)
- [Chat completions](https://openrouter.ai/docs/api-reference/chat-completion)
- [Models API](https://openrouter.ai/docs/api-reference/models/get-models)
## Setup
Start with [overview.md](overview.md): run `npx @insforge/cli ai setup` and keep
`OPENROUTER_API_KEY` server-side. Check image-capable models before use:
```bash
curl "https://openrouter.ai/api/v1/models?output_modalities=image" \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
```
## Generate an Image
OpenRouter image generation uses chat completions with image output
modalities. Direct `fetch` avoids TypeScript friction around OpenRouter-specific
fields such as `modalities`, `image_config`, and `message.images`.
```typescript
const prompt = 'Create a clean product mockup on a white desk.'
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: process.env.OPENROUTER_IMAGE_MODEL ?? 'google/gemini-2.5-flash-image',
modalities: ['image', 'text'],
messages: [
{ role: 'user', content: prompt },
],
image_config: {
aspect_ratio: '16:9',
},
}),
})
if (!response.ok) {
throw new Error(`OpenRouter image request failed: ${response.status}`)
}
const result = await response.json()
const imageUrl = result.choices?.[0]?.message?.images?.[0]?.image_url?.url
if (!imageUrl) {
throw new Error('OpenRouter response did not include an image URL')
}
```
## Store the Output
OpenRouter output URLs should be treated as transfer URLs, not your app's
permanent asset store. Upload the generated file to InsForge Storage and save
both `url` and `key` in the database.
Do not store raw base64 image data or large binary blobs in Postgres. Store
metadata and Storage references in the database; keep image bytes in Storage.
Derive owner, tenant, or session fields from the authenticated server-side
context; never accept ownership IDs from the browser.
```typescript
const imageResponse = await fetch(imageUrl)
const imageBlob = await imageResponse.blob()
const storageKey = `generated/${crypto.randomUUID()}.png`
const { data, error } = await insforge.storage
.from('images')
.upload(storageKey, imageBlob)
if (error) {
throw error
}
const { data: userData, error: userError } = await insforge.auth.getCurrentUser()
if (userError || !userData?.user?.id) {
throw userError ?? new Error('Authentication required')
}
await insforge.database.from('generated_images').insert([{
user_id: userData.user.id,
prompt,
image_url: data.url,
image_key: data.key,
}])
```
## Best Practices
1. Use [models-list.md](models-list.md) to verify `output_modalities` includes
`image`.
2. Keep image prompts and model calls on the server.
3. Persist generated images in InsForge Storage before storing database rows.
4. Save the original prompt, model ID, storage `url`, and storage `key` when the
product needs reproducibility or deletion.
5. Use authenticated owner, session, or tenant fields on generated-image rows so
RLS can restrict access.
6. Use direct HTTP when OpenAI SDK types reject OpenRouter-specific image
fields; do not remove those fields to satisfy TypeScript.
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Using a text-only model for image output | Filter models with `output_modalities=image` |
| Storing raw base64 image data or temporary image URLs in Postgres | Upload to Storage, save `url` and `key` |
| Removing `modalities` because TypeScript complains | Use direct `fetch` or a narrow local type cast |
| Assuming every image model supports the same options | Check the model and image generation docs first |
SHA-256: d83d45fc9a1b6c02eab4a45f77b944bc0413826b715d430a48438f5a130f2d95