Files API
The Files API provides upload, metadata management, folder organization, and asset serving backed by Supabase Storage.
Endpoints
GET /api/files → 200 — list files
POST /api/files → 201 — upload a file (multipart) or create metadata (JSON)
GET /api/files/:id → 200 — get file metadata
PATCH /api/files/:id → 200 — update file metadata or replace file (multipart)
DELETE /api/files/:id → 204 — delete a file
PATCH /api/files → 200 — batch update
DELETE /api/files → 204 — batch delete
POST /api/files/import → 200 — import from URL
GET /api/files/:id/download → 200 — get download URL
GET /api/assets/:id → 200 — serve file asset (public URL)
GET /api/folders → 200 — list folders
POST /api/folders → 201 — create folder
PATCH /api/folders/:id → 200 — update folder
DELETE /api/folders/:id → 204 — delete folderFile Object
The File entity has the following fields. Fields marked * are always returned; others are null when not applicable.
| Field | Type | Description |
|---|---|---|
id * | string | Unique identifier (UUID) |
storage * | string | Storage bucket name (default: "files") |
filename_disk | string | null | Filename as stored on disk |
filename_download * | string | Original filename for downloads |
title | string | null | Display title |
type | string | null | MIME type |
filesize * | number | File size in bytes (0 for metadata-only records) |
width | number | null | Image width in pixels (not computed by server — set client-side) |
height | number | null | Image height in pixels (not computed by server — set client-side) |
duration | number | null | Duration in seconds (video/audio) |
charset | string | null | Character encoding for text files |
folder | string | null | Parent folder ID |
description | string | null | Description of the file |
location | string | null | Location metadata |
tags | string[] | Array of tags |
metadata | object | Arbitrary JSON metadata (EXIF, etc.) |
embed | string | null | Embed code/URL for external media |
focal_point_x | number | null | Focal point X coordinate for cropping |
focal_point_y | number | null | Focal point Y coordinate for cropping |
tus_id | string | null | TUS resumable upload identifier |
tus_data | object | null | TUS upload data |
uploaded_by | string | null | User who uploaded the file |
uploaded_on | string | null | Upload timestamp |
created_on * | string | Record creation timestamp |
modified_by | string | null | User who last modified the file |
modified_on * | string | Last modification timestamp |
Upload a File
Multipart Upload
Send a multipart/form-data request with the file binary and optional metadata:
curl
curl -X POST https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-F "file=@/path/to/image.png" \
-F "title=My Image" \
-F "description=A sample image" \
-F "folder=<folder_id>" \
-F "storage=files"| Form field | Required | Description |
|---|---|---|
file | Yes (multipart) | The file binary |
title | No | Display title |
description | No | Description text |
folder | No | Parent folder ID |
storage | No | Storage bucket name (default: "files") |
Response (201):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"storage": "files",
"filename_disk": "550e8400-e29b-41d4-a716-446655440000.png",
"filename_download": "image.png",
"title": "My Image",
"description": "A sample image",
"type": "image/png",
"filesize": 204800,
"width": null,
"height": null,
"folder": null,
"tags": [],
"metadata": {},
"uploaded_by": "<user_id>",
"uploaded_on": "2024-01-15T10:30:00Z",
"created_on": "2024-01-15T10:30:00Z",
"modified_on": "2024-01-15T10:30:00Z"
}
}width and height are not computed by the server. They will be null unless the client provides them on upload or updates them later via PATCH.
Upload Constraints
| Constraint | Default | Env Variable |
|---|---|---|
| Maximum upload size | 100 MB | FILES_MAX_UPLOAD_SIZE (bytes) |
| Allowed MIME types | All types allowed | FILES_MIME_TYPE_ALLOW_LIST (comma-separated, e.g. image/png,image/jpeg) |
Uploads exceeding FILES_MAX_UPLOAD_SIZE or with a MIME type not in FILES_MIME_TYPE_ALLOW_LIST will be rejected with a 400 error.
JSON Mode (Metadata-Only)
Create a file record without uploading binary content. Useful for tracking external assets or placeholder entries:
curl -X POST https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "image/png",
"filename_download": "external-asset.png",
"title": "External Asset",
"storage": "files"
}'The type and filename_download fields are required in JSON mode. The response has the same shape as the multipart upload, with filesize: 0.
List Files
GET /api/files supports the following query parameters for filtering, sorting, and pagination:
| Param | Type | Description |
|---|---|---|
limit | number | Maximum number of items to return |
offset | number | Number of items to skip (for cursor-based pagination) |
page | number | Page number (alternative to offset) |
search | string | Free-text search across title and filename |
sort | string | Sort field(s), comma-separated. Prefix with - for descending (e.g. -uploaded_on) |
fields | string | Comma-separated list of fields to return (e.g. id,title,filesize) |
filter | string (JSON) | JSON-encoded filter object (e.g. {"folder":{"_eq":"<id>"}}) |
meta | string | Set to filter_count to include metadata in the response |
Example — list with pagination and metadata:
GET /api/files?limit=10&offset=0&meta=filter_count&sort=-uploaded_onResponse (200):
{
"data": [
{ "id": "...", "title": "Image 1", "filesize": 204800, "..." },
{ "id": "...", "title": "Image 2", "filesize": 102400, "..." }
],
"meta": {
"filter_count": 10,
"total_count": 42
}
}When meta=filter_count is not passed, the meta key is omitted from the response.
Get File Metadata
Retrieve a single file’s metadata by ID:
GET /api/files/:id| Param | Type | Description |
|---|---|---|
fields | string | Comma-separated list of fields to return |
meta | string | Set to filter_count to include count metadata |
Response (200):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Image",
"filename_download": "image.png",
"type": "image/png",
"filesize": 204800,
"width": null,
"height": null,
"folder": null,
"uploaded_by": "<user_id>",
"uploaded_on": "2024-01-15T10:30:00Z"
}
}Update File Metadata
JSON Body Update
Update metadata fields via JSON:
curl -X PATCH https://your-domain.com/api/files/<id> \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title", "description": "A better description", "tags": ["hero", "blog"]}'Response (200):
{
"data": {
"id": "<id>",
"title": "Updated Title",
"description": "A better description",
"tags": ["hero", "blog"],
"...": "..."
}
}Multipart File Replacement
Replace the file binary for an existing file record using multipart form data:
curl -X PATCH https://your-domain.com/api/files/<id> \
-H "Authorization: Bearer <token>" \
-F "file=@/path/to/new-version.png"The response has the same shape as the JSON update. The old file binary is replaced in storage; the file record’s filesize, type, and filename_download are updated automatically from the new file.
Delete a File
curl -X DELETE https://your-domain.com/api/files/<id> \
-H "Authorization: Bearer <token>"Returns 204 No Content on success.
Serving Files
Use the asset endpoint to serve file content. The response is raw binary (not JSON) with appropriate Content-Type and Content-Disposition headers — suitable for <img src>, <video src>, or direct download links.
GET /api/assets/:id| Query param | Description |
|---|---|
download | Forces a file download (Content-Disposition: attachment) |
width | Resize image to this width (Supabase Pro plans only) |
height | Resize image to this height (Supabase Pro plans only) |
Assets are served with Cache-Control: public, max-age=31536000 (1 year). Updated files may not reflect immediately in all clients due to aggressive caching.
<img src="https://your-domain.com/api/assets/550e8400-e29b-41d4-a716-446655440000"
alt="My Image"
<!-- Authorization header must be set via fetch/XHR for protected assets -->
/>All asset requests require a valid Authorization header. There is no unauthenticated asset serving — grant the requesting user’s policy read access on daas_files to allow access.
Download File
Get a signed download URL for a file. The response is JSON containing a temporary URL, not the file content itself.
GET /api/files/:id/download| Query param | Type | Default | Description |
|---|---|---|---|
expires_in | number | 3600 | URL expiration time in seconds |
Response (200):
{
"url": "https://<project>.supabase.co/storage/v1/object/sign/files/<filename_disk>?token=...",
"expires_in": 3600
}Import from URL
Download a remote file and store it in Supabase Storage:
curl -X POST https://your-domain.com/api/files/import \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/image.jpg", "data": {"title": "Imported Image"}}'Returns 200 with the imported file data:
{
"data": {
"id": "550e8400-...",
"filename_download": "image.jpg",
"title": "Imported Image",
"...": "..."
}
}Folder Organization
Files can be organized into hierarchical folders.
Create a Folder
curl -X POST https://your-domain.com/api/folders \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "Blog Images", "parent": null}'Response (201):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Blog Images",
"parent": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}List Folders
GET /api/foldersResponse (200):
{
"data": [
{
"id": "550e8400-...",
"name": "Blog Images",
"parent": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"meta": {
"filter_count": 1,
"total_count": 1
}
}Update a Folder
curl -X PATCH https://your-domain.com/api/folders/<id> \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "Renamed Folder"}'Response (200):
{
"data": {
"id": "550e8400-...",
"name": "Renamed Folder",
"parent": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:00:00Z"
}
}Setting a folder as its own parent (PATCH /api/folders/:id with "parent": "<same_id>") is rejected with a 400 error: A folder cannot be its own parent.
Delete a Folder
curl -X DELETE https://your-domain.com/api/folders/<id> \
-H "Authorization: Bearer <token>"Returns 204 No Content on success.
A folder with subfolders or containing files cannot be deleted. Attempting to delete a non-empty folder returns a 400 error with code FOLDER_NOT_EMPTY. Delete all files and subfolders first.
Upload into a Folder
curl -X POST https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-F "file=@hero.jpg" \
-F "folder=<folder_id>"Batch Operations
Batch Update
Update metadata for multiple files in a single request. Two formats are supported:
Keys format — apply the same update to multiple files:
curl -X PATCH https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"keys": ["id-1", "id-2", "id-3"], "data": {"title": "Bulk Updated"}}'Array format — apply per-file updates:
curl -X PATCH https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '[
{"id": "id-1", "title": "New Title A"},
{"id": "id-2", "title": "New Title B", "description": "Updated desc"}
]'Returns 200 with the updated file data.
Batch Delete
Batch delete accepts a JSON body with an array of file IDs (or an object with a keys array):
# Batch delete by ID list
curl -X DELETE https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '["id-1", "id-2", "id-3"]'
# Alternative: keys object
curl -X DELETE https://your-domain.com/api/files \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"keys": ["id-1", "id-2"]}'Returns 204 No Content.