Airtable stores images in attachment fields, and an attachment can be added from any public image URL: Airtable downloads the file and keeps its own copy. So a website screenshot is one API call away. Below are two copy-paste scripts: one that fills a screenshot for every row of a table, and one automation that captures each new record as it arrives.
The script asks Shotmint for a signed screenshot URL (GET /v1/screenshot/url, free) and writes it into the attachment field as { url, filename }. Airtable then downloads that URL, which renders the page. Your API key only goes to Shotmint, never into a cell. The image that ends up in the field is a copy that Airtable stores, so it's a snapshot of the page at that moment.
Cost: 1 credit per screenshot. The same URL and size requested again within 24 hours comes from cache and costs nothing, and a failed render (dead domain, timeout) is refunded.
Get a key with 100 free renders at /pricing (no signup). In your table, you need a field with the URLs (URL or text type; bare domains like example.com work) and an empty Attachment field for the screenshots.
Open Extensions → Add an extension → Scripting, replace the code with the script below, and pick the table, the two fields and the device in the settings panel, then paste your key. Click Run. Rows that already have a screenshot are skipped, so you can run it again after adding new URLs. Updates are written 50 records at a time, the most Airtable accepts per call.
// Shotmint: website screenshots into an Airtable attachment field.
// Airtable: Extensions -> Add an extension -> Scripting, paste this, click Run.
const config = input.config({
title: 'Website screenshots',
description: 'Captures the page in the URL field and saves it in the attachment field. Rows that already have a screenshot are skipped.',
items: [
input.config.table('table', { label: 'Table' }),
input.config.field('urlField', { parentTable: 'table', label: 'URL field' }),
input.config.field('shotField', { parentTable: 'table', label: 'Attachment field for the screenshot' }),
input.config.text('apiKey', { label: 'Shotmint API key' }),
input.config.select('device', {
label: 'Device',
options: [{ label: 'Desktop 1280x800', value: 'desktop' }, { label: 'Mobile 390x844', value: 'mobile' }],
}),
],
});
const API = 'https://shotmint.creasework.com';
async function screenshotUrl(pageUrl) {
if (!pageUrl.toLowerCase().startsWith('http')) pageUrl = 'https://' + pageUrl;
const p = { url: pageUrl, block_ads: 'true', width: '1280', height: '800' };
if (config.device === 'mobile') Object.assign(p, { width: '390', height: '844', mobile: 'true' });
const res = await remoteFetchAsync(API + '/v1/screenshot/url?' + new URLSearchParams(p), {
headers: { 'X-API-Key': config.apiKey.trim() },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error || 'HTTP ' + res.status);
return body.url;
}
const query = await config.table.selectRecordsAsync({ fields: [config.urlField, config.shotField] });
const todo = query.records.filter(r => r.getCellValueAsString(config.urlField).trim() && !r.getCellValue(config.shotField));
output.text(`${todo.length} record(s) to capture`);
let updates = [];
for (const record of todo) {
const pageUrl = record.getCellValueAsString(config.urlField).trim();
try {
const url = await screenshotUrl(pageUrl);
const filename = pageUrl.replace(/^https?:\/\//, '').replace(/[^a-z0-9.-]+/gi, '_') + '.png';
updates.push({ id: record.id, fields: { [config.shotField.id]: [{ url, filename }] } });
} catch (e) {
output.text(`${pageUrl}: ${e.message}`);
}
if (updates.length === 50) { await config.table.updateRecordsAsync(updates); updates = []; }
}
if (updates.length) await config.table.updateRecordsAsync(updates);
output.text('Done. Airtable downloads each screenshot into the attachment field within a few seconds.');
The script uses remoteFetchAsync, not fetch: extensions run in your browser, and remoteFetchAsync sends the request through Airtable's servers so the browser's cross-origin rules don't block it. Note that the key in the settings panel can be seen by anyone who can edit the extension, so use a key just for this base.
To capture pages as they're added (a form, a sync, an import), create an automation. Trigger: When a record is created, or When a record matches conditions with "URL is not empty" if the URL is filled in later. Action: Run a script. In the left panel add two input variables: recordId (the trigger record's Airtable record ID) and url (its URL field). Then paste:
// Shotmint: screenshot a record's URL when it's created (Airtable automation).
// Trigger: "When a record is created" (or "matches conditions": URL is not empty).
// Action: "Run a script" with input variables recordId (Airtable record ID) and url (the URL field).
const API_KEY = 'YOUR_SHOTMINT_KEY';
const TABLE = 'Sites'; // table name
const SHOT_FIELD = 'Screenshot'; // attachment field
const { recordId, url } = input.config();
if (!url) throw new Error('The record has no URL');
const pageUrl = url.trim().toLowerCase().startsWith('http') ? url.trim() : 'https://' + url.trim();
const params = new URLSearchParams({ url: pageUrl, width: '1280', height: '800', block_ads: 'true' });
const res = await fetch('https://shotmint.creasework.com/v1/screenshot/url?' + params, {
headers: { 'X-API-Key': API_KEY },
});
const body = await res.json();
if (!res.ok) throw new Error('Shotmint: ' + (body.error || res.status));
await base.getTable(TABLE).updateRecordAsync(recordId, {
[SHOT_FIELD]: [{ url: body.url, filename: 'screenshot.png' }],
});
output.set('screenshotUrl', body.url);
Automation scripts run on Airtable's servers, so plain fetch works there. Change TABLE and SHOT_FIELD to your names. For a phone-sized capture, use width: '390', height: '844', mobile: 'true'. If the render fails, the script throws and the automation's run history shows Shotmint's error message.
Any of these can go in the parameters of either script (they're the ones a signed URL carries):
| Parameter | Effect |
|---|---|
width, height | Viewport size in px (default 1280×800) |
full_page: 'true' | The whole scrolling page instead of the first screen |
mobile: 'true' | Phone emulation (mobile viewport and touch events) |
dark_mode: 'true' | Prefers-color-scheme: dark |
block_ads: 'true' | Blocks ads and common cookie banners |
delay: '2000' | Waits 2 s after load, for animations |
hide_selectors | CSS selectors to hide, e.g. a chat widget Comma-separated: '#chat, .popup' |
Airtable scripts only run inside Airtable, so we ran both scripts above, unchanged, in Node.js with stand-ins for input, output, base, the table methods and remoteFetchAsync, against the live API with a test key. The extension skipped rows with an existing screenshot and rows without a URL, and every URL it wrote downloaded as a real PNG: 1280×800 for desktop, 390×844 for mobile, including a long Wikipedia article. An unreachable domain gave a URL that returns a 400 error instead of an image, so Airtable has nothing to attach for that row. We haven't run the scripts inside Airtable itself.
For other tools, see all integrations: Google Sheets, n8n, Make and Zapier, and more.
No signup, no card. Get a key in one click, then buy credits only if you need more (from €5 for 2,000, never expire).
Get a free API key Full API reference