If you’ve spent any time writing SuiteScript, you’ve almost certainly needed to work with files β reading a CSV from the File Cabinet, writing an export, attaching a PDF to a transaction, or generating a log file for an integration. The N/file module is what makes all of that possible in SuiteScript 2.x, and it’s one of those modules you’ll use constantly once you know it well.
This guide covers everything you need: how to load and read files, how to create and write files, how to manage the File Cabinet programmatically, and real-world patterns you can drop into your own scripts today.
What Is the N/file Module?
The N/file module is a core SuiteScript 2.x API that lets you interact with NetSuite’s File Cabinet β the built-in file storage system inside NetSuite. With it, you can load existing files, read their contents, create new files, stream large files, copy, move, and delete files, all from within your server-side scripts.
It works across almost every script type: Scheduled Scripts, Map/Reduce Scripts, RESTlets, Suitelets, and User Event Scripts. Whether you’re dealing with text files, CSVs, JSONs, PDFs, or binary attachments, N/file is your go-to module.
Prerequisites
- NetSuite account with SuiteScript 2.x enabled
- Access to the File Cabinet (Setup > Documents > File Cabinet)
- Appropriate script execution permissions
- Understanding of SuiteScript 2.x module loading syntax
Loading the N/file Module
Like all SuiteScript modules, you load N/file using the define or require syntax at the top of your script:
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/file'], (file) => {
const execute = (context) => {
// file operations here
};
return { execute };
});
Reading a File from the File Cabinet
The most common operation is reading an existing file. You do this with file.load(), passing either the file’s internal ID or its path.
Load by Internal ID
const myFile = file.load({ id: 1234 });
const content = myFile.getContents();
log.debug('File Contents', content);
Load by Path
const myFile = file.load({ id: '/SuiteScripts/imports/orders.csv' });
const content = myFile.getContents();
log.debug('File Contents', content);
The path must be the full path from the root of the File Cabinet. The getContents() method returns the entire file as a string, which works well for text-based files like CSV, JSON, XML, and plain text.
Key Properties on a Loaded File Object
- id β Internal ID of the file
- name β File name including extension
- fileType β Type of the file (e.g.,
file.Type.CSV,file.Type.JSON) - size β File size in bytes
- url β Public URL of the file (if the folder is set to public)
- folder β Internal ID of the parent folder
- description β File description
- isOnline β Whether the file is available externally
- isInactive β Whether the file is inactive
Creating a New File
Creating a new file uses file.create(). You define the file name, type, and contents, then call save() to write it to the File Cabinet.
Basic Example: Writing a Text File
const newFile = file.create({
name: 'export_log.txt',
fileType: file.Type.PLAINTEXT,
contents: 'Export completed at ' + new Date().toISOString(),
folder: 123, // Internal ID of the target folder
description: 'Daily export log',
isOnline: false
});
const fileId = newFile.save();
log.debug('Saved File ID', fileId);
The save() method returns the internal ID of the newly created file. Store that ID if you need to reference or attach the file later.
Creating a CSV File
CSV generation is one of the most common real-world uses of N/file. Build your CSV string manually, then save it:
const rows = [
['Order ID', 'Customer', 'Amount', 'Status'],
['1001', 'Acme Corp', '5000.00', 'Shipped'],
['1002', 'Globex', '1200.00', 'Pending'],
['1003', 'Initech', '850.00', 'Closed']
];
const csvContent = rows.map(row => row.join(',')).join('
');
const csvFile = file.create({
name: 'orders_export_' + new Date().toISOString().slice(0,10) + '.csv',
fileType: file.Type.CSV,
contents: csvContent,
folder: 456
});
const csvFileId = csvFile.save();
log.debug('CSV Created', csvFileId);
Creating a JSON File
const data = {
exportDate: new Date().toISOString(),
recordCount: 42,
status: 'success'
};
const jsonFile = file.create({
name: 'sync_result.json',
fileType: file.Type.JSON,
contents: JSON.stringify(data, null, 2),
folder: 456
});
jsonFile.save();
Updating an Existing File
NetSuite doesn’t have a direct “update in place” for file contents. The standard pattern is to delete the old file and create a new one with the same name, or simply overwrite by saving a new file with the same name into the same folder β NetSuite will create a new version or a new file depending on your File Cabinet settings.
If you need to append to a file, load it first, get the contents, append your new data, then save a new file:
const existingFile = file.load({ id: '/SuiteScripts/logs/daily_log.txt' });
const existingContent = existingFile.getContents();
const updatedContent = existingContent + '
' + new Date().toISOString() + ' - New entry added';
const updatedFile = file.create({
name: 'daily_log.txt',
fileType: file.Type.PLAINTEXT,
contents: updatedContent,
folder: existingFile.folder
});
updatedFile.save();
Deleting a File
You can delete a file from the File Cabinet using file.delete(). Be careful β this is permanent and does not move the file to a trash folder.
file.delete({ id: 1234 });
Or by path:
file.delete({ id: '/SuiteScripts/temp/old_export.csv' });
Streaming Large Files with getIterator()
For large files, reading the entire contents into memory with getContents() can hit governance limits or cause performance issues. N/file provides a line-by-line iterator through lines property and getIterator().
const largeFile = file.load({ id: '/SuiteScripts/imports/large_feed.csv' });
const iterator = largeFile.lines.iterator();
iterator.each((line) => {
const lineContent = line.value;
// Process each line individually
log.debug('Line', lineContent);
return true; // return true to continue iterating
});
This is the right approach for CSV imports with thousands of rows. Processing line by line keeps your governance usage predictable and avoids loading huge strings into memory all at once.
Working with File Types
The file.Type enum defines all supported file types. Here are the ones you’ll use most often:
| Enum Value | Description |
|---|---|
| file.Type.PLAINTEXT | Plain text (.txt) |
| file.Type.CSV | Comma-separated values (.csv) |
| file.Type.JSON | JSON file (.json) |
| file.Type.XMLDOC | XML document (.xml) |
| file.Type.PDF | PDF file (.pdf) |
| file.Type.JAVASCRIPT | JavaScript file (.js) |
| file.Type.STYLESHEET | CSS file (.css) |
| file.Type.IMAGE | Image files (PNG, JPG, GIF, etc.) |
| file.Type.HTMLDOC | HTML document (.html) |
Real-World Use Cases
1. CSV Import Processor (Scheduled Script)
A common integration pattern is dropping a CSV file into a File Cabinet folder and using a Scheduled Script to pick it up, process the rows, and create or update records in NetSuite.
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/file', 'N/record', 'N/search'], (file, record, search) => {
const execute = (context) => {
const importFile = file.load({ id: '/SuiteScripts/imports/vendor_prices.csv' });
const iterator = importFile.lines.iterator();
let isHeader = true;
iterator.each((line) => {
if (isHeader) {
isHeader = false;
return true; // skip header row
}
const cols = line.value.split(',');
const itemId = cols[0].trim();
const newPrice = parseFloat(cols[1].trim());
try {
const results = search.create({
type: search.Type.INVENTORY_ITEM,
filters: [['itemid', 'is', itemId]],
columns: ['internalid']
}).run().getRange({ start: 0, end: 1 });
if (results.length > 0) {
record.submitFields({
type: record.Type.INVENTORY_ITEM,
id: results[0].id,
values: { baseprice: newPrice }
});
log.audit('Updated', itemId + ' β $' + newPrice);
}
} catch (e) {
log.error('Error on row', itemId + ': ' + e.message);
}
return true;
});
};
return { execute };
});
2. Export Records to CSV and Save to File Cabinet
Generate a daily export of open sales orders and save it as a CSV file for downstream systems to consume:
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/file', 'N/search'], (file, search) => {
const execute = (context) => {
const rows = [['Order ID', 'Customer', 'Amount', 'Date', 'Status']];
search.create({
type: search.Type.SALES_ORDER,
filters: [['status', 'anyof', 'SalesOrd:A', 'SalesOrd:B']],
columns: ['tranid', 'entity', 'amount', 'trandate', 'status']
}).run().each((result) => {
rows.push([
result.getValue('tranid'),
result.getText('entity'),
result.getValue('amount'),
result.getValue('trandate'),
result.getText('status')
]);
return true;
});
const csvContent = rows.map(r => r.join(',')).join('
');
const today = new Date().toISOString().slice(0, 10);
const exportFile = file.create({
name: 'open_orders_' + today + '.csv',
fileType: file.Type.CSV,
contents: csvContent,
folder: 789, // export folder internal ID
description: 'Auto-generated open orders export',
isOnline: false
});
const savedId = exportFile.save();
log.audit('Export Complete', 'File ID: ' + savedId + ' | Rows: ' + (rows.length - 1));
};
return { execute };
});
3. Attach a Generated File to a Transaction Record
After generating a file, you can attach it directly to a NetSuite record using N/record’s attach capability. This is useful for attaching confirmation documents to sales orders or vendor bills:
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/file', 'N/record'], (file, record) => {
const afterSubmit = (context) => {
if (context.type !== context.UserEventType.CREATE) return;
const soId = context.newRecord.id;
const soName = context.newRecord.getValue({ fieldId: 'tranid' });
// Create a simple text summary file
const summaryContent = 'Sales Order: ' + soName + '
Created: ' + new Date().toISOString() + '
Status: Pending Approval';
const summaryFile = file.create({
name: 'SO_Summary_' + soName + '.txt',
fileType: file.Type.PLAINTEXT,
contents: summaryContent,
folder: 321
});
const fileId = summaryFile.save();
// Attach the file to the sales order
record.attach({
record: { type: 'file', id: fileId },
to: { type: record.Type.SALES_ORDER, id: soId }
});
log.audit('File Attached', 'File ' + fileId + ' attached to SO ' + soId);
};
return { afterSubmit };
});
4. Read a JSON Configuration File
Rather than hardcoding integration parameters or mapping tables inside your scripts, store them in a JSON file in the File Cabinet and load them at runtime. This makes your scripts much easier to maintain without touching the code:
const loadConfig = () => {
const configFile = file.load({ id: '/SuiteScripts/config/integration_settings.json' });
return JSON.parse(configFile.getContents());
};
const config = loadConfig();
const apiEndpoint = config.endpoint;
const batchSize = config.batchSize || 100;
log.debug('Config Loaded', JSON.stringify(config));
Error Handling Best Practices
File operations can fail for several reasons: the file doesn’t exist, the folder ID is wrong, the file is too large, or you don’t have permission to access it. Always wrap file operations in try/catch blocks:
try {
const configFile = file.load({ id: '/SuiteScripts/config/settings.json' });
const config = JSON.parse(configFile.getContents());
return config;
} catch (e) {
if (e.name === 'USER_ERROR' && e.message.includes('not found')) {
log.error('Config Missing', 'Settings file not found in File Cabinet. Using defaults.');
return getDefaultConfig();
}
log.error('File Load Error', e.message);
throw e;
}
For file creation, also handle the case where a folder doesn’t exist or the file system is full:
try {
const exportFile = file.create({
name: 'export.csv',
fileType: file.Type.CSV,
contents: csvData,
folder: targetFolderId
});
return exportFile.save();
} catch (e) {
log.error('File Creation Failed', 'Folder ID: ' + targetFolderId + ' | Error: ' + e.message);
return null;
}
Governance Considerations
File operations do consume SuiteScript governance units, so keep these points in mind when working with files at scale:
- file.load() and file.create() both consume governance units β treat them like record loads, not free operations
- Use getIterator() for large files instead of getContents() to avoid loading everything into memory at once
- Map/Reduce scripts are the best choice for processing large files because governance resets between reduce calls
- Avoid reading large files inside beforeLoad or beforeSubmit User Event scripts β this will slow down every form interaction
- Cache configuration files in script parameters where possible so you’re not re-loading the same file on every execution
N/file Module Quick Reference
| Method / Property | Description |
|---|---|
| file.load(options) | Load an existing file by ID or path |
| file.create(options) | Create a new file object (call .save() to persist) |
| file.delete(options) | Permanently delete a file from the File Cabinet |
| fileObj.getContents() | Return entire file content as a string |
| fileObj.lines.iterator() | Return a line-by-line iterator for large files |
| fileObj.save() | Save the file to the File Cabinet, returns internal ID |
| fileObj.id | Internal ID of the file |
| fileObj.name | File name with extension |
| fileObj.fileType | File type (file.Type enum value) |
| fileObj.size | File size in bytes |
| fileObj.url | External URL (if folder is public) |
| fileObj.folder | Internal ID of the parent folder |
| file.Type | Enum of supported file types (CSV, JSON, PDF, etc.) |
Summary
The N/file module is one of those workhorses you’ll reach for in almost every real integration or automation project in NetSuite. Once you’re comfortable with file.load(), file.create(), and the line iterator, you can handle everything from simple config files to large CSV imports to automated report exports.
The key things to keep in mind: use the iterator for large files, always wrap operations in try/catch, store your folder IDs in script parameters rather than hardcoding them, and prefer Scheduled Scripts or Map/Reduce for heavy file processing rather than User Event scripts. Get those habits in place early and you’ll avoid most of the common headaches.
If you’re building an integration that exchanges files with an external system over SFTP, pair N/file with the N/sftp module β that combination handles the full cycle of downloading a file, processing it, and archiving the result entirely within SuiteScript.
Discover more from The NetSuite Pro
Subscribe to get the latest posts sent to your email.
Leave a Reply