Skip to main content

Overview

The FTPClient provides methods for common file operations including uploading, downloading, renaming, deleting, and querying file information.

Downloading files

Download files from the FTP server as Uint8Array:
const fileData = await client.download('path/to/file.txt');
The download() method retrieves the entire file into memory. For large files, consider using streaming instead.
The download() method is implemented in src/classes/ftp-client.ts:216 and automatically handles stream finalization.

Download implementation

public async download(fileName: string): Promise<Uint8Array> {
  const readable = await this.downloadReadable(fileName);
  const data = await streamToUint8Array(readable);
  await this.finalizeStream();
  return data;
}

Uploading files

Upload files to the FTP server from a Uint8Array:
const data = new TextEncoder().encode('Hello, FTP!');
await client.upload('remote/file.txt', data);

Upload with allocation

Some FTP servers require pre-allocation of file space. The upload() method automatically allocates based on your data size:
const largeFile = new Uint8Array(1024 * 1024); // 1MB
await client.upload('large-file.bin', largeFile);
// Upload text content
const content = new TextEncoder().encode('File contents');
await client.upload('document.txt', content);

Renaming files

Rename or move files on the server:
await client.rename('old-name.txt', 'new-name.txt');
You can also use rename() to move files between directories:
await client.rename('file.txt', 'archive/file.txt');
The rename() method uses the FTP RNFR (rename from) and RNTO (rename to) commands, implemented in src/classes/ftp-client.ts:438.

Deleting files

Remove files from the server using the rm() method:
await client.rm('unwanted-file.txt');
File deletion is permanent and cannot be undone. Ensure you have the correct file path before calling rm().

Getting file information

Retrieve detailed file metadata using the stat() method:
const fileInfo = await client.stat('document.txt');

console.log(fileInfo.size);        // File size in bytes
console.log(fileInfo.mtime);       // Last modified time
console.log(fileInfo.isFile);      // true for files
console.log(fileInfo.isDirectory); // false for files

FTPFileInfo structure

The stat() method returns an FTPFileInfo object with these properties:
size
number
File size in bytes.
mtime
Date | null
Last modification time.
ctime
Date | null
Creation time (if supported by server).
isFile
boolean
true if the path is a file.
isDirectory
boolean
true if the path is a directory.
true if the path is a symbolic link.
ftpperms
string | null
FTP-specific permissions string.
mode
number | null
Unix file mode (if available).
uid
number | null
Unix user ID (if available).
gid
number | null
Unix group ID (if available).

Feature-dependent behavior

The stat() method uses different strategies based on server capabilities:
If the server supports MLST (Machine-readable Listing), stat() uses it for comprehensive metadata:
// Uses MLST command for detailed info
const status = await this.command(Commands.ExData, filename);
// Returns parsed MLST response with all available fields
The implementation automatically detects server features during connection and uses the best available method. See src/classes/ftp-client.ts:333 for details.

Getting file size

Retrieve just the file size in bytes:
const bytes = await client.size('large-file.zip');
console.log(`File size: ${bytes} bytes`);
The size() method is implemented in src/classes/ftp-client.ts:395 using the FTP SIZE command:
public async size(filename: string): Promise<number> {
  await this.lock.lock();
  if (this.conn === undefined) {
    this.lock.unlock();
    throw FTPClient.notInit();
  }
  
  const res = await this.command(Commands.Size, filename);
  this.assertStatus(StatusCodes.FileStat, res);
  
  this.lock.unlock();
  return Number.parseInt(res.message);
}
Calling size() on a directory may fail or return an error. Use stat() to check if a path is a file before getting its size.

Getting modification time

Retrieve the last modification time of a file:
const modTime = await client.modified('document.txt');
console.log(`Last modified: ${modTime.toISOString()}`);

Requirements

The modified() method requires the server to support the MDTM feature:
if (!this.feats.MDTM) {
  throw new Error(
    "Feature is missing. Feature MDTM is not implemented by the FTP server."
  );
}
Most modern FTP servers support MDTM. The client automatically discovers this during connection via the FEAT command.

Example: Complete file workflow

import { FTPClient } from 'workerd-ftp';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const client = new FTPClient(env.FTP_HOST, {
      user: env.FTP_USER,
      pass: env.FTP_PASS,
      secure: true
    });
    
    try {
      await client.connect();
      
      // Upload a file
      const content = new TextEncoder().encode('Hello, World!');
      await client.upload('hello.txt', content);
      
      // Get file info
      const info = await client.stat('hello.txt');
      console.log(`Uploaded ${info.size} bytes at ${info.mtime}`);
      
      // Rename the file
      await client.rename('hello.txt', 'greetings.txt');
      
      // Download it back
      const downloaded = await client.download('greetings.txt');
      const text = new TextDecoder().decode(downloaded);
      console.log(`Downloaded: ${text}`);
      
      // Clean up
      await client.rm('greetings.txt');
      
      return new Response('Operations complete');
    } finally {
      await client.close();
    }
  }
};

Error handling

File operations may fail for various reasons. Always wrap them in try-catch:
try {
  await client.download('missing-file.txt');
} catch (error: any) {
  if (error.code === 550) {
    console.error('File not found');
  } else {
    console.error('Download failed:', error);
  }
}
Common FTP status codes:
  • 550 - File not found or no permission
  • 553 - File name not allowed
  • 450 - File unavailable (busy)
  • 426 - Connection closed (transfer aborted)

Next steps

Streaming operations

Stream large files efficiently

Directory operations

Navigate and manage directories

Build docs developers (and LLMs) love