# Files

URL: https://docs.domainruntime.dev/docs/data/files
Status: Guide
Reviewed: 2026-09-22



<div className="docs-note">
  File storage runs in production today, on Amazon S3. The 

  `db.storage`

   and 

  `runtime.files`

   helpers are 

  **planned**

  ; today upload with 

  `PUT /storage/upload`

   (

  [HTTP API](/docs/platform/admin-http-api#files)

  ).
</div>

Files are objects of the `$files` entity. You upload them, link them to your own entities, and read them with a query like any other data.

## Upload from the browser [#upload-from-the-browser]

```ts
const { fileId } = await db.storage.upload(`invoices/${invoiceId}.pdf`, file);
```

## Read [#read]

```ts
const { data } = db.useQuery({
  $files: { $: { where: { path: `invoices/${invoiceId}.pdf` } } },
});
// data.$files[0].url  →  a signed URL that expires
```

`$files` objects have `path`, `size`, `content-type`, `content-disposition` and `url`. The URL expires: store the path, or link the file, and read `url` fresh each time.

## Link files to your data [#link-files-to-your-data]

```ts title="schema"
links: {
  billing_invoicePdf: {
    forward: { on: "billing_invoice", has: "one", label: "pdf" },
    reverse: { on: "$files", has: "many", label: "invoices" },
  },
},
```

```ts
db.useQuery({ billing_invoice: { pdf: {} } });
```

## Create files inside an action [#create-files-inside-an-action]

When a file is the result of business logic — a generated PDF, an export — create it in the action, so it exists only if the action succeeded:

```ts
async execute({ input, runtime }) {
  const pdf = await renderInvoice(input);
  const { fileId } = await runtime.files.upload(`invoices/${input.invoiceId}.pdf`, pdf, {
    contentType: "application/pdf",
  });
  await runtime.tx((tx) => tx.billing_invoice[input.invoiceId].link({ pdf: fileId }));
}
```

## Files as action input [#files-as-action-input]

An action can take files as input. The client uploads them before the call, and your code receives a handle to read them:

```ts
import { file } from "@domainruntime/domain";

input: z.object({ invoiceId: z.string().uuid(), scan: file() }),
```

Up to 16 files, 32 MiB each and 128 MiB per call. See [Limits](/docs/reference/limits).

## Delete [#delete]

```ts
await db.storage.delete(`invoices/${invoiceId}.pdf`);
```

Deleting the `$files` object removes the file from queries immediately.

## Permissions [#permissions]

`$files` is **denied by default**: add rules for `view`, `create` and `delete`, usually by `path` or by the entity the file is linked to. See [Permissions](/docs/auth/permissions).
