---
title: SFTP
description: SFTP (SSH File Transfer Protocol) via ssh2-sftp-client. Node-only. Connect-per-operation with an injectable client for batch work; url() needs an HTTP front.
peerDeps:
  - "ssh2-sftp-client"
---

## Installation

`ssh2-sftp-client` is an optional peer dependency of `files-sdk` - install alongside the SDK so the adapter's imports resolve at runtime.

```package-install
files-sdk ssh2-sftp-client
```

## Usage

SFTP via the [`ssh2-sftp-client`](https://www.npmjs.com/package/ssh2-sftp-client) library (built on `ssh2`). Virtual keys map to paths under a configurable `root` on the remote server, with a `..` traversal guard. **Node-only** — SFTP uses raw sockets, so this adapter does not run on edge/browser/Workers runtimes.

By default the adapter opens a fresh connection per operation and closes it afterwards. For batch work, connect once and pass the `client` so every call reuses the same connection — you own its lifecycle.

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

const files = new Files({
  adapter: sftp({
    host: "files.example.com",
    username: process.env.SFTP_USERNAME!,
    // Password or private-key auth (privateKey takes precedence):
    privateKey: process.env.SFTP_PRIVATE_KEY!,
    // passphrase: process.env.SFTP_PASSPHRASE,
    root: "/uploads", // virtual keys resolve under here; defaults to "."
  }),
});

await files.upload("reports/q1.csv", csv, { contentType: "text/csv" });
const file = await files.download("reports/q1.csv");
```

Auth falls back to `SFTP_HOST`, `SFTP_USERNAME`, `SFTP_PASSWORD`, `SFTP_PRIVATE_KEY`, `SFTP_PASSPHRASE`, and `SFTP_PORT` (default `22`) when the matching option is omitted. Pass `connectOptions` to forward anything else to `ssh2` (e.g. `hostVerifier`, `algorithms`, `agent`).

### Reusing a connection

```ts
import SftpClient from "ssh2-sftp-client";

const client = new SftpClient();
await client.connect({ host: "files.example.com", username, privateKey });

const files = new Files({ adapter: sftp({ client }) });
// ...many operations over the one connection...
await client.end();
```

## Options

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

## Limitations

Connect-per-operation means a high call rate becomes a high connection rate, and SSH servers commonly cap sessions per IP - inject a pre-connected `client` (shown above) for batch jobs.

## Compatibility

| Method            | Status | Notes                                                                                                                                                                                                                                                                                                                                                                |
| ----------------- | :----: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upload`          |   ⚠️   | User `metadata` and `cacheControl` throw - SFTP files have no arbitrary-metadata or cache-header field. `contentType` is accepted for the return value but not stored (it's inferred from the key's extension on read). Stream bodies upload directly. Node-only (raw sockets).                                                                                      |
| `download`        |   ✅   |                                                                                                                                                                                                                                                                                                                                                                      |
| `delete`          |   ✅   |                                                                                                                                                                                                                                                                                                                                                                      |
| `list`            |   ⚠️   | Walks the directory tree recursively on every call - SFTP has no native prefix scan or pagination - and skips symlinks. `prefix`/`limit`/`cursor` are applied client-side over the full walk, so they're accurate but a large tree means a full traversal per call. Content type is inferred from each key's extension; `size`/`lastModified` come from the listing. |
| `search`          |   ⚠️   | Built on `listAll` — inherits this adapter's `list` behavior above. Client-side key match (glob, regex, substring, exact).                                                                                                                                                                                                                                           |
| `head`            |   ⚠️   | SFTP stores no content type, etag, or user metadata - `head()` infers the type from the key's extension (or `application/octet-stream`) and returns no etag. `size` and `lastModified` come from `stat`.                                                                                                                                                             |
| `exists`          |   ✅   |                                                                                                                                                                                                                                                                                                                                                                      |
| `copy`            |   ⚠️   | Read-then-write - base SFTP has no portable server-side copy, so the source is downloaded and re-uploaded over one connection. The whole object is buffered in memory; not atomic.                                                                                                                                                                                   |
| `url`             |   ❌   | Throws unless `publicBaseUrl` is set (an HTTP server fronting the same tree), in which case it returns `<publicBaseUrl>/<key>`. SFTP serves no HTTP and has no signing primitive. `responseContentDisposition` always throws because the HTTP-front URL cannot bind the override.                                                                                    |
| `signedUploadUrl` |   ❌   | Throws - SFTP has no presigned-upload concept. Use `upload()`, or inject a pre-connected `client` for batch transfers.                                                                                                                                                                                                                                               |
