This article explains how to configure workflow sync and contract sync NetSuite and Ironclad. Workflow sync allows Ironclad workflows to pull vendor data from NetSuite into launch forms, while Contract Sync allows completed Ironclad contracts to be written back to NetSuite as contract records.
Permissions
| Features | Integrations |
| Permissions |
|
Configure the Integration Role
Before you’re able to set up a workflow sync or record sync, you must create a non-administrator role.
Note: NetSuite restricts admin access via API, so this step is required.
Step 1: Assign the required permissions to the non-administrator role
- Reports: SuiteAnalytics Workbook (to verify configuration via SuiteQL).
- Lists:
- Documents and Files (Full). Enables File Cabinet uploads.
- Vendors (Full). Full is only required for populating the workflow link on the vendor if the user has that configured for Workflow Sync.
- Setup: * Log in using OAuth 2.0 Access Tokens.
- REST Web Services (Full).
- SuiteScript (View).
- Custom Record: Ironclad Contract (Full).
Warning: Do not check the Web Services Only or Require Single Sign On checkboxes.
Step 2: Create the OAuth Integration Record.
- Navigate to Setup > Integration > Manage Integrations.
- Enable Client Credentials (Machine to Machine) Grant.
- Select scopes: RESTlets and REST Web Services.
- Save and store the Client ID and Client Secret as a secure backup copy.
Step 3: Map Client Credentials
- Navigate to Setup > Integration > Manage Integrations.
- Create a new mapping using the Integration Record from Step 2: Create the OAuth Integration Record, and the non-administrator role created in Step 1.
- Click Save.
Step 4: Deploy the RESTlet
- Create a Script Record and Deployment for the RESTlet.
- Upload the provided JavaScript code, name it, and save it with the .js file extension: [Example Name].js
- Ensure the Deployment status is set to Released.
NetSuite RESTlet JavaScript Code
/**
* @NApiVersion 2.1
* @NScriptType Restlet
* @description Handles file uploads with strict record attachment and atomic rollback.
*/
define(['N/file', 'N/record', 'N/search', 'N/error', 'N/log'], (file, record, search, error, log) => { // Fixed =>
const getNSFileType = (fileName) => { // Fixed =>
if (!fileName || fileName.indexOf('.') === -1) {
throw error.create({
name: 'INVALID_FILE_NAME',
message: 'File name must include a valid extension.',
notifyOff: true,
});
}
const ext = fileName.split('.').pop().toLowerCase();
const typeMap = {
pdf: file.Type.PDF,
doc: file.Type.WORD,
docx: file.Type.WORD,
rtf: file.Type.RTF,
txt: file.Type.PLAINTEXT,
xls: file.Type.EXCEL,
xlsx: file.Type.EXCEL,
xlsm: file.Type.EXCEL,
csv: file.Type.CSV,
ppt: file.Type.POWERPOINT,
pptx: file.Type.POWERPOINT,
jpg: file.Type.JPGIMAGE,
jpeg: file.Type.JPGIMAGE,
png: file.Type.PNGIMAGE,
gif: file.Type.GIFIMAGE,
tif: file.Type.TIFFIMAGE,
tiff: file.Type.TIFFIMAGE,
xml: file.Type.XMLDOC,
htm: file.Type.HTMLDOC,
html: file.Type.HTMLDOC,
eml: file.Type.MESSAGERFC,
msg: file.Type.AUTODETECT,
asice: file.Type.AUTODETECT,
adoc: file.Type.AUTODETECT,
bdoc: file.Type.AUTODETECT,
ddoc: file.Type.AUTODETECT,
edoc: file.Type.AUTODETECT,
};
if (!typeMap[ext]) {
throw error.create({
name: 'INVALID_FILE_TYPE',
message: `File type .${ext} is not supported.`,
notifyOff: true,
});
}
return typeMap[ext];
};
const checkFileExists = (fileName, folderId) => { // Fixed =>
const fileSearch = search.create({
type: 'file',
filters: [['name', 'is', fileName], 'AND', ['folder', 'anyof', folderId]],
});
return fileSearch.runPaged().count > 0; // Fixed: Added '>' operator
};
const isValidRecord = (recType, recId) => { // Fixed =>
try {
if (!recType || !recId) return false;
const lookup = search.lookupFields({
type: recType,
id: recId,
columns: ['internalid'],
});
return !!lookup.internalid;
} catch (e) {
return false;
}
};
return {
post: (request) => { // Fixed =>
log.debug({ title: 'Incoming Request', details: request });
// 1. Input Validation
if (!request.name || !request.folderId || !request.content || !request.recordType || !request.recordId) {
throw error.create({
name: 'MISSING_REQUIRED_FIELDS',
message: 'Payload must contain: name, folderId, content, recordType, and recordId',
notifyOff: true,
});
}
const fileName = request.name.trim();
const folderId = parseInt(request.folderId);
const { recordType, recordId } = request;
// 2. Record Validation
if (!isValidRecord(recordType, recordId)) {
throw error.create({
name: 'INVALID_RECORD_TARGET',
message: `Target record (${recordType}:${recordId}) does not exist.`,
notifyOff: true,
});
}
// 3. Duplicate Prevention
if (checkFileExists(fileName, folderId)) {
throw error.create({
name: 'FILE_ALREADY_EXISTS',
message: `File ${fileName} already exists in folder ${folderId}.`,
notifyOff: true,
});
}
// TRANSACTIONAL OPERATIONS (Rollback Active)
let fileId = null;
try {
// 4. File Creation
const fileObj = file.create({
name: fileName,
fileType: getNSFileType(fileName),
contents: request.content,
folder: folderId,
encoding: file.Encoding.BASE64,
isOnline: true,
});
fileId = fileObj.save();
log.audit({ title: 'File Saved', details: `File ID: ${fileId} created.` });
// 5. Attach to Record
record.attach({
record: { type: 'file', id: fileId },
to: { type: recordType, id: recordId },
});
return {
status: 'success',
fileId: fileId,
fileName: fileName,
attachedToRecordType: recordType,
attachedToRecordId: recordId,
};
} catch (e) {
log.error({ title: 'Transaction Failed', details: e });
// ROLLBACK: Delete the file if it was created but attachment failed
if (fileId) {
try {
file.delete({ id: fileId });
log.audit({ title: 'Rollback Executed', details: `Deleted orphan file ${fileId}` });
} catch (delErr) {
log.emergency({ title: 'Rollback Failed', details: delErr });
}
}
throw error.create({
name: e.name || 'UPLOAD_FAILED',
message: e.message || 'An unexpected error occurred.',
notifyOff: true,
});
}
},
};
});Step 5: Finalize Ironclad Settings
In the Ironclad NetSuite settings page:
Input the Script ID, Deployment ID, and the Destination Folder ID from your File Cabinet.
Ensure the record sync setting is set to Sync Document.
Once a document is signed, Ironclad will now automatically:
- Upload the file to the designated NetSuite folder.
- Attach the file to the corresponding Ironclad Contract record in NetSuite via the RESTlet.
Now, you can start setting up workflow syncs and record syncs.
Set Up Workflow Sync
Workflow sync allows you to pull vendor details from NetSuite into Ironclad workflow launch forms. After selecting a vendor, all mapped attributes automatically populate on the launch form.
- In Ironclad, navigate to Company settings > Integrations > NetSuite.
- Click the Workflows tab.
- Click +Add Workflow Configuration.
- Select the workflow you want to integrate with NetSuite.
- Click +Add Attribute to select workflow properties to map to NetSuite.
- Select the corresponding NetSuite property.
- Repeat steps 5 and 6 until all desired workflow properties are mapped.
- Click Save.
Once configured, users can select Pull from NetSuite when launching a workflow to populate vendor details automatically.
Set Up Contract Sync in Ironclad
Step 1: Create a Contract Record Type in NetSuite
Contract sync requires a custom Contract record type in NetSuite to store Ironclad contract metadata.
- In NetSuite, navigate to Settings > Set Preferences.
- Under Defaults, enable Show Internal IDs.
- Click Save.
- Navigate to Customization > Lists, Records & Fields > Record Types.
- Click New.
- Enter ironcladcontract as the name.
- (Optional) Under Fields, click New Field to create custom fields that Ironclad contract metadata will populate.
- Click Save.
- Copy and save the Internal ID of the new Custom Record Type. You’ll need to use the ID in Ironclad.
Step 2: Set up Contract Sync
Required Permissions: Ironclad Administrator or users with Integrations group permissions AND NetSuite Administrator
Contract sync allows Ironclad to create and populate NetSuite contract records with executed contract data.
- In Ironclad, navigate to Company settings > Integrations > NetSuite.
- Click the Contract Sync tab.
- Click +Add Contract Configuration.
- Select the record type you want to integrate with NetSuite.
- Click Edit.
- In Custom Record Type Internal ID, enter the Internal ID created in NetSuite.
- Click +Add Attribute to map Ironclad contract metadata to NetSuite fields.
- Select the NetSuite field and the corresponding Ironclad attribute.
- Repeat for all required attribute mappings.
- Click Save.
After configuration, completed Ironclad contracts will sync to NetSuite using the defined mappings.