---
title: "Recipe: File Upload Gallery"
description: "An image gallery on teenybase: upload images with metadata to R2, serve them back, and delete the files when the record is deleted."
url: https://teenybase.com/docs/recipes/file-gallery
last_updated: 2026-04-19
---

> Documentation index: https://teenybase.com/llms.txt lists every teenybase docs page with a link to its Markdown version. All docs in one file: https://teenybase.com/llms-full.txt

# File Upload Gallery

Upload images with metadata, serve them back, and auto-clean storage on delete.

### Config

```typescript
{
    name: 'images',
    autoSetUid: true,
    r2Base: 'gallery',           // R2 bucket prefix for this table
    autoDeleteR2Files: true,     // delete files from R2 when record is deleted
    fields: [
        ...baseFields,
        { name: 'owner_id', type: 'relation', sqlType: 'text', notNull: true,
          foreignKey: { table: 'users', column: 'id', onDelete: 'CASCADE' } },
        { name: 'title', type: 'text', sqlType: 'text', notNull: true },
        { name: 'file', type: 'file', sqlType: 'text', notNull: true },
        { name: 'thumb', type: 'file', sqlType: 'text' },
        { name: 'caption', type: 'text', sqlType: 'text' },
    ],
    triggers: [createdTrigger, updatedTrigger],
    indexes: [{ fields: 'owner_id' }],
    extensions: [{
        name: 'rules',
        listRule: 'true',
        viewRule: 'true',
        createRule: 'auth.uid != null & owner_id == auth.uid',
        updateRule: 'auth.uid == owner_id',
        deleteRule: 'auth.uid == owner_id',
    } as TableRulesExtensionData],
}
```

### Upload a file

Files are uploaded via multipart/form-data. Use `@filePayload` for the actual file and `@jsonPayload` for the record data.

```bash
curl -X POST http://localhost:8787/api/v1/table/images/insert \
  -H 'Authorization: Bearer <token>' \
  -F '@jsonPayload={"values":{"owner_id":"USER_ID","title":"Sunset"}};type=application/json' \
  -F '@filePayload[file]=@/path/to/sunset.jpg'
```

### Download a file

```
GET http://localhost:8787/api/v1/files/<table>/<record_id>/<field_name>/<file_name>
```

Example:
```
http://localhost:8787/api/v1/files/images/abc123/file/sunset.jpg
```

### Delete (auto-cleans R2)

```bash
curl -X POST http://localhost:8787/api/v1/table/images/delete \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"where": "id = \"abc123\""}'
```

With `autoDeleteR2Files: true`, the actual file in R2 is deleted when the record is deleted. No orphaned files.
