appscript.dev
Automation Beginner Sheets

Generate image-prompt variations for design

Brainstorm visual concepts in bulk — Northwind design team feeds these into Midjourney or DALL-E.

Published Jan 18, 2026

When Northwind’s design team needs concept art, the slow part is not the image tool — it is writing the prompts. A good Midjourney or DALL-E prompt names the subject, the lighting, the composition, and the style, and the team wants ten of those, each varied enough to be worth generating, before they spend a single credit.

This script does the prompt-writing in bulk. You give it a one-line brief, it asks Claude for a batch of image-generation prompts that each vary the lighting, composition, and style, and it logs every prompt to a sheet with a timestamp. The design team copies the list straight into their image tool and picks the directions worth pursuing.

What you’ll need

  • A Google Sheet to log the prompts. The first tab needs a header row: Generated, Brief, Prompt.
  • An Anthropic API key saved as ANTHROPIC_API_KEY in Script Properties — see Store API keys and secrets securely.

The script

// The spreadsheet that logs every generated image prompt.
const IMAGE_PROMPTS_SHEET_ID = '1abcImagePromptsId';

// Default number of prompt variations to generate per brief.
const DEFAULT_PROMPT_COUNT = 10;

/**
 * Asks Claude for a batch of image-generation prompts based on a brief.
 *
 * @param {string} brief A short description of the visual concept.
 * @param {number} count How many variations to generate.
 * @return {Array<string>} One image prompt per array element.
 */
function generateImagePrompts(brief, count = DEFAULT_PROMPT_COUNT) {
  // 1. Ask for one prompt per line, with the variables to vary spelled out.
  const prompt =
    'Generate ' + count + ' image-generation prompts for: ' + brief +
    '. Vary the lighting, composition, and style across the set. ' +
    'Return one prompt per line, with no numbering and no prose.';

  // 2. Sonnet does the brainstorming; split the reply into separate lines.
  const reply = callClaude(prompt, 'claude-sonnet-4-6', 1500);
  return reply.split('\n').filter(Boolean);
}

/**
 * Generates image prompts for a brief and appends each one, with a
 * timestamp, to the tracking sheet.
 *
 * @param {string} brief A short description of the visual concept.
 */
function generateAndLog(brief) {
  if (!brief) {
    Logger.log('No brief supplied — nothing to generate.');
    return;
  }

  // Get the prompts, then write one row per prompt.
  const prompts = generateImagePrompts(brief);
  const sheet = SpreadsheetApp.openById(IMAGE_PROMPTS_SHEET_ID).getSheets()[0];
  prompts.forEach((p) => sheet.appendRow([new Date(), brief, p]));
  Logger.log('Logged ' + prompts.length + ' image prompts.');
}

/**
 * Minimal Anthropic API call. The key lives in Script Properties — it
 * is never pasted into the code.
 */
function callClaude(prompt, model = 'claude-haiku-4-5-20251001', maxTokens = 400) {
  const key = PropertiesService.getScriptProperties()
    .getProperty('ANTHROPIC_API_KEY');
  const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
    method: 'post',
    contentType: 'application/json',
    headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
    payload: JSON.stringify({
      model,
      max_tokens: maxTokens,
      messages: [{ role: 'user', content: prompt }],
    }),
    muteHttpExceptions: true,
  });
  return JSON.parse(res.getContentText()).content[0].text.trim();
}

How it works

  1. generateImagePrompts takes a brief and a count, then builds a prompt that asks for one image prompt per line and tells Claude to vary the lighting, composition, and style across the set.
  2. It calls Claude Sonnet, which writes richer, more visual prompts than a smaller model, then splits the reply on newlines and drops blank lines.
  3. generateAndLog is the entry point. It bails out early if no brief was passed, so an empty call never hits the API.
  4. It calls generateImagePrompts, then appends one row per prompt to the tracking sheet, stamping each with the current date and the brief.

Example run

Call generateAndLog('a cosy independent coffee shop interior') and the tracking sheet gains rows like these:

GeneratedBriefPrompt
2026-01-18a cosy independent coffee shop interiorWarm morning light through tall windows, wide eye-level shot, soft film-photography style
2026-01-18a cosy independent coffee shop interiorMoody evening interior lit by pendant lamps, close-up on a corner table, cinematic look
2026-01-18a cosy independent coffee shop interiorOverhead flat-lay of a counter, bright even lighting, clean editorial style

The design team copies the Prompt column straight into Midjourney or DALL-E and generates from the directions that look most promising.

Run it

This is an on-demand job — you run it whenever a project needs concept directions:

  1. In the Apps Script editor, write a one-line wrapper that calls generateAndLog with your brief, then select it and click Run.
  2. Approve the authorisation prompt the first time.
  3. Open the tracking sheet and copy the prompts into your image tool.

Watch out for

  • The reply is split purely on newlines. If Claude adds a numbered list or a stray intro line, those land in the sheet as rows — tighten the prompt or filter the list before logging if it happens.
  • Each run appends, it never clears. The log builds up across briefs, which is useful, but means you should sort or filter by Brief when you review.
  • Prompt style is tool-specific. Midjourney and DALL-E respond to slightly different phrasing; mention the target tool in the brief so Claude writes prompts in the right dialect.
  • More variations need headroom. If you raise the count well past ten, raise the max_tokens argument too, or the last few prompts will be cut off.

Related