---
title: Filesystem
description: Local filesystem - the dev/test adapter. Uses node:fs/promises with a sidecar .meta.json per file. Not for production.
---

## Installation

This adapter has no extra peer dependencies - the runtime (Node or Bun) provides everything it needs.

```package-install
files-sdk
```

## Usage

Local filesystem. The dev/test adapter - point it at a directory and it implements the same `Adapter` contract as the cloud adapters using `node:fs/promises`. Each upload writes the body and a sidecar `.meta.json` file alongside it (Content-Type, ETag, user metadata) so reads round-trip cleanly. Not for production: there's no replication, no signing, no auth.

```ts lineNumbers
import { Files } from "files-sdk";
import { fs } from "files-sdk/fs";

// Writes objects under `./.uploads` with a sidecar `.meta.json`
// per file for Content-Type, ETag, and user metadata. Designed for
// dev and CI - same Adapter contract as the cloud adapters, so swap
// it in via env without changing call sites.
const files = new Files({
  adapter: fs({
    root: "./.uploads",
    // Optional: configure if a dev server exposes the same root over
    // HTTP, so url() returns a browser-friendly URL instead of file://.
    // urlBaseUrl: "http://localhost:3000/files",
  }),
});
```

## Options

<AutoTypeTable
  path="../../packages/files-sdk/src/fs/index.ts"
  name="FsAdapterOptions"
/>

## Storage layout

Body at `` `${root}/${key}` ``; sidecar at `` `${root}/${key}.meta.json` ``. Sidecars survive `cp -r` / `git mv` / partial-tree deletion. `list()` hides them. ETag is a SHA-1-derived stable hash computed at upload time. Files written into `root` by hand without a sidecar are still readable - `contentType` falls back to `application/octet-stream` and `etag` is absent.

## Compatibility

| Method | Status | Notes |
| --- | :-: | --- |
| `upload` | ✅ |  |
| `download` | ✅ |  |
| `delete` | ✅ |  |
| `list` | ✅ |  |
| `search` | ✅ |  |
| `head` | ✅ |  |
| `exists` | ✅ |  |
| `copy` | ✅ |  |
| `url` | ⚠️ | Returns a `file://` URL by default - fine for CLIs and tests, not browsers. With `urlBaseUrl` set, returns `<urlBaseUrl>/<key>` so a dev server (Next.js `/public` mount, `serve-static`, etc.) can deliver the body. `responseContentDisposition` throws because neither `file://` nor static-server URLs have a signature mechanism in which to bind the override. |
| `signedUploadUrl` | ❌ | Throws - the fs adapter has no built-in upload server, signer, or verifier, so it cannot bind expiry, content type, or size limits into an upload capability. Upload through `files.upload()` or an application route that enforces those controls server-side. |
