# Asset Uploads in Func | Creght API for AI

> The two upload paths for a Creght site: signed direct CDN upload with talizen/assets, and ctx.assets.upload inside Func. Return values, the 20 MiB limit, table field shape, and the common mistakes.

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[View llms.txt](/llms.txt)

Overview

- [Creght API for AI](/api.md)

Discoverability

- [How to optimize llms.txt](/api/optimize-llms-txt.md)

Site configuration

- [Implement domain-based locale routing](/api/domain-locale-routing.md)

Backend

- [Calling external APIs on the server and managing the cache](/api/ssr-external-api-cache.md)
- [Build site backend workflows with Func](/api/func-backend.md)
- [JSON tables: definition, reads, and queries](/api/func-json-tables.md)
- [Uploads: signed direct upload and Func-generated files](/api/func-assets-upload.md)
- [Timeouts and streaming responses](/api/func-timeout-streaming.md)
- [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md)

Integrations

- [Send Email and Verification Codes with Integrations](/api/func-email-integration.md)
- [Take Alipay payments with an integration](/api/func-alipay-integration.md)

Auth

- [Require a Verified Email to Sign Up](/api/auth-verified-registration.md)
- [Reset and Change Passwords](/api/auth-password-reset.md)
- [Sign In From a Func](/api/auth-func-login.md)
- [Query users from a Func](/api/func-user-directory.md)

On this page

- [Choose the path first](#which-path)
- [Browser files → signed upload](#browser-files-signed-upload)
- [Func-generated files → ctx.assets.upload](#func-generated-files-ctx-assets-upload)
- [Browser files: signed CDN upload](#browser-signed-upload)
- [Files generated inside Func](#func-generated-upload)
- [Storing files in a JSON table](#storing)
- [Acceptance checklist](#checklist)

Backend/Uploads: signed direct upload and Func-generated files

# Uploads: signed direct upload and Func-generated files

Choosing between the two upload paths: browser-selected files go straight to the CDN through a signed upload, and Func-generated bytes go through ctx.assets.upload at 20 MiB per call. Why a base64 relay always fails, and how a file belongs in a table.

Copy Markdown link

A site has two upload paths, and picking the wrong one runs straight into a size limit: **files the user selects in the browser go through a signed CDN upload**, and **bytes generated inside Func go through `ctx.assets.upload`**. Both return a URL you can use directly — what you store in a table is always the URL, never the file content.

**Agent objective**

Use `uploadAsset()` for a browser `File` or `Blob`. Use `ctx.assets.upload()` only for bytes produced inside Func that the browser never had. Never base64-encode a file through `invoke()`, and never store base64 in a JSON table.

## Choose the path first

### Browser files → signed upload

Avatars, attachments, and images the user selects or drops. The bytes go straight from the browser to the CDN and **never pass through Func**, so they are not bound by the Func execution timeout or result-size limit.

### Func-generated files → ctx.assets.upload

Model-generated images, server-assembled PDFs, bytes fetched from a third party. The browser never had this content, so it can only leave through Func. One upload is capped at **20 MiB**.

> Base64-encoding a browser file and relaying it through `invoke()` is the one genuinely wrong pattern here: base64 inflates the payload by about a third while consuming the Func input, execution time, and result-size budgets at once. Anything but a small file will fail.

## Browser files: signed CDN upload

For a `File` or `Blob` selected or dropped in the browser, use `talizen/assets`:

```typescript
import { uploadAsset } from 'talizen/assets'

const asset = await uploadAsset(file, {
  onFileUploadProcess(fileName, progress) {
    console.log(fileName, progress)
  },
})

// asset.fileUrl === asset.url
```

`uploadAsset()` calls `POST /api/asset/file/preupload` to obtain a short-lived signed URL, uploads the file bytes directly from the browser with `PUT` to that CDN storage URL, then confirms the upload through `POST /api/asset/file/ack`. File bytes do not pass through Func, and matching content can reuse an existing object by hash.

Both `File` and `Blob` are accepted. For an unnamed `Blob`, call `uploadAsset(blob, { fileName: 'avatar.webp' })`. The result contains `{ fileUrl, url, fileName, mimeType, size, hash }`, with identical URL fields. Signed upload works on both preview and published site domains.

Once the upload finishes, hand the URL to a Func to persist. The Func only ever handles strings:

```typescript
import { invoke } from 'talizen/func'

const asset = await uploadAsset(file)
await invoke('profile.setAvatar', { url: asset.url, size: asset.size })
```

> That URL arrives from the browser, so treat it as **untrusted input**: confirm it points at the platform CDN domain before storing it.

## Files generated inside Func

```typescript
const asset = ctx.assets.upload({
  filename: 'report.pdf',
  mimeType: 'application/pdf',
  base64: input.base64,
})
await ctx.db.insert('reports', {
  userId: ctx.auth.requireUser().id,
  url: asset.url,
  size: asset.size,
})
```

Use `ctx.assets.upload()` only for bytes generated inside Func that cannot originate in the browser. It synchronously returns `{ fileUrl, url, size }`, with identical URL fields. Persist the URL and size only; do not store internal paths or large base64 payloads in tables or Func JSON. **A single upload is limited to 20 MiB.**

The typical case is moving bytes returned by a third party onto your own CDN so you do not depend on their temporary link:

```typescript
export async function generate(input, ctx) {
  const user = ctx.auth.requireUser()

  const response = await fetch('https://api.example.com/v1/images', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.EXAMPLE_API_KEY },
    body: JSON.stringify({ prompt: input.prompt }),
  })
  if (!response.ok) {
    ctx.response.status(502)
    throw new Error('upstream request failed')
  }

  const { image_base64 } = await response.json()
  const asset = ctx.assets.upload({
    filename: 'generated.png',
    mimeType: 'image/png',
    base64: image_base64,
  })

  ctx.db.insert('generations', { userId: user.id, url: asset.url, prompt: input.prompt })
  return { url: asset.url }
}
```

Calls like this usually exceed the 5-second default, so the caller must raise its timeout explicitly — see [Timeouts and streaming responses](/api/func-timeout-streaming.md).

## Storing files in a JSON table

Store the URL and metadata, not the file. Describe the field as a string with `format: "uri"` — **table schemas do not support `"format": "file"`**:

```json
{
  "url": { "type": "string", "format": "uri", "contentMediaType": "image/*" },
  "size": { "type": "number" },
  "fileName": { "type": "string" }
}
```

For field and query rules, see [JSON tables: definition, reads, and queries](/api/func-json-tables.md).

## Acceptance checklist

- Browser-selected files go through `uploadAsset()`, with no base64 relay through `invoke()`.
- `ctx.assets.upload()` is used only for Func-generated bytes, each under 20 MiB.
- Tables store URLs and sizes — no base64, no internal storage paths.
- URLs supplied by the browser are validated before being persisted.
- Long-running generation calls set a matching `timeoutMs` at the call site.

**Completion criteria**

File bytes travel exactly one path matching their origin, Func inputs and results carry only URLs and metadata, and uploads are verified on both the preview and the published domain.

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

This website is built with [Creght](/)

[Discord](https://discord.gg/Qvq2nmZNnb)

## Links

- [Pricing](/price.md)
- [Solutions](/solution.md)
- [Customers](/customers.md)
- [Help Center](/help.md)
- [Contact Us](/contact.md)
- [Update Logs & Blogs](/blogs.md)
- [Refund Policy](/tuikuan.md)

## Resources

- [All Resources](/resources.md)
- [Templates](/templates.md)
- [Components](https://creghtlib.site.creght.com)
- [Animations](/design/effects.md)
- [Figma to Creght](/figma2creght.md)
- [API for AI](/api.md)

## Terms

- [Terms of Service](/legal/terms.md)
- [Privacy Policy](/legal/privacy.md)
- [Acceptable Use Policy](/legal/acceptable-use.md)

## Social Media

- [Twitter](https://twitter.com)
- [Facebook](https://www.facebook.com)
- [LinkedIn](https://www.linkedin.com)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
