|:------------------------------------------------------:|
| β‘οΈ B o x L a n g β‘οΈ
| Dynamic : Modular : Productive
|:------------------------------------------------------:|
π A comprehensive FTP client module for BoxLang that brings seamless file transfer capabilities to your applications!
This module provides powerful FTP and SFTP client functionality to the BoxLang language, making it easy to connect to FTP/SFTP servers, transfer files, and manage remote directories with minimal code.
- π FTP/FTPS/SFTP Support: Connect to standard FTP, secure FTP, and SFTP servers
- π Complete File Operations: Upload, download, rename, and delete files on both FTP and SFTP
- π Directory Management: Create, remove, rename, list, and navigate directories on both FTP and SFTP
- π Secure Connections: Support for passive/active modes (FTP) and SSH key authentication (SFTP)
- π SSH Key Support: SFTP with public/private key authentication and optional passphrases
- π― Connection Pooling: Named connections tracked globally via FTPService
- π Proxy Support: Connect through proxy servers with custom ports (FTP only)
- π Flexible Results: Query or array return types for directory listings
- β‘ Event-Driven: Interception points for logging, monitoring, and custom logic
- π οΈ Production Ready: Built by Ortus Solutions with enterprise-grade quality
- π§ Zero Configuration: Sensible defaults get you started quickly
- BoxLang: 1+
- Java: JDK 21+
If you are using CommandBox for your web applications, you can install it via CommandBox:
box install bx-ftpIf you want to install it globally for the BoxLang OS binary, use the install-bx-module command:
install-bx-module bx-ftpThe module will automatically register and be available as bx:ftp or <bx:ftp> in your BoxLang script and template applications.
# Clone the repository
git clone https://github.com/ortus-boxlang/bx-ftp.git
cd bx-ftp
# Build the module
./gradlew build
# The built module will be in build/distributions/Here's how to connect to an FTP server and upload a file in just a few lines:
// Connect to FTP server
bx:ftp
action="open"
connection="myFTP"
server="ftp.example.com"
username="myuser"
password="mypass";
// Upload a file
bx:ftp
action="putfile"
connection="myFTP"
localFile="/path/to/local/file.txt"
remoteFile="/remote/file.txt";
// Close connection
bx:ftp action="close" connection="myFTP";That's it! π You now have a fully functional FTP client.
Here's how to connect to an SFTP server with password authentication:
// Connect to SFTP server with password
bx:ftp
action="open"
connection="mySFTP"
server="sftp.example.com"
username="myuser"
password="mypass"
secure="true"; // This makes it SFTP!
// Upload a file via SFTP
bx:ftp
action="putfile"
connection="mySFTP"
localFile="/path/to/local/file.txt"
remoteFile="/remote/file.txt";
// Close connection
bx:ftp action="close" connection="mySFTP";Or with SSH key authentication:
// Connect to SFTP server with SSH key
bx:ftp
action="open"
connection="mySFTP"
server="sftp.example.com"
username="myuser"
key="/path/to/private/key"
passphrase="optional_passphrase"
secure="true";
// Now you can use the connection for file operations// Open connection
bx:ftp
action="open"
connection="myConn"
server="ftp.example.com"
username="user"
password="pass";
// List directory contents
bx:ftp
action="listdir"
connection="myConn"
directory="/"
name="files";
// Display results
writeDump(files);
// Close connection
bx:ftp action="close" connection="myConn";bx:ftp action="open" connection="uploader" server="ftp.example.com" username="user" password="pass";
bx:ftp
action="putfile"
connection="uploader"
localFile="/Users/documents/report.pdf"
remoteFile="/uploads/monthly-report.pdf"
result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("File uploaded successfully!");
} else {
writeOutput("Upload failed: #uploadResult.statusText#");
}
bx:ftp action="close" connection="uploader";bx:ftp action="open" connection="downloader" server="ftp.example.com" username="user" password="pass";
bx:ftp
action="getfile"
connection="downloader"
remoteFile="/reports/sales-2025.csv"
localFile="/Users/downloads/sales-2025.csv"
failIfExists="false"
result="downloadResult";
writeOutput("Download complete: #downloadResult.succeeded#");
bx:ftp action="close" connection="downloader";try {
// Open secure connection
bx:ftp
action="open"
connection="secureFTP"
server="secure.ftp.example.com"
port="990"
username="admin"
password="secretpass"
secure="true"
passive="true"
timeout="60"
result="ftpResult";
if (ftpResult.succeeded) {
writeOutput("Connected to secure FTP server");
// Perform operations...
} else {
throw("Connection failed: #ftpResult.statusText#");
}
} catch (any e) {
writeOutput("FTP Error: #e.message#");
} finally {
bx:ftp action="close" connection="secureFTP";
}// Connect via proxy with custom port
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com:8080";
// Connect via proxy with default port (1080)
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com";bx:ftp action="open" connection="dirManager" server="ftp.example.com" username="user" password="pass";
// Create a new directory
bx:ftp
action="createdir"
connection="dirManager"
new="/uploads/2025"
result="createResult";
// Check if directory exists
bx:ftp
action="existsdir"
connection="dirManager"
directory="/uploads/2025"
result="existsResult";
writeOutput("Directory exists: #existsResult.returnValue#");
// Change to directory
bx:ftp
action="changedir"
connection="dirManager"
directory="/uploads/2025";
// Get current directory
bx:ftp
action="getcurrentdir"
connection="dirManager"
result="cwdResult";
writeOutput("Current directory: #cwdResult.returnValue#");
bx:ftp action="close" connection="dirManager";bx:ftp action="open" connection="lister" server="ftp.example.com" username="user" password="pass";
// Get directory listing as array of structs
bx:ftp
action="listdir"
connection="lister"
directory="/uploads"
name="fileList"
returnType="array";
// Process each file
for (file in fileList) {
writeOutput("File: #file.name# - Size: #file.size# bytes - Type: #file.type#<br>");
if (file.isDirectory) {
writeOutput(" β This is a directory<br>");
} else {
writeOutput(" β Last modified: #file.lastModified#<br>");
}
}
bx:ftp action="close" connection="lister";bx:ftp action="open" connection="bulk" server="ftp.example.com" username="user" password="pass";
files = ["report1.pdf", "report2.pdf", "report3.pdf"];
successCount = 0;
failCount = 0;
for (fileName in files) {
bx:ftp
action="putfile"
connection="bulk"
localFile="/local/reports/#fileName#"
remoteFile="/remote/reports/#fileName#"
result="uploadResult";
if (uploadResult.succeeded) {
successCount++;
writeOutput("β Uploaded: #fileName#<br>");
} else {
failCount++;
writeOutput("β Failed: #fileName# - #uploadResult.statusText#<br>");
}
}
writeOutput("<br>Summary: #successCount# successful, #failCount# failed");
bx:ftp action="close" connection="bulk";bx:ftp action="open" connection="renamer" server="ftp.example.com" username="user" password="pass";
// Rename a file
bx:ftp
action="renamefile"
connection="renamer"
existing="/uploads/temp.txt"
new="/uploads/final-report.txt"
result="renameResult";
if (renameResult.returnValue) {
writeOutput("File renamed successfully");
}
// Rename a directory
bx:ftp
action="renamedir"
connection="renamer"
existing="/old-folder"
new="/new-folder"
result="dirRenameResult";
bx:ftp action="close" connection="renamer";// Connect to SFTP server
bx:ftp
action="open"
connection="sftpConn"
server="sftp.example.com"
port="22" // Default SFTP port
username="sftpuser"
password="securepass"
secure="true";
// Upload a file via SFTP
bx:ftp
action="putfile"
connection="sftpConn"
localFile="/local/path/document.pdf"
remoteFile="/remote/path/document.pdf";
// List directory
bx:ftp
action="listdir"
connection="sftpConn"
directory="/uploads"
name="sftpFiles";
// Close SFTP connection
bx:ftp action="close" connection="sftpConn";// Connect using SSH private key
bx:ftp
action="open"
connection="sftpKey"
server="sftp.example.com"
username="deployuser"
key="/path/to/private/id_rsa"
secure="true";
// Or with a passphrase-protected key
bx:ftp
action="open"
connection="sftpSecure"
server="sftp.example.com"
username="deployuser"
key="/path/to/private/id_rsa"
passphrase="my-key-passphrase"
secure="true";
// Use the connection normally
bx:ftp
action="putfile"
connection="sftpKey"
localFile="/build/app.zip"
remoteFile="/deployments/app.zip";
bx:ftp action="close" connection="sftpKey";// Connect with host key fingerprint verification
bx:ftp
action="open"
connection="sftpVerified"
server="sftp.example.com"
username="verifieduser"
password="securepass"
fingerprint="SHA256:abc123def456..."
secure="true";
// Proceed with secure verified connection
bx:ftp
action="listdir"
connection="sftpVerified"
directory="/"
name="files";
bx:ftp action="close" connection="sftpVerified";// Open SFTP connection
bx:ftp
action="open"
connection="sftpMgr"
server="sftp.example.com"
username="admin"
password="adminpass"
secure="true";
// Create directory
bx:ftp
action="createdir"
connection="sftpMgr"
new="/backups/2024";
// Check if directory exists
bx:ftp
action="existsdir"
connection="sftpMgr"
directory="/backups/2024"
result="dirExists";
if (dirExists.returnValue) {
writeOutput("Backup directory ready!");
}
// Remove empty directory
bx:ftp
action="removedir"
connection="sftpMgr"
directory="/temp/old-data";
bx:ftp action="close" connection="sftpMgr";// Open SFTP connection
bx:ftp
action="open"
connection="sftpRename"
server="sftp.example.com"
username="admin"
password="adminpass"
secure="true";
// Rename a file
bx:ftp
action="renamefile"
connection="sftpRename"
existing="/uploads/draft-report.pdf"
new="/uploads/final-report-2026.pdf"
result="renameResult";
if (renameResult.returnValue) {
writeOutput("File renamed successfully via SFTP");
}
// Rename a directory
bx:ftp
action="renamedir"
connection="sftpRename"
existing="/projects/old-project-name"
new="/projects/new-project-name"
result="dirRenameResult";
if (dirRenameResult.returnValue) {
writeOutput("Directory renamed successfully via SFTP");
}
bx:ftp action="close" connection="sftpRename";// Open SFTP connection
bx:ftp
action="open"
connection="sftpRemove"
server="sftp.example.com"
username="admin"
password="adminpass"
secure="true";
// Remove a file
bx:ftp
action="removefile"
connection="sftpRemove"
remoteFile="/temp/cache-file.tmp"
result="removeResult";
if (removeResult.returnValue) {
writeOutput("File removed successfully via SFTP");
} else {
writeOutput("Failed to remove file: #removeResult.errorText#");
}
// Alternative: use 'remove' action (alias)
bx:ftp
action="remove"
connection="sftpRemove"
remoteFile="/logs/old-log.txt"
result="removeResult2";
bx:ftp action="close" connection="sftpRemove";All actions can use a result attribute to store the result of the action in a variable. If not provided, the result will be stored in a variable called bxftp (or cftp if you are in CFML compat mode).
Opens a connection to an FTP or SFTP server and tracks it in the FTPService.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection |
string | β Yes | - | Name of the connection to track |
server |
string | β Yes | - | Server IP or hostname |
port |
numeric | No | 21 (FTP) 22 (SFTP) |
Server port number |
username |
string | β Yes | - | Authentication username |
password |
string | Conditional | - | Authentication password (required if key not provided) |
timeout |
numeric | No | 30 | Connection timeout in seconds |
secure |
boolean | No | false | Use SFTP when true, FTP when false |
passive |
boolean | No | true | Use passive mode (FTP only) |
proxyServer |
string | No | - | Proxy server hostname:port (FTP only) |
key |
string | No | - | Path to SSH private key file (SFTP only) |
passphrase |
string | No | - | Passphrase for encrypted SSH key (SFTP only) |
fingerprint |
string | No | - | Server's host key fingerprint for verification (SFTP only) |
Examples:
// Basic FTP connection
bx:ftp
action="open"
connection="myConn"
server="ftp.example.com"
username="user"
password="pass";
// SFTP connection with password
bx:ftp
action="open"
connection="sftpConn"
server="sftp.example.com"
username="user"
password="pass"
secure="true";
// SFTP connection with SSH key
bx:ftp
action="open"
connection="sftpKey"
server="sftp.example.com"
username="deployuser"
key="/path/to/id_rsa"
secure="true";
// Connection via proxy (FTP only)
bx:ftp
action="open"
connection="proxyConn"
server="ftp.example.com"
username="user"
password="pass"
proxyServer="proxy.company.com:8080";Closes an open FTP or SFTP connection and removes it from the FTPService.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Name of the connection to close |
Example:
bx:ftp action="close" connection="myConn";Changes the current working directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
directory |
string | β Yes | Directory path to change to |
Example:
bx:ftp action="changedir" connection="myConn" directory="/uploads/2025";Creates a new directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
new |
string | β Yes | Path of directory to create |
Example:
bx:ftp action="createdir" connection="myConn" new="/uploads/reports" result="createResult";
if (createResult.returnValue) {
writeOutput("Directory created successfully");
}Checks if a directory exists on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
directory |
string | β Yes | Directory path to check |
Example:
bx:ftp action="existsdir" connection="myConn" directory="/uploads" result="existsResult";
if (existsResult.returnValue) {
writeOutput("Directory exists");
} else {
writeOutput("Directory not found");
}Retrieves the current working directory path.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
Example:
bx:ftp action="getcurrentdir" connection="myConn" result="cwdResult";
writeOutput("Current directory: #cwdResult.returnValue#");Lists files and directories in the specified directory.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection |
string | β Yes | - | Connection name |
directory |
string | β Yes | - | Directory to list |
name |
string | β Yes | - | Variable name to store results |
returnType |
string | No | "query" | Return format: "query" or "array" |
Query Columns:
name- File/directory nameisDirectory- Boolean indicating if item is a directorylastModified- Last modification timestampsize- File size in bytes (aliased aslengthfor CFML compatibility)mode- File permissions modepath- File path without drive designationurl- Complete URL for the itemtype- Type: "file", "directory", "symbolic link", or "unknown"raw- Raw FTP listing representationattributes- File attributesisReadable- Boolean for read permissionisWritable- Boolean for write permissionisExecutable- Boolean for execute permission
Examples:
// List as query (default)
bx:ftp action="listdir" connection="myConn" directory="/" name="files";
// List as array of structs
bx:ftp action="listdir" connection="myConn" directory="/" name="files" returnType="array";
// Process results
for (file in files) {
writeOutput("#file.name# - #file.size# bytes<br>");
}Removes a directory from the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
directory |
string | β Yes | Directory path to remove |
Example:
bx:ftp action="removedir" connection="myConn" directory="/temp/old-data" result="removeResult";
if (removeResult.returnValue) {
writeOutput("Directory removed successfully");
}Renames a directory on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
existing |
string | β Yes | Current directory path |
new |
string | β Yes | New directory path |
Example:
bx:ftp
action="renamedir"
connection="myConn"
existing="/old-name"
new="/new-name"
result="renameResult";Checks if a file exists on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
remoteFile |
string | β Yes | Remote file path to check |
Example:
bx:ftp action="existsfile" connection="myConn" remoteFile="/data/report.pdf" result="existsResult";
if (existsResult.returnValue) {
writeOutput("File exists");
}Downloads a file from the FTP server to the local filesystem.
Attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
connection |
string | β Yes | - | Connection name |
remoteFile |
string | β Yes | - | Remote file path to download |
localFile |
string | β Yes | - | Local file path to save to |
failIfExists |
boolean | No | true | Fail if local file already exists |
Example:
// Download with overwrite protection
bx:ftp
action="getfile"
connection="myConn"
remoteFile="/reports/sales.csv"
localFile="/Users/downloads/sales.csv";
// Download and overwrite if exists
bx:ftp
action="getfile"
connection="myConn"
remoteFile="/reports/sales.csv"
localFile="/Users/downloads/sales.csv"
failIfExists="false"
result="downloadResult";
if (downloadResult.succeeded) {
writeOutput("Download complete");
}Uploads a file from the local filesystem to the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
localFile |
string | β Yes | Local file path to upload |
remoteFile |
string | β Yes | Remote file path destination |
Example:
bx:ftp
action="putfile"
connection="myConn"
localFile="/Users/documents/report.pdf"
remoteFile="/uploads/report.pdf"
result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("Upload successful: #uploadResult.statusText#");
}Deletes a file from the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
remoteFile |
string | β Yes | Remote file path to delete |
Example:
bx:ftp action="removefile" connection="myConn" remoteFile="/temp/old-file.txt" result="removeResult";
if (removeResult.returnValue) {
writeOutput("File deleted successfully");
}Renames a file on the FTP server.
Attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
connection |
string | β Yes | Connection name |
existing |
string | β Yes | Current file path |
new |
string | β Yes | New file path |
Example:
bx:ftp
action="renamefile"
connection="myConn"
existing="/uploads/temp.txt"
new="/uploads/final-report.txt"
result="renameResult";
if (renameResult.returnValue) {
writeOutput("File renamed successfully");
}All FTP actions return a result object with the following structure:
{
statusCode : 200, // Numeric FTP status code
statusText : "Success", // Human-readable status message
errorCode : 0, // Error code (if any)
errorText : "", // Error message (if any)
returnValue : true, // Action-specific return value
succeeded : true // Boolean indicating success/failure
}| Code | Meaning | Description |
|---|---|---|
| 200 | Success | Command successful |
| 221 | Goodbye | Service closing control connection |
| 226 | Transfer complete | Closing data connection, file transfer successful |
| 230 | Login successful | User logged in |
| 250 | Success | Requested file action okay, completed |
| 550 | Failed | File unavailable or permission denied |
| 552 | Exceeded | Storage allocation exceeded |
| 553 | Not allowed | File name not allowed |
// Store result in custom variable
bx:ftp action="putfile" connection="myConn" localFile="test.txt" remoteFile="/test.txt" result="uploadResult";
if (uploadResult.succeeded) {
writeOutput("Success! Status: #uploadResult.statusCode#");
} else {
writeOutput("Failed: #uploadResult.errorText#");
}
// Default result variable (bxftp)
bx:ftp action="putfile" connection="myConn" localFile="test.txt" remoteFile="/test.txt";
if (bxftp.succeeded) {
writeOutput("Upload successful");
}The FTP module announces several interception points that allow you to hook into the FTP operation lifecycle for logging, monitoring, metrics, or custom logic.
Announced before any FTP action is executed.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action being performed
result : FTPResult, // The result object (empty at this point)
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
// ... other attributes
}
}Use Cases:
- Pre-flight validation
- Logging all FTP operations
- Performance monitoring (start timer)
- Security auditing
Example:
class {
function beforeFTPCall( event, interceptData ) {
var logger = getLogger();
logger.info(
"FTP Action Starting: #interceptData.action# on connection #interceptData.attributes.connection#"
);
// Store start time for performance tracking
interceptData.startTime = now();
}
}Announced after an FTP action completes successfully.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action that was performed
result : FTPResult, // The populated result object
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
}
}Use Cases:
- Success logging
- Performance metrics (calculate duration)
- Notifications or alerts
- Analytics and reporting
Example:
class {
function afterFTPCall( event, interceptData ) {
var logger = getLogger();
var result = interceptData.result;
logger.info(
"FTP Action Completed: #interceptData.action# - Status: #result.statusCode# - Success: #result.succeeded#"
);
// Track successful transfers
if ( result.succeeded && interceptData.action == "putfile" ) {
metrics.recordUpload( interceptData.attributes.remoteFile );
}
}
}Announced when a new FTP connection is opened.
Interceptor Data:
{
connection : FTPConnection, // The newly opened connection
attributes : { // Connection attributes
server : "ftp.example.com",
username : "user",
port : 21,
passive : true
// ... other connection attributes
}
}Use Cases:
- Connection logging
- Connection pooling metrics
- Security monitoring
- Access auditing
Example:
class {
function onFTPConnectionOpen( event, interceptData ) {
var logger = getLogger();
var attrs = interceptData.attributes;
logger.info(
"FTP Connection Opened: #attrs.server#:#attrs.port# as #attrs.username# (Passive: #attrs.passive#)"
);
// Track active connections
connectionMonitor.recordConnection(
server = attrs.server,
user = attrs.username,
timestamp = now()
);
}
}Announced when an FTP connection is closed.
Interceptor Data:
{
connection : FTPConnection // The connection being closed
}Use Cases:
- Connection cleanup logging
- Session duration tracking
- Connection pool management
- Resource monitoring
Example:
component {
function onFTPConnectionClose( event, interceptData ) {
var logger = getLogger();
var conn = interceptData.connection;
logger.info(
"FTP Connection Closed: #conn.getName()# - Status: #conn.getStatus()#"
);
// Track connection closures
connectionMonitor.recordClosure(
connection = conn.getName(),
timestamp = now()
);
}
}Announced when an FTP operation encounters an error.
Interceptor Data:
{
connection : FTPConnection, // The FTP connection object
action : "putfile", // The action that failed
error : IOException, // The exception object
attributes : { // All attributes passed to the component
connection : "myConn",
localFile : "/path/to/file.txt",
remoteFile : "/remote/file.txt"
}
}Use Cases:
- Error logging and alerting
- Retry logic
- Fallback mechanisms
- Error analytics
Example:
component {
function onFTPError( event, interceptData ) {
var logger = getLogger();
var error = interceptData.error;
logger.error(
"FTP Action Failed: #interceptData.action# - Error: #error.getMessage()#",
error
);
// Send alert for critical errors
if ( interceptData.action == "putfile" ) {
alertService.send(
message = "FTP upload failed: #error.getMessage()#",
severity = "high",
details = interceptData
);
}
}
}The FTP module uses the FTPService to manage named connections globally across your application.
- Open: Connection is created and tracked by name
- Use: Multiple actions can use the same connection
- Close: Connection is removed from tracking
// Open once
bx:ftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass";
// Use multiple times
bx:ftp action="putfile" connection="myConn" localFile="file1.txt" remoteFile="/file1.txt";
bx:ftp action="putfile" connection="myConn" localFile="file2.txt" remoteFile="/file2.txt";
bx:ftp action="listdir" connection="myConn" directory="/" name="files";
// Close when done
bx:ftp action="close" connection="myConn";- β Always close connections when done to free resources
- β Use descriptive names for connections to avoid conflicts
- β Reuse connections for multiple operations to improve performance
- β Use try/finally to ensure connections are closed even on errors
- β Don't leave connections open indefinitely
After opening a connection, it's stored in a variable with the connection name:
bx:ftp action="open" connection="info" server="ftp.example.com" username="user" password="pass";
writeDump( info );
// Outputs connection details: server, port, username, status, etc.Connection Refused Errors:
- Verify FTP server is running and accessible
- Check firewall settings for FTP ports (21 for control, 20 for data)
- For passive mode, ensure passive port range is accessible
- Verify credentials are correct
Passive vs Active Mode:
- Passive Mode (default): Client initiates both control and data connections
- Active Mode: Server initiates data connection back to client
- Use passive mode for connections through firewalls/NAT
try {
bx:ftp action="open" connection="safeConn" server="ftp.example.com" username="user" password="pass";
bx:ftp action="putfile" connection="safeConn" localFile="file.txt" remoteFile="/file.txt" result="uploadResult";
if (!uploadResult.succeeded) {
throw("Upload failed: #uploadResult.statusText#");
}
} catch (any e) {
writeOutput("FTP Error: #e.message#<br>");
// Log error, send alert, etc.
} finally {
// Always close connection
bx:ftp action="close" connection="safeConn";
}bx:ftp action="putfile" connection="conn" localFile="test.txt" remoteFile="/test.txt" result="ftpResult";
switch (ftpResult.statusCode) {
case 226:
writeOutput("Transfer successful");
break;
case 550:
writeOutput("File not found or permission denied");
break;
case 552:
writeOutput("Storage allocation exceeded");
break;
case 553:
writeOutput("File name not allowed");
break;
default:
writeOutput("Operation failed: #ftpResult.statusText#");
}Problem: FTP operations fail with "Connection refused" errors.
Solutions:
- β
Verify FTP server is running:
nc -v hostname port - β Check firewall allows FTP ports (21, 20, and passive range)
- β Ensure passive mode is configured on server (for passive=true)
- β Verify credentials are correct
- β Test connection with FTP client (FileZilla, etc.)
Problem: Connection succeeds but file transfers fail.
Solutions:
- β Ensure FTP server has passive mode enabled
- β Check passive port range is open in firewall
- β Verify server announces correct IP for passive connections
- β
Try active mode instead:
passive="false"
Problem: Files fail to upload with permission errors.
Solutions:
- β Verify user has write permissions on remote directory
- β Check disk space on FTP server
- β Ensure remote directory exists
- β Try uploading to a different directory
Problem: Connection attempts timeout.
Solutions:
- β
Increase timeout:
timeout="60" - β Check network connectivity to FTP server
- β Verify server is not blocking your IP
- β Test if server is behind a firewall or proxy
Problem: Cannot connect through proxy server.
Solutions:
- β
Verify proxy server address and port:
proxyServer="proxy.company.com:8080" - β Check proxy supports FTP protocol
- β Ensure proxy authentication (if required) is configured
- β Test proxy with other FTP clients
This module includes Docker configuration for local FTP and SFTP server testing:
# Copy Docker environment configuration
cp src/test/resources/.env.docker src/test/resources/.env
# Start FTP and SFTP test servers
docker-compose up -d --build
# Run tests
./gradlew test
# Stop servers
docker-compose downFTP Server:
- Host: localhost
- Control Port: 2221
- Data Port: 2220
- Passive Ports: 10000-10010
- Username: test_user
- Password: testpass
SFTP Server:
- Host: localhost
- Port: 2222
- Username: test_user
- Password: testpass
# Clone repository
git clone https://github.com/ortus-boxlang/bx-ftp.git
cd bx-ftp
# Download BoxLang dependency
./gradlew downloadBoxLang
# Build module
./gradlew build
# Run tests (requires Docker FTP server)
docker-compose up -d
./gradlew test
# Create distribution
./gradlew zipModuleStructure- FTPService: Global singleton service managing all FTP connections
- FTP Component: BoxLang component (
@BoxComponent) providing FTP operations - FTPConnection: Represents an active FTP connection with state management
- FTPResult: Encapsulates operation results with status codes and messages
- Dependencies: Apache Commons Net 3.11.1 for FTP protocol implementation
The included Docker setup provides a consistent FTP/SFTP testing environment:
# Copy Docker-specific environment configuration
cp src/test/resources/.env.docker src/test/resources/.env
# Start FTP and SFTP servers with specific configuration
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop and remove containers
docker-compose down
# Force recreate (after config changes)
docker-compose up -d --build --force-recreateDocker Port Mapping:
- FTP Control:
2221(mapped from container port 21) - FTP Data:
2220(mapped from container port 20) - SFTP:
2222(mapped from container port 22) - FTP Passive:
10000-10010
CI Environment: Tests in GitHub Actions use standard ports (21 for FTP, 22 for SFTP) as servers run directly on the Ubuntu runner. The .env.example file uses these standard ports by default.
Important: The vsftpd.conf file includes critical passive mode configuration:
pasv_enable=YES
pasv_min_port=10000
pasv_max_port=10010
pasv_address=127.0.0.1
This module will require the bx-cfml-compat module if you want it to work like Adobe ColdFusion/Lucee in your CFML applications.
- Result Variable: BoxLang uses
bxftpby default, CFML compat mode usescftp - Component Name: Use
bx:ftpin BoxLang,cftpwith compat module - Features: BoxLang FTP includes modern features like event interception
All CFML <cfftp> should work with no changes when using bx-cfml-compat. However, for native BoxLang usage, update tags as follows:
// CFML
<cfftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">
// BoxLang (with bx-cfml-compat)
<cftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">
// BoxLang (native)
<bx:ftp action="open" connection="myConn" server="ftp.example.com" username="user" password="pass">We β€οΈ contributions! This project is open source and welcomes your help to make it even better.
If you discover a bug, please:
- Check existing issues at GitHub Issues
- Create a new issue with:
- Clear title and description
- Steps to reproduce
- Expected vs actual behavior
- BoxLang version and environment details
- Sample code that demonstrates the issue
We'd love to hear your ideas! Please:
- Open a Feature Request
- Describe the feature and its use case
- Explain how it would benefit users
Documentation improvements are always welcome:
- Fix typos or unclear explanations
- Add more examples
- Improve code comments
- Create tutorials or guides
You can support BoxLang and all Ortus Solutions open source projects:
- π Become a Patron
- π΅ One-time PayPal Donation
Patrons get exclusive benefits like:
- Priority support
- Early access to new features
- FORGEBOX Pro account
- CFCasts account
Need help? Don't create an issueβuse our support channels:
- π¬ Ortus Community Discourse
- π± Box Team Slack
- π’ Professional Support
Thank you to all our amazing contributors! β€οΈ
Made with contributors-img
If you discover a security vulnerability:
- DO NOT create a public issue
- Email [email protected]
- Report in
#securitychannel on Box Team Slack
All vulnerabilities will be promptly addressed.
This project is licensed under the Apache License 2.0.
Copyright 2025 Ortus Solutions, Corp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
See LICENSE file for full details.
- Module Docs: You're reading them! π
- BoxLang Docs: https://boxlang.ortusbooks.com/
- Apache Commons Net: https://commons.apache.org/proper/commons-net/
- BoxLang Website: https://boxlang.io
- Ortus Solutions: https://www.ortussolutions.com
- GitHub Repository: https://github.com/ortus-boxlang/bx-ftp
- Issue Tracker: https://github.com/ortus-boxlang/bx-ftp/issues
- ForgeBox: https://forgebox.io/view/bx-ftp
- BoxLang Training: https://www.ortussolutions.com/services/training
- CFCasts: https://www.cfcasts.com
- Blog: https://www.ortussolutions.com/blog
"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12
Copyright Since 2025 by Ortus Solutions, Corp
www.boxlang.io | www.ortussolutions.com