Published on August 26, 2019.
A few weeks ago, I wrote about how you can use SuiteQL to query a NetSuite instance's File Cabinet. It has been one of the most popular posts that I've written, and I've received a lot of very nice feedback about it.
In the post, I mentioned that I would follow-up and explain how you can use SuiteScript to get to a file's contents. This post does that, and also provides a RESTlet to show how you can use SuiteScript to do other things with the File Cabinet as well, including querying it, creating and deleting folders, uploading files, and more.
/** * @NApiVersion 2.x * @NScriptType Restlet * @NModuleScope Public */ /* ------------------------------------------------------------------------------------------ Script Information ------------------------------------------------------------------------------------------ Name: File Cabinet API ID: _file_cabinet_api Description A RESTlet that serves as an RPC-style API to a NetSuite instance's File Cabinet. ------------------------------------------------------------------------------------------ Developer(s) ------------------------------------------------------------------------------------------ TD: • Tim Dietrich • timdietrich@me.com ------------------------------------------------------------------------------------------ History ------------------------------------------------------------------------------------------ 20200826 - TD Initial version. */ var file, log, query, record; define( [ 'N/file', 'N/log', 'N/query', 'N/record' ], main ); function main( fileModule, logModule, queryModule, recordModule ) { file = fileModule; log = logModule; query = queryModule; record = recordModule; return { post: postProcess } } function postProcess( request ) { if ( ( typeof request.function == 'undefined' ) || ( request.function == '' ) ) { return { 'error': 'No function was specified.' } } switch ( request.function ) { case 'fileCreate': return fileCreate( request ) break; case 'fileEnumerationsGet': return file; break; case 'fileGet': return fileGet( request ) break; case 'folderCreate': return folderCreate( request ) break; case 'folderDelete': return folderDelete( request ) break; case 'requestEcho': return request; break; case 'suiteQLRun': return suiteQLRun( request ) break; default: var response = { 'error': 'Unsupported Function' } return response; } } function fileCreate( request ) { // Validate the request... if ( typeof request.name == 'undefined' ) { return { 'error': 'No name was specified.' } } if ( typeof request.fileType == 'undefined' ) { return { 'error': 'No fileType was specified.' } } if ( typeof request.contents == 'undefined' ) { return { 'error': 'No content was specified.' } } if ( typeof request.description == 'undefined' ) { return { 'error': 'No description was specified.' } } if ( typeof request.encoding == 'undefined' ) { return { 'error': 'No encoding was specified.' } } if ( typeof request.folderID == 'undefined' ) { return { 'error': 'No folderID was specified.' } } if ( typeof request.isOnline == 'undefined' ) { return { 'error': 'No isOnline was specified.' } } // Load the file. try { var fileObj = file.create( { name: request.name, fileType: request.fileType, contents: request.contents, description: request.description, encoding: request.encoding, folder: request.folderID, isOnline: request.isOnline } ); // Save the file and get its ID. var fileID = fileObj.save(); // Load the file. fileObj = file.load( { id: fileID } ); // Create the response. var response = {}; response['info'] = fileObj; response['content'] = fileObj.getContents(); return response; } catch (e) { return { 'error': e } } } function fileGet( request ) { // If no file ID was specified... if ( typeof request.fileID == 'undefined' ) { return { 'error': 'No fileID was specified.' } } // Load the file. try { var fileObj = file.load( { id: request.fileID } ); // Create the response. var response = {}; response['info'] = fileObj; response['content'] = fileObj.getContents(); return response; } catch (e) { return { 'error': e } } } function folderCreate( request ) { // If no folder name was specified... if ( typeof request.name == 'undefined' ) { return { 'error': 'No name was specified.' } } // Create the folder record. var objRecord = record.create( { type: record.Type.FOLDER, isDynamic: true } ); // Set the folder name. objRecord.setValue( { fieldId: 'name', value: request.name } ); // If this is a subfolder... if ( typeof request.parent !== 'undefined' ) { objRecord.setValue( { fieldId: 'parent', value: request.parent } ); } // Save the record. var folderId = objRecord.save(); // Get the record. result = record.load( { type: record.Type.FOLDER, id: folderId, isDynamic: false } ); return result; } function folderDelete( request ) { // If no folder ID was specified... if ( typeof request.folderID == 'undefined' ) { return { 'error': 'No folderID was specified.' } } try { // Load the record. var folder = record.load( { type: record.Type.FOLDER, id: request.folderID, isDynamic: false } ); // Delete the record. var objRecord = record.delete( { type: record.Type.FOLDER, id: request.folderID } ); // Create the response. var response = {}; response['folderDeleted'] = folder; return response; } catch (e) { return { 'error': e } } } function suiteQLRun( request ) { try { var records = query.runSuiteQL( request['sql'] ).asMappedResults(); return { 'records': records } } catch (e) { return { 'error': e } } }
Let's review the RESTlet from beginning to end, discuss the functions that it provides, and how to utilize them.
The RESTlet provides an RPC-style ("remote procedure call") API, where all requests are sent as HTTP POSTs, with JSON-encoded request bodies. The "main" function hands requests off to a "postProcess" function, which evaluates a "function" parameter that is included in each request, and in turn passes the request to the appropriate sub-function for processing.
The "fileCreate" function can be used to upload a file to the File Cabinet and save it in a specified folder. You indicate the file's name, type, contents, description, encoding type, the ID of the folder to store the file in, and whether or not the file should be made accessible externally via a URL ("isOnline"). The file type and encoding types are enumerations, and you can easily get a list of the supported values by making a call to the "fileEnumerationsGet" function (which is described below).
When calling the "fileCreate" function, the content must be passed as text. If you are creating a binary file, you need to Base64 encode it.
Here's a sample cURL command that shows how you can call the RESTlet and use the fileCreate function.
curl -X "POST" "*** YOUR DEPLOYMENT URL ***" \ -H 'Content-Type: application/json' \ -H 'Authorization: OAuth oauth_consumer_key="*** YOUR CONSUMER KEY ***", oauth_nonce="*** YOUR NONCE ***", oauth_signature="*** YOUR OAUTH SIGNATURE ***", oauth_signature_method="HMAC-SHA1", oauth_timestamp="*** YOUR TIMESTAMP ***", oauth_token="*** YOUR OAUTH TOKEN ***", oauth_version="1.0", realm=*** YOUR NS ACCOUNT NUMBER ***' \ -d $'{ "encoding": "UTF-8", "function": "fileCreate", "folderID": 16752789, "contents": "Hello, NetSuite.", "isOnline": true, "description": "A test file.", "name": "test.txt", "fileType": "PLAINTEXT" }'
The RESTlet will respond with a JSON representation of the file. Information about the file is provided via the "info" element, and the contents are provided via the "content" element. You can use the response to determine if the file really was created properly.
To retrieve a file from the File Cabinet, you can call the RESTlet and use its "fileGet" function. To do so, you simply specify the ID of the file that you want to retrieve.
Here's an example cURL command showing a "fileGet" call.curl -X "POST" "*** YOUR DEPLOYMENT URL ***" \ -H 'Content-Type: application/json' \ -H 'Authorization: OAuth oauth_consumer_key="*** YOUR CONSUMER KEY ***", oauth_nonce="*** YOUR NONCE ***", oauth_signature="*** YOUR SIGNATIRE ***", oauth_signature_method="HMAC-SHA1", oauth_timestamp="*** YOUR TIMESTAMP ***", oauth_token="*** YOUR OAUTH TOKEN ***", oauth_version="1.0", realm=*** YOUR NS ACCOUNT NUMBER ***' \ -d $'{ "fileID": 71441061, "function": "fileGet" }'
The response is similar to the one returned by the fileCreate function. Information about the file is provided via the "info" element, and the contents are provided via the "content" element. The content of binary files is provided as Base64-encoded text. Therefore, to make use of the content, you'll need to decode it in the calling application.
Here's an example response:
{ "info": { "type": "file.File", "id": "71441061", "name": "test.txt", "description": "A test file.", "path": "Temp/Test Folder/test.txt", "url": "*** REDACTED ***", "folder": 1635789, "fileType": "PLAINTEXT", "isText": true, "size": 7, "encoding": "UTF-8", "isInactive": false, "isOnline": true }, "content": "Test!!!" }
I mentioned earlier that to create files (via the fileCreate function) you must specify enumerated values for the file type and encoding type. If you'd like to get a list showing those enumerations, you can call the RESTlet's "fileEnumerationsGet" function, like this:
curl -X "POST" "*** YOUR DEPLOYMENT URL ***" \ -H 'Content-Type: application/json' \ -H 'Authorization: OAuth oauth_consumer_key="*** YOUR CONSUMER KEY ***", oauth_nonce="*** YOUR NONCE ***", oauth_signature="*** YOUR SIGNATIRE ***", oauth_signature_method="HMAC-SHA1", oauth_timestamp="*** YOUR TIMESTAMP ***", oauth_token="*** YOUR OAUTH TOKEN ***", oauth_version="1.0", realm=*** YOUR NS ACCOUNT NUMBER ***' \ -d $'{ "function": "fileEnumerationsGet" }'
The response will look something like this:
{ "Type": { "APPCACHE": "APPCACHE", "AUTOCAD": "AUTOCAD", "BMPIMAGE": "BMPIMAGE", "CERTIFICATE": "CERTIFICATE", "CONFIG": "CONFIG", "CSV": "CSV", "EXCEL": "EXCEL", "FLASH": "FLASH", "FREEMARKER": "FREEMARKER", "GIFIMAGE": "GIFIMAGE", "GZIP": "GZIP", "HTMLDOC": "HTMLDOC", "ICON": "ICON", "JAVASCRIPT": "JAVASCRIPT", "JPGIMAGE": "JPGIMAGE", "JSON": "JSON", "MESSAGERFC": "MESSAGERFC", "MP3": "MP3", "MPEGMOVIE": "MPEGMOVIE", "MSPROJECT": "MSPROJECT", "PDF": "PDF", "PJPGIMAGE": "PJPGIMAGE", "PLAINTEXT": "PLAINTEXT", "PNGIMAGE": "PNGIMAGE", "POSTSCRIPT": "POSTSCRIPT", "POWERPOINT": "POWERPOINT", "QUICKTIME": "QUICKTIME", "RTF": "RTF", "SCSS": "SCSS", "SMS": "SMS", "STYLESHEET": "STYLESHEET", "SVG": "SVG", "TAR": "TAR", "TIFFIMAGE": "TIFFIMAGE", "VISIO": "VISIO", "WEBAPPPAGE": "WEBAPPPAGE", "WEBAPPSCRIPT": "WEBAPPSCRIPT", "WORD": "WORD", "XMLDOC": "XMLDOC", "XSD": "XSD", "ZIP": "ZIP" }, "Encoding": { "UTF_8": "UTF-8", "WINDOWS_1252": "windows-1252", "ISO_8859_1": "ISO-8859-1", "GB18030": "GB18030", "SHIFT_JIS": "SHIFT_JIS", "MAC_ROMAN": "MacRoman", "GB2312": "GB2312", "BIG5": "Big5" } }
To create a folder, call the "folderCreate" function and provide the folder's name. If you're creating a subfolder, you can specify the ID of the "parent" folder. If you do not specify a parent folder ID, then it is assumed that the folder is to be created in the File Cabinet's root (as a "top level" folder).
Here's an example of a call to the folderCreate function.
curl -X "POST" "*** YOUR DEPLOYMENT URL ***" \ -H 'Content-Type: application/json' \ -H 'Authorization: OAuth oauth_consumer_key="*** YOUR CONSUMER KEY ***", oauth_nonce="*** YOUR NONCE ***", oauth_signature="*** YOUR SIGNATIRE ***", oauth_signature_method="HMAC-SHA1", oauth_timestamp="*** YOUR TIMESTAMP ***", oauth_token="*** YOUR OAUTH TOKEN ***", oauth_version="1.0", realm=*** YOUR NS ACCOUNT NUMBER ***' \ -d $'{ "name": "Test Folder", "function": "folderCreate", "parent": "2632926" }'
The response will be a JSON representation of the "folder" record that was created, and it will include the ID of the newly created folder.
To delete a folder, call the "folderDelete" function and provide the folder's ID. If the request is successful, the response will include a "folderDeleted" element, and its value will be the a JSON representation of the deleted "folder" record.
You must delete the contents of a folder - including the files and any subfolders in it - before you can delete a folder.
In my original post, I showed how you can use SuiteQL to query the File Cabinet to get lists of folders and files. That post referred to the SuiteQL capabilities of NetSuite's SuiteTalk REST Web Services.
You can use a similar approach to query the File Cabinet via the RESTlet, and you can do so by utilizing the "suiteQLRun" function. This is the same approach that I discussed last week in my post titled "An Alternative to SuiteTalk REST's SuiteQL Support."
To get a list of top-level folders in the File Cabinet, you would use this query.
SELECT * FROM MediaItemFolder WHERE ( Parent IS NULL )
The response will look something like this.
{ "records": [ { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "SuiteApps", "bundleable": null, "lastmodifieddate": "7/9/2020", "numfolderfiles": 0, "stripmetadata": null, "externalid": null, "description": "SuiteApps Folder", "enablecompression": "F", "class": null, "foldertype": "SUITEAPPS", "name": "SuiteApps", "id": -19, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 0, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "App Packages", "bundleable": null, "lastmodifieddate": "10/15/2011", "numfolderfiles": 0, "stripmetadata": null, "externalid": null, "description": "App Packages Folder", "enablecompression": "F", "class": null, "foldertype": "APPPACKAGES", "name": "App Packages", "id": -18, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 0, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "Images", "bundleable": null, "lastmodifieddate": "8/19/2020", "numfolderfiles": 1447, "stripmetadata": null, "externalid": null, "description": "Images Folder", "enablecompression": "F", "class": null, "foldertype": "IMAGES", "name": "Images", "id": -4, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 7621232.0458984375, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "Templates", "bundleable": null, "lastmodifieddate": "8/1/2020", "numfolderfiles": 2121, "stripmetadata": null, "externalid": null, "description": "Templates Folder", "enablecompression": "F", "class": null, "foldertype": "TEMPLATES", "name": "Templates", "id": -9, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 98511.1121, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "Attachments Received", "bundleable": null, "lastmodifieddate": "8/16/2020", "numfolderfiles": 44, "stripmetadata": null, "externalid": null, "description": "Attachments Received Folder", "enablecompression": "F", "class": null, "foldertype": "ATTACHMENTSRECEIVED", "name": "Attachments Received", "id": -10, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 23495.1123, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "Attachments Sent", "bundleable": null, "lastmodifieddate": "10/8/2019", "numfolderfiles": 4, "stripmetadata": null, "externalid": null, "description": "Attachments Sent Folder", "enablecompression": "F", "class": null, "foldertype": "ATTACHMENTSTOSEND", "name": "Attachments Sent", "id": -14, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 234.1331, "group": null }, { "istoplevel": "T", "isinactive": "F", "isprivate": "F", "owner": -4, "parent": null, "appfolder": "SuiteScripts", "bundleable": null, "lastmodifieddate": "8/26/2020", "numfolderfiles": 9, "stripmetadata": null, "externalid": null, "description": "SuiteScripts Folder", "enablecompression": "F", "class": null, "foldertype": "SUITESCRIPTS", "name": "SuiteScripts", "id": -15, "compressedimagequality": null, "chromasubsampling": null, "foldersize": 177.34, "group": null } ] }
To get a list of sub folders in a parent folder, you would use a query like this. As you can see, I'm specifying the ID of the parent folder.
SELECT MediaItemFolder.* FROM MediaItemFolder WHERE ( MediaItemFolder.Parent = 2632926)
And finally, to get a list of filers in a folder, you would use a query such as this.
SELECT File.* FROM File WHERE ( File.Folder = 2675789 )
The response will look something like this.
{ "records": [ { "isinactive": "F", "filetype": "PLAINTEXT", "createddate": "8/26/2020", "bundleable": "F", "lastmodifieddate": "8/26/2020", "addtimestamptourl": "F", "externalid": null, "filesize": 7, "isonline": "T", "description": "A test file.", "url": null, "folder": 2675789, "iscompanywidereadaccess": "F", "islink": "F", "hideinbundle": "F", "name": "test2.txt", "id": 2744061 } ] }
The RESTlet also provides support for a "requestEcho" function. This is a utility function that can help when debugging calls to the RESTlet, so I include it in most of the RESTlets that I develop. It simply takes the request body and returns it ("echoes it back") as the response.
To call this RESTlet and interact with the File Cabinet, you'll need to use an account and role that has the necessary permissions. Specifically, you'll need the Lists > Documents and Files permission. And if you'll be using the RESTlet to create or delete folders or files, the permission level will need to be set accordingly.
To make use of SuiteQL, the role will also need the Reports > SuiteAnalytics Workbook permission.
I hope the SuiteScript that I've shared in this post, and the RESTlet, are helpful to other NetSuite developers that have been tasked with performing actions on, or integrating with, the File Cabinet.
Hello, I'm Tim Dietrich. I develop custom software for businesses that are running on NetSuite, including mobile apps, Web portals, Web APIs, and more.
I'm the developer of several popular NetSuite open source solutions, including the SuiteQL Query Tool, SuiteAPI, and more.
I founded SuiteStep, a NetSuite development studio, to provide custom software and AI solutions - and continue pushing the boundaries of what's possible on the NetSuite platform.
Copyright © 2025 Tim Dietrich.