Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

SFTP

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.

Installation

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

npm install files-sdk ssh2-sftp-client
pnpm add files-sdk ssh2-sftp-client
yarn add files-sdk ssh2-sftp-client
bun add files-sdk ssh2-sftp-client
nub add files-sdk ssh2-sftp-client
aube add files-sdk ssh2-sftp-client

Usage

SFTP via the 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.

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

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

PropType
host?string

SFTP host. Falls back to `SFTP_HOST`.

Typestring
port?number

Port. Falls back to `SFTP_PORT`, then `22`.

Typenumber
username?string

Username. Falls back to `SFTP_USERNAME`.

Typestring
password?string

Password authentication. Falls back to `SFTP_PASSWORD`. When both this and `privateKey` are set, ssh2 tries the key first, then the password.

Typestring
privateKey?string | Buffer

Private key (PEM) for key-based auth. Falls back to `SFTP_PRIVATE_KEY`.

Typestring | Buffer
passphrase?string

Passphrase for an encrypted `privateKey`. Falls back to `SFTP_PASSPHRASE`.

Typestring
root?string

Remote base directory. Virtual keys resolve under it; keys that escape it (e.g. `../etc/passwd`) throw `Provider`. An absolute root (`/uploads`) yields absolute paths; the default (`"."`) keeps paths relative to the connection's login directory — the common chroot/home case.

Typestring
publicBaseUrl?string

Origin used to build URLs from `url()`. When set, `url(key)` returns `${publicBaseUrl}/${key}` — useful when an HTTP server fronts the same tree. When unset, `url()` throws: SFTP serves no HTTP and has no signing primitive.

Typestring
readyTimeout?number

Connection-ready timeout in milliseconds, passed through to ssh2.

Typenumber
connectOptions?SftpClient.ConnectOptions

Extra ssh2 connect options merged into the resolved config (e.g. `hostVerifier`, `algorithms`, `agent`). Wins over the discrete fields.

TypeSftpClient.ConnectOptions
client?SftpClient

Pre-connected `ssh2-sftp-client` instance. When passed, the adapter reuses it for every call and never opens or closes a connection — the caller owns the socket lifecycle. This is the high-throughput path: connect once and inject, rather than paying a handshake per operation.

TypeSftpClient

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.

Resumable uploads (upload({ control })) append each chunk to a <key>.fls-part staging file next to the target and rename it over the key only when the upload completes, so an existing object stays readable until then and abort() removes just the partial. list() hides staging files, and writes to keys ending in .fls-part throw. Base SFTP rename refuses an existing target on most servers, so when one is in the way it is deleted and the rename retried: the key is briefly absent at that moment.

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. A subdirectory that disappears mid-walk is skipped; a missing root lists as empty. 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.

Was this page helpful?