Skip to content
Files SDK
Esc
navigateopen⌘Jpreview
On this page

FTP

FTP / FTPS via basic-ftp. Node-only. Connect-per-operation with an injectable client for batch work; url() needs an HTTP front.

Installation

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

npm install files-sdk basic-ftp
pnpm add files-sdk basic-ftp
yarn add files-sdk basic-ftp
bun add files-sdk basic-ftp

Usage

FTP and FTPS via the basic-ftp library. Virtual keys map to paths under a configurable root on the server, with a .. traversal guard. Node-only — FTP 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 { ftp } from "files-sdk/ftp";

const files = new Files({
  adapter: ftp({
    host: "ftp.example.com",
    user: process.env.FTP_USERNAME!,
    password: process.env.FTP_PASSWORD!,
    secure: true, // FTPS over explicit TLS — strongly recommended
    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 FTP_HOST, FTP_USERNAME (alias FTP_USER), FTP_PASSWORD, FTP_SECURE ("true" or "implicit"), and FTP_PORT (default 21) when the matching option is omitted.

Plain FTP is cleartext. Without secure, credentials and file contents are transmitted unencrypted. Prefer secure: true (explicit TLS / AUTH TLS); use "implicit" only for legacy servers.

Options

PropType
host?string

FTP host. Falls back to `FTP_HOST`.

Typestring
port?number

Port. Falls back to `FTP_PORT`, then `21`.

Typenumber
user?string

Username. Falls back to `FTP_USERNAME` (alias `FTP_USER`), then `anonymous`.

Typestring
password?string

Password. Falls back to `FTP_PASSWORD`.

Typestring
secure?boolean | "implicit"

FTPS over TLS. `true` is preferred explicit TLS (AUTH TLS); `"implicit"` is legacy implicit TLS. Defaults to `false` — **plain FTP transmits credentials and data in cleartext; prefer `secure: true`.** Falls back to `FTP_SECURE` (`"true"` or `"implicit"`).

Typeboolean | "implicit"
secureOptions?TLSConnectionOptions

TLS options forwarded to the secure connection (e.g. `rejectUnauthorized`).

TypeTLSConnectionOptions
root?string

Remote base directory. Virtual keys resolve under it; keys that escape it (e.g. `../etc/passwd`) throw `Provider`. Defaults to `"."` (the login directory). An absolute root (`/uploads`) yields absolute paths.

Typestring
publicBaseUrl?string

Origin used to build URLs from `url()`. When set, `url(key)` returns `${publicBaseUrl}/${key}`. When unset, `url()` throws: FTP serves no HTTP and has no signing primitive.

Typestring
timeout?number

Socket timeout in milliseconds for new connections (basic-ftp default 30s).

Typenumber
client?Client

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

TypeClient

Limitations

Connect-per-operation means a high call rate becomes a high connection rate, and FTP servers commonly cap connections per IP - inject a pre-connected client for batch jobs.

Compatibility

Method Status Notes
upload ⚠️ User metadata and cacheControl throw - FTP 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 - FTP 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; lastModified comes from the listing.
search ⚠️ Built on listAll — inherits this adapter’s list behavior above. Client-side key match (glob, regex, substring, exact).
head ⚠️ FTP 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 comes from SIZE; lastModified is an MDTM probe that many servers don’t support, so it can be absent.
exists
copy ⚠️ Read-then-write - FTP has no 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>. FTP serves no HTTP and has no signing primitive. responseContentDisposition always throws because the HTTP-front URL cannot bind the override.
signedUploadUrl Throws - FTP has no presigned-upload concept. Use upload(), or inject a pre-connected client for batch transfers.

Was this page helpful?