[{"data":1,"prerenderedAt":2072},["ShallowReactive",2],{"content-page-\u002Fblog\u002Fgenerate-images-from-spreadsheet":3,"icon-harvest":2058},{"id":4,"title":5,"body":6,"description":2033,"extension":2034,"meta":2035,"navigation":462,"path":2054,"seo":2055,"stem":2056,"__hash__":2057},"blogPosts\u002Fblog\u002Fgenerate-images-from-spreadsheet.md","Generate images from a spreadsheet: one template, one image per row",{"type":7,"value":8,"toc":2015},"minimark",[9,22,25,30,33,36,39,43,50,53,133,148,151,155,158,230,237,272,283,287,290,309,312,327,331,338,504,527,541,1601,1604,1611,1666,1670,1673,1684,1769,1776,1810,1826,1837,1841,1844,1876,1887,1891,1930,1934,1942,1949,1956,1960,1965,1972,1976,1979,1983,1992,1996,2004,2008,2011],[10,11,12,13,17,18,21],"p",{},"To generate images from a spreadsheet, you need ",[14,15,16],"strong",{},"one design with named slots"," and ",[14,19,20],{},"one row of data per image",". Each column fills one slot. Each row becomes one file. You upload the sheet as a CSV (or send the rows to an API) and get back a ZIP with one image per row, or a link per row you can paste back into the sheet.",[10,23,24],{},"This post shows how to set up the sheet so the columns line up with the design, and three ways to run it: no code, a bulk API call, and a signed link per row.",[26,27,29],"h2",{"id":28},"the-problem-200-rows-one-design","The problem: 200 rows, one design",[10,31,32],{},"You have the data already. Product names and prices in Google Sheets. Speakers and talk titles in Airtable. Listings exported from your CMS as Excel. What you need is a picture for each row: a product card, a speaker card, a \"just listed\" post.",[10,34,35],{},"Making them by hand works for ten. At two hundred, someone spends a day copying cells into a design tool, and the next price change means doing it again.",[10,37,38],{},"The fix is to stop treating each image as a separate design. There is one design, and the spreadsheet fills it in.",[26,40,42],{"id":41},"how-it-works-your-header-row-is-the-variable-list","How it works: your header row is the variable list",[10,44,45,46,49],{},"In bannx you design the image once in the editor. Then you mark the parts that change as ",[14,47,48],{},"variables",": the product name text, the price text, the photo layer, the badge colour. Each variable has a name.",[10,51,52],{},"The spreadsheet's header row uses those same names. That's the whole mapping:",[54,55,56,72],"table",{},[57,58,59],"thead",{},[60,61,62,66,69],"tr",{},[63,64,65],"th",{},"Header cell",[63,67,68],{},"Fills the template variable",[63,70,71],{},"Bound to",[73,74,75,91,105,119],"tbody",{},[60,76,77,84,88],{},[78,79,80],"td",{},[81,82,83],"code",{},"title",[78,85,86],{},[81,87,83],{},[78,89,90],{},"the product name text layer",[60,92,93,98,102],{},[78,94,95],{},[81,96,97],{},"price",[78,99,100],{},[81,101,97],{},[78,103,104],{},"the price text layer",[60,106,107,112,116],{},[78,108,109],{},[81,110,111],{},"photo",[78,113,114],{},[81,115,111],{},[78,117,118],{},"the image layer's source",[60,120,121,126,130],{},[78,122,123],{},[81,124,125],{},"badge",[78,127,128],{},[81,129,125],{},[78,131,132],{},"a \"New\" or \"Sale\" sticker",[10,134,135,136,138,139,141,142,147],{},"Columns are matched to variables by name. A column called ",[81,137,83],{}," fills the ",[81,140,83],{}," variable. A variable your sheet has no column for keeps the value saved in the template, so a logo or a footer line only needs setting once. More on declaring variables in ",[143,144,146],"a",{"href":145},"\u002Fdocs\u002Fvariables","the variables docs",".",[10,149,150],{},"Publish the template when the design is ready. Bulk renders use the published version.",[26,152,154],{"id":153},"prepare-the-sheet","Prepare the sheet",[10,156,157],{},"A few rules make the first run go right:",[159,160,161,174,184,194,212],"ul",{},[162,163,164,167,168,17,171,173],"li",{},[14,165,166],{},"Header names match variable names exactly."," ",[81,169,170],{},"Price",[81,172,97],{}," are different names. Copy them from the template's Variables panel.",[162,175,176,179,180,183],{},[14,177,178],{},"Image columns hold URLs."," A cell with a pasted picture won't travel through a CSV. Put an ",[81,181,182],{},"https:\u002F\u002F"," link to the image in the cell instead (your shop's product image URL works).",[162,185,186,189,190,193],{},[14,187,188],{},"One row per image."," If a product needs a square and a story version, that's either two rows or one row with a ",[81,191,192],{},"_size"," column (below).",[162,195,196,203,204,207,208,211],{},[14,197,198,199,202],{},"Optional ",[81,200,201],{},"id"," column."," It names that row's file inside the ZIP, so ",[81,205,206],{},"linen-shirt.png"," is easy to match back to its row. A ",[81,209,210],{},"name"," key does the same.",[162,213,214,218,219,222,223,226,227,229],{},[14,215,198,216,202],{},[81,217,192],{}," Put a size preset like ",[81,220,221],{},"story"," or ",[81,224,225],{},"instagram-post"," in it, and that row renders at that size from the same template. Leave it empty to use the template's own size. The ",[81,228,192],{}," column never reaches the design as a variable.",[10,231,232,233,236],{},"Example ",[81,234,235],{},"products.csv",":",[238,239,244],"pre",{"className":240,"code":241,"language":242,"meta":243,"style":243},"language-csv shiki shiki-themes bannx-docs-light bannx-docs-dark","id,title,price,photo,_size\nlinen-shirt,Linen shirt,€49,https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg,\ncanvas-tote,Canvas tote,€19,https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg,story\nwool-scarf,Wool scarf,€29,https:\u002F\u002Fexample.com\u002Fimg\u002Fscarf.jpg,instagram-post\n","csv","",[81,245,246,254,260,266],{"__ignoreMap":243},[247,248,251],"span",{"class":249,"line":250},"line",1,[247,252,253],{},"id,title,price,photo,_size\n",[247,255,257],{"class":249,"line":256},2,[247,258,259],{},"linen-shirt,Linen shirt,€49,https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg,\n",[247,261,263],{"class":249,"line":262},3,[247,264,265],{},"canvas-tote,Canvas tote,€19,https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg,story\n",[247,267,269],{"class":249,"line":268},4,[247,270,271],{},"wool-scarf,Wool scarf,€29,https:\u002F\u002Fexample.com\u002Fimg\u002Fscarf.jpg,instagram-post\n",[10,273,274,275,278,279,282],{},"In Google Sheets this is ",[14,276,277],{},"File → Download → Comma-separated values",". In Excel it's ",[14,280,281],{},"Save As → CSV UTF-8",". Pick the UTF-8 option so currency signs and accents survive.",[26,284,286],{"id":285},"route-1-no-code-csv-in-zip-out","Route 1, no code: CSV in, ZIP out",[10,288,289],{},"The fastest way for a one-off batch.",[291,292,293,303,306],"ol",{},[162,294,295,296,299,300,147],{},"Open your template's page and switch to the ",[14,297,298],{},"Bulk"," tab, then ",[14,301,302],{},"Playground",[162,304,305],{},"Upload the CSV. The header row is read as variable names.",[162,307,308],{},"Pick the output format and run it.",[10,310,311],{},"You get one file per row, packed into a ZIP. Stills come out as PNG, SVG or PDF. If the template is animated, each row can be an MP4, WebM or GIF instead.",[10,313,314,315,317,318,321,322,326],{},"If the data already lives in bannx as content (a collection of products or speakers), you don't need a CSV at all. In the same ",[14,316,298],{}," tab, pick the content type, check the rows you want, and hover a row to preview it on the design before anything renders. ",[14,319,320],{},"Render"," runs the batch and emails you the ZIP when it's done. The ",[143,323,325],{"href":324},"\u002Fdocs\u002Ftemplates#bulk-rendering-from-content","templates docs"," cover that flow.",[26,328,330],{"id":329},"route-2-api-send-rows-poll-for-the-zip","Route 2, API: send rows, poll for the ZIP",[10,332,333,334,337],{},"When the sheet updates every week, or the export comes out of another system, call the bulk endpoint. Each spreadsheet row is a JSON object whose keys are the variable names. You need the template ID (from the editor URL) and an API key with the ",[81,335,336],{},"template.render"," scope.",[238,339,343],{"className":340,"code":341,"language":342,"meta":243,"style":243},"language-bash shiki shiki-themes bannx-docs-light bannx-docs-dark","# 1. start the job: one object per spreadsheet row, keys = template variable names\ncurl -X POST https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002FTEMPLATE_ID\u002Fbulk \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"format\": \"png\",\n    \"rows\": [\n      { \"id\": \"linen-shirt\", \"title\": \"Linen shirt\", \"price\": \"€49\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg\" },\n      { \"id\": \"canvas-tote\", \"title\": \"Canvas tote\", \"price\": \"€19\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg\", \"_size\": \"story\" }\n    ]\n  }'\n# → { \"ok\": true, \"jobId\": \"…\", \"total\": 2 }\n\n# 2. poll until state is \"completed\", then download zipUrl\ncurl https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002Fbulk\u002FJOB_ID \\\n  -H \"Authorization: Bearer YOUR_API_KEY\"\n# → { \"jobId\": \"…\", \"state\": \"active\", \"total\": 2, \"completed\": 1, \"succeeded\": 1, \"failed\": 0, \"errors\": [] }\n# → { …, \"state\": \"completed\", \"zipUrl\": \"https:\u002F\u002F…\u002Frenders.zip\" }\n","bash",[81,344,345,351,370,387,400,412,418,424,430,436,442,451,457,464,470,480,492,498],{"__ignoreMap":243},[247,346,347],{"class":249,"line":250},[247,348,350],{"class":349},"sBsij","# 1. start the job: one object per spreadsheet row, keys = template variable names\n",[247,352,353,357,361,364,367],{"class":249,"line":256},[247,354,356],{"class":355},"sMrpa","curl",[247,358,360],{"class":359},"sYgty"," -X",[247,362,363],{"class":359}," POST",[247,365,366],{"class":359}," https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002FTEMPLATE_ID\u002Fbulk",[247,368,369],{"class":355}," \\\n",[247,371,372,375,379,382,385],{"class":249,"line":262},[247,373,374],{"class":359},"  -H",[247,376,378],{"class":377},"sS_0z"," \"",[247,380,381],{"class":359},"Authorization: Bearer YOUR_API_KEY",[247,383,384],{"class":377},"\"",[247,386,369],{"class":355},[247,388,389,391,393,396,398],{"class":249,"line":268},[247,390,374],{"class":359},[247,392,378],{"class":377},[247,394,395],{"class":359},"Content-Type: application\u002Fjson",[247,397,384],{"class":377},[247,399,369],{"class":355},[247,401,403,406,409],{"class":249,"line":402},5,[247,404,405],{"class":359},"  -d",[247,407,408],{"class":377}," '",[247,410,411],{"class":359},"{\n",[247,413,415],{"class":249,"line":414},6,[247,416,417],{"class":359},"    \"format\": \"png\",\n",[247,419,421],{"class":249,"line":420},7,[247,422,423],{"class":359},"    \"rows\": [\n",[247,425,427],{"class":249,"line":426},8,[247,428,429],{"class":359},"      { \"id\": \"linen-shirt\", \"title\": \"Linen shirt\", \"price\": \"€49\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg\" },\n",[247,431,433],{"class":249,"line":432},9,[247,434,435],{"class":359},"      { \"id\": \"canvas-tote\", \"title\": \"Canvas tote\", \"price\": \"€19\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg\", \"_size\": \"story\" }\n",[247,437,439],{"class":249,"line":438},10,[247,440,441],{"class":359},"    ]\n",[247,443,445,448],{"class":249,"line":444},11,[247,446,447],{"class":359},"  }",[247,449,450],{"class":377},"'\n",[247,452,454],{"class":249,"line":453},12,[247,455,456],{"class":349},"# → { \"ok\": true, \"jobId\": \"…\", \"total\": 2 }\n",[247,458,460],{"class":249,"line":459},13,[247,461,463],{"emptyLinePlaceholder":462},true,"\n",[247,465,467],{"class":249,"line":466},14,[247,468,469],{"class":349},"# 2. poll until state is \"completed\", then download zipUrl\n",[247,471,473,475,478],{"class":249,"line":472},15,[247,474,356],{"class":355},[247,476,477],{"class":359}," https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002Fbulk\u002FJOB_ID",[247,479,369],{"class":355},[247,481,483,485,487,489],{"class":249,"line":482},16,[247,484,374],{"class":359},[247,486,378],{"class":377},[247,488,381],{"class":359},[247,490,491],{"class":377},"\"\n",[247,493,495],{"class":249,"line":494},17,[247,496,497],{"class":349},"# → { \"jobId\": \"…\", \"state\": \"active\", \"total\": 2, \"completed\": 1, \"succeeded\": 1, \"failed\": 0, \"errors\": [] }\n",[247,499,501],{"class":249,"line":500},18,[247,502,503],{"class":349},"# → { …, \"state\": \"completed\", \"zipUrl\": \"https:\u002F\u002F…\u002Frenders.zip\" }\n",[10,505,506,507,510,511,514,515,518,519,522,523,526],{},"The job is queued, so the first call returns straight away with a ",[81,508,509],{},"jobId",". Poll the second URL until ",[81,512,513],{},"state"," is ",[81,516,517],{},"completed"," (or ",[81,520,521],{},"failed","). Rows that didn't render are listed in ",[81,524,525],{},"errors",", and the rest of the batch still finishes.",[10,528,529,530,536,537,540],{},"The same thing from Node, reading a CSV export. It uses a real CSV parser (",[143,531,535],{"href":532,"rel":533},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fcsv-parse",[534],"nofollow","csv-parse","), because product names have commas in them and splitting lines on ",[81,538,539],{},","," breaks on the first one. Sheets with more than 500 rows are split into one job per 500.",[238,542,546],{"className":543,"code":544,"language":545,"meta":243,"style":243},"language-js shiki shiki-themes bannx-docs-light bannx-docs-dark","import { readFile } from \"node:fs\u002Fpromises\";\nimport { parse } from \"csv-parse\u002Fsync\";\n\nconst BASE = \"https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\";\nconst TEMPLATE_ID = process.env.BANNX_TEMPLATE_ID;\nconst headers = {\n  Authorization: `Bearer ${process.env.BANNX_KEY}`,\n  \"Content-Type\": \"application\u002Fjson\",\n};\n\n\u002F\u002F header row becomes the object keys: { id, title, price, photo, _size }\nconst rows = parse(await readFile(\"products.csv\"), {\n  columns: true,\n  skip_empty_lines: true,\n  bom: true,\n}).map((row) =>\n  \u002F\u002F drop empty cells so they keep the template's saved value\n  Object.fromEntries(Object.entries(row).filter(([, v]) => v !== \"\")),\n);\n\nasync function startJob(chunk) {\n  const res = await fetch(`${BASE}\u002F${TEMPLATE_ID}\u002Fbulk`, {\n    method: \"POST\",\n    headers,\n    body: JSON.stringify({ format: \"png\", rows: chunk }),\n  });\n  if (!res.ok) throw new Error(`Bulk start failed: ${res.status} ${await res.text()}`);\n  return (await res.json()).jobId;\n}\n\nasync function waitForZip(jobId) {\n  for (;;) {\n    const res = await fetch(`${BASE}\u002Fbulk\u002F${jobId}`, { headers });\n    const job = await res.json();\n    if (job.state === \"completed\") return job;\n    if (job.state === \"failed\") throw new Error(`Job ${jobId} failed`);\n    await new Promise((r) => setTimeout(r, 3000));\n  }\n}\n\nfor (let i = 0; i \u003C rows.length; i += 500) {\n  const jobId = await startJob(rows.slice(i, i + 500));\n  const job = await waitForZip(jobId);\n  console.log(`rows ${i + 1}-${i + job.total}: ${job.succeeded} ok, ${job.failed} failed → ${job.zipUrl}`);\n  for (const err of job.errors) console.warn(err);\n}\n","js",[81,547,548,576,598,602,622,646,658,694,716,721,725,730,764,776,787,798,818,823,877,883,888,909,956,973,981,1025,1031,1100,1124,1130,1135,1153,1164,1204,1225,1260,1309,1344,1350,1355,1360,1408,1448,1467,1558,1596],{"__ignoreMap":243},[247,549,550,553,556,560,563,566,568,571,573],{"class":249,"line":250},[247,551,552],{"class":355},"import",[247,554,555],{"class":377}," {",[247,557,559],{"class":558},"s7Tna"," readFile ",[247,561,562],{"class":377},"}",[247,564,565],{"class":355}," from",[247,567,378],{"class":377},[247,569,570],{"class":359},"node:fs\u002Fpromises",[247,572,384],{"class":377},[247,574,575],{"class":377},";\n",[247,577,578,580,582,585,587,589,591,594,596],{"class":249,"line":256},[247,579,552],{"class":355},[247,581,555],{"class":377},[247,583,584],{"class":558}," parse ",[247,586,562],{"class":377},[247,588,565],{"class":355},[247,590,378],{"class":377},[247,592,593],{"class":359},"csv-parse\u002Fsync",[247,595,384],{"class":377},[247,597,575],{"class":377},[247,599,600],{"class":249,"line":262},[247,601,463],{"emptyLinePlaceholder":462},[247,603,604,607,610,613,615,618,620],{"class":249,"line":268},[247,605,606],{"class":355},"const",[247,608,609],{"class":558}," BASE ",[247,611,612],{"class":355},"=",[247,614,378],{"class":377},[247,616,617],{"class":359},"https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates",[247,619,384],{"class":377},[247,621,575],{"class":377},[247,623,624,626,629,631,634,636,639,641,644],{"class":249,"line":402},[247,625,606],{"class":355},[247,627,628],{"class":558}," TEMPLATE_ID ",[247,630,612],{"class":355},[247,632,633],{"class":558}," process",[247,635,147],{"class":377},[247,637,638],{"class":558},"env",[247,640,147],{"class":377},[247,642,643],{"class":558},"BANNX_TEMPLATE_ID",[247,645,575],{"class":377},[247,647,648,650,653,655],{"class":249,"line":414},[247,649,606],{"class":355},[247,651,652],{"class":558}," headers ",[247,654,612],{"class":355},[247,656,657],{"class":377}," {\n",[247,659,660,664,667,670,673,676,679,681,683,685,688,691],{"class":249,"line":420},[247,661,663],{"class":662},"sNgYv","  Authorization",[247,665,236],{"class":666},"selWe",[247,668,669],{"class":377}," `",[247,671,672],{"class":359},"Bearer ",[247,674,675],{"class":377},"${",[247,677,678],{"class":359},"process",[247,680,147],{"class":377},[247,682,638],{"class":359},[247,684,147],{"class":377},[247,686,687],{"class":359},"BANNX_KEY",[247,689,690],{"class":377},"}`",[247,692,693],{"class":377},",\n",[247,695,696,699,703,705,707,709,712,714],{"class":249,"line":426},[247,697,698],{"class":666},"  \"",[247,700,702],{"class":701},"smTa4","Content-Type",[247,704,384],{"class":666},[247,706,236],{"class":666},[247,708,378],{"class":377},[247,710,711],{"class":359},"application\u002Fjson",[247,713,384],{"class":377},[247,715,693],{"class":377},[247,717,718],{"class":249,"line":432},[247,719,720],{"class":377},"};\n",[247,722,723],{"class":249,"line":438},[247,724,463],{"emptyLinePlaceholder":462},[247,726,727],{"class":249,"line":444},[247,728,729],{"class":349},"\u002F\u002F header row becomes the object keys: { id, title, price, photo, _size }\n",[247,731,732,734,737,739,742,745,748,751,753,755,757,759,762],{"class":249,"line":453},[247,733,606],{"class":355},[247,735,736],{"class":558}," rows ",[247,738,612],{"class":355},[247,740,741],{"class":355}," parse",[247,743,744],{"class":377},"(",[247,746,747],{"class":355},"await",[247,749,750],{"class":355}," readFile",[247,752,744],{"class":377},[247,754,384],{"class":377},[247,756,235],{"class":359},[247,758,384],{"class":377},[247,760,761],{"class":377},"),",[247,763,657],{"class":377},[247,765,766,769,771,774],{"class":249,"line":459},[247,767,768],{"class":662},"  columns",[247,770,236],{"class":666},[247,772,773],{"class":355}," true",[247,775,693],{"class":377},[247,777,778,781,783,785],{"class":249,"line":466},[247,779,780],{"class":662},"  skip_empty_lines",[247,782,236],{"class":666},[247,784,773],{"class":355},[247,786,693],{"class":377},[247,788,789,792,794,796],{"class":249,"line":472},[247,790,791],{"class":662},"  bom",[247,793,236],{"class":666},[247,795,773],{"class":355},[247,797,693],{"class":377},[247,799,800,803,806,809,812,815],{"class":249,"line":482},[247,801,802],{"class":377},"}).",[247,804,805],{"class":355},"map",[247,807,808],{"class":377},"((",[247,810,811],{"class":558},"row",[247,813,814],{"class":377},")",[247,816,817],{"class":355}," =>\n",[247,819,820],{"class":249,"line":494},[247,821,822],{"class":349},"  \u002F\u002F drop empty cells so they keep the template's saved value\n",[247,824,825,828,830,833,835,838,840,843,845,847,850,853,856,859,862,865,868,871,874],{"class":249,"line":500},[247,826,827],{"class":558},"  Object",[247,829,147],{"class":377},[247,831,832],{"class":355},"fromEntries",[247,834,744],{"class":377},[247,836,837],{"class":558},"Object",[247,839,147],{"class":377},[247,841,842],{"class":355},"entries",[247,844,744],{"class":377},[247,846,811],{"class":558},[247,848,849],{"class":377},").",[247,851,852],{"class":355},"filter",[247,854,855],{"class":377},"(([,",[247,857,858],{"class":558}," v",[247,860,861],{"class":377},"])",[247,863,864],{"class":355}," =>",[247,866,867],{"class":558}," v ",[247,869,870],{"class":355},"!==",[247,872,873],{"class":377}," \"\"",[247,875,876],{"class":377},")),\n",[247,878,880],{"class":249,"line":879},19,[247,881,882],{"class":377},");\n",[247,884,886],{"class":249,"line":885},20,[247,887,463],{"emptyLinePlaceholder":462},[247,889,891,894,897,900,902,905,907],{"class":249,"line":890},21,[247,892,893],{"class":355},"async",[247,895,896],{"class":355}," function",[247,898,899],{"class":355}," startJob",[247,901,744],{"class":377},[247,903,904],{"class":558},"chunk",[247,906,814],{"class":377},[247,908,657],{"class":377},[247,910,912,915,918,920,923,926,928,931,934,936,939,941,944,946,949,952,954],{"class":249,"line":911},22,[247,913,914],{"class":355},"  const",[247,916,917],{"class":558}," res ",[247,919,612],{"class":355},[247,921,922],{"class":355}," await",[247,924,925],{"class":355}," fetch",[247,927,744],{"class":377},[247,929,930],{"class":377},"`${",[247,932,933],{"class":359},"BASE",[247,935,562],{"class":377},[247,937,938],{"class":359},"\u002F",[247,940,675],{"class":377},[247,942,943],{"class":359},"TEMPLATE_ID",[247,945,562],{"class":377},[247,947,948],{"class":359},"\u002Fbulk",[247,950,951],{"class":377},"`",[247,953,539],{"class":377},[247,955,657],{"class":377},[247,957,959,962,964,966,969,971],{"class":249,"line":958},23,[247,960,961],{"class":662},"    method",[247,963,236],{"class":666},[247,965,378],{"class":377},[247,967,968],{"class":359},"POST",[247,970,384],{"class":377},[247,972,693],{"class":377},[247,974,976,979],{"class":249,"line":975},24,[247,977,978],{"class":558},"    headers",[247,980,693],{"class":377},[247,982,984,987,989,992,994,997,1000,1003,1005,1007,1010,1012,1014,1017,1019,1022],{"class":249,"line":983},25,[247,985,986],{"class":662},"    body",[247,988,236],{"class":666},[247,990,991],{"class":558}," JSON",[247,993,147],{"class":377},[247,995,996],{"class":355},"stringify",[247,998,999],{"class":377},"({",[247,1001,1002],{"class":662}," format",[247,1004,236],{"class":666},[247,1006,378],{"class":377},[247,1008,1009],{"class":359},"png",[247,1011,384],{"class":377},[247,1013,539],{"class":377},[247,1015,1016],{"class":662}," rows",[247,1018,236],{"class":666},[247,1020,1021],{"class":558}," chunk ",[247,1023,1024],{"class":377},"}),\n",[247,1026,1028],{"class":249,"line":1027},26,[247,1029,1030],{"class":377},"  });\n",[247,1032,1034,1037,1040,1043,1046,1048,1051,1053,1056,1059,1062,1064,1066,1069,1071,1073,1075,1078,1080,1083,1085,1088,1090,1093,1096,1098],{"class":249,"line":1033},27,[247,1035,1036],{"class":355},"  if",[247,1038,1039],{"class":377}," (",[247,1041,1042],{"class":355},"!",[247,1044,1045],{"class":558},"res",[247,1047,147],{"class":377},[247,1049,1050],{"class":558},"ok",[247,1052,814],{"class":377},[247,1054,1055],{"class":355}," throw",[247,1057,1058],{"class":355}," new",[247,1060,1061],{"class":355}," Error",[247,1063,744],{"class":377},[247,1065,951],{"class":377},[247,1067,1068],{"class":359},"Bulk start failed: ",[247,1070,675],{"class":377},[247,1072,1045],{"class":359},[247,1074,147],{"class":377},[247,1076,1077],{"class":359},"status",[247,1079,562],{"class":377},[247,1081,1082],{"class":377}," ${",[247,1084,747],{"class":355},[247,1086,1087],{"class":359}," res",[247,1089,147],{"class":377},[247,1091,1092],{"class":355},"text",[247,1094,1095],{"class":377},"()",[247,1097,690],{"class":377},[247,1099,882],{"class":377},[247,1101,1103,1106,1108,1110,1112,1114,1117,1120,1122],{"class":249,"line":1102},28,[247,1104,1105],{"class":355},"  return",[247,1107,1039],{"class":377},[247,1109,747],{"class":355},[247,1111,1087],{"class":558},[247,1113,147],{"class":377},[247,1115,1116],{"class":355},"json",[247,1118,1119],{"class":377},"()).",[247,1121,509],{"class":558},[247,1123,575],{"class":377},[247,1125,1127],{"class":249,"line":1126},29,[247,1128,1129],{"class":377},"}\n",[247,1131,1133],{"class":249,"line":1132},30,[247,1134,463],{"emptyLinePlaceholder":462},[247,1136,1138,1140,1142,1145,1147,1149,1151],{"class":249,"line":1137},31,[247,1139,893],{"class":355},[247,1141,896],{"class":355},[247,1143,1144],{"class":355}," waitForZip",[247,1146,744],{"class":377},[247,1148,509],{"class":558},[247,1150,814],{"class":377},[247,1152,657],{"class":377},[247,1154,1156,1159,1162],{"class":249,"line":1155},32,[247,1157,1158],{"class":355},"  for",[247,1160,1161],{"class":377}," (;;)",[247,1163,657],{"class":377},[247,1165,1167,1170,1172,1174,1176,1178,1180,1182,1184,1186,1189,1191,1193,1195,1197,1199,1201],{"class":249,"line":1166},33,[247,1168,1169],{"class":355},"    const",[247,1171,917],{"class":558},[247,1173,612],{"class":355},[247,1175,922],{"class":355},[247,1177,925],{"class":355},[247,1179,744],{"class":377},[247,1181,930],{"class":377},[247,1183,933],{"class":359},[247,1185,562],{"class":377},[247,1187,1188],{"class":359},"\u002Fbulk\u002F",[247,1190,675],{"class":377},[247,1192,509],{"class":359},[247,1194,690],{"class":377},[247,1196,539],{"class":377},[247,1198,555],{"class":377},[247,1200,652],{"class":558},[247,1202,1203],{"class":377},"});\n",[247,1205,1207,1209,1212,1214,1216,1218,1220,1222],{"class":249,"line":1206},34,[247,1208,1169],{"class":355},[247,1210,1211],{"class":558}," job ",[247,1213,612],{"class":355},[247,1215,922],{"class":355},[247,1217,1087],{"class":558},[247,1219,147],{"class":377},[247,1221,1116],{"class":355},[247,1223,1224],{"class":377},"();\n",[247,1226,1228,1231,1233,1236,1238,1241,1244,1246,1248,1250,1252,1255,1258],{"class":249,"line":1227},35,[247,1229,1230],{"class":355},"    if",[247,1232,1039],{"class":377},[247,1234,1235],{"class":558},"job",[247,1237,147],{"class":377},[247,1239,1240],{"class":558},"state ",[247,1242,1243],{"class":355},"===",[247,1245,378],{"class":377},[247,1247,517],{"class":359},[247,1249,384],{"class":377},[247,1251,814],{"class":377},[247,1253,1254],{"class":355}," return",[247,1256,1257],{"class":558}," job",[247,1259,575],{"class":377},[247,1261,1263,1265,1267,1269,1271,1273,1275,1277,1279,1281,1283,1285,1287,1289,1291,1293,1296,1298,1300,1302,1305,1307],{"class":249,"line":1262},36,[247,1264,1230],{"class":355},[247,1266,1039],{"class":377},[247,1268,1235],{"class":558},[247,1270,147],{"class":377},[247,1272,1240],{"class":558},[247,1274,1243],{"class":355},[247,1276,378],{"class":377},[247,1278,521],{"class":359},[247,1280,384],{"class":377},[247,1282,814],{"class":377},[247,1284,1055],{"class":355},[247,1286,1058],{"class":355},[247,1288,1061],{"class":355},[247,1290,744],{"class":377},[247,1292,951],{"class":377},[247,1294,1295],{"class":359},"Job ",[247,1297,675],{"class":377},[247,1299,509],{"class":359},[247,1301,562],{"class":377},[247,1303,1304],{"class":359}," failed",[247,1306,951],{"class":377},[247,1308,882],{"class":377},[247,1310,1312,1315,1317,1320,1322,1325,1327,1329,1332,1334,1336,1338,1341],{"class":249,"line":1311},37,[247,1313,1314],{"class":355},"    await",[247,1316,1058],{"class":355},[247,1318,1319],{"class":355}," Promise",[247,1321,808],{"class":377},[247,1323,1324],{"class":558},"r",[247,1326,814],{"class":377},[247,1328,864],{"class":355},[247,1330,1331],{"class":355}," setTimeout",[247,1333,744],{"class":377},[247,1335,1324],{"class":558},[247,1337,539],{"class":377},[247,1339,1340],{"class":355}," 3000",[247,1342,1343],{"class":377},"));\n",[247,1345,1347],{"class":249,"line":1346},38,[247,1348,1349],{"class":377},"  }\n",[247,1351,1353],{"class":249,"line":1352},39,[247,1354,1129],{"class":377},[247,1356,1358],{"class":249,"line":1357},40,[247,1359,463],{"emptyLinePlaceholder":462},[247,1361,1363,1366,1368,1371,1374,1376,1379,1382,1384,1387,1389,1391,1394,1396,1398,1401,1404,1406],{"class":249,"line":1362},41,[247,1364,1365],{"class":355},"for",[247,1367,1039],{"class":377},[247,1369,1370],{"class":355},"let",[247,1372,1373],{"class":558}," i ",[247,1375,612],{"class":355},[247,1377,1378],{"class":355}," 0",[247,1380,1381],{"class":377},";",[247,1383,1373],{"class":558},[247,1385,1386],{"class":355},"\u003C",[247,1388,1016],{"class":558},[247,1390,147],{"class":377},[247,1392,1393],{"class":558},"length",[247,1395,1381],{"class":377},[247,1397,1373],{"class":558},[247,1399,1400],{"class":355},"+=",[247,1402,1403],{"class":355}," 500",[247,1405,814],{"class":377},[247,1407,657],{"class":377},[247,1409,1411,1413,1416,1418,1420,1422,1424,1427,1429,1432,1434,1437,1439,1441,1444,1446],{"class":249,"line":1410},42,[247,1412,914],{"class":355},[247,1414,1415],{"class":558}," jobId ",[247,1417,612],{"class":355},[247,1419,922],{"class":355},[247,1421,899],{"class":355},[247,1423,744],{"class":377},[247,1425,1426],{"class":558},"rows",[247,1428,147],{"class":377},[247,1430,1431],{"class":355},"slice",[247,1433,744],{"class":377},[247,1435,1436],{"class":558},"i",[247,1438,539],{"class":377},[247,1440,1373],{"class":558},[247,1442,1443],{"class":355},"+",[247,1445,1403],{"class":355},[247,1447,1343],{"class":377},[247,1449,1451,1453,1455,1457,1459,1461,1463,1465],{"class":249,"line":1450},43,[247,1452,914],{"class":355},[247,1454,1211],{"class":558},[247,1456,612],{"class":355},[247,1458,922],{"class":355},[247,1460,1144],{"class":355},[247,1462,744],{"class":377},[247,1464,509],{"class":558},[247,1466,882],{"class":377},[247,1468,1470,1473,1475,1478,1480,1482,1485,1487,1490,1492,1495,1497,1500,1502,1504,1506,1508,1510,1513,1515,1518,1520,1522,1524,1527,1529,1532,1534,1536,1538,1540,1542,1545,1547,1549,1551,1554,1556],{"class":249,"line":1469},44,[247,1471,1472],{"class":558},"  console",[247,1474,147],{"class":377},[247,1476,1477],{"class":355},"log",[247,1479,744],{"class":377},[247,1481,951],{"class":377},[247,1483,1484],{"class":359},"rows ",[247,1486,675],{"class":377},[247,1488,1489],{"class":359},"i ",[247,1491,1443],{"class":355},[247,1493,1494],{"class":355}," 1",[247,1496,562],{"class":377},[247,1498,1499],{"class":359},"-",[247,1501,675],{"class":377},[247,1503,1489],{"class":359},[247,1505,1443],{"class":355},[247,1507,1257],{"class":359},[247,1509,147],{"class":377},[247,1511,1512],{"class":359},"total",[247,1514,562],{"class":377},[247,1516,1517],{"class":359},": ",[247,1519,675],{"class":377},[247,1521,1235],{"class":359},[247,1523,147],{"class":377},[247,1525,1526],{"class":359},"succeeded",[247,1528,562],{"class":377},[247,1530,1531],{"class":359}," ok, ",[247,1533,675],{"class":377},[247,1535,1235],{"class":359},[247,1537,147],{"class":377},[247,1539,521],{"class":359},[247,1541,562],{"class":377},[247,1543,1544],{"class":359}," failed → ",[247,1546,675],{"class":377},[247,1548,1235],{"class":359},[247,1550,147],{"class":377},[247,1552,1553],{"class":359},"zipUrl",[247,1555,690],{"class":377},[247,1557,882],{"class":377},[247,1559,1561,1563,1565,1567,1570,1573,1575,1577,1579,1581,1584,1586,1589,1591,1594],{"class":249,"line":1560},45,[247,1562,1158],{"class":355},[247,1564,1039],{"class":377},[247,1566,606],{"class":355},[247,1568,1569],{"class":558}," err ",[247,1571,1572],{"class":355},"of",[247,1574,1257],{"class":558},[247,1576,147],{"class":377},[247,1578,525],{"class":558},[247,1580,814],{"class":377},[247,1582,1583],{"class":558}," console",[247,1585,147],{"class":377},[247,1587,1588],{"class":355},"warn",[247,1590,744],{"class":377},[247,1592,1593],{"class":558},"err",[247,1595,882],{"class":377},[247,1597,1599],{"class":249,"line":1598},46,[247,1600,1129],{"class":377},[10,1602,1603],{},"Keep the key on the server or in your script's environment. It never belongs in a sheet, a browser or a public repo.",[10,1605,1606,1607,236],{},"A few options worth knowing, all documented under ",[143,1608,1610],{"href":1609},"\u002Fdocs\u002Frendering#bulk-rendering","bulk rendering",[159,1612,1613,1639,1658],{},[162,1614,1615,514,1620,1622,1623,1622,1626,1622,1629,1622,1632,222,1635,1638],{},[14,1616,1617],{},[81,1618,1619],{},"format",[81,1621,1009],{},", ",[81,1624,1625],{},"svg",[81,1627,1628],{},"pdf",[81,1630,1631],{},"mp4",[81,1633,1634],{},"webm",[81,1636,1637],{},"gif",". One PDF per row is the usual choice for print: name badges, shelf labels, flyers.",[162,1640,1641,1646,1647,1650,1651,1653,1654,1657],{},[14,1642,1643],{},[81,1644,1645],{},"size"," on the request renders every row at another size, like ",[81,1648,1649],{},"\"size\": \"story\"",". A row's own ",[81,1652,192],{}," (and ",[81,1655,1656],{},"_fit",") overrides it, so one run can deliver a story for one record and a square for the next.",[162,1659,1660,1665],{},[14,1661,1662],{},[81,1663,1664],{},"scale"," renders at 2× for retina screens without touching the design.",[26,1667,1669],{"id":1668},"route-3-a-live-image-link-per-row","Route 3, a live image link per row",[10,1671,1672],{},"Sometimes you don't want files. You want a column in the sheet with an image URL per row, because the sheet feeds a CMS, a newsletter tool or a shop feed that reads image URLs.",[10,1674,1675,1676,1679,1680,1683],{},"That's what ",[14,1677,1678],{},"signed image URLs"," are for. Send up to 500 rows to the ",[81,1681,1682],{},"signed-urls"," endpoint and get one URL back per row, in the same order:",[238,1685,1687],{"className":340,"code":1686,"language":342,"meta":243,"style":243},"curl -X POST https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002FTEMPLATE_ID\u002Fsigned-urls \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"rows\": [\n      { \"variables\": { \"title\": \"Linen shirt\", \"price\": \"€49\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg\" } },\n      { \"variables\": { \"title\": \"Canvas tote\", \"price\": \"€19\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg\" } }\n    ],\n    \"options\": { \"format\": \"webp\" }\n  }'\n# → { \"templateId\": \"…\", \"published\": true, \"urls\": [ { \"url\": \"https:\u002F\u002F…\u002Fr\u002F…&sig=…\", \"variables\": { … } }, … ] }\n",[81,1688,1689,1702,1714,1726,1734,1738,1743,1748,1753,1758,1764],{"__ignoreMap":243},[247,1690,1691,1693,1695,1697,1700],{"class":249,"line":250},[247,1692,356],{"class":355},[247,1694,360],{"class":359},[247,1696,363],{"class":359},[247,1698,1699],{"class":359}," https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002FTEMPLATE_ID\u002Fsigned-urls",[247,1701,369],{"class":355},[247,1703,1704,1706,1708,1710,1712],{"class":249,"line":256},[247,1705,374],{"class":359},[247,1707,378],{"class":377},[247,1709,381],{"class":359},[247,1711,384],{"class":377},[247,1713,369],{"class":355},[247,1715,1716,1718,1720,1722,1724],{"class":249,"line":262},[247,1717,374],{"class":359},[247,1719,378],{"class":377},[247,1721,395],{"class":359},[247,1723,384],{"class":377},[247,1725,369],{"class":355},[247,1727,1728,1730,1732],{"class":249,"line":268},[247,1729,405],{"class":359},[247,1731,408],{"class":377},[247,1733,411],{"class":359},[247,1735,1736],{"class":249,"line":402},[247,1737,423],{"class":359},[247,1739,1740],{"class":249,"line":414},[247,1741,1742],{"class":359},"      { \"variables\": { \"title\": \"Linen shirt\", \"price\": \"€49\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Fshirt.jpg\" } },\n",[247,1744,1745],{"class":249,"line":420},[247,1746,1747],{"class":359},"      { \"variables\": { \"title\": \"Canvas tote\", \"price\": \"€19\", \"photo\": \"https:\u002F\u002Fexample.com\u002Fimg\u002Ftote.jpg\" } }\n",[247,1749,1750],{"class":249,"line":426},[247,1751,1752],{"class":359},"    ],\n",[247,1754,1755],{"class":249,"line":432},[247,1756,1757],{"class":359},"    \"options\": { \"format\": \"webp\" }\n",[247,1759,1760,1762],{"class":249,"line":438},[247,1761,447],{"class":359},[247,1763,450],{"class":377},[247,1765,1766],{"class":249,"line":444},[247,1767,1768],{"class":349},"# → { \"templateId\": \"…\", \"published\": true, \"urls\": [ { \"url\": \"https:\u002F\u002F…\u002Fr\u002F…&sig=…\", \"variables\": { … } }, … ] }\n",[10,1770,1771,1772,1775],{},"Write each returned ",[81,1773,1774],{},"url"," into a new column next to the row it came from. The links:",[159,1777,1778,1784,1798,1804],{},[162,1779,1780,1783],{},[14,1781,1782],{},"Contain no API key."," Anyone can open them, so it's safe to put them in a sheet the whole team (or a supplier) can see.",[162,1785,1786,1789,1790,1793,1794,1797],{},[14,1787,1788],{},"Can't be edited."," Each URL carries a signature over every value in it. Change ",[81,1791,1792],{},"price=49"," to ",[81,1795,1796],{},"price=4"," and the link stops rendering.",[162,1799,1800,1803],{},[14,1801,1802],{},"Render once, then serve from cache."," The first open renders the image, and every open after that gets the stored file. Signing itself is free.",[162,1805,1806,1809],{},[14,1807,1808],{},"Are stills only:"," PNG or WebP. Use the bulk API for PDFs or video.",[10,1811,1812,1813,1816,1817,1821,1822,147],{},"Add ",[81,1814,1815],{},"exp"," (unix seconds) if the links should stop working after a sale ends. More detail in ",[143,1818,1820],{"href":1819},"\u002Fdocs\u002Frendering#signed-image-urls","the signed URL docs"," and in ",[143,1823,1825],{"href":1824},"\u002Fblog\u002Fsigned-image-urls-use-cases","our post on signed URL use cases",[10,1827,1828,1829,1832,1833,1836],{},"There is also a plain ",[81,1830,1831],{},"GET"," render URL that takes ",[81,1834,1835],{},"api_token"," in the query string. Don't use it in a spreadsheet. Anyone who can see the sheet can copy the key out of the cell. If you must, the key has to be a render-only key, but signed URLs are the better fit for anything shared.",[26,1838,1840],{"id":1839},"automating-it-from-google-sheets-with-make-zapier-or-n8n","Automating it from Google Sheets with Make, Zapier or n8n",[10,1842,1843],{},"bannx doesn't have a Google Sheets connector, and there's no dedicated Make, Zapier or n8n app yet. The way in is the generic HTTP request module, which all three have. A typical scenario:",[291,1845,1846,1852,1867],{},[162,1847,1848,1851],{},[14,1849,1850],{},"Trigger:"," a new or updated row in the sheet.",[162,1853,1854,167,1857,1860,1861,1864,1865,147],{},[14,1855,1856],{},"HTTP request:",[81,1858,1859],{},"POST https:\u002F\u002Fbannx.com\u002Fapi\u002Fv1\u002Ftemplates\u002FTEMPLATE_ID\u002Frender"," with the Bearer key and a body of ",[81,1862,1863],{},"{ \"format\": \"png\", \"variables\": { \"title\": \"{{row.title}}\", \"price\": \"{{row.price}}\" } }",". That renders one image per row as it changes. The response has a ",[81,1866,1774],{},[162,1868,1869,1872,1873,1875],{},[14,1870,1871],{},"Update the row:"," write that ",[81,1874,1774],{}," back into an \"image\" column.",[10,1877,1878,1879,167,1882,1886],{},"For a whole sheet at once, point the HTTP step at the bulk endpoint instead and poll the job, or use a ",[81,1880,1881],{},"render.completed",[143,1883,1885],{"href":1884},"\u002Fdocs\u002Fwebhooks","webhook"," so the scenario doesn't have to wait. The render endpoint allows 30 requests a minute per key, so for hundreds of rows the bulk endpoint is the better choice.",[26,1888,1890],{"id":1889},"limits-worth-knowing","Limits worth knowing",[159,1892,1893,1899,1909,1918,1924],{},[162,1894,1895,1898],{},[14,1896,1897],{},"500 rows per API call",", for both bulk jobs and signed URLs. Split bigger sheets into several calls, like the Node example does.",[162,1900,1901,1904,1905,1908],{},[14,1902,1903],{},"The balance must cover the whole job up front."," If it doesn't, the job is refused with a ",[81,1906,1907],{},"402"," before anything renders, so you never get half a batch.",[162,1910,1911,1914,1915,1917],{},[14,1912,1913],{},"Failed rows cost nothing."," They're listed in the job's ",[81,1916,525],{}," with the reason, and the other rows still render.",[162,1919,1920,1923],{},[14,1921,1922],{},"Image cells must be URLs"," the renderer can fetch. A picture pasted into a cell doesn't export to CSV.",[162,1925,1926,1929],{},[14,1927,1928],{},"No spreadsheet connector."," Export a CSV, or use an automation tool's HTTP step.",[26,1931,1933],{"id":1932},"where-bannx-fits","Where bannx fits",[10,1935,1936,1937,1941],{},"bannx is a template-to-file engine. You design once in the browser editor, mark what changes, and render one file per row from a CSV upload, the bulk API, a signed link or an AI agent through the ",[143,1938,1940],{"href":1939},"\u002Fdocs\u002Fintegrations\u002Fmcp","MCP server",". The same engine draws the editor preview and the server render, so the row you preview is the file you get.",[10,1943,1944,1945,1948],{},"It fits when you have data and volume: a catalog of products that need cards every season, an event with a hundred speakers, an agency producing the same post for twenty clients. For one image, any public template on ",[143,1946,1947],{"href":938},"bannx.com"," can be edited in the browser and downloaded free, no account and no watermark.",[10,1950,1951,1952,147],{},"For a worked example of one use case end to end, see the ",[143,1953,1955],{"href":1954},"\u002Fblog\u002Fcertificate-generator-api","certificate generator API post",[26,1957,1959],{"id":1958},"faq","FAQ",[1961,1962,1964],"h3",{"id":1963},"does-it-work-with-excel-or-only-csv","Does it work with Excel, or only CSV?",[10,1966,1967,1968,1971],{},"Save the Excel file as ",[14,1969,1970],{},"CSV UTF-8"," and upload that. The playground reads CSV or JSON rows, and the API takes JSON rows, so any tool that can export a table works.",[1961,1973,1975],{"id":1974},"how-do-i-put-a-different-picture-in-each-image","How do I put a different picture in each image?",[10,1977,1978],{},"Add a column with an image URL per row and bind the template's image layer to a variable of the same name. Each row's URL becomes that row's picture.",[1961,1980,1982],{"id":1981},"can-one-row-come-out-in-several-sizes","Can one row come out in several sizes?",[10,1984,1985,1986,1988,1989,1991],{},"One row renders at one size per job. For two sizes, either add a second row with a different ",[81,1987,192],{},", or run the job twice with a different ",[81,1990,1645],{}," on the request. The design reflows to each size if it's built with constraints or auto-layout.",[1961,1993,1995],{"id":1994},"can-i-get-pdfs-instead-of-pngs","Can I get PDFs instead of PNGs?",[10,1997,1998,1999,1793,2001,2003],{},"Yes. Set ",[81,2000,1619],{},[81,2002,1628],{}," and every row comes out as a vector PDF, with text kept as real text. That suits name badges, labels and anything printed.",[1961,2005,2007],{"id":2006},"does-the-image-update-when-the-sheet-changes","Does the image update when the sheet changes?",[10,2009,2010],{},"Not on its own. A rendered file is fixed. Re-run the batch, or let a Make, Zapier or n8n scenario re-render the row when it changes. A signed URL is fixed too: new values mean a new URL.",[2012,2013,2014],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBsij, html code.shiki .sBsij{--shiki-default:#71717A;--shiki-default-font-style:italic;--shiki-dark:#A1A1AA;--shiki-dark-font-style:italic}html pre.shiki code .sMrpa, html code.shiki .sMrpa{--shiki-default:#18181B;--shiki-dark:#FAFAFA}html pre.shiki code .sYgty, html code.shiki .sYgty{--shiki-default:#52525B;--shiki-dark:#D4D4D8}html pre.shiki code .sS_0z, html code.shiki .sS_0z{--shiki-default:#A1A1AA;--shiki-dark:#71717A}html pre.shiki code .s7Tna, html code.shiki .s7Tna{--shiki-default:#27272A;--shiki-dark:#E4E4E7}html pre.shiki code .sNgYv, html code.shiki .sNgYv{--shiki-default:#09090B;--shiki-default-font-weight:bold;--shiki-dark:#FFFFFF;--shiki-dark-font-weight:bold}html pre.shiki code .selWe, html code.shiki .selWe{--shiki-default:#A1A1AA;--shiki-default-font-weight:bold;--shiki-dark:#71717A;--shiki-dark-font-weight:bold}html pre.shiki code .smTa4, html code.shiki .smTa4{--shiki-default:#52525B;--shiki-default-font-weight:bold;--shiki-dark:#D4D4D8;--shiki-dark-font-weight:bold}",{"title":243,"searchDepth":256,"depth":256,"links":2016},[2017,2018,2019,2020,2021,2022,2023,2024,2025,2026],{"id":28,"depth":256,"text":29},{"id":41,"depth":256,"text":42},{"id":153,"depth":256,"text":154},{"id":285,"depth":256,"text":286},{"id":329,"depth":256,"text":330},{"id":1668,"depth":256,"text":1669},{"id":1839,"depth":256,"text":1840},{"id":1889,"depth":256,"text":1890},{"id":1932,"depth":256,"text":1933},{"id":1958,"depth":256,"text":1959,"children":2027},[2028,2029,2030,2031,2032],{"id":1963,"depth":262,"text":1964},{"id":1974,"depth":262,"text":1975},{"id":1981,"depth":262,"text":1982},{"id":1994,"depth":262,"text":1995},{"id":2006,"depth":262,"text":2007},"Turn every row of a Google Sheet, Excel file or CSV into its own image: product cards, speaker cards, listings. How columns map onto a design, and three ways to get the files out: a ZIP from a CSV upload, a bulk API call, or a live image link per row.","md",{"image":2036,"layout":2037,"author":2038,"featured":2039,"publishedAt":2040,"updatedAt":2040,"tags":2041,"head":2046},"\u002Fblog\u002Fcovers\u002Fgenerate-images-from-spreadsheet.webp","blog","Bannx Team",false,"2026-10-22",[2042,2043,2044,2045],"api","bulk","automation","tutorials",{"meta":2047},[2048,2051],{"name":2049,"content":2050},"robots","index, follow",{"name":2052,"content":2053},"keywords","generate images from spreadsheet, bulk image generator from csv, google sheets image generator, csv to images, create images from excel data, bulk image generation api","\u002Fblog\u002Fgenerate-images-from-spreadsheet",{"title":5,"description":2033},"blog\u002Fgenerate-images-from-spreadsheet","QJP3_PisLsus2ObqOGXV2wIwYh7sCAnsaVUZM65wU6I",[2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071],"i-ph:caret-down","i-ph:list","i-ph:caret-right","i-ph:arrow-left","i-ph:x-logo","i-ph:youtube-logo","i-ph:instagram-logo","i-ph:linkedin-logo","i-tabler:brand-producthunt","i-ph:desktop","i-ph:sun","i-ph:moon","i-ph:translate",1794678114489]