Extensions and Scripting
Extensions allow you to extend CompleteFTP's functionality with custom logic written in JSS (JavaScript Server-Side) or compiled .NET assemblies. You can add custom authentication, virtual file systems, FTP/SFTP commands, event handlers, and IP filtering — all managed through the CLI.
This chapter covers:
- Extension types - Authentication, file system, custom command, event, and IP filter extensions
- JSS extensions - Adding and registering JavaScript Server-Side scripts
- .NET assembly extensions - Registering compiled .NET libraries
- addscript vs registerscript - Choosing how script content is managed
- JSS API - Built-in objects for file operations, FTP/SFTP, HTTP, email, and database access
- Custom commands - Calling extension functions from the CLI
- Extension lifecycle - Enabling, disabling, and removing extensions
All extension operations use the completeftp extension command group.
Extension System Overview
CompleteFTP's extension system supports two development approaches:
| Approach | Language | Command | Best For |
|---|---|---|---|
| JSS scripts | JavaScript (server-side) | addscript or registerscript |
Rapid development, simple logic, scripting |
| .NET assemblies | C#, F#, VB.NET | registerassembly |
Complex logic, external library integration, performance |
Extension Types
Each extension must declare a type that determines when and how it is invoked:
| Type | Description | Use Cases |
|---|---|---|
authentication |
Custom authentication logic | LDAP lookups, API-based login, multi-source auth |
filesystem |
Custom virtual file system provider | Database-backed folders, API-driven file listings |
customcommand |
Custom FTP/SFTP commands | SITE commands, custom server operations |
event |
Event handler that fires on server events | Logging, notifications, workflow automation |
ipfilter |
Custom IP filtering logic | Dynamic allow/deny lists, geo-blocking (.NET assemblies only) |
Listing and Viewing Extensions
List Extensions
# List all custom extensions
completeftp extension list
# List with specific properties
completeftp extension list name type
# Include built-in system extensions
completeftp extension list --includeSystem
Show Extension Details
# Show all properties of an extension
completeftp extension show MyExtension
# Show specific properties
completeftp extension show MyExtension name type configurationPath
Adding JSS Extensions
JSS extensions can be added in two ways: addscript and registerscript. The choice determines how the script content is stored and updated.
addscript — Store Script in Configuration Database
With addscript, the script file is read once and its content is stored in CompleteFTP's configuration database. Changes to the original file after adding are not reflected.
# Basic syntax
completeftp extension addscript <extensionName> <extensionType> <scriptFilePath> [properties]
# Add an authentication extension from a script file
completeftp extension addscript MyAuth authentication /opt/cftp/scripts/my-auth.jss
# Add an event extension with properties
completeftp extension addscript AuditLogger event /opt/cftp/scripts/audit.jss
# Read script from stdin
echo 'console.log("Hello from extension");' | \
completeftp extension addscript HelloEvent event -
Note: After using
addscript, the original script file is no longer needed. The script content lives entirely in the configuration database. To update the script, useextension setor remove and re-add the extension.
registerscript — Reference Script File Path
With registerscript, only the file path is stored. The script is re-read from the file every time the extension is used. Changes to the file are reflected immediately.
# Basic syntax
completeftp extension registerscript <extensionName> <extensionType> <scriptFilePath> [properties]
# Register an authentication extension
completeftp extension registerscript MyAuth authentication /opt/cftp/scripts/my-auth.jss
# Register an IP filter extension
completeftp extension registerscript GeoFilter ipfilter /opt/cftp/scripts/geofilter.jss
# Register a custom command extension
completeftp extension registerscript DiskUsage customcommand /opt/cftp/scripts/disk-usage.jss
Important: The script file must remain accessible at the registered path. If the file is moved or deleted, the extension will fail at runtime.
Choosing Between addscript and registerscript
| Consideration | addscript | registerscript |
|---|---|---|
| Script storage | Configuration database | File system |
| Updates after adding | Must re-add or use extension set |
Edit the file directly |
| File dependency | None after adding | File must remain at path |
| Deployment | Self-contained in config | Requires file on disk |
| Development workflow | Better for stable, production scripts | Better for active development |
Registering .NET Assembly Extensions
Compiled .NET assemblies provide full access to the .NET runtime and external libraries.
# Basic syntax
completeftp extension registerassembly <extensionName> <extensionType> <assemblyPath> <className> [properties]
# Register an authentication assembly
completeftp extension registerassembly LdapAuth authentication \
/opt/cftp/extensions/LdapAuth.dll \
MyCompany.Extensions.LdapAuthProvider
# Register a file system assembly
completeftp extension registerassembly DbFileSystem filesystem \
/opt/cftp/extensions/DbFileSystem.dll \
MyCompany.Extensions.DatabaseFileSystem
# Register a custom command assembly
completeftp extension registerassembly AdminTools customcommand \
/opt/cftp/extensions/AdminTools.dll \
MyCompany.Extensions.AdminCommands
Note: The assembly DLL must remain accessible at the registered path. For full documentation on developing .NET extensions, including the required interfaces and base classes, refer to the CompleteFTP Windows User Guide.
Extension Types with Examples
Authentication Extensions
Authentication extensions implement custom login logic. A JSS authentication extension must implement loadUserInfo(userName, userInfo), which is called when a user attempts to log in. Return an object with a password (or passwordHash) field to authenticate, or null to reject/fall through to the next authenticator.
# Create an authentication script file
cat > /opt/cftp/scripts/api-auth.jss << 'SCRIPT'
// Authenticate against an external REST API
// Authentication extensions must implement loadUserInfo(userName, userInfo)
// Return an object with "password" (or "passwordHash") to authenticate,
// or null to reject / fall through to the next authenticator.
function loadUserInfo(userName, userInfo) {
try {
var response = http.get("https://auth.example.com/validate?user=" +
encodeURIComponent(userName));
var result = JSON.parse(response.body);
if (result.valid) {
return { password: result.password };
}
} catch (e) {
console.log("API auth error: " + e);
}
return null;
}
SCRIPT
# Register the authentication extension
completeftp extension registerscript ApiAuth authentication /opt/cftp/scripts/api-auth.jss
File System Extensions
File system extensions provide virtual folders backed by custom logic. At minimum, implement getFileInfos(path) returning an array of {name, isDirectory, length} objects. Optionally implement read(path) for downloads (return {text: "..."}, {file: "/path"}, or {base64: "..."}) and write(path, data) for uploads.
# Create a file system script
cat > /opt/cftp/scripts/report-fs.jss << 'SCRIPT'
// Virtual file system that generates reports on the fly
// File system extensions must implement getFileInfos(path) at minimum.
// The read(path) function enables file downloads.
function getFileInfos(path) {
var files = [];
files.push({name: "daily-report.csv", length: 0, isDirectory: false});
files.push({name: "weekly-report.csv", length: 0, isDirectory: false});
return files;
}
function read(path) {
if (path === "daily-report.csv") {
return { text: "date,total\n2026-03-16,42\n" };
}
if (path === "weekly-report.csv") {
return { text: "week,total\n2026-W11,280\n" };
}
throw "No such file: " + path;
}
SCRIPT
# Add the file system extension
completeftp extension addscript ReportFS filesystem /opt/cftp/scripts/report-fs.jss
Custom Command Extensions
Custom command extensions add new FTP SITE commands or SFTP extended commands that clients can invoke.
# Create a custom command script
cat > /opt/cftp/scripts/disk-usage.jss << 'SCRIPT'
// Custom SITE DISKUSAGE command
// Custom command extensions define functions that become SITE commands in FTP
// and shell commands in SSH. Each function is a command; arguments are strings.
function diskUsage(path) {
if (!path) path = "/";
var folder = system.getFolder(path);
var files = folder.getFiles();
var totalSize = 0;
for (var i = 0; i < files.length; i++) {
totalSize += files[i].size;
}
return "Total size: " + totalSize + " bytes (" + files.length + " files)";
}
SCRIPT
# Register the custom command extension
completeftp extension registerscript DiskUsage customcommand /opt/cftp/scripts/disk-usage.jss
Event Extensions
Event extensions fire in response to server events such as file uploads, logins, and disconnections. They are similar to JSS process triggers but are registered as extensions rather than configured through the trigger command.
Note: For most event-driven automation, Process Triggers are the recommended approach. Event extensions are best suited for cases where you need the logic to be part of the extension system (e.g., for packaging or distribution).
# Create an event handler script
# Event extensions receive event details through the event object,
# the same as JSS process triggers (see Chapter 10).
cat > /opt/cftp/scripts/upload-notify.jss << 'SCRIPT'
if (event.eventType === "UploadFile") {
mail.send(
"admin@example.com",
"File Upload Notification",
"User " + event.loginUserName + " uploaded " + event.fileName +
" at " + event.time
);
}
SCRIPT
# Add the event extension
completeftp extension addscript UploadNotify event /opt/cftp/scripts/upload-notify.jss
IP Filter Extensions
IP filter extensions implement custom allow/deny logic evaluated for each incoming connection. The extension must implement GetFilterAction(ipAddress) which returns whether to allow or deny the connection.
Note: IP filter extensions require a compiled .NET assembly. JSS scripts are not supported for this extension type. For the required .NET interface and development instructions, refer to the CompleteFTP Windows User Guide.
# Register a .NET IP filter assembly
completeftp extension registerassembly BusinessHours ipfilter \
/opt/cftp/extensions/BusinessHoursFilter.dll \
MyCompany.Extensions.BusinessHoursFilter
Calling Custom Commands via CLI
Extensions that define custom command functions can be invoked directly from the CLI using extension call.
# Basic syntax
completeftp extension call <functionName> [arguments]
# Call a custom function with no arguments
completeftp extension call diskUsage
# Call a custom function with arguments
completeftp extension call generateReport "2026-03-01" "2026-03-16"
# Call a function and capture output
result=$(completeftp extension call checkStatus)
echo "Status: $result"
Note: Only functions defined in
customcommandextensions are callable viaextension call. The function must be exported by the extension script.
JSS API Overview
JSS (JavaScript Server-Side) scripts have access to a rich set of built-in objects and functions for interacting with CompleteFTP and external systems.
The system Object
The system object provides access to the CompleteFTP virtual file system:
# Example: file operations in a JSS script
cat > /opt/cftp/scripts/file-ops.jss << 'SCRIPT'
// Get a file reference
var file = system.getFile("/Home/alice/report.csv");
// Get file properties
console.log("Size: " + file.size);
console.log("Modified: " + file.lastModified);
// Move and copy files
file.copyTo("/Archive");
file.moveTo("/Processed");
// Get a folder reference
var folder = system.getFolder("/Home/alice");
var files = folder.getFiles();
for (var i = 0; i < files.length; i++) {
console.log(files[i].name + " - " + files[i].size + " bytes");
}
// Delete a file
system.getFile("/Temp/old-data.csv").remove();
SCRIPT
FTP/SFTP Client
JSS includes a built-in FTP/SFTP client for transferring files to and from remote servers:
cat > /opt/cftp/scripts/remote-transfer.jss << 'SCRIPT'
// Connect to a remote SFTP server
var ftp = new Ftp("partner.example.com", "xferuser", "xferpass", "sftp");
ftp.connect();
// Upload a file
ftp.upload("/Outbound/data.csv", "data.csv");
// Download a file
ftp.download("incoming/report.csv", "/Inbound/report.csv");
// List remote directory
var files = ftp.getFiles("incoming/");
for (var i = 0; i < files.length; i++) {
console.log(files[i]);
}
ftp.close();
SCRIPT
HTTP Client
Make HTTP requests to external APIs and web services:
cat > /opt/cftp/scripts/http-example.jss << 'SCRIPT'
// GET request
var response = http.get("https://api.example.com/status");
console.log("Status: " + response.statusCode);
console.log("Body: " + response.body);
// POST request with JSON body
var data = JSON.stringify({user: loginUserName, action: "upload"});
var response = http.post("https://api.example.com/audit", data, "application/json");
// POST with custom headers
var headers = {"Authorization": "Bearer mytoken123"};
var response = http.post("https://api.example.com/notify", data, "application/json", headers);
SCRIPT
Send email notifications from JSS scripts:
cat > /opt/cftp/scripts/email-example.jss << 'SCRIPT'
// Send a simple email
mail.send("admin@example.com", "Alert: Upload Complete",
"File " + fileName + " was uploaded by " + loginUserName);
// Send to multiple recipients
mail.send("admin@example.com,ops@example.com", "Transfer Report",
"Daily transfer report attached.");
SCRIPT
Note: Email sending requires SMTP configuration. Configure the notification SMTP settings using
completeftp notification smtp set.
Database Access
Query and update databases from JSS scripts:
cat > /opt/cftp/scripts/db-example.jss << 'SCRIPT'
// Connect to a database
var db = new DatabaseSync("Server=dbhost;Database=ftplog;User=logger;Password=secret");
// Execute a query
db.execute("INSERT INTO transfer_log (username, filename, timestamp) VALUES ('" +
loginUserName + "', '" + fileName + "', NOW())");
// Query data
var rows = db.query("SELECT COUNT(*) as cnt FROM transfer_log WHERE username='" +
loginUserName + "'");
console.log("Total transfers: " + rows[0].cnt);
db.close();
SCRIPT
PGP Encryption
The pgp object provides PGP encryption and decryption for both strings and files. Keys must be in armored ASCII format.
String Encryption and Decryption
cat > /opt/cftp/scripts/pgp-string.jss << 'SCRIPT'
var publicKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----";
var privateKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n...\n-----END PGP PRIVATE KEY BLOCK-----";
var passphrase = "my passphrase";
// Encrypt a string
var encrypted = pgp.encrypt("Sensitive data", publicKey);
console.log("Encrypted:\n" + encrypted);
// Decrypt it back
var decrypted = pgp.decrypt(encrypted, privateKey, passphrase);
console.log("Decrypted: " + decrypted);
SCRIPT
File Encryption and Decryption
cat > /opt/cftp/scripts/pgp-file.jss << 'SCRIPT'
var publicKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----";
var privateKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n...\n-----END PGP PRIVATE KEY BLOCK-----";
var passphrase = "my passphrase";
// Encrypt a file
pgp.encryptFileWithKey("/Data/report.csv", "/Data/report.csv.pgp", publicKey);
// Decrypt a file
pgp.decryptFileWithKey("/Data/report.csv.pgp", "/Data/report.csv", privateKey, passphrase);
SCRIPT
File arguments can be either a path string or a File object obtained from system.getFile().
Using Folder PGP Keys
PGP keys can be stored on folders (see Folder Management). When encryptFile or decryptFile are called without explicit keys, CompleteFTP automatically searches the input file's folder and its ancestors for the appropriate key. This means triggers and scripts can encrypt and decrypt files without needing admin access:
cat > /opt/cftp/scripts/pgp-folder-keys.jss << 'SCRIPT'
// Encrypt using the folder's public key — no key argument needed
pgp.encryptFile(filePath, filePath + ".pgp");
system.getFile(filePath).remove();
console.log("Encrypted: " + filePath + ".pgp");
// Decrypt using the folder's private key — no key argument needed
pgp.decryptFile(filePath, outputPath);
system.getFile(filePath).remove();
console.log("Decrypted: " + outputPath);
SCRIPT
You can still pass explicit keys when needed — for example, when using a key that isn't associated with any folder.
PGP Methods Reference
| Method | Description |
|---|---|
pgp.encrypt(plaintext, publicKey) |
Encrypt a string, returns armored ASCII ciphertext |
pgp.decrypt(ciphertext, privateKey, passphrase) |
Decrypt an armored ASCII string |
pgp.encryptFile(inputFile, outputFile) |
Encrypt a file using the folder's public key |
pgp.encryptFileWithKey(inputFile, outputFile, publicKey) |
Encrypt a file with an explicit public key |
pgp.decryptFile(inputFile, outputFile) |
Decrypt a file using the folder's private key |
pgp.decryptFileWithKey(inputFile, outputFile, privateKey, passphrase) |
Decrypt a file with an explicit private key |
Using .NET Classes in JSS
JSS scripts can instantiate and use .NET classes directly:
cat > /opt/cftp/scripts/dotnet-in-jss.jss << 'SCRIPT'
// Use .NET System.IO for local file operations
var content = System.IO.File.ReadAllText("/etc/hostname");
console.log("Hostname: " + content.trim());
// Use .NET XML parsing
var doc = new System.Xml.XmlDocument();
doc.Load("/opt/cftp/config/settings.xml");
console.log("Root: " + doc.DocumentElement.Name);
// Use .NET date/time
var now = System.DateTime.Now;
console.log("Server time: " + now.ToString("yyyy-MM-dd HH:mm:ss"));
SCRIPT
.NET Extension Development
For complex extension logic, you can develop extensions as compiled .NET assemblies. This provides full access to the .NET runtime, NuGet packages, and strongly-typed APIs.
Overview
.NET extensions are compiled DLLs that implement specific CompleteFTP interfaces depending on the extension type:
| Extension Type | Interface / Base Class |
|---|---|
authentication |
Authentication provider interface |
filesystem |
File system provider interface |
customcommand |
Custom command interface |
event |
Event handler interface |
ipfilter |
IP filter interface |
Registering a .NET Extension
# Compile your .NET project (on a development machine)
# dotnet build -c Release
# Copy the DLL to the server
# scp bin/Release/net6.0/MyExtension.dll server:/opt/cftp/extensions/
# Register the assembly
completeftp extension registerassembly MyExtension authentication \
/opt/cftp/extensions/MyExtension.dll \
MyCompany.Extensions.CustomAuthProvider
Using .NET from JSS
JSS scripts can instantiate .NET classes directly using the new keyword with the fully qualified class name (e.g., new System.Xml.XmlDocument()). This allows JSS to leverage the full .NET framework without needing a bridge extension.
Note: For complete .NET extension development documentation, including interface definitions, project setup, and debugging, refer to the CompleteFTP Windows User Guide.
Managing Extensions
Extensions cannot be enabled or disabled individually — they are active as long as they are registered. To temporarily deactivate an extension, remove it and re-add it later.
Modifying Extension Properties
# Update the script content for an addscript extension
completeftp extension set MyExtension configuration="console.log('updated');"
# Update the file path for a registerscript extension
completeftp extension set MyExtension configurationPath=/opt/cftp/scripts/updated-auth.jss
Removing Extensions
# Remove an extension
completeftp extension remove MyExtension
Note: Removing an extension deletes its configuration from the database but does not delete the associated script or assembly files from disk.
Workflow Examples
Custom Authentication with Fallback
Set up an authentication extension that checks an external API, falling back to CompleteFTP's built-in authentication:
# 1. Create the authentication script
cat > /opt/cftp/scripts/api-auth-fallback.jss << 'SCRIPT'
// Try external API authentication first.
// Return user info object to authenticate, or null to fall through
// to the next authenticator (e.g., built-in auth).
function loadUserInfo(userName, userInfo) {
try {
var response = http.get("https://auth.internal.example.com/check?user=" +
encodeURIComponent(userName));
var result = JSON.parse(response.body);
if (result.authenticated) {
return { password: result.password, homeDirectory: "/Home/" + userName };
}
} catch (e) {
// API unavailable — fall through to built-in auth
console.log("External auth API error: " + e);
}
return null;
}
SCRIPT
# 2. Register the extension
completeftp extension registerscript ApiAuth authentication \
/opt/cftp/scripts/api-auth-fallback.jss
# 3. Verify registration
completeftp extension show ApiAuth name type
Event-Driven File Processing Pipeline
Automatically process, archive, and notify on file uploads:
# 1. Create the event handler script
# Event extensions use the event object for event details (same as JSS triggers)
cat > /opt/cftp/scripts/upload-pipeline.jss << 'SCRIPT'
if (event.eventType === "UploadFile") {
try {
// Copy to archive
var file = system.getFile(event.virtualPath);
file.copyTo("/Archive");
// Forward to partner via SFTP
var sftp = new Ftp("partner.example.com", "xfer", "xferpass", "sftp");
sftp.connect();
sftp.upload(event.virtualPath, event.fileName);
sftp.close();
// Log to database
var db = new DatabaseSync("Server=localhost;Database=ftplog;User=logger;Password=secret");
db.execute("INSERT INTO uploads (username, filename, timestamp) VALUES ('" +
event.loginUserName + "', '" + event.fileName + "', NOW())");
db.close();
// Send notification
mail.send("ops@example.com", "Upload Processed",
"File: " + event.fileName + "\nUser: " + event.loginUserName + "\nStatus: OK");
} catch (e) {
mail.send("ops@example.com", "Upload Processing Error",
"File: " + event.fileName + "\nError: " + e);
}
}
SCRIPT
# 2. Register the event extension
completeftp extension registerscript UploadPipeline event \
/opt/cftp/scripts/upload-pipeline.jss
# 3. Verify registration
completeftp extension show UploadPipeline
Dynamic IP Filtering from a Database
Block or allow IPs based on a database table that can be updated externally. Since IP filter extensions require .NET assemblies, this example uses the built-in rule-based IP filtering combined with a scheduled trigger to sync rules from a database:
# 1. Create a script that syncs IP rules from a database into CompleteFTP's IP filter
cat > /opt/cftp/scripts/sync-ip-rules.sh << 'SCRIPT'
#!/bin/bash
# Query blocked IPs from database and add as deny rules
# This is a simplified example — adapt the database query for your setup
mysql -h localhost -u reader -psecret ftpconfig \
-N -e "SELECT ip_address FROM ip_rules WHERE blocked=1" | \
while read ip; do
completeftp site ipfilter add default deny "$ip" 2>/dev/null
done
SCRIPT
chmod +x /opt/cftp/scripts/sync-ip-rules.sh
# 2. Create a scheduled trigger to sync every 15 minutes
completeftp trigger add "SyncIPRules" Timer Program /opt/cftp/scripts/sync-ip-rules.sh
completeftp trigger set SyncIPRules 'schedule=cron: 0 */15 * * * *'
Administrative Custom Commands
Add custom SITE commands for server administration:
# 1. Create the custom command script
cat > /opt/cftp/scripts/admin-commands.jss << 'SCRIPT'
function serverInfo() {
var hostname = System.IO.File.ReadAllText("/etc/hostname").trim();
var now = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
return "Host: " + hostname + ", Time: " + now;
}
function userCount() {
var db = new DatabaseSync("Server=localhost;Database=ftplog;User=reader;Password=secret");
var rows = db.query("SELECT COUNT(DISTINCT username) as cnt FROM sessions WHERE active=1");
db.close();
return "Active users: " + rows[0].cnt;
}
SCRIPT
# 2. Register the custom command extension
completeftp extension registerscript AdminTools customcommand \
/opt/cftp/scripts/admin-commands.jss
# 3. Call the commands via CLI
completeftp extension call serverInfo
completeftp extension call userCount
Troubleshooting
Extension Not Loading
# Check that the extension is registered
completeftp extension list name type
completeftp extension show MyExtension
# Verify the script file exists and is readable (for registerscript)
ls -la /opt/cftp/scripts/my-extension.jss
# Check server logs for extension loading errors
completeftp monitor set logging.level=Debug
Authentication Extension Not Working
# Verify the extension type is authentication
completeftp extension show MyAuth type
# Verify the extension is registered
completeftp extension show MyAuth
# Test with a simple script that always authenticates
cat > /tmp/test-auth.jss << 'SCRIPT'
function loadUserInfo(userName, userInfo) {
console.log("Auth extension called for user: " + userName);
return { password: "test123" };
}
SCRIPT
completeftp extension registerscript TestAuth authentication /tmp/test-auth.jss
Script Errors at Runtime
# Check server logs for JSS errors
# Extension errors are logged with the extension name
# Verify script syntax by reviewing the file
cat /opt/cftp/scripts/my-extension.jss
# Test with minimal script first, then add complexity
.NET Assembly Not Found
# Verify the assembly file exists
ls -la /opt/cftp/extensions/MyExtension.dll
# Check the registered path matches the actual path
completeftp extension show MyExtension assemblyPath
# Verify the class name is correct (fully qualified)
completeftp extension show MyExtension
Custom Command Not Callable
# Verify the extension type is customcommand
completeftp extension show MyExtension type
# Verify the extension is registered
completeftp extension show MyExtension
# Check that the function name matches exactly
completeftp extension call myFunction
Best Practices
Development
- Use registerscript during development - Changes are picked up immediately without re-registering
- Use addscript for production - Script is self-contained in the configuration, no external file dependency
- Start simple - Begin with minimal scripts and add complexity incrementally
- Add error handling - Wrap logic in try/catch blocks to prevent silent failures
- Log diagnostic information - Use
console.log()to trace execution in server logs
Security
- Validate all input - Never trust user-supplied values in authentication or command extensions
- Sanitize database queries - Avoid SQL injection by validating parameters before building queries
- Limit file access - Keep extension scripts in a dedicated directory with restricted permissions
- Protect credentials - Do not hard-code passwords in scripts; use configuration or environment variables where possible
- Review IP filter logic - Test IP filter extensions thoroughly to avoid locking out legitimate users
Organization
- Use descriptive names - Name extensions clearly (e.g.,
LdapAuth,UploadNotify,GeoIpFilter) - Separate concerns - Create one extension per responsibility rather than combining unrelated logic
- Document scripts - Add comments explaining the purpose and expected behavior
- Store scripts in a dedicated directory - Use a consistent path such as
/opt/cftp/scripts/or/opt/cftp/extensions/ - Version control scripts - Track extension scripts in source control for auditability
Performance
- Keep scripts efficient - Avoid long-running operations in authentication and IP filter extensions
- Close connections - Always close FTP, database, and HTTP connections when done
- Minimize external calls - Cache results where possible to reduce latency
- Use .NET assemblies for heavy logic - Compiled code runs faster than interpreted JSS for complex operations
- Disable unused extensions - Disabled extensions have zero runtime overhead
Quick Reference
Extension Management Commands
# List extensions
completeftp extension list [properties]
# Show extension details
completeftp extension show <extensionName> [properties]
# Add JSS extension (stored in config DB)
completeftp extension addscript <extensionName> <extensionType> <scriptFilePath> [properties]
# Register JSS extension (file path stored, re-read each use)
completeftp extension registerscript <extensionName> <extensionType> <scriptFilePath> [properties]
# Register .NET assembly extension
completeftp extension registerassembly <extensionName> <extensionType> <assemblyPath> <className> [properties]
# Modify extension
completeftp extension set <extensionName> property=value
# Remove extension
completeftp extension remove <extensionName>
# Call custom command function
completeftp extension call <functionName> [arguments]
Extension Types
authentication = Custom authentication logic
filesystem = Custom virtual file system provider
customcommand = Custom FTP/SFTP commands
event = Event handler (fires on server events)
ipfilter = Custom IP filtering logic
ipfilter = Custom IP filtering (.NET assemblies only)
addscript vs registerscript
# addscript: script content stored in config DB
# - Changes to file NOT reflected after adding
# - No file dependency at runtime
completeftp extension addscript MyExt event /opt/cftp/scripts/handler.jss
# registerscript: file path stored, re-read each use
# - Changes to file ARE reflected immediately
# - File must remain at registered path
completeftp extension registerscript MyExt event /opt/cftp/scripts/handler.jss
Common JSS Objects
system.getFile(path) # Get file reference in virtual file system
system.getFolder(path) # Get folder reference in virtual file system
new Ftp(host, user, pass) # FTP/SFTP client
http.get(url) # HTTP GET request
http.post(url, body, ct) # HTTP POST request
mail.send(to, subj, body) # Send email
new DatabaseSync(connStr) # Database connection
console.log(message) # Log to server logs