Airtable: Remove Image Backgrounds Automatically with a Run-Script Automation
Airtable is where a lot of small product catalogues, creator databases and print-on-demand queues live, and its automations can run JavaScript with fetch. That is enough to remove backgrounds without leaving the base: a record gets a photo, the automation sends it to the bgclear API, and the cutout comes back as a second attachment. Below is the per-record automation, a bulk version for an existing table, and the two Airtable quirks (expiring attachment URLs, no secrets store) you need to design around.
In this guide
How it fits together
Airtable attachment fields expose a URL for each file, but those URLs expire a couple of hours after they are generated. The API fetches image_url server-side at request time, so as long as the script reads the URL and calls the API in the same run, expiry never bites. In JSON mode the API returns a hosted result URL (valid 24 hours); writing that URL into an attachment field makes Airtable download and store the file permanently. Two fields are needed on your table: Photo (attachment, the input) and Cutout (attachment, the output).
The automation: trigger + Run script
Automation trigger: "When record matches conditions" — Photo is not empty AND Cutout is empty. Action: "Run script". In the script's Input variables panel add recordId (the triggering record's Airtable record ID) and apiKey (your bgclear key). Airtable has no secret store for automation scripts, so an input variable is the least-bad place; anyone who can edit the automation can read it, so use a key you can rotate from the dashboard.
const { recordId, apiKey } = input.config();
const table = base.getTable("Products");
const record = await table.selectRecordAsync(recordId, { fields: ["Photo", "Cutout"] });
const photo = record?.getCellValue("Photo")?.[0];
const done = record?.getCellValue("Cutout")?.length;
if (!photo || done) { output.set("status", "skipped"); return; }
const res = await fetch("https://www.bgclear.ai/api/v1/remove", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
"Idempotency-Key": `airtable-${photo.id}`, // re-runs never double-charge
},
body: JSON.stringify({ image_url: photo.url, size: "auto", format: "png" }),
});
const data = await res.json();
if (!res.ok) throw new Error(`bgclear ${res.status} ${data.error?.code}: ${data.error?.message}`);
await table.updateRecordAsync(recordId, {
Cutout: [{ url: data.url, filename: photo.filename.replace(/\.[^.]+$/, "") + "-cutout.png" }],
});
output.set("status", `done, credits remaining ${data.credits_remaining}`);Airtable fetches the result URL when it stores the attachment, well inside the 24-hour window. For marketplace-ready white backgrounds, send format: "jpg", bg_color: "ffffff" and name the file .jpg.
Bulk: a scripting-extension run for the whole table
For a table that already has hundreds of photos, run this once from the Scripting extension (Extensions → Scripting). It paces requests under the API's 60-per-minute limit and updates records in batches of 50, which is Airtable's write limit per call.
const apiKey = "bgc_live_PASTE_YOUR_KEY"; // remove before sharing the base
const table = base.getTable("Products");
const query = await table.selectRecordsAsync({ fields: ["Photo", "Cutout"] });
const size = "auto"; // "preview" = free dry run
const updates = [];
for (const record of query.records) {
const photo = record.getCellValue("Photo")?.[0];
if (!photo || record.getCellValue("Cutout")?.length) continue;
const res = await fetch("https://www.bgclear.ai/api/v1/remove", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json",
"Content-Type": "application/json", "Idempotency-Key": `airtable-${photo.id}` },
body: JSON.stringify({ image_url: photo.url, size, format: "png" }),
});
const data = await res.json();
if (res.status === 429) { await new Promise(r => setTimeout(r, 5000)); continue; }
if (res.status === 402) { output.text("Out of credits — https://www.bgclear.ai/api-pricing/"); break; }
if (!res.ok) { output.text(`${record.name}: ${data.error?.message}`); continue; }
updates.push({ id: record.id, fields: { Cutout: [{ url: data.url, filename: "cutout.png" }] } });
if (updates.length === 50) { await table.updateRecordsAsync(updates.splice(0)); }
await new Promise(r => setTimeout(r, 1100)); // ≤60 requests/minute
}
if (updates.length) await table.updateRecordsAsync(updates);
output.text("Done.");Set size = "preview" first: it is free, returns 800 px results, and shows you edge quality on your own product photos before any credit is spent.
Cost and limits
One credit per full-resolution image — $9 for 100, $39 for 500, $129 for 2,000; credits never expire, previews are free, failed requests are never charged (pricing). Airtable's own limits: automation scripts get 30 seconds of run time (one image fits comfortably; the bulk job belongs in the Scripting extension), and attachments larger than 25 MB should be resized before upload since that is the API's cap. If you previously used a remove.bg-based Airtable script, the JSON body is the same and remove.bg's API ends on 1 December 2026 — what changes. The same pattern works in Google Sheets with Apps Script and in n8n, Zapier and Make.
Frequently asked questions
Can the automation send the file itself instead of the URL?
Airtable automation scripts have no FormData/file access, so the URL route is the one that works — and it is simpler, because the API fetches the attachment directly.
Will the expiring Airtable attachment URL break this?
No, as long as the script reads the URL and calls the API in the same run, which the scripts above do. Do not store attachment URLs for later.
Where should the API key live?
As an automation input variable (per-record automation) or a constant you remove after the bulk run. Anyone who can edit the automation can see it, so rotate it from the API dashboard if the base is shared widely.
How much does a 300-product base cost to process?
300 credits — the $39 pack covers 500 images at $0.078 each, and unused credits never expire.