Hi everyone!
I recently put together a YouTube video exploring the different ways you can manipulate and process files directly within Retool Workflows. I wanted to share the blueprint and a breakdown of the code snippets here for anyone looking to handle file operations server-side.
Here's a JSON you can import to a retool workflow and see all the codes in action straight away.
In this workflow, we cover several common use cases: extracting files from a Zip archive using Python, parsing CSVs with JavaScript, and even using AI to analyze invoices (PDFs/Images).
You can watch the full walkthrough here: https://youtu.be/wFUs26il6kc
Here is a deep dive into the blocks used in the workflow:
1. Extracting Zip Files with Python
One of the most powerful features of Retool Workflows is the ability to run Python code. This is perfect for handling binary data like Zip files.
In the workflow, I use a Python block to take a base64 encoded zip file, extract its contents in memory, and return the files as a list of objects.
Code Snippet (Python)
import io
import zipfile
import mimetypes
import json
def extract_zip_files(zipFile_data):
# Handle JSON string input from Retool
if isinstance(zipFile_data, str):
zipFile_data = json.loads(zipFile_data)
results = []
for file in zipFile_data:
base64_str = file.get("base64Data")
if not base64_str:
continue
# Decode Base64 into bytes
zip_bytes = base64.b64decode(base64_str)
zip_buffer = io.BytesIO(zip_bytes)
with zipfile.ZipFile(zip_buffer, 'r') as zf:
for name in zf.namelist():
if name.endswith("/"): # skip folders
continue
with zf.open(name) as extracted:
data_bytes = extracted.read()
mime_type = mimetypes.guess_type(name)[0] or "application/octet-stream"
base64_data = base64.b64encode(data_bytes).decode("utf-8")
results.append({
"name": name,
"type": mime_type,
"base64Data": base64_data
})
return results # ✅ local return from the function
# ✅ this final return is required in Retool Workflows
return extract_zip_files(zipFile.data)`
2. Parsing CSV Files with JavaScript
For text-based formats like CSV, a JavaScript block works great. We can decode the base64 string provided by Retool's file input and parse the text into a JSON array.
Code Snippet (JavaScript)
// 1. Get the Base64 data string from the input object
const base64Data = csvFile.data[0].base64Data;
// 2. Decode the Base64 string into a raw CSV string
// The atob() function decodes a Base64 encoded string.
const csvString = atob(base64Data);
// 3. Parse the CSV string into an array of objects
// This assumes the first line is the header row.
const lines = csvString.split('\n').filter(line => line.trim() !== ''); // Split by line break and remove empty lines
const headers = lines[0].split(','); // Get headers from the first line
// Map the remaining lines (data rows) into an array of objects
const result = lines.slice(1).map(line => {
const values = line.split(',');
const rowObject = {};
// Create an object for each row, mapping headers to values
headers.forEach((header, i) => {
// Trim whitespace from header and value, as a simple cleanup
rowObject[header.trim()] = values[i] ? values[i].trim() : '';
});
return rowObject;
});
// The final result is an array of JavaScript objects
return result;`
3. Parsing XLSX Files with JavaScript
For Excel files(.xlsx) we can decode the file and being granular with things like:
- Which row to use as header
- The name of the sheet to target (in case file has multiple sheets)
- Format columns, e.g. date columns that come across as text
Code Snippet (Javascript)
const XLSX = require('xlsx');
//
Decode attachment
const att = {
contentType: xlsxFile.data[0].type,
name: xlsxFile.data[0].name,
contentBytes:xlsxFile.data[0].base64Data
};
//
Decode base64 -> buffer
const buf = Buffer.from(att.contentBytes, 'base64');
//
Parse workbook
const wb = XLSX.read(buf, { type: 'buffer' });
//
Pick first sheet
const sheetName = "MOCK_DATA (2)";
if (!sheetName) throw new Error('No worksheets found in the XLSX file.');
const ws = wb.Sheets[sheetName];
//
Convert sheet to JSON
let rows = XLSX.utils.sheet_to_json(ws, { defval: null, raw: true, range: 3 });
//
Excel serial → ISO converter
const excelDateToISO = n => {
if (typeof n !== 'number' || n < 25569) return null;
const utc_days = Math.floor(n - 25569);
const utc_value = utc_days * 86400;
const date_info = new Date(utc_value * 1000);
return date_info.toISOString().split('T')[0];
};
//
Dynamically detect and convert if present
rows = rows.map(r => {
const updated = { ...r };
const dateFields = [
"created_date",
];
for (const field of dateFields) {
if (field in updated && typeof updated[field] === "number") {
updated[field] = excelDateToISO(updated[field]);
}
}
return updated;
});
//
Return normalized payload
return {
filename: att.name,
sheet: sheetName,
rowsCount: rows.length,
rows
};
4. AI Document Analysis (Images & PDFs)
The workflow also demonstrates how to send file data (like an invoice image or PDF) to an AI endpoint (like OpenAI) using Structured Outputs to extract specific data fields.
These blocks are using the OpenAI resource that Retool offers out of the box, but the below can be also used in a rest endpoint (which I sometimes prefer to be honest)
OpenAI Call (with 1 image, sanitized base64)
{
"model": "gpt-4.1-mini",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "what's in this image?"
},
{
"type": "input_image",
"image"_url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTg...."
]
}
],
"text": {
"format": {
"type": "json_schema",
"name": "invoice_schema",
"strict": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"description": "Total invoice amount"
},
"invoice_number": {
"type": "string",
"description": "Invoice Number"
},
"invoice_date": {
"type": "string",
"format": "date",
"description": "YYYY-MM-DD"
},
"line_items": {
"type": "array",
"description": "Invoice line items",
"items": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"units": {
"type": [
"string",
"number"
]
},
"amount": {
"type": "number"
},
"total": {
"type": "number"
}
},
"required": [
"description",
"amount",
"total",
"units"
],
"additionalProperties": false
}
},
"other_relevant_info": {
"type": "string",
"description": "Free-text notes or context"
},
"vendor": {
"type": [
"string"
],
"description": "Vendor Name"
}
},
"required": [
"amount",
"invoice_number",
"invoice_date",
"vendor",
"line_items",
"other_relevant_info"
],
"additionalProperties": false
}
}
}
}
Once the AI processes the file, we simply parse the JSON response to use in the rest of our application.
Code Snippet
JavaScriptreturn JSON.parse(analyzeInvoicePDF.data.output[0].content[0].text)
5. Uploading files to an S3 bucket
This simple flow demonstrates how you can structure the S3 block to upload a file. You will need to have an S3 resource in your account and select that as resource.
How to use this Blueprint
- Download the JSON file attached to this post.
- Go to your Retool Workflows dashboard.
- Click "Create new" -> "Import via JSON".
- Upload the file and explore the blocks!
Let me know if you have any questions or recommendations. From now on I will keep updating this Wiki as I work on more use cases within my consultancy, but do feel free to ask for anything else that you think would be helpful.