Retool file upload & storage: from File Input to a permanent CDN URL
A Retool File Input hands you a file's bytes (and, with Retool Storage, an opaque storage id) โ but a real app needs a durable link, private access, CDN delivery, and predictable cost. This guide shows how to store files from Retool on files.link and get a permanent CDN URL back, without hand-rolling an S3 bucket, IAM user, or CORS policy.
The whole thing is one JavaScript query: request a presigned URL, PUT the bytes, confirm โ then drop the resulting URL into a table cell, a form submission, or your own database. It's the same three-call file upload API used everywhere else on files.link, just driven from a Retool query.
The Retool file-storage problem
Retool is great at the UI. Where builders get stuck is what happens after a user picks a file:
- Retool Storage keeps files behind Retool. Fine for internal-only assets, but you don't get a public, shareable, CDN-backed URL to use elsewhere.
- Wiring an S3 resource means creating a bucket, an IAM user with the right policy, and a CORS configuration โ then maintaining all of it. That's a lot of AWS console for "save a file and give me a link."
- Base64 in a database works until the files get big, then your queries and your DB bill both suffer.
files.link is the storage + CDN layer in between: your Retool query uploads the file and gets a stable https://cdn.files.link/<key> URL, served over a global CDN with edge locations. No IAM, no CORS, no bucket to babysit.
The workflow in three calls
- Create โ
POST /v1/files/{folderId}with the file's metadata. You get back a short-lived presigned upload URL and a fileid. - Upload โ
PUTthe raw bytes straight to that presigned URL. - Confirm โ
POST /v1/files/confirm-uploadwith theidto finalize. Uploads you never confirm are cleaned up automatically.
Authentication is your API key in the Authorization header โ the raw key, with no Bearer prefix. Step 2 needs no auth header at all; the credentials are baked into the presigned URL.
The Retool query (JavaScript)
Add a File Input (or File Button) component, then create a JavaScript query with the code below. Replace <FOLDER_ID> and <YOUR_API_KEY> with values from your dashboard.
// Retool JavaScript query โ uploads the selected file and returns a
// permanent files.link CDN URL. Wire it to a File Input / File Button
// component (here called fileInput1) and run it on upload.
const API = "https://api.files.link/v1";
const API_KEY = "<YOUR_API_KEY>"; // store as a Retool config variable, not inline
const FOLDER_ID = "<FOLDER_ID>"; // from your files.link dashboard
// Retool's File Input exposes parallel arrays: .files (metadata) and
// .value (base64-encoded contents). Take the first selected file.
const meta = fileInput1.files[0]; // { name, size, type }
const base64 = fileInput1.value[0]; // base64 string of the file contents
// 1. Ask files.link for a short-lived presigned upload URL.
const create = await fetch(`${API}/files/${FOLDER_ID}`, {
method: "POST",
headers: { Authorization: API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
filesMetadata: [{ name: meta.name, size: meta.size, type: meta.type }],
}),
});
const { urls } = await create.json();
const { url, id, key } = urls[0];
// 2. PUT the raw bytes straight to storage โ decode Retool's base64 first.
// No Authorization header here; the signature is baked into the URL.
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
await fetch(url, {
method: "PUT",
headers: { "Content-Type": meta.type || "application/octet-stream" },
body: bytes,
});
// 3. Confirm so the file is finalized (unconfirmed uploads are cleaned up).
await fetch(`${API}/files/confirm-upload`, {
method: "POST",
headers: { Authorization: API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ ids: [id] }),
});
// Permanent CDN URL โ write it to a table cell, your DB, or a state variable.
return `https://cdn.files.link/${key}`;PUT in the browser.Alternative: a Retool REST API resource
Prefer Retool's resource model over raw JS? Point a REST API resource at https://api.files.link/v1 and run the create + confirm calls through it โ they go out from Retool's backend, so there's no browser CORS to deal with.
# Prefer Retool's REST API resource? Point one at files.link and run
# steps 1 and 3 through it โ the calls go out from Retool's backend, so
# there is no browser CORS to worry about.
Resource โ REST API
Base URL: https://api.files.link/v1
Headers: Authorization: <YOUR_API_KEY> (raw key, no "Bearer" prefix)
# Query "createUpload" ยท POST /files/{{ FOLDER_ID }}
{
"filesMetadata": [
{
"name": {{ fileInput1.files[0].name }},
"size": {{ fileInput1.files[0].size }},
"type": {{ fileInput1.files[0].type }}
}
]
}
# Query "confirmUpload" ยท POST /files/confirm-upload
{ "ids": [ {{ createUpload.data.urls[0].id }} ] }
# The presigned PUT in step 2 targets a different host (upload.files.link),
# so keep that one as a small JavaScript query (see the recipe above).Using the file in tables, forms & downloads
- Store the URL. Write the returned
cdn.files.linkURL into your database (via your existing SQL/REST resource) or a Retool state variable, right next to the row it belongs to. - Show it. Public files render directly โ use the CDN URL as an
Imagesrc, aLinkhref, or an image/link column in a Table. - Keep some files private. A file's privacy follows its folder. Private files aren't publicly reachable โ you request a signed URL when you need to serve one, and it expires after 10 minutes, so generate it on demand rather than caching it.
Retool Storage / S3 resource vs files.link
| Retool Storage | Retool + S3 resource | files.link | |
|---|---|---|---|
| Setup | Built in | Bucket + IAM + CORS | API key + folder id |
| Public shareable URL | Behind Retool | You configure it | CDN URL out of the box |
| CDN delivery | No | Only if you add CloudFront | Global CDN included |
| Private / signed links | Limited | You build it | Signed URLs (10-min expiry) |
| Cost model | Bundled in Retool | AWS metered | Prepaid, pay for what you use |
Honest limitations: files.link is a separate paid service โ there's no free tier; you add a payment method and pay for the storage and bandwidth you use. It isn't embedded in Retool the way Retool Storage is, so you manage an API key. If all you need is a throwaway internal asset that never leaves Retool, Retool Storage is simpler. The moment you need durable, fast, shareable, or signed links, files.link is the better fit.
FAQ
How do I handle file upload in Retool?
Add a File Input component and run a JavaScript query that reads its files/value, requests a presigned URL from files.link, PUTs the bytes, and confirms โ you get a permanent CDN URL back to store in your table. No S3 bucket or IAM policy required.
What's the best storage for a Retool app's files?
Retool Storage is fine for internal-only assets. For durable, shareable, CDN-backed links (or private signed URLs), files.link stores the file and gives you a stable cdn.files.link URL you can use inside Retool and out.
Can I use files.link as a Retool S3 uploader?
Yes โ it's the storage + CDN layer, so you skip the S3 resource setup (bucket policy, IAM user, CORS rules). Your Retool query calls the files.link API and gets a CDN URL back.
How do I upload a file to S3 from Retool without configuring IAM?
Point your Retool query at files.link instead of S3 directly. It mints a presigned upload URL per file and serves the result over a CDN, so you never create an IAM user, bucket policy, or CORS configuration yourself.
How do I download or display a stored file in Retool?
Public files: use the CDN URL directly as an Image src, Link href, or table column. Private files: request a short-lived signed URL (10-minute expiry) and use it as the download link.
Wire files.link into your Retool app
files.link is prepaid and pay-as-you-go: create an account, add a payment method, grab an API key, and paste the query above into Retool. You pay only for what you store and serve.
Related
- File upload API โ the full three-call upload endpoint reference.
- Files SDK and cloud file storage API โ the developer surface behind this workflow.
- How to upload a file via API and the Next.js file upload guide โ the same flow from curl, the browser, and Next.js.
- File storage with CDN and temporary file hosting โ delivery and expiring-link options.
More guides on the Developer Guides hub.