Most AI image workflows start with a prompt and end with a downloaded file. That is fine for a one-off experiment, but it breaks down as soon as a team needs to reproduce the result, revise one detail, generate another aspect ratio, or explain why version 12 is better than version 11.
Developers already have a mental model for this problem: a build pipeline.
- The image brief is the source.
- Generation settings are the build configuration.
- Reference images are dependencies.
- The output is an artifact.
- Visual review is the test suite.
- A targeted edit is a patch, not a rebuild from scratch.
Once image generation is treated this way, prompting becomes less mysterious and much easier to debug.
1. Define an image job, not a loose prompt
A prompt string hides important decisions inside prose. A small schema makes those decisions explicit.
type ImageJob = {
purpose: "hero" | "thumbnail" | "product" | "editorial";
subject: string;
composition: {
aspectRatio: "1:1" | "4:3" | "16:9" | "9:16";
framing: string;
negativeSpace?: "left" | "right" | "top" | "none";
};
camera: string;
lighting: string;
palette: string[];
preserve: string[];
avoid: string[];
output: {
background: "opaque" | "transparent";
format: "png" | "jpeg" | "webp";
};
};
Here is a job for a developer-tool landing page:
const heroJob: ImageJob = {
purpose: "hero",
subject: "one translucent interface panel above a dark desk",
composition: {
aspectRatio: "16:9",
framing: "eye-level, medium-wide",
negativeSpace: "left",
},
camera: "subtle depth of field, no wide-angle distortion",
lighting: "soft blue rim light with one restrained amber accent",
palette: ["#0B1020", "#4EA1FF", "#E5A24A"],
preserve: [
"single-panel geometry",
"clean left-side negative space",
"realistic reflections",
],
avoid: [
"people",
"readable UI text",
"logos",
"watermarks",
"extra screens",
],
output: {
background: "opaque",
format: "webp",
},
};
This is not about sending JSON to a model. The value is having a stable source of truth before turning the job into natural language.
2. Compile the job into a prompt
Prompt construction can be deterministic:
function buildPrompt(job: ImageJob): string {
const { composition, output } = job;
return [
`Create a ${composition.aspectRatio} ${job.purpose} image.`,
`Subject: ${job.subject}.`,
`Composition: ${composition.framing}; negative space: ${composition.negativeSpace ?? "none"}.`,
`Camera: ${job.camera}.`,
`Lighting: ${job.lighting}.`,
`Palette: ${job.palette.join(", ")}.`,
`Must preserve: ${job.preserve.join("; ")}.`,
`Avoid: ${job.avoid.join("; ")}.`,
`Output: ${output.background} background, ${output.format.toUpperCase()}.`,
].join("\n");
}
The generated prompt is boring on purpose. It is specific, reviewable, and easy to diff in version control.
Style words such as “cinematic” or “premium” can still be useful, but they should not replace testable constraints. “Premium” is subjective. “One centered bottle, label facing camera, empty space on the left, no extra objects” is something a reviewer can verify.
3. Separate exploration from finalization
A common failure mode is trying to discover the composition and perfect every detail in the same run.
Use two passes:
- Exploration — test camera angle, layout, color direction, and silhouette.
- Finalization — preserve the chosen structure while improving fidelity and delivery details.
In GPT Image 2.5, Flare fits the exploration pass, while Sunburst is better used after the composition is worth preserving.
The important rule is to make exploratory candidates meaningfully different. Four near-identical images are not four options. Change one structural decision per candidate:
- subject on the left versus right;
- close framing versus medium-wide;
- soft studio light versus hard directional light;
- geometric background versus environmental background.
Choose the candidate with the strongest structure, not necessarily the most detail. Texture is easy to add later. A weak composition is expensive to repair.
4. Treat every edit as a patch
Once a direction is selected, broad instructions such as “make it better” are dangerous. They give the model permission to redesign parts that already work.
Represent the edit as a patch:
type ImagePatch = {
preserve: string[];
change: string;
forbid: string[];
};
const lightingPatch: ImagePatch = {
preserve: [
"camera angle",
"panel geometry",
"desk layout",
"left-side negative space",
],
change: "soften the blue rim light and reduce the amber accent",
forbid: ["new objects", "text", "icons", "logos"],
};
Then turn it into a direct edit request:
Preserve the camera angle, panel geometry, desk layout, and left-side negative space. Change only the lighting: soften the blue rim light and reduce the amber accent. Do not add objects, text, icons, or logos.
This is the visual equivalent of a small pull request. It has a narrow scope, named invariants, and an obvious review target.
5. Add visual regression checks
A generated image can look impressive and still fail its intended use. Review it against the job instead of relying on a general “looks good” judgment.
Content checks
- Are all required objects present?
- Did any unrequested object appear?
- Does the subject remain consistent with its reference?
- Are product proportions, labels, and distinctive details correct?
Composition checks
- Does the crop work at the target aspect ratio?
- Is reserved negative space actually usable for copy?
- Is the focal point still clear at thumbnail size?
- Did an edit move the camera or subject unexpectedly?
Craft checks
- Are hands, faces, edges, and reflections coherent?
- Do shadows agree with the light direction?
- Is embedded text exact, or should it be added later in a design tool?
- Are there accidental symbols, logos, or watermarks?
Delivery checks
- Is transparency present when required?
- Is the output format appropriate?
- Does the asset remain sharp at its final display size?
- Are the source job, prompt, references, and accepted output saved together?
A simple manifest keeps the result auditable:
{
"job": "homepage-hero",
"version": 7,
"prompt": "prompts/homepage-hero-v7.txt",
"references": ["refs/panel.png"],
"artifact": "dist/homepage-hero-v7.webp",
"approvedChecks": [
"subject-consistency",
"negative-space-left",
"no-text",
"16:9"
]
}
6. Version outputs like build artifacts
Do not overwrite the last successful image. Keep accepted checkpoints and branch from the most recent good state.
A practical naming scheme is enough:
homepage-hero-v01-exploration.webp
homepage-hero-v04-selected.webp
homepage-hero-v05-lighting.webp
homepage-hero-v07-final.webp
For a larger project, store the job definition, generated prompt, reference files, and QA notes next to the artifact. You do not need a complex MLOps system; you need enough context to answer three questions:
- What produced this image?
- What changed from the previous version?
- Why was this version accepted?
A compact production loop
The full workflow can be summarized as:
define job
-> compile prompt
-> explore structural variants
-> select a checkpoint
-> lock invariants
-> apply one patch
-> run visual QA
-> export and version the artifact
The best prompt is not necessarily the most poetic one. It is the prompt that makes the output easy to evaluate and the next edit easy to control.
Treat prompts as configuration, edits as patches, and generated images as versioned artifacts. That shift turns image generation from a guessing game into an engineering-friendly creative workflow.
Top comments (0)