> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CDN

> Upload files to the Lua CDN and read them back by file id

`CDN` stores files the [agent](/concepts/agents) produces or receives and returns them as web `File` objects. An uploaded file is served at `https://cdn.heylua.ai/<fileId>` to anyone who has the id. Available in tools, jobs, webhooks, triggers, processors, and workflow code steps.

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { CDN } from 'lua-cli';
```

## Quick example

Upload returns an id; get returns a `File`.

```ts theme={null}
import { CDN } from 'lua-cli';

const csv = 'order,total\nORD-4471,199.00\n';
const fileId = await CDN.upload(new File([csv], 'report.csv', { type: 'text/csv' }));

const file = await CDN.get(fileId);
const text = await file.text();
```

## Methods

### upload(file)

Uploads one file and returns its id.

```ts theme={null}
CDN.upload(file: File): Promise<string>
```

<ParamField path="file" type="File" required>
  A web `File`, for example `new File([bytes], 'invoice.pdf', { type: 'application/pdf' })`. Its `name` and `type` are stored with the file. The maximum size is 100 MB.
</ParamField>

Images (`image/*`) are scaled so the longest edge is at most 2048 px, and a WebP variant is stored next to the original; the CDN serves the variant to clients whose `Accept` header includes `image/webp` and the original to everyone else. The `width` and `height` query parameters resize on fetch, and `compressed=false` returns the stored original.

**Returns** — the file id, for example `abc123-def456-ghi789`.

**Example**

```ts theme={null}
import { LuaTool, CDN, Data } from 'lua-cli';
import { z } from 'zod';

export default class SaveTranscriptTool implements LuaTool {
  name = 'save_transcript';
  description = 'Store a call transcript and return a link to it';
  inputSchema = z.object({ callId: z.string(), transcript: z.string() });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const file = new File([input.transcript], `${input.callId}.txt`, { type: 'text/plain' });
    const fileId = await CDN.upload(file);
    await Data.create('transcripts', { callId: input.callId, fileId }, input.transcript.slice(0, 500));
    return { url: `https://cdn.heylua.ai/${fileId}` };
  }
}
```

**Errors** — `Upload failed: <status>`, or the server's own message in `lua test`.

### get(fileId)

Fetches a file by id.

```ts theme={null}
CDN.get(fileId: string): Promise<File>
```

<ParamField path="fileId" type="string" required>
  The id `upload` returned.
</ParamField>

**Returns** — a `File` whose `name` is the stored filename (the id when none was stored), `type` the served content type, and `size` the byte length. Read it with `text()`, `arrayBuffer()`, or `stream()`. The fetch sends no `Accept` header, so images come back in their stored format, not as WebP.

**Example**

```ts theme={null}
import { CDN, AI } from 'lua-cli';

const fileId = 'abc123-def456-ghi789';
const file = await CDN.get(fileId);
const bytes = Buffer.from(await file.arrayBuffer());

const caption = await AI.generate('Write a one-line caption.', [
  { type: 'text', text: 'Caption this image.' },
  { type: 'image', image: bytes, mediaType: file.type },
]);
```

**Errors** — `File not found: <status>`, for example `File not found: 404`.

## Types

None are exported. `File` is the standard web `File`, a global in Node 20 and later.

## See also

* [`AI`](/reference/sdk/ai) — pass file bytes as `image` and `file` parts
* [`Data`](/reference/sdk/data) — store file ids next to your own records
* [Device uploads](/devices/cdn-uploads) — how devices put files on the CDN
