Żeby generować obrazki z Arkuszy Google, dodaj do arkusza krótki skrypt Apps Script: polecenie w menu czyta każdy wiersz, wywołuje https://shareglint.com/v1/images?wait=true przez UrlFetchApp, wpisuje zwrócony adres obrazka do wiersza i wstawia obok =IMAGE(…), żeby obrazek pokazał się w komórce. Cały arkusz naraz obsłuży też API zleceń hurtowych, które przyjmuje opublikowany arkusz jako CSV. Nie ma tu żadnego dodatku; wszystko poniżej działa w twoim własnym arkuszu.
Sposób 1: menu do renderowania obrazków
Arkusz: kolumna A title, kolumna B author, kolumna C adres obrazka (wypełnia go skrypt), kolumna D podgląd. Otwórz Rozszerzenia → Apps Script, wklej skrypt i zapisz klucz API w Ustawieniach projektu → Właściwości skryptu jako API_KEY — nie w komórce, bo tę może przeczytać każdy, komu udostępniono arkusz.
// Rozszerzenia → Apps Script. Dodaje menu Images → Render new rows.
function onOpen() {
SpreadsheetApp.getUi().createMenu('Images')
.addItem('Render new rows', 'renderNewRows')
.addToUi();
}
function renderNewRows() {
const key = PropertiesService.getScriptProperties().getProperty('API_KEY');
const sheet = SpreadsheetApp.getActiveSheet();
const last = sheet.getLastRow();
if (last < 2) return;
const rows = sheet.getRange(2, 1, last - 1, 3).getValues();
rows.forEach(([title, author, url], i) => {
if (!title || url) return; // pomija puste wiersze i wiersze już wyrenderowane
const res = UrlFetchApp.fetch('https://shareglint.com/v1/images?wait=true', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + key },
payload: JSON.stringify({ template: 'blog-og', modifications: { title: String(title), author: String(author) } }),
muteHttpExceptions: true,
});
const body = JSON.parse(res.getContentText());
const row = i + 2;
if (res.getResponseCode() !== 200) {
// Błąd trafia do kolumny podglądu, więc przy następnym uruchomieniu wiersz zostanie ponowiony.
sheet.getRange(row, 4).setValue('Error ' + res.getResponseCode() + ': ' + (body.error || ''));
return;
}
sheet.getRange(row, 3).setValue(body.url);
sheet.getRange(row, 4).setFormula('=IMAGE(C' + row + ')');
});
}
Odśwież arkusz, wybierz Images → Render new rows i za pierwszym razem pozwól skryptowi łączyć się z usługą zewnętrzną. Każdy wiersz dostaje adres obrazka w kolumnie C i podgląd w kolumnie D. Wiersze, które mają już adres, są pomijane, więc po dopisaniu wierszy możesz uruchomić menu jeszcze raz; wyczyść adres, żeby wyrenderować wiersz od nowa. Te same wartości zwracają istniejący obrazek bez zużywania limitu, więc uruchomienie przerwane w połowie i powtórzone nic dodatkowo nie kosztuje.
Kody błędów lądują w kolumnie podglądu, a wiersz zostaje bez adresu, więc następne uruchomienie spróbuje go ponownie: 402 oznacza, że renderów na ten miesiąc już nie ma, 422 — błędne zapytanie (kod w error mówi, co jest nie tak), 503 — że obrazek nie był gotowy w czasie oczekiwania; uruchom menu jeszcze raz, a zostanie pobrany z pamięci podręcznej.
Limity Google (limity Apps Script, ostatnia aktualizacja 3 września 2026): uruchomienie skryptu kończy się po 6 minutach, a wywołań URL Fetch może być najwyżej 20 000 dziennie na koncie prywatnym i 100 000 w Google Workspace, na użytkownika. Statyczny obrazek renderuje się zwykle w kilka sekund, więc jedno uruchomienie menu obsłuży od kilkudziesięciu do kilkuset wierszy; przy większej liczbie użyj sposobu 3.
Wyświetlanie obrazka. =IMAGE(url; tryb) wstawia obrazek do komórki; tryb 1 dopasowuje go do komórki z zachowaniem proporcji, 3 zostawia oryginalny rozmiar, 4 przyjmuje własną wysokość i szerokość (Pomoc Edytorów Dokumentów Google). Google zastrzega: żadnych obrazów z drive.google.com i żadnego SVG — adres renderu nie jest ani jednym, ani drugim. W polskich ustawieniach regionalnych argumenty formuł rozdziela się średnikiem, stąd ; w przykładach w tekście.
Sposób 2: podpisany adres jako formuła
Podpisany adres obrazka oblicza się z identyfikatora konta i sekretu do podpisów, bez żadnego zapytania do API: obrazek renderuje się, gdy coś pierwszy raz pobierze ten adres. Apps Script ma wbudowane HMAC-SHA256 (Utilities.computeHmacSha256Signature, które zwraca tablicę bajtów — dokumentacja), więc funkcja niestandardowa zbuduje adres w samym arkuszu:
/**
* Podpisany adres obrazka: =SIGNED_IMAGE_URL("blog-og"; "title"; A2; "author"; B2)
* @customfunction
*/
function SIGNED_IMAGE_URL(template, ...pairs) {
const props = PropertiesService.getScriptProperties();
const account = props.getProperty('ACCOUNT_ID');
const secret = props.getProperty('SIGNING_SECRET');
// RFC 3986: encodeURIComponent nie koduje ! ' ( ) *.
const enc = (v) => encodeURIComponent(String(v))
.replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
const fields = {};
for (let i = 0; i + 1 < pairs.length; i += 2) fields[pairs[i]] = pairs[i + 1];
const path = '/i/' + account + '/' + template + '.png';
const query = Object.keys(fields).sort().map((k) => enc(k) + '=' + enc(fields[k])).join('&');
const bytes = Utilities.computeHmacSha256Signature(path + '?' + query, secret);
const hex = bytes.map((b) => ((b + 256) % 256).toString(16).padStart(2, '0')).join('');
return 'https://shareglint.com' + path + '?' + query + '&s=' + hex.slice(0, 32);
}
W komórce wpisujesz =SIGNED_IMAGE_URL("blog-og"; "title"; A2; "author"; B2). Tekst kanoniczny to ścieżka, ? i wszystkie parametry poza s, posortowane po nazwie i zakodowane według RFC 3986; s to pierwsze 32 znaki szesnastkowe HMAC — ten sam algorytm co w dokumentacji. Apps Script zwraca HMAC jako bajty ze znakiem (od −128 do 127), stąd + 256.
Formuły używaj tam, gdzie adres jest wykorzystywany poza arkuszem: w kolumnie, którą CMS importuje jako og:image, w polu korespondencji seryjnej kampanii mailowej, na liście linków dla partnera. Sekret do podpisów powinien leżeć we „Właściwościach skryptu”, jak wyżej; każdy, kto może edytować skrypt, go przeczyta, więc prawa edycji zostaw osobom, które mogą podpisywać adresy. Funkcje niestandardowe muszą zwrócić wynik w ciągu 30 sekund (funkcje niestandardowe) — obliczenie podpisu trwa milisekundy. Daty i liczby są podpisywane jako tekst (String(value)); żeby zapanować nad formatem, owiń datę w TEXT(A2; "yyyy-mm-dd").
Sposób 3: cały arkusz jako jedno zlecenie hurtowe
Przy setkach albo tysiącach wierszy obejdź się bez skryptu: opublikuj arkusz (albo jedną kartę) jako CSV i wyślij jego link do POST /v1/batches w polu csv_url. Nagłówki kolumn stają się nazwami pól, id i filename zostają przy wynikach, a jeden ZIP zawiera wszystkie obrazki. Limit wierszy na zlecenie według planu: Free 50, Starter 500, Pro 1 000, Scale 2 000, Business 5 000. Jak opublikować arkusz i jak nazwać kolumny, opisuje poradnik o CSV i Arkuszach Google (po angielsku).
Ile to kosztuje
Jeden render na obrazek i rozmiar; powtórki z tymi samymi wartościami są darmowe. Plan Free ma 100 renderów miesięcznie ze znakiem wodnym; Starter kosztuje $9 miesięcznie za 2 000 renderów (cennik).
Czego nie robi
- To nie jest dodatek do Google Workspace: niczego nie instalujesz z Marketplace, jest tylko skrypt w twoim arkuszu.
- Nie czyta arkusza sam z siebie; skrypt wysyła tylko te kolumny, które ma w kodzie.
- Nie trzyma obrazków na Dysku — adres wskazuje nasz plik, przechowywany przez okres retencji twojego planu. Kopia na Dysku wymaga jeszcze jednej linii w skrypcie (
DriveApp.createFile(UrlFetchApp.fetch(url).getBlob())).