Expand role bullets into full job descriptions
Generate Northwind job postings from a Sheet of role bullets — outputs polished copy per role.
Published Nov 11, 2025
When Northwind opens a new role, the hiring manager jots a few bullet points — the skills, the day-to-day, the must-haves — and then someone has to turn that shorthand into a full job posting. It is a slow, repetitive write every time, and the postings drift in tone because a different person writes each one.
This script does the expansion in bulk. You keep a sheet with one row per role: a title, a column of rough bullets, and an empty column for the posting. The script reads each unfinished row, asks Claude to expand the bullets into a structured posting, and writes the result back. Every posting follows the same sections, so the whole careers page reads as one voice.
What you’ll need
- A Google Sheet with one row per role. The first tab needs a header row with
these column names exactly:
title,bullets,posting. Put the rough bullets in thebulletscolumn and leavepostingempty. - An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely.
The script
// The spreadsheet that holds one row per open role.
const ROLES_SHEET_ID = '1abcRolesId';
// The fixed sections every generated posting should contain.
const POSTING_SECTIONS =
'About us (1 para), The role, What you bring, What we offer';
/**
* Reads every role row that has bullets but no posting yet, asks Claude
* to expand the bullets into a full job posting, and writes it back.
*/
function expandJobs() {
const sheet = SpreadsheetApp.openById(ROLES_SHEET_ID).getSheets()[0];
// 1. Read the whole sheet and split the header from the data rows.
const [header, ...rows] = sheet.getDataRange().getValues();
if (!rows.length) {
Logger.log('No role rows to expand — nothing to do.');
return;
}
// 2. Map column names to indexes so the code does not depend on order.
const col = Object.fromEntries(header.map((name, i) => [name, i]));
// 3. Walk each row; skip ones already done or with no bullets.
rows.forEach((row, i) => {
if (row[col.posting] || !row[col.bullets]) return;
// 4. Build a prompt that pins the posting to a fixed set of sections.
const prompt =
'Expand these bullets into a full Northwind Studios job posting. ' +
'Sections: ' + POSTING_SECTIONS + '.\n\n' +
'Role: ' + row[col.title] + '\n' +
'Bullets:\n' + row[col.bullets];
// 5. Sonnet writes the posting; write it back to the posting column.
const posting = callClaude(prompt, 'claude-sonnet-4-6', 1500);
sheet.getRange(i + 2, col.posting + 1).setValue(posting);
});
Logger.log('Finished expanding role bullets.');
}
/**
* 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
expandJobsopens the roles spreadsheet and reads the whole sheet in one call, splitting the header row off from the data rows.- If there are no data rows, it logs a message and stops — no wasted API calls.
- It builds a
collookup that maps each header name to its column index, so the script keeps working even if the columns are reordered. - It walks every row and skips two cases: rows that already have a posting (so reruns are cheap) and rows with no bullets to expand.
- For each remaining row it builds a prompt that names the role and lists the
bullets, and pins the output to the fixed
POSTING_SECTIONS. - It calls Claude Sonnet — worth the cost for polished long-form copy — and
writes the posting back into the
postingcolumn for that row.
Example run
Say one row of the sheet looks like this before a run:
| title | bullets | posting |
|---|---|---|
| Junior Motion Designer | After Effects; 1-2 yrs experience; works to brief; portfolio required | (empty) |
After a run, the posting cell holds a full, structured advert:
About us — Northwind Studios is a small creative team building brand and motion work for ambitious clients…
The role — As our Junior Motion Designer you will take briefs from concept to finished animation in After Effects…
What you bring — One to two years of motion design experience, fluency in After Effects, and a portfolio that shows range…
What we offer — A close-knit team, real ownership of projects…
Every role in the sheet comes out with the same four sections, so the careers page reads consistently no matter who wrote the original bullets.
Run it
This is an on-demand job — run it whenever you have added new roles to the sheet:
- In the Apps Script editor, select
expandJobsand click Run. - Approve the authorisation prompt the first time.
- Open the sheet and read the filled-in
postingcolumn.
To let non-editors trigger it, add a custom menu so it appears in the spreadsheet itself:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Hiring tools')
.addItem('Expand job postings', 'expandJobs')
.addToUi();
}
Watch out for
- It only fills empty
postingcells. To regenerate a posting, clear its cell first — otherwise the script skips that row. - The header names must match exactly.
title,bullets, andpostingare case-sensitive; a stray capital and thecollookup returnsundefined. - A long list of roles can hit the six-minute execution limit, since each row is a separate API call. For a big batch, process the sheet in slices.
- The copy still needs a human read. Claude invents plausible “About us” and “What we offer” detail — check it matches Northwind’s real benefits and tone before the posting goes live.
Related
Generate and test email subject lines
A/B test AI-written Northwind subject lines for open rate — outputs ranked by past performance.
Updated Mar 3, 2026
Build retrieval-augmented Q&A over your data
Answer Northwind questions grounded in your own Sheet data — pass relevant rows as context.
Updated Feb 27, 2026
Build an AI weekly-report narrator
Turn Northwind metrics into a written executive summary — numbers in, prose out.
Updated Feb 23, 2026
Build a multi-step AI agent workflow
Chain Claude prompts to complete a Northwind task end to end — research → draft → critique → finalise.
Updated Feb 11, 2026
Adapt marketing copy per region
Localise Northwind tone and references by market with AI — same message, regional flavour.
Updated Jan 30, 2026