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 );
Simple upload
Binary upload
In Workers
// Upload text content
const content = new TextEncoder (). encode ( 'File contents' );
await client . upload ( 'document.txt' , content );
// Upload binary data
const imageData = await fetch ( 'https://example.com/image.jpg' )
. then ( r => r . arrayBuffer ())
. then ( b => new Uint8Array ( b ));
await client . upload ( 'images/photo.jpg' , imageData );
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
const formData = await request . formData ();
const file = formData . get ( 'file' ) as File ;
const buffer = await file . arrayBuffer ();
const client = new FTPClient ( env . FTP_HOST , {
user: env . FTP_USER ,
pass: env . FTP_PASS
});
await client . connect ();
await client . upload ( file . name , new Uint8Array ( buffer ));
await client . close ();
return new Response ( 'Upload complete' );
}
} ;
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().
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:
Creation time (if supported by server).
true if the path is a file.
true if the path is a directory.
true if the path is a symbolic link.
FTP-specific permissions string.
Unix file mode (if available).
Unix user ID (if available).
Unix group ID (if available).
Feature-dependent behavior
The stat() method uses different strategies based on server capabilities:
With MLST support
Without MLST support
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
Falls back to SIZE and MDTM commands: // Get size with SIZE command
retn . size = await this . size ( filename );
// Get modification time with MDTM command
retn . mtime = await this . modified ( filename );
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