Guides
Files and uploads
Let people attach files in your app, and read or write your account's Files from your server, with public addresses for anything you want shared.
Files are the folders on your account's automation account (its n8n): public/, private/ and temp/. Your workflows read and write them, the hub shows them under Knowledge, Files, and the SDK reaches them two ways:
- In your app, people who are signed in can attach a file, such as a photo of a job or a signed form. You keep a handle to it in a record.
- On your server, the account's token reads and writes any file, and gets a public address for anything in
public/.
Files comes with Support Plus, Pro and Embedded, after the account's owner switches on Open your automation account's file folders in Settings, Privacy. Apps are Pro and above.
Uploads in your app
1. Switch it on for the app
On the app's page (Website, Apps, then the app), choose under File uploads:
| Setting | What happens |
|---|---|
| Off | The default. Uploads are refused. |
| Private | Files land in private/apps/<app id>/. Only someone signed in to this app who has the file's handle can open it. |
| Public | Files land in public/apps/<app id>/, and each also gets a public address anyone with it can open. Web pages and scripts (html, svg, js, xml) are refused. |
Or ask Claude Code: awesomate_crm_apps, update with file_uploads. With open sign-up, anyone can sign up and upload, so uploads stop when your Files space is full.
2. Add an upload
const mode = await app.files.mode(); // 'off' | 'private' | 'public'
async function attachPhoto(input: HTMLInputElement, jobId: string) {
const picked = input.files?.[0];
if (!picked) return;
const file = await app.files.upload(picked); // up to 10 MB
await app.write('message', { body: `Photo: ${file.name}`, attachment: file.handle }, { links: { job: jobId } });
}upload() answers { handle, name, size, public_url }. Keep the handle in a record. Here the message kind has an attachment attribute of type file (give it one in defineKind when you make the kind, or add one later with Claude Code: awesomate_crm_kinds, add_attribute).
3. Show it
async function photoUrl(handle: string): Promise<string> {
const blob = await app.files.download(handle);
return URL.createObjectURL(blob);
}Anyone signed in to this app who can read the record holding a handle can open the file. The kind's read rule decides who sees the record, so it decides who sees the file. A handle only works in the app that made it. With public uploads you can also use public_url directly in an <img>.
When an upload is refused
upload() throws an AwesomateError. Its serverCode says why, and its personMessage is a sentence you can show:
serverCode |
Means |
|---|---|
uploads_off |
The app doesn't take uploads. |
too_large |
Over 10 MB. |
type_not_allowed |
A web page or script, in public mode. |
files_storage_full |
The account's Files space is full. |
files_consent_off, files_not_on_plan, files_not_ready |
Files isn't available on the account right now. |
Each person can upload 30 files in 10 minutes.
Files on your server
With the account's token (amt_pat_...), db.files reaches every folder. An app's server key (ak_...) can't. Keep the token on a server.
import { createClient } from '@awesomate/sdk';
import { readFile } from 'node:fs/promises';
const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
const logo = await db.files.upload('public/brand/logo.png', await readFile('logo.png'));
console.log(logo.public_url); // https://<account>.awesomate.io/files/public/brand/logo.png
const reports = await db.files.search({ q: 'invoice', top: 'private' });
const latest = await db.files.download(reports.results[0].path);| Method | Does |
|---|---|
list(path) |
One folder; no path lists the three top folders. |
search({ q, top, extensions }) |
Files by part of their name, up to 200. |
upload(path, data, { overwrite }) |
Writes a file, up to 50 MB. |
download(path) |
A Blob of the file. |
mkdir(path), move(from, to) |
Folders, and moving or renaming. |
makePublic(path), makePrivate(path) |
Moves between private/ and public/. A file made private stops being served from its address within seconds (a browser that already opened it may keep its own copy for a while). |
remove(path) |
Moves to the trash. It no longer counts toward your space, and it's erased automatically after 90 days. |
trash() |
What is in the trash: each entry's original name, size and when it was removed, and total_bytes. |
emptyTrash({ olderThanDays }) |
Erases what is in the trash for good, or only what was removed more than that many days ago. |
usage() |
Space used against the plan's Files space. |
const { entries, total_bytes } = await db.files.trash();
console.log(`${entries.length} in the trash, ${total_bytes} bytes`);
const { removed } = await db.files.emptyTrash({ olderThanDays: 30 });emptyTrash() is the only call that deletes a file outright: there is no getting it back. It needs files:write; trash() needs files:read.
Check first, and unpack a zip
files.capabilities() says whether Files works for the account now, and why not: the plan, the automation account's version, or the owner's privacy switch. It works with any account token:
const can = await db.files.capabilities();
if (!can.enabled) console.log({ plan: can.planEligible, version: can.variantEligible, switchedOn: can.consentGranted });files.extract(path) unpacks a .zip into a folder beside it named after the archive, at most 4,000 entries and 250 MB unpacked. It needs files:write:
const { extractedTo, fileCount } = await db.files.extract('private/skills.zip'); // into private/skillsSpace
| Plan | Files space |
|---|---|
| Support Plus | 10 GB |
| Pro | 25 GB |
| Embedded | 50 GB |
Uploads from your app, from your server, from the hub and from your workflows all count. An upload that would go over is refused with files_storage_full.
From n8n
Your workflows reach the same folders through the Local Files node on your n8n (the one Awesomate templates use to save files), with paths like public/reports/weekly.pdf. So a workflow can save what it makes into public/ and your app can show it by its address.