{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/openai-image-generation-nodejs/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-06-10 01:33:52 +0200"}},"id":"e78900a9-9197-575e-a7b5-2685242efeb9","excerpt":"OpenAI exposes image generation through the  Image API  ( POST /images/generations ). The official  openai  npm package wraps it as  client…","frontmatter":{"title":"AI image generation with OpenAI API","date":"2026-06-10 00:02:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["openai","api","node","ai","images"],"cover":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAhUlEQVQoz9WRXQ7DIAyDuf85y9pVGxAGFMKPp6YPvQAPW6RIkb/IsmSFyaOmG44xMHPnJ7SWkWJHyR3kKpxlhNDApYOowrkqdwztYp+beWpg7qKdfzkPKL14kDtNGNsaoReCMQUpMp5bFO1IFcZkaO0vlqqwfT+EvV8Z6yNIsD8o5edb/gLPNRb8iAwLUAAAAABJRU5ErkJggg==","aspectRatio":2.0869565217391304,"src":"/static/4d13f4453ab9c2a99d72d7f78a2440c7/c4ecb/cover.png","srcSet":"/static/4d13f4453ab9c2a99d72d7f78a2440c7/57ab0/cover.png 192w,\n/static/4d13f4453ab9c2a99d72d7f78a2440c7/f4739/cover.png 384w,\n/static/4d13f4453ab9c2a99d72d7f78a2440c7/c4ecb/cover.png 768w","srcWebp":"/static/4d13f4453ab9c2a99d72d7f78a2440c7/dd090/cover.webp","srcSetWebp":"/static/4d13f4453ab9c2a99d72d7f78a2440c7/ae504/cover.webp 192w,\n/static/4d13f4453ab9c2a99d72d7f78a2440c7/fef30/cover.webp 384w,\n/static/4d13f4453ab9c2a99d72d7f78a2440c7/dd090/cover.webp 768w","sizes":"(max-width: 768px) 100vw, 768px","presentationWidth":768,"presentationHeight":366},"resize":{"src":"/static/4d13f4453ab9c2a99d72d7f78a2440c7/c4ecb/cover.png"}}}},"fields":{"slug":"/notes/openai-image-generation-nodejs/","readingTime":{"text":"5 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"AI image generation with OpenAI API\",\n  \"date\": \"2026-06-10 00:02:00 UTC\",\n  \"cover\": \"./cover.png\",\n  \"tags\": [\"openai\", \"api\", \"node\", \"ai\", \"images\"],\n  \"canonical_url\": \"https://sevic.dev/notes/openai-image-generation-nodejs/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"OpenAI exposes image generation through the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developers.openai.com/api/docs/guides/image-generation\"\n  }), \"Image API\"), \" (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"POST /images/generations\"), \"). The official \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.npmjs.com/package/openai\"\n  }), mdx(\"inlineCode\", {\n    parentName: \"a\"\n  }, \"openai\")), \" npm package wraps it as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"client.images.generate\"), \". This post walks through the main request parameters and how to save generated images from Node.js.\"), mdx(\"p\", null, \"The examples use \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"gpt-image-2\")), \", OpenAI's latest GPT Image model. GPT Image models always return base64-encoded image data in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"data[].b64_json\"), \". Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"output_format\"), \" for the on-disk file type and put artistic direction in the prompt.\"), mdx(\"p\", null, \"For text generation with the same package, see the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/chatgpt-api-nodejs/\"\n  }), \"Chat Completions API\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/llm-integration-openai-responses-api/\"\n  }), \"Responses API\"), \" posts. Image generation is also available through Responses API tools, but this post focuses on the dedicated Image API endpoint.\"), mdx(\"p\", null, \"The running scenario: generate marketing hero images for a fictional todo app.\"), mdx(\"h3\", {\n    \"id\": \"prerequisites\"\n  }, \"Prerequisites\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"OpenAI account\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Generated API key\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Enabled billing\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Node.js version 26\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"openai\"), \" package installed (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"npm i openai\"), \")\")), mdx(\"h3\", {\n    \"id\": \"client-setup\"\n  }, \"Client setup\"), mdx(\"p\", null, \"Create a client with your API key (read from the environment in production).\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import OpenAI from 'openai';\\n\\nconst client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\\n\")), mdx(\"p\", null, \"The same SDK can target other hosts that implement a compatible API by setting \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"baseURL\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiKey\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const client = new OpenAI({\\n  apiKey: process.env.LLM_API_KEY,\\n  baseURL: 'https://your-gateway.example/v1',\\n});\\n\")), mdx(\"p\", null, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/openai/openai-node/blob/master/azure.md\"\n  }), \"Azure OpenAI\"), \" uses \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AzureOpenAI\"), \" instead. Confirm your provider supports the Image API and the model you pass to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"model\"), \".\"), mdx(\"h3\", {\n    \"id\": \"basic-integration\"\n  }, \"Basic integration\"), mdx(\"p\", null, \"Call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"client.images.generate\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"model\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"prompt\"), \". The examples use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-2\"), \", older snapshots include \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-1.5\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-1\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-1-mini\"), \". Pin a snapshot (for example \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-2-2026-04-21\"), \") when you need stable behavior across deploys.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"prompt\"), \" describes what to generate. GPT Image models accept up to about 32,000 characters. Be specific about subject, layout, colors, and style.\"), mdx(\"p\", null, \"GPT Image models always return base64 in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"data[].b64_json\"), \". Decode it and write the file yourself.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { writeFile } from 'node:fs/promises';\\n\\nconst prompt = `\\nMinimal flat illustration for a productivity app landing page.\\nShow a todo dashboard with a checklist, calendar widget, and soft pastel palette.\\nNo text labels on screen elements.\\n`.trim();\\n\\nconst result = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt,\\n});\\n\\nawait writeFile('hero.png', Buffer.from(result.data[0].b64_json, 'base64'));\\n\")), mdx(\"h3\", {\n    \"id\": \"n\"\n  }, \"n\"), mdx(\"p\", null, \"Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"n\"), \" to generate multiple images in one request (default \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1\"), \", maximum \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"10\"), \"). Loop over \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"result.data\"), \" to save each image.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { writeFile } from 'node:fs/promises';\\n\\nconst result = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, variant layout, soft pastel colors',\\n  n: 2,\\n});\\n\\nfor (const [index, item] of result.data.entries()) {\\n  await writeFile(\\n    `hero-${index}.png`,\\n    Buffer.from(item.b64_json, 'base64'),\\n  );\\n}\\n\")), mdx(\"h3\", {\n    \"id\": \"size\"\n  }, \"Size\"), mdx(\"p\", null, \"Control dimensions with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"size\"), \". Common presets are \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1024x1024\"), \" (square), \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1536x1024\"), \" (landscape), and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1024x1536\"), \" (portrait). \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"auto\"), \" lets the model pick based on the prompt.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-2\"), \" also accepts custom \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"WIDTHxHEIGHT\"), \" strings when width and height are multiples of 16, the aspect ratio is between 1:3 and 3:1, and total pixels stay within the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developers.openai.com/api/docs/guides/image-generation\"\n  }), \"documented limits\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const result = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, portrait orientation, soft pastel colors',\\n  size: '1024x1536',\\n});\\n\")), mdx(\"h3\", {\n    \"id\": \"quality\"\n  }, \"Quality\"), mdx(\"p\", null, \"Set rendering quality with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"quality\"), \". Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"low\"), \" for fast drafts and iterations, then \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"medium\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"high\"), \" for final assets. Default is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"auto\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const draft = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, soft pastel colors',\\n  quality: 'low',\\n});\\n\\nconst final = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, soft pastel colors, polished details',\\n  quality: 'high',\\n  size: '1024x1536',\\n});\\n\")), mdx(\"h3\", {\n    \"id\": \"output-format\"\n  }, \"Output format\"), mdx(\"p\", null, \"GPT Image models return base64 in the JSON response. Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"output_format\"), \" to control the encoded file type: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"png\"), \" (default), \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jpeg\"), \", or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"webp\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { writeFile } from 'node:fs/promises';\\n\\nconst result = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, soft pastel colors',\\n  output_format: 'jpeg',\\n});\\n\\nawait writeFile('hero.jpg', Buffer.from(result.data[0].b64_json, 'base64'));\\n\")), mdx(\"h3\", {\n    \"id\": \"compression\"\n  }, \"Compression\"), mdx(\"p\", null, \"When \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"output_format\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jpeg\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"webp\"), \", set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"output_compression\"), \" from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"0\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"100\"), \" to trade file size for quality. JPEG is often faster than PNG when latency matters.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const result = await client.images.generate({\\n  model: 'gpt-image-2',\\n  prompt:\\n    'Minimal flat illustration of a todo app dashboard, soft pastel colors',\\n  output_format: 'webp',\\n  output_compression: 50,\\n});\\n\")), mdx(\"h3\", {\n    \"id\": \"background\"\n  }, \"Background\"), mdx(\"p\", null, \"Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"background: 'transparent'\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"png\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"webp\"), \" on models that support it when you need a cutout asset. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-2\"), \" does \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"not\"), \" support transparent backgrounds; use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gpt-image-1.5\"), \" or an earlier GPT Image model for that workflow, or bake the background into the prompt.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const result = await client.images.generate({\\n  model: 'gpt-image-1.5',\\n  prompt: 'Flat icon of a checkmark, no background, centered',\\n  output_format: 'png',\\n  background: 'transparent',\\n});\\n\")), mdx(\"h3\", {\n    \"id\": \"production-notes\"\n  }, \"Production notes\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Cost\"), \" scales with \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"quality\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"size\"), \". See \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://openai.com/api/pricing/\"\n  }), \"OpenAI pricing\"), \" before generating at scale.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Moderation\"), \" - use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"moderation: 'auto'\"), \" (default) or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"'low'\"), \" on GPT Image models when you need less restrictive filtering.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Errors\"), \" - handle \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"image_generation_user_error\"), \" (for example \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"moderation_blocked\"), \") by changing the prompt or inputs; do not blindly retry.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Latency\"), \" - complex prompts can take up to about two minutes.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Storage\"), \" - decode and persist files yourself. GPT Image responses are base64 in JSON.\")), mdx(\"h3\", {\n    \"id\": \"demo\"\n  }, \"Demo\"), mdx(\"p\", null, \"Runnable scripts for each section live in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"openai-image-generation-demo\"), \" folder. Get access via \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/demos\"\n  }), \"code demos\"), \".\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"e78900a9-9197-575e-a7b5-2685242efeb9"}},"staticQueryHashes":["1961101537","2542493696"]}