Skip to content

Instantly share code, notes, and snippets.

@nocodesupplyco
Last active August 22, 2025 21:53
Show Gist options
  • Select an option

  • Save nocodesupplyco/319a7db6866f7f3251a01dc8712c6482 to your computer and use it in GitHub Desktop.

Select an option

Save nocodesupplyco/319a7db6866f7f3251a01dc8712c6482 to your computer and use it in GitHub Desktop.
Optimize Images w/ Airtable Script
// Define settings fields and descriptions
const config = input.config({
title: "Compress Images",
description: "Check and compress an image in each record of a view using the [Kraken.io API](https://kraken.io/docs/getting-started). Script created by [Base Scripts](https://nocodesupply.co/basescripts) — use at your own discretion.",
items: [
input.config.text("krakenAPIKey", {
label: "Kraken API Key",
description: "Sign up for a Kraken account, and get your key from the Account > API Credentials page.",
}),
input.config.text("krakenAPISecret", {
label: "Kraken API Secret",
description: "Sign up for a Kraken account, and get your secret from the Account > API Credentials page.",
}),
input.config.text("minImageSize", {
label: "Minimum Image Size (kb)",
description: "When an image is checked, if its current file size is above this value then it will get compressed, if its below it will remain as is.",
}),
input.config.text("imageResizeWidth", {
label: "Resize Image Width",
description: "When an image is compressed, what is the width it should be resized too?",
}),
input.config.select("convertWebp", {
label: "Convert Image to WebP?",
description: "When the image is compressed, do you want it also converted to a Webp file type?",
options: [
{ label: "True", value: "true" },
{ label: "False", value: "false" },
],
}),
input.config.table("tableSelect", {
label: "Table",
description: "Select the table where the images should be checked and compressed.",
}),
input.config.view("viewSelect", {
label: "View",
description: "Select the view where the images should be checked and compressed.",
parentTable: "tableSelect",
}),
input.config.field("imageSelect", {
label: "Image Field",
description: "Select the attachment field where the image to check and compress is currently stored.",
parentTable: "tableSelect",
}),
input.config.field("imageSizeSelect", {
label: "Image Size Field",
description: "Select the text field where the size of the image should be output.",
parentTable: "tableSelect",
}),
input.config.field("imageTypeSelect", {
label: "Image Type Field",
description: "Select the text field where the file type of the image should be output.",
parentTable: "tableSelect",
}),
input.config.field("imageErrorSelect", {
label: "Image Error Field",
description: "Select the checkbox field that gets checked if there is any error when trying to compress the image.",
parentTable: "tableSelect",
}),
],
});
// Set config choices to variables
const configAPIKey = config.krakenAPIKey;
const configAPISecret = config.krakenAPISecret;
const configMinSize = parseInt(config.minImageSize, 10); // Convert to number
const configImageResize = parseInt(config.imageResizeWidth, 10); // Convert to number
const configConvertWebp = config.convertWebp === "true"; // Convert string to boolean
const configTable = config.tableSelect.name;
const configView = config.viewSelect.name;
const configImage = config["imageSelect"].name;
const configImageSize = config["imageSizeSelect"].name;
const configImageType = config["imageTypeSelect"].name;
const configImageError = config["imageErrorSelect"].name;
// Set table and view
const table = base.getTable(configTable);
const view = table.getView(configView);
// Loop through each image field to get size
let query = await view.selectRecordsAsync({ fields: [configImage, "Name"] });
for (let record of query.records) {
try {
// Get image size
let imageSize = record.getCellValue(configImage)[0].size / 1000;
// Get image URL
let imageToResizeUrl = record.getCellValue(configImage)[0].url;
// Get image type
let imageType = record.getCellValue(configImage)[0].type;
// Get record name
let imageRecordName = record.getCellValue("Name");
console.log(`Processing: ${imageRecordName} (${imageSize}KB)`);
if (imageSize >= configMinSize) {
// IF image size is over minimum size set then compress the image via API
let callToKraken = await remoteFetchAsync("https://api.kraken.io/v1/url", {
method: "POST",
body: JSON.stringify({
auth: {
api_key: configAPIKey,
api_secret: configAPISecret,
},
// "dev": true,
url: imageToResizeUrl,
wait: true,
lossy: true,
webp: configConvertWebp,
resize: [
{
id: "compressed",
strategy: "landscape",
width: configImageResize,
},
],
}),
headers: {
"Content-type": "application/json",
Accept: "application/json",
},
});
let returnedImages;
try {
returnedImages = await callToKraken.json();
} catch (jsonError) {
console.error(`JSON parsing error for ${imageRecordName}:`, jsonError);
console.error(`Response status: ${callToKraken.status} ${callToKraken.statusText}`);
await table.updateRecordAsync(record, {
[configImageError]: true,
});
continue;
}
if (!callToKraken.ok) {
// Log detailed error information from Kraken API
console.error(`❌ Kraken API Error for ${imageRecordName}:`);
console.error(`Status: ${callToKraken.status} ${callToKraken.statusText}`);
if (returnedImages && returnedImages.success === false) {
console.error(`Error Message: ${returnedImages.message || 'No message provided'}`);
if (returnedImages.error) {
console.error(`Error Details:`, returnedImages.error);
}
} else {
console.error(`Raw Response:`, returnedImages);
}
await table.updateRecordAsync(record, {
[configImageError]: true,
});
continue;
} else {
// Check if the API response indicates success
if (returnedImages.success === false) {
console.error(`❌ Kraken API returned success=false for ${imageRecordName}:`);
console.error(`Message: ${returnedImages.message || 'No message provided'}`);
if (returnedImages.error) {
console.error(`Error Details:`, returnedImages.error);
}
await table.updateRecordAsync(record, {
[configImageError]: true,
});
continue;
}
// Verify the expected structure exists
if (!returnedImages.results || !returnedImages.results.compressed) {
console.error(`❌ Unexpected API response structure for ${imageRecordName}:`, returnedImages);
await table.updateRecordAsync(record, {
[configImageError]: true,
});
continue;
}
// IF successful update image and size fields
await table.updateRecordAsync(record, {
[configImage]: [{ url: returnedImages.results.compressed.kraked_url }],
[configImageSize]: returnedImages.results.compressed.kraked_size / 1000,
[configImageType]: configConvertWebp ? "image/webp" : imageType,
});
console.log(`✅ Compressed ${imageRecordName}: ${imageSize}KB → ${returnedImages.results.compressed.kraked_size / 1000}KB`);
}
} else {
//IF image size that is under the minimum size set then just update image size field
await table.updateRecordAsync(record, {
[configImageSize]: imageSize,
[configImageType]: imageType,
});
console.log(`⏭️ Skipped ${imageRecordName}: ${imageSize}KB (below ${configMinSize}KB threshold)`);
}
} catch (error) {
// Catch any unexpected errors in the processing loop
let recordName = "Unknown";
try {
recordName = record.getCellValue("Name") || record.id;
} catch {}
console.error(`❌ Unexpected error processing record ${recordName}:`, error);
try {
await table.updateRecordAsync(record, {
[configImageError]: true,
});
} catch (updateError) {
console.error(`Failed to update error field for record ${recordName}:`, updateError);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment