---
title: Files
description: Upload files to a collection, attach them to records, and hand out short-lived download links.
group: Working with data
order: 5
keywords: [file upload, file storage, attachments, signed url, multipart upload]
---

# Files

Records hold JSON. For images, PDFs and other binaries, upload a file and attach it to a
record.

Needs `files:write` to upload and `files:read` to download.

## Upload

Send `multipart/form-data` with a field named `file`:

```bash
curl -s -X POST "$BAAS_URL/workspaces/$BAAS_WS/collections/todos/files" \
  -H "Authorization: Bearer $BAAS_KEY" \
  -F "file=@./receipt.pdf"
```

```js
const form = new FormData();
form.append('file', fileFromInput); // a File or Blob

const res = await fetch(`${BASE}/workspaces/${WS}/collections/todos/files`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${KEY}` }, // do not set Content-Type yourself
  body: form,
});
const { data: uploaded } = await res.json();
```

Let `fetch` set the `Content-Type` for a `FormData` body. If you set it by hand the
multipart boundary is lost and the upload fails.

## Download

Files are private. You ask for a link and get one that expires shortly after:

```bash
# metadata plus a short-lived signed URL
curl -s "$BAAS_URL/files/FILE_ID" -H "Authorization: Bearer $BAAS_KEY"

# or be redirected straight to the file
curl -sL "$BAAS_URL/files/FILE_ID/download" -H "Authorization: Bearer $BAAS_KEY" -o receipt.pdf
```

Because the link expires, generate it when someone asks rather than storing it in your
database.

## Attach a file to a record

A file belongs to a collection; attaching links it to one record in that collection.

```bash
curl -s -X POST "$BAAS_URL/files/FILE_ID/attach" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"recordId":"01a12621-…"}'

curl -s -X POST "$BAAS_URL/files/FILE_ID/detach" -H "Authorization: Bearer $BAAS_KEY"
```

List a collection's files with `GET …/collections/todos/files`, and delete one with
`DELETE /v1/files/FILE_ID`.

Erasing a record for good also removes the files attached to it.

## Limits

|          |       |
| -------- | ----- |
| One file | 10 MB |

Storage is part of your deployment's setup, so the exact available space depends on how
the backend was deployed.

## Next steps

- [Records](/docs/records) — the JSON side.
- [API keys](/docs/api-keys) — the `files:read` and `files:write` scopes.
