Structuring a Prompt

Write one line per concern, subject first, and give exact colors as hex codes. Keeping the parts in an object makes them easy to vary one at a time:

nestor.mjs: a prompt assembled from named partsJavaScript
import fs from 'node:fs';
import OpenAI from 'openai';
const parts = {                                        // one line per concern
  subject: 'Nestor, BookNest\'s mascot, a small round bluebird with a saffron belly',
  pose: 'standing, full body, three-quarter view, holding a tiny open book',
  style: 'flat vector illustration, clean outlines, no gradients',
  palette: 'deep blue #1f5f8b, saffron #e09a10, cream #fff3e0',
  composition: 'centered on a plain white background with generous margins',
  avoid: 'no text, no other characters',
};
const prompt = Object.entries(parts).map(([key, value]) => `${key}: ${value}.`).join('\n');
const res = await new OpenAI().images.generate({
  model: 'gpt-image-2.5-flare', prompt, size: '1024x1024', quality: 'low' });
fs.writeFileSync('nestor.png', Buffer.from(res.data[0].b64_json, 'base64'));
const { input_tokens, output_tokens } = res.usage;
console.log(`${prompt}\n${input_tokens} prompt tokens, ${output_tokens} out`);
Output
subject: Nestor, BookNest's mascot, a small round bluebird with a saffron belly.
pose: standing, full body, three-quarter view, holding a tiny open book.
style: flat vector illustration, clean outlines, no gradients.
palette: deep blue #1f5f8b, saffron #e09a10, cream #fff3e0.
composition: centered on a plain white background with generous margins.
avoid: no text, no other characters.
104 prompt tokens, 196 out

Every part shows in the first image of Consistent Characters's figure. Quote text only when you want lettering, name styles rather than artists, and say what must not appear.

A prompt built from one line per concern; switch parts on and off to see what each one controls in the pictureHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; max-width: 600px; }
  .row { display: flex; gap: 10px; align-items: flex-start; }
  pre { flex: 1; margin: 0; background: #fff; border: 1px solid #e0d8c8; padding: 6px; font-size: 11px; white-space: pre-wrap; min-width: 0; }
  label { display: block; }
  canvas { display: block; background: #fff; border: 1px solid #e0d8c8; }
</style>
<div id="toggles"></div>
<div class="row"><canvas id="c" width="220" height="220"></canvas><pre id="prompt"></pre></div>
<script>
  const parts = {                                         // one line per concern
    subject: "Nestor, BookNest's mascot, a small round bluebird with a saffron belly",
    pose: 'standing, full body, three-quarter view, holding a tiny open book',
    style: 'flat vector illustration, clean outlines, no gradients',
    palette: 'deep blue #1f5f8b, saffron #e09a10, cream #fff3e0',
    composition: 'centered on a plain white background with generous margins',
    avoid: 'no text, no other characters',
  };
  const on = Object.fromEntries(Object.keys(parts).map(k => [k, true]));
  document.getElementById('toggles').innerHTML = Object.keys(parts).map(k =>
    `<label><input type="checkbox" data-k="${k}" checked> ${k}</label>`).join('');

  const ctx = document.getElementById('c').getContext('2d');
  // A stand-in for what each part contributes (a real model interprets the words)
  function draw() {
    ctx.clearRect(0, 0, 220, 220);
    const blue = on.palette ? '#1f5f8b' : '#6d8fa8', saffron = on.palette ? '#e09a10' : '#d9b36b';
    const cx = on.composition ? 110 : 150, cy = on.composition ? 115 : 150, s = on.composition ? 1 : 0.8;
    if (!on.composition) { ctx.fillStyle = '#dfe7cf'; ctx.fillRect(0, 0, 220, 220); }       // busy background
    if (!on.avoid) {                                      // unrequested extras sneak in
      ctx.fillStyle = '#8d6e63';  ctx.font = 'bold 14px Georgia';  ctx.fillText('NESTR BOOKS', 10, 24);
      ctx.fillStyle = '#b5452f';  ctx.beginPath();  ctx.arc(40, 180, 14, 0, 7);  ctx.fill();
    }
    ctx.save();  ctx.translate(cx, cy);  ctx.scale(s, s);
    if (on.subject) {
      const body = on.style ? blue : ctx.createRadialGradient(-10, -20, 5, 0, 0, 60);
      if (!on.style) { body.addColorStop(0, '#9cc3e0');  body.addColorStop(1, blue); }
      ctx.fillStyle = body;  ctx.beginPath();  ctx.ellipse(0, 0, 48, 52, 0, 0, 7);  ctx.fill();
      ctx.fillStyle = saffron;  ctx.beginPath();  ctx.ellipse(6, 14, 28, 32, 0, 0, 7);  ctx.fill();    // belly
      ctx.fillStyle = '#fff';  ctx.beginPath();  ctx.arc(14, -22, 10, 0, 7);  ctx.fill();
      ctx.fillStyle = '#263238';  ctx.beginPath();  ctx.arc(17, -22, 5, 0, 7);  ctx.fill();
      ctx.fillStyle = saffron;  ctx.beginPath();  ctx.moveTo(40, -12);  ctx.lineTo(58, -6);  ctx.lineTo(40, 0);  ctx.fill();
      if (on.style) { ctx.strokeStyle = '#263238';  ctx.lineWidth = 3;  ctx.beginPath();  ctx.ellipse(0, 0, 48, 52, 0, 0, 7);  ctx.stroke(); }
      if (on.pose) {                                      // standing, holding a tiny open book
        ctx.strokeStyle = saffron;  ctx.lineWidth = 4;
        ctx.beginPath();  ctx.moveTo(-12, 50);  ctx.lineTo(-12, 70);  ctx.moveTo(12, 50);  ctx.lineTo(12, 70);  ctx.stroke();
        ctx.fillStyle = '#fff3e0';  ctx.fillRect(-30, 20, 26, 20);  ctx.fillRect(-4, 20, 26, 20);
        ctx.strokeStyle = blue;  ctx.lineWidth = 2;  ctx.strokeRect(-30, 20, 52, 20);
      }
    } else {
      ctx.fillStyle = '#b0bec5';  ctx.font = '13px system-ui';  ctx.textAlign = 'center';  ctx.fillText('(no subject: anything)', 0, 0);
    }
    ctx.restore();
    const prompt = Object.entries(parts).filter(([k]) => on[k]).map(([k, v]) => `${k}: ${v}.`).join('\n');
    document.getElementById('prompt').textContent = prompt + `\n\n≈ ${Math.ceil(prompt.length / 4)} prompt tokens`;
  }
  document.querySelectorAll('[data-k]').forEach(c => c.onchange = () => { on[c.dataset.k] = c.checked;  draw(); });
  draw();
</script>