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

# Sandbox file system: read, write, and manage files

> Read, write, list, and delete files inside a Blaxel sandbox using a simple file system interface from the SDK, MCP server, or REST API.

Manage files and directories within sandboxes through the `fs` module of Blaxel SDK. This module provides essential operations for creating, reading, writing, copying, and deleting files and directories.

<Tip>Complete code examples demonstrating all operations are available on Blaxel's GitHub: [in TypeScript](https://github.com/blaxel-ai/sdk-typescript/tree/main/tests/sandbox), [in Python](https://github.com/blaxel-ai/sdk-python/tree/main/tests/integration/sandbox), and [in Go](https://github.com/blaxel-ai/sdk-go/tree/main/integration_tests).</Tip>

## Basic file system operations

<Accordion title="Learn more about authentication on Blaxel">
  The Blaxel SDK requires two environment variables to authenticate:

  | Variable       | Description                |
  | -------------- | -------------------------- |
  | `BL_WORKSPACE` | Your Blaxel workspace name |
  | `BL_API_KEY`   | Your Blaxel API key        |

  You can create an API key from the [Blaxel console](https://app.blaxel.ai/profile/security). Your workspace name is visible in the URL when you log in to the console (e.g. `app.blaxel.ai/{workspace}`).

  Set them as environment variables or add them to a `.env` file at the root of your project:

  ```bash theme={null}
  export BL_WORKSPACE=my-workspace
  export BL_API_KEY=my-api-key
  ```

  The Blaxel SDK does not accept credentials as constructor arguments. Credentials must come from environment variables, a `.env` file, or a local CLI login session (see below).

  When developing locally, you can also **log in to your workspace with Blaxel CLI** (as shown above). This allows you to run Blaxel SDK functions that will automatically connect to your workspace without additional setup. When you deploy on Blaxel, authentication is handled automatically — no environment variables needed.
</Accordion>

### Create directory

Create a new directory at a specific path in the sandbox:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { SandboxInstance } from "@blaxel/core";

  const sandbox = await SandboxInstance.get("my-sandbox");

  await sandbox.fs.mkdir("/blaxel/app/uploads");
  ```

  ```python Python theme={null}
  from blaxel.core import SandboxInstance

  sandbox = await SandboxInstance.get("my-sandbox")
  await sandbox.fs.mkdir("/blaxel/app/uploads")
  ```
</CodeGroup>

### List files

List files in a specific path:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { subdirectories, files } = await sandbox.fs.ls("/blaxel/app");
  ```

  ```python Python theme={null}
  result = await sandbox.fs.ls("/blaxel/app")
  directories = result.subdirectories
  files = result.files
  ```
</CodeGroup>

### Find files and directories

<Note>
  Blaxel's Sandbox API uses an optimized `find()` method, which is faster than using the native `find` tool.
</Note>

Find files and directories matching specified patterns:

<CodeGroup>
  ```typescript TypeScript theme={null}
  let result = await sandbox.fs.find(
    "/app",
    {
      type: "file",
      patterns: ["*.md", "*.html"],
      maxResults: 1000,
    }
  );

  if (result.matches && result.matches.length > 0) {
    for (const file of result.matches) {
      console.log(`Found file: ${file.path}`);
    }
  } else {
    console.log("No files found.");
  }
  ```

  ```python Python theme={null}
  result = await sandbox.fs.find(
      path="/app",
      type="file",
      patterns=["*.md", "*.html"],
      max_results=1000,
  )
  if result.matches:
      for file in result.matches:
          logger.info(f"Found file: {file.path}")
  else:
      logger.info("No files found.")
  ```
</CodeGroup>

### Search for text content within files

<Note>
  Blaxel's Sandbox API uses an optimized `grep()` method, which is faster than using the native `grep` tool.
</Note>

Find files containing specified text content:

<CodeGroup>
  ```typescript TypeScript theme={null}
  let result = await sandbox.fs.grep(
    "agentic",
    "/app",
    {
      caseSensitive: true,
      contextLines: 2,
      maxResults: 5,
      filePattern: "*.mdx",
      excludeDirs: ["images", "node_modules"],
    }
  );

  if (result.matches && result.matches.length > 0) {
    for (const match of result.matches) {
      console.log(`${match.path}:${match.line} -> ${match.text}`);
    }
  } else {
    console.log("No matches found.");
  }
  ```

  ```python Python theme={null}
  result = await sandbox.fs.grep(
      query="agentic",
      path="/app",
      case_sensitive=True,
      context_lines=2,
      max_results=5,
      file_pattern="*.mdx",
      exclude_dirs=["images", "node_modules"],
  )
  if result.matches:
      for match in result.matches:
          logger.info(
              f"{match.path}:{match.line} -> {match.text}"
          )
  else:
      logger.info("No matches found.")
  ```
</CodeGroup>

### Read file

Read a file from a specific filepath:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const content = await sandbox.fs.read("/blaxel/app/config.json");
  ```

  ```python Python theme={null}
  content = await sandbox.fs.read("/blaxel/app/config.json")
  ```
</CodeGroup>

### Write file

Create a file in a specific path:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await sandbox.fs.write("/blaxel/app/config.json", "{}");
  ```

  ```python Python theme={null}
  await sandbox.fs.write("/blaxel/app/config.json", "{}")
  ```
</CodeGroup>

See down below for how to upload/write a binary, or multiple files at once.

### Write multiple files

You can write multiple files or directories simultaneously. The second path parameter in `writeTree` specifies the base directory for writing the file tree, eliminating the need to repeat the full path for each file.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const files = [
    { path: "src/app.js", content: "console.log('Hello');" },
    { path: "src/utils.js", content: "export const helper = () => {};" },
    { path: "package.json", content: '{"name": "my-app"}' },
    { path: "docs/README.md", content: "# My App" }
  ];
  await sandbox.fs.writeTree(files, "/blaxel/app");
  ```

  ```python Python theme={null}
  files = [
      {"path": "src/app.py", "content": "print('Hello')"},
      {"path": "src/utils.py", "content": "def helper(): pass"},
      {"path": "requirements.txt", "content": "flask==2.0.1"},
      {"path": "docs/README.md", "content": "# My App"}
  ]
  await sandbox.fs.write_tree(files, "/blaxel/app")
  ```
</CodeGroup>

### Read binary file

Read a binary file from the sandbox filesystem:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const binaryData = await sandbox.fs.readBinary("/tmp/image.webp");
  ```

  ```python Python theme={null}
  binary_data = await sandbox.fs.read_binary("/tmp/image.webp")
  ```
</CodeGroup>

The binary content is returned as a Web API *Blob* object.

### Write binary file

Write binary content to a file in the sandbox filesystem:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const binaryData = fs.readFileSync("./image.webp");
  await sandbox.fs.writeBinary("/blaxel/app/assets/image.webp", binaryData);
  ```

  ```python Python theme={null}
  with open("image.webp", "rb") as f:
      binary_data = f.read()
      await sandbox.fs.write_binary("/blaxel/app/assets/image.webp", binary_data)
  ```
</CodeGroup>

The binary content to write can be provided as:

* *Buffer*: Node.js Buffer object
* *Blob*: Web API Blob object
* *File*: Web API File object
* *Uint8Array*: Typed array containing binary data

<Note>
  If you're calling the sandbox filesystem API directly over HTTP instead of using an SDK, a single request is capped at 5 MB. Requests larger than this are rejected with an HTTP 413 before they reach the sandbox. Therefore, if using HTTP, larger files must be uploaded in parts via the sandbox's multipart upload endpoints rather than in a single request. Blaxel SDKs handle this automatically: SDK functions like `write()`, `writeBinary()`, and `write_binary()` automatically split files over 5 MB and send via multipart upload in 5 MB chunks.
</Note>

### Download file to host

Download a file from the sandbox filesystem to the host:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await sandbox.fs.download("/tmp/foo.bin", "foo2.bin");
  ```

  ```python Python theme={null}
  await sandbox.fs.download("/tmp/foo.bin", "foo2.bin")
  ```
</CodeGroup>

### Copy file

Copy a file from a path to another path:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await sandbox.fs.cp("/blaxel/app/config.json", "/blaxel/app/config.backup.json");
  ```

  ```python Python theme={null}
  await sandbox.fs.cp("/blaxel/app/config.json", "/blaxel/app/config.backup.json")
  ```
</CodeGroup>

### Delete file or directory

Delete a file or directory by specifying its path:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await sandbox.fs.rm(`/blaxel/app/config.json`);
  ```

  ```python Python theme={null}
  await sandbox.fs.rm(f"/blaxel/app/config.json")
  ```
</CodeGroup>

## Watch filesystem for events

The `watch` function monitors all file system changes **in the specified directory.** You can also watch subdirectories by passing a `/my/directory/**` pattern.

By default (when *withContent: false*), the events will only include metadata about the changes, not the actual file contents. Here's what you'll get in the callback events:

1. For ALL operations (CREATE, WRITE, DELETE, etc.), you'll receive:
   1. op: The operation type (e.g., "CREATE", "WRITE", "DELETE")
   2. path: The directory path where the change occurred
   3. name: The name of the file/directory that changed
2. You will NOT receive:
   1. The actual content of the files
   2. File contents for CREATE or WRITE operations

<CodeGroup>
  ```typescript TypeScript theme={null}
  // You can specify if you want the content of the files or not
  const handle = sandbox.fs.watch("/", (fileEvent) => {
    console.log(fileEvent.op, fileEvent.path, fileEvent.content)
  }, {
    withContent: true
  });

  // Do file operations

  // At the end, close the watch handle
  handle.close();
  ```

  ```python Python theme={null}
  def watch_callback(file_event):
    print(file_event.op, file_event.path, file_event.content)

  # You can specify if you want the content of the files or not
  handle = sandbox.fs.watch("/", watch_callback, { "with_content": True })

  # Do file operations

  # At the end, close the watch handle
  handle["close"]()
  ```
</CodeGroup>

### Watch sub-directories

Watch all sub-directories recursively with `/**`:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const handle = sandbox.fs.watch("/folder/**", (fileEvent) => {
    console.log(fileEvent.op, fileEvent.path)
  });

  ```

  ```python Python theme={null}
  def watch_callback(file_event):
    print(file_event.op, file_event.path)

  # You can specify if you want the content of the files or not
  handle = sandbox.fs.watch("/folder/**", watch_callback)
  ```
</CodeGroup>

### Ignore files or directories

You can ignore changes in certain files or directories by providing an array of filepaths to ignore:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const handle = sandbox.fs.watch("/", (fileEvent) => {
    console.log(fileEvent.op, fileEvent.path)
  }, {
    ignore: ["/folder", "/folder_two/test2.txt"]
  });
  ```

  ```python Python theme={null}
  def watch_callback(file_event):
    print(file_event.op, file_event.path)

  # You can specify if you want the content of the files or not
  handle = sandbox.fs.watch("/", watch_callback, {
    "ignore": ["/folder", "/folder_two/test2.txt"]
  })
  ```
</CodeGroup>

Specify `withContent: true` so the events include the actual file contents.
