<!-- section: Files API · status: building · source: docs/files-api/08-file-object.md -->

> Every field a file response carries, and which ones can be absent.


# The file object

The same shape everywhere a file is returned.

```json A file
{
  "id": "E7iVnWA1LiEpu3u8dRTVK",
  "projectId": "WXm71CLq69pZAZCWR2l_m",
  "ownerUserId": null,
  "filename": "sneaker-front-dEPY6g.jpg",
  "originalFilename": "sneaker front.jpg",
  "key": "files/originals/public/…/sneaker-front-dEPY6g.jpg",
  "contentType": "image/jpeg",
  "size": 204800,
  "metadata": null,
  "visibility": "public",
  "uploadStatus": "confirmed",
  "createdAt": "2026-08-14T13:38:29.830Z",
  "updatedAt": "2026-08-14T13:38:33.412Z",
  "transformations": { "image": { "enabled": true, "visibility": "public" } },
  "entity": { "type": "product", "id": "prod_abc123", "role": "gallery", "position": 0 },
  "url": "https://cdn…/files/originals/public/…",
  "variants": [{ "width": 100, "url": "…" }],
  "srcset": "…100w, …300w, …"
}
```

| Field | Notes |
|---|---|
| `id` | Ours. Stable for the file's life |
| `projectId` | Always your own project |
| `ownerUserId` | Whoever you said uploaded it, or `null` |
| `filename` | Sanitised, with a uniqueness suffix |
| `originalFilename` | What the user called it — display this |
| `key` | The storage key. Returned so you can recognise your own objects; you never need to build one |
| `contentType` | As declared at upload |
| `size` | Bytes |
| `visibility` | `public` or `private` |
| `uploadStatus` | Always `confirmed` in list responses |
| `transformations` | What was *asked for*, not what exists |
| `entity` | The attachment, or `null` |
| `url` | Permanent when public, signed for five minutes when private |
| `variants` | Absent when the file has no sizes |
| `srcset` | Absent for the same reason |

## What can be absent

`variants` and `srcset` appear only for a transform-enabled image in a format we
can resize. Their absence is information — check for it rather than assuming:

```tsx Rendering any file
<img src={file.url} srcSet={file.srcset} sizes="(max-width: 768px) 100vw, 400px" />
```

`srcSet={undefined}` is valid and the browser falls back to `src`, so the same
markup works for a PDF, an SVG and a photograph.

Pick sizes with `sizes`, not by choosing a width yourself. Hard-coding one means
shipping a 1200px image to a phone, or a 100px one to a retina display — the
browser knows both facts and you do not.

If you are not rendering to a browser — a PDF, an email, a native canvas — use
`variants[]`, which carries the same widths as the `srcset`:

```ts Picking a width by hand
const width = file.variants?.find((v) => v.width >= target);
const url = width?.url ?? file.url;
```

## transformations means requested, not built

`transformations.image.enabled` records what you asked for at upload. It does
not promise the sizes exist — each is generated the first time that width is
requested. `variants` is the field that tells you which ones are on offer.
