---
title: Files
description: List, read, write, upload and download — with an encoding you name rather than one that is guessed.
---

File access needs no [input lease](/docs/sdk/input#the-input-lease).

## List

<CodeGroup>

```ts TypeScript
const listing = await machine.files.list("/home/agent");
for (const entry of listing.entries) console.log(entry.name, entry.type, entry.size);
```

```python Python
listing = machine.files.list("/home/agent")
for entry in listing["entries"]:
    print(entry["name"], entry["type"], entry["size"])
```

</CodeGroup>

An entry's `type` is `file`, `dir`, `symlink` or `other`.

## Read

<CodeGroup>

```ts TypeScript
const text = await machine.files.read("/etc/hostname");
text.content;

const chunk = await machine.files.read("/var/log/app.log", { offset: 0, length: 4096 });
chunk.eof;

const raw = await machine.files.read("/tmp/logo.png", { encoding: "base64" });
```

```python Python
text = machine.files.read("/etc/hostname")
text["content"]

chunk = machine.files.read("/var/log/app.log", offset=0, length=4096)
chunk["eof"]

raw = machine.files.read("/tmp/logo.png", encoding="base64")
```

</CodeGroup>

### Encoding is named, never guessed

A `utf8` read of bytes that are not UTF-8 is an `invalid_request` telling you to ask for
`base64`. It is never silently decoded, because a decode-and-re-encode is exactly where a PNG
turns into a field of replacement characters — and the corruption surfaces later, somewhere else,
as a mystery.

## Write

<CodeGroup>

```ts TypeScript
await machine.files.write("/home/agent/notes.md", "# notes\n");
await machine.files.write("/home/agent/notes.md", "more\n", { append: true });
await machine.files.write("/home/agent/run.sh", script, { mode: "0755" });
```

```python Python
machine.files.write("/home/agent/notes.md", "# notes\n")
machine.files.write("/home/agent/notes.md", "more\n", append=True)
machine.files.write("/home/agent/run.sh", script, mode="0755")
```

</CodeGroup>

| Prop | Type | Default | Description |
| - | - | - | - |
| `encoding?` | `"utf8" \| "base64"` | `"utf8"` | |
| `append?` | `boolean` | `false` | Append rather than replace. |
| `createDirs?` | `boolean` | `true` | TypeScript `createDirs` / Python `create_dirs`. Create parent directories as needed. |
| `mode?` | `string` | - | File mode, e.g. "0644". |

## Upload and download

<CodeGroup>

```ts TypeScript
await machine.files.upload("/home/agent/logo.png", bytes); // Uint8Array in
const png = await machine.files.download("/home/agent/logo.png"); // Uint8Array out

const big = await machine.files.download("/var/log/huge.log", { maxBytes: 256 * 1024 * 1024 });
```

```python Python
machine.files.upload("/home/agent/logo.png", png_bytes)           # bytes in
png = machine.files.download("/home/agent/logo.png")              # bytes out

big = machine.files.download("/var/log/huge.log", max_bytes=256 * 1024 * 1024)
```

</CodeGroup>

`upload` is `write` with base64, which is an exact round trip rather than a guess.

`download` does the chunking for you. The guest bounds an inline read, so a large file arrives as
several 4 MB ranges and is reassembled client-side. It ends on the guest's own `eof` rather than a
size comparison, so **a file that grows under the read does not truncate**, and it stops at 64 MB
unless you raise the limit.

## What is refused

The machine is your own computer and the microVM is the boundary, so the default readable area is
**everything**. What is denied is the small set of paths that are not yours: the substrate's guest
credentials, the machine agent's own, and the host's control channel into the guest.

<CodeGroup>

```ts TypeScript
await machine.files.read("/etc/machine-agent/credentials.json");
// RasterError: permission_denied
```

```python Python
machine.files.read("/etc/machine-agent/credentials.json")
# RasterError: permission_denied
```

</CodeGroup>

The denylist is fixed and not configurable, and the check runs against **every form the path
takes** — as written, each symlink hop, and the fully resolved result. See
[files](/docs/reference/files#what-is-refused) for why each of those is necessary.

It does not sandbox the machine from itself: a process inside the machine still reads whatever it
can. The denylist keeps the control plane's own credentials out of the API surface.
