Most social images are not one-offs. An online shop needs a card for every product, a blog for every post in the archive, a conference for every speaker, a job board for every opening. The design is the same each time; only the words and the photo change. That is exactly what a spreadsheet is good at — one row per item, one column per field — and exactly what bulk generation turns into images: one template, a list of rows, an image for every row.
This guide shows how to go from a CSV file or a Google Sheet to a folder of finished Open Graph images, with the API or without writing code, and how to avoid the problems that usually make bulk runs painful: wrong column names, broken encodings, private sheets, and paying twice for the same picture.
The model: a template plus rows
A template is the fixed part — layout, fonts, colours — with named fields for the parts that change: title, author, price, photo. A row fills those fields. If your template has the fields title and author, a spreadsheet for it looks like this:
| title | author | id | filename |
|---|---|---|---|
| Ten things that ruin your sleep | Anna Kowalska | post-1 | ten-things |
| Why we moved to Postgres | Maya Chen | post-2 | postgres |
Two extra columns are worth adding. id is your own identifier — a product SKU, a post slug — and comes back with each row's result, so you can match images to your records without relying on row order. filename names the file in the ZIP; without it, files are named after id, and without that, after the row number.
Columns are matched to fields by name, ignoring case, spaces and punctuation: Title, title and TITLE all fill title, and Author Name fills author_name. Columns that match no field — notes, publishing dates, internal flags — are simply ignored, so you can point the tool at the sheet you already have instead of building a copy for it.
Getting the CSV right
CSV looks simple and is not. RFC 4180 describes the common format — comma-separated, fields with commas or line breaks wrapped in double quotes, a quote inside a field doubled — but real files differ from it in a few predictable ways, and a good importer handles all of them:
- Semicolons. Excel in Poland, Germany, France and much of Europe writes
;instead of,, because the comma is the decimal separator there. The separator should be detected, not assumed. - The byte-order mark. Excel's "CSV UTF-8" puts three invisible bytes at the start of the file. Without stripping them, the first column's name starts with an invisible character and never matches
title. - Older encodings. A file saved as plain "CSV" by an old Excel is in Windows-1252, so
Caféarrives as broken bytes unless the importer falls back to that encoding. - Empty lines and empty cells. A trailing blank line is not a row. An empty cell should leave the field to the template's default rather than print nothing.
Shareglint handles each of these, so the practical advice is short: keep a header row, one row per image, and export with whatever your spreadsheet offers.
Using a Google Sheet directly
You do not need to download the sheet. Google Sheets can serve any tab as CSV, and a bulk tool can read it from there — as long as the sheet is readable without signing in. Two ways to allow that, both explained in Google's help on publishing and sharing files:
- Share → General access → "Anyone with the link" (Viewer). Then paste the address from your browser bar, for example
https://docs.google.com/spreadsheets/d/1AbC…/edit#gid=0. Thegidpicks the tab. - File → Share → Publish to web. Publishing creates a separate public address; it is the better choice when the sheet holds other data you do not want to share in edit view.
A private sheet answers with Google's sign-in page instead of data. Shareglint recognises that and stops with batch_sheet_not_shared and the steps above, instead of rendering a batch of images that say <!DOCTYPE html>.
Doing it with the API
One request creates the whole batch. Pass the template, then exactly one source of rows: rows as a JSON array, csv with the text of a file, or csv_url with a CSV address or a sheet link.
curl -X POST "https://shareglint.com/v1/batches" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"template": "blog-og",
"sizes": ["og", "square"],
"format": "webp",
"csv_url": "https://docs.google.com/spreadsheets/d/1AbC…/edit#gid=0",
"webhook_url": "https://example.com/hooks/images"
}'
The answer comes at once — 202 with the batch id — and the images render in the background. sizes renders each row in several formats from the same design (here the 1200×630 Open Graph card and a 1080×1080 square for Instagram), one image per row and size. To map columns yourself, add columns, for example {"title": "Headline", "filename": "Slug"}.
Follow progress with GET /v1/batches/{id}: the status, counts of finished and failed images, a 0–100 progress, and every row with its image URL or the reason it failed. When the last row is done, GET /v1/batches/{id}/zip redirects to a ZIP of all the images, and the webhook_url receives one batch.completed event carrying the ZIP's address — one event for the batch, not one per row.
The same request is available to AI assistants through the MCP server as create_batch and get_batch, so "make cards for every product in this sheet" works from Claude or Cursor.
Doing it without code
The dashboard has the same flow under Bulk: pick a template and sizes, upload a CSV or paste a sheet link, and check the column matching it proposes. Then preview the first rows — previews are free and do not touch your quota — start the batch, and download the ZIP when the progress bar fills. Past batches stay listed with their ZIPs for as long as your images are kept.
What it costs, and what it does not
Every image counts as one render, exactly like a single API call, and the rules that make single renders cheap apply to batches too:
- The whole batch is checked first. If it needs more images than you have left this month (top-up packs included), nothing starts and you get
402with how many images are needed and how many are available — rather than half a batch and a surprise. - Images are charged as they render. A row whose render fails is refunded automatically.
- Duplicates are free. A row identical to an image you already rendered — through the API, a signed URL, or an earlier batch — reuses that image. Repeated rows within one batch render once.
Rows per batch depend on the plan: Free 50, Starter 500, Pro 1,000, Scale 2,000, Business 5,000. A batch runs on its own low-priority queue, a few images at a time per account, so a thousand-row import never slows down the images your site requests live.
How this compares
Template image services such as Bannerbear and Placid have offered spreadsheet-driven generation for years, mostly inside their editors and no-code integrations. If your designers live in those editors, that is a good reason to stay. The difference here is that the template can be your own HTML and CSS, the whole run is one API call that also accepts a sheet link, and you pay per image at Shareglint's prices — $4.50 per 1,000 on Starter — with rows already rendered costing nothing.