NetSuite: Use SuiteScript and SuiteQL to Work With the File Cabinet

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.

The RESTlet

Let's start with the RESTlet, which is included below and available for download here.

/**
* @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.

All Requests Are POSTs

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.

Creating Files

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.

Getting Files

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!!!"
}

Getting File Enumerations

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"
  }
}

Creating Folders

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.

Deleting Folders

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.

Querying the File Cabinet Using SuiteQL

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 requestEcho Function

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.

Permissions

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.

Wrapping Up

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.

About Me

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.