🔹 What you’ll learn
- Connect to an SFTP server from SuiteScript using
N/sftp
- Upload, download, and list files with robust error handling
- Store credentials & keys safely (via Script Parameters / credentials)
- Understand host keys, folders, and common pitfalls
🔹 Prerequisites & Setup
- Modules enabled: You’ll use
N/sftp
,N/file
, andN/runtime
. - Credentials & config: Put these in Script Parameters (deployment) so you never hardcode:
custscript_sftp_host
(e.g.,sftp.partner.com
)custscript_sftp_port
(usually22
)custscript_sftp_username
custscript_sftp_password
or key + passphrasecustscript_sftp_hostkey
(server’s public host key / fingerprint)custscript_sftp_base_dir
(e.g.,/inbound
or/
)
- SSH Key (if using key auth): Upload the private key file (PEM) to File Cabinet (e.g.,
/SuiteScripts/keys/id_rsa.pem
) and store its file ID in a parametercustscript_sftp_key_file
. - Firewall / allow-listing: Ensure your SFTP server allows NetSuite IPs (if required by your vendor).
⚠️ Security tip: Prefer parameters or NetSuite secure credentials. Don’t hardcode secrets in code or logs.
🔹 Connect helper (password OR key auth)
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/sftp', 'N/runtime', 'N/file'], (sftp, runtime, file) => {
/**
* Creates and returns an SFTP connection using either password or key-based auth.
* All values come from deployment parameters to avoid hardcoding.
*/
function createConnection() {
const script = runtime.getCurrentScript();
// ---- Required basics ----
const host = script.getParameter({ name: 'custscript_sftp_host' }); // e.g., sftp.partner.com
const port = parseInt(script.getParameter({ name: 'custscript_sftp_port' }) || '22', 10);
const username = script.getParameter({ name: 'custscript_sftp_username' });
const hostKey = script.getParameter({ name: 'custscript_sftp_hostkey' }); // server’s host key/fingerprint
const baseDir = script.getParameter({ name: 'custscript_sftp_base_dir' }) || '/';
// ---- Auth options (choose one) ----
const password = script.getParameter({ name: 'custscript_sftp_password' }); // if password auth
const keyFileId = script.getParameter({ name: 'custscript_sftp_key_file' }); // if key auth (File Cabinet ID)
const passphrase = script.getParameter({ name: 'custscript_sftp_passphrase' }); // for encrypted key
// Build options object for N/sftp
const options = {
username,
url: host, // server host
port, // 22 by default
hostKey, // required by many servers for verification
directory: baseDir // optional default working dir
};
// Use password OR private key
if (password) {
options.password = password;
} else if (keyFileId) {
const keyFile = file.load({ id: keyFileId }); // load PEM private key
options.privateKey = keyFile.getContents(); // string content of the key
if (passphrase) options.passphrase = passphrase;
} else {
throw new Error('No SFTP auth provided. Set password or key file parameter.');
}
// Create and return the SFTP connection
return sftp.createConnection(options);
}
const execute = () => {
try {
const conn = createConnection();
// If we got here, we’re connected. Do work (upload/list/download) below.
log.debug('SFTP', 'Connection established successfully.');
} catch (e) {
log.error('SFTP Connect Error', (e && e.message) || e);
}
};
return { execute };
});
What to notice
- All sensitive data is read from parameters.
- Supports password or key auth (with optional passphrase).
hostKey
helps prevent man-in-the-middle issues—get this from your SFTP provider.
🔹 Upload a file (to remote directory)
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/sftp', 'N/runtime', 'N/file'], (sftp, runtime, file) => {
function connect() {
// reuse the createConnection() logic from previous snippet
const script = runtime.getCurrentScript();
const conn = sftp.createConnection({
url: script.getParameter({ name: 'custscript_sftp_host' }),
port: parseInt(script.getParameter({ name: 'custscript_sftp_port' }) || '22', 10),
username: script.getParameter({ name: 'custscript_sftp_username' }),
password: script.getParameter({ name: 'custscript_sftp_password' }), // or privateKey/passphrase
hostKey: script.getParameter({ name: 'custscript_sftp_hostkey' }),
directory: script.getParameter({ name: 'custscript_sftp_base_dir' }) || '/'
});
return conn;
}
const execute = () => {
try {
const conn = connect();
// 1) Build a local NetSuite file (could also load an existing file)
const csv = 'id,name\n1001,ACME\n1002,Wayne Enterprises\n';
const localFile = file.create({
name: 'customers.csv',
fileType: file.Type.CSV,
contents: csv
});
// 2) Choose a remote directory/filename
const remoteDir = '/inbound'; // ensure this exists and you have permissions
const remoteName = 'customers_' + Date.now() + '.csv';
// 3) Upload with replaceExisting when appropriate
conn.upload({
directory: remoteDir,
filename: remoteName,
file: localFile,
replaceExisting: true
});
log.debug('SFTP Upload', `Uploaded ${remoteName} to ${remoteDir}`);
} catch (e) {
log.error('SFTP Upload Error', (e && e.message) || e);
}
};
return { execute };
});
Notes
- Create or
file.load()
a File Cabinet file. - Remote directories must exist and be writable by your SFTP account.
🔹 Download a file (from remote directory)
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/sftp', 'N/runtime', 'N/file'], (sftp, runtime, file) => {
function connect() {
const script = runtime.getCurrentScript();
return sftp.createConnection({
url: script.getParameter({ name: 'custscript_sftp_host' }),
port: parseInt(script.getParameter({ name: 'custscript_sftp_port' }) || '22', 10),
username: script.getParameter({ name: 'custscript_sftp_username' }),
password: script.getParameter({ name: 'custscript_sftp_password' }),
hostKey: script.getParameter({ name: 'custscript_sftp_hostkey' }),
directory: script.getParameter({ name: 'custscript_sftp_base_dir' }) || '/'
});
}
const execute = () => {
try {
const conn = connect();
// 1) Identify remote file
const remoteDir = '/outbound';
const remoteName = 'report_latest.csv';
// 2) Download returns file contents as a string/bytes
const contents = conn.download({
directory: remoteDir,
filename: remoteName
});
// 3) Save to File Cabinet (optional)
const saved = file.create({
name: 'downloaded_report.csv',
fileType: file.Type.CSV,
contents: contents
});
saved.folder = 123; // replace with a real folder ID
const fileId = saved.save();
log.debug('SFTP Download', `Saved File Cabinet copy as ID ${fileId}`);
} catch (e) {
log.error('SFTP Download Error', (e && e.message) || e);
}
};
return { execute };
});
🔹 List a directory
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/sftp', 'N/runtime'], (sftp, runtime) => {
const execute = () => {
try {
const script = runtime.getCurrentScript();
const conn = sftp.createConnection({
url: script.getParameter({ name: 'custscript_sftp_host' }),
port: parseInt(script.getParameter({ name: 'custscript_sftp_port' }) || '22', 10),
username: script.getParameter({ name: 'custscript_sftp_username' }),
password: script.getParameter({ name: 'custscript_sftp_password' }),
hostKey: script.getParameter({ name: 'custscript_sftp_hostkey' })
});
const path = script.getParameter({ name: 'custscript_sftp_list_dir' }) || '/';
const items = conn.list({ path }); // returns array of file/dir metadata
items.forEach((it) => {
log.debug('SFTP Item', `${it.name} | ${it.directory ? 'DIR' : 'FILE'} | size=${it.size}`);
});
} catch (e) {
log.error('SFTP List Error', (e && e.message) || e);
}
};
return { execute };
});
🔹 Common errors & fixes
Error / Symptom | Likely Cause | Fix |
---|---|---|
Handshake failed / Permission denied | Wrong username/password or key/permissions | Verify creds, reset password or re-upload correct private key; confirm account permissions on SFTP |
Host key verification failed | hostKey doesn’t match server | Ask your partner for the current host key or fingerprint; update parameter |
No such file | Wrong remote directory or filename | Confirm path (case-sensitive), ensure directory exists and your user has rights |
Upload works but empty file appears | Sending empty content or encoding issue | Confirm file contents, avoid binary mismatch; for binary, ensure proper handling |
Random failures on large files | Timeout / network hiccups | Retry logic, smaller chunks, coordinate with vendor on size/timeouts |
Works in Sandbox, fails in Prod | Different params / firewall | Copy parameters to Prod; allow-list NetSuite IPs if vendor requires |
🔹 Best practices
- Never hardcode passwords or keys; use parameters (or NetSuite secure credentials).
- Use an integration-only SFTP user with least privileges.
- Log context, not secrets (e.g., don’t log passwords, host keys, file contents).
- Validate remote paths before calling
upload/download
. - For large batch transfers, stage files to File Cabinet first; then upload.
- Add retries and governance checks in larger jobs; consider Map/Reduce to generate or process many files.
âś… Key Takeaway
SFTP from SuiteScript is straightforward once you:
- Store secrets as parameters
- Provide the correct host key
- Use
N/sftp
for connect → upload/list/download - Wrap everything in try–catch and log clearly