Skip to main content

Error Handling

Proper error handling ensures your scripts fail gracefully, provide useful information for debugging, and maintain data integrity.


Error Handling Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│ ERROR HANDLING FLOW │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────┐
│ Operation │
└────────┬─────────┘


┌──────────────────────────────────────┐
│ try { ... } │
└────────┬─────────────────────┬───────┘
│ Success │ Error
▼ ▼
┌──────────────────┐ ┌──────────────────────────────────────┐
│ Continue │ │ catch (e) { │
│ Processing │ │ 1. Log error with context │
│ │ │ 2. Handle appropriately │
│ │ │ 3. Notify if needed │
│ │ │ 4. Throw or return │
│ │ │ } │
└──────────────────┘ └──────────────────────────────────────┘

Basic Error Handling Pattern

/**
* Standard try/catch pattern
*/
const processRecord = (recordId) => {
try {
const rec = record.load({
type: record.Type.INVOICE,
id: recordId
});

// Validate
const total = rec.getValue({ fieldId: 'total' });
if (!total || total <= 0) {
throw new Error(`Invalid total: ${total}`);
}

// Process
rec.setValue({ fieldId: 'custbody_processed', value: true });
rec.save();

log.audit('Success', `Processed invoice ${recordId}`);
return { success: true, id: recordId };

} catch (e) {
log.error({
title: 'processRecord Error',
details: {
recordId,
name: e.name,
message: e.message,
stack: e.stack
}
});

return {
success: false,
id: recordId,
error: e.message
};
}
};

Error Types in SuiteScript

┌─────────────────────────────────────────────────────────────────────────────┐
│ SUITESCRIPT ERROR TYPES │
└─────────────────────────────────────────────────────────────────────────────┘

N/error Module Errors:
├── error.Type.INSUFFICIENT_PERMISSION
├── error.Type.INVALID_KEY_OR_REF
├── error.Type.RECORD_ALREADY_EXISTS
├── error.Type.EXCEED_MAX_WAIT_TIME
└── error.Type.SCRIPT_EXECUTION_USAGE_LIMIT_EXCEEDED

Standard JavaScript Errors:
├── Error (generic)
├── TypeError (type issues)
├── ReferenceError (undefined variables)
└── SyntaxError (code syntax problems)

NetSuite System Errors:
├── SSS_RECORD_TYPE_MISMATCH
├── SSS_MISSING_REQD_ARGUMENT
├── SSS_INVALID_SUBLIST_OPERATION
└── RCRD_DSNT_EXIST (record doesn't exist)

Checking Error Types

const loadRecord = (type, id) => {
try {
return record.load({ type, id });
} catch (e) {
// Check specific error types
if (e.name === 'RCRD_DSNT_EXIST') {
log.debug('Record Not Found', `${type} ${id} does not exist`);
return null;
}

if (e.name === 'SSS_MISSING_REQD_ARGUMENT') {
log.error('Missing Argument', 'Type or ID not provided');
throw e;
}

if (e.name === 'INSUFFICIENT_PERMISSION') {
log.error('Permission Denied', `Cannot access ${type} ${id}`);
throw new Error('You do not have permission to access this record');
}

// Unknown error - rethrow
throw e;
}
};

Creating Custom Errors

/**
* Custom error module
* @NApiVersion 2.1
* @NModuleScope SameAccount
*/
define(['N/error'], (error) => {

/**
* Create business validation error
*/
const createValidationError = (field, message) => {
return error.create({
name: 'VALIDATION_ERROR',
message: `Validation failed for ${field}: ${message}`,
notifyOff: false
});
};

/**
* Create configuration error
*/
const createConfigError = (setting) => {
return error.create({
name: 'CONFIG_ERROR',
message: `Missing or invalid configuration: ${setting}`,
notifyOff: true
});
};

/**
* Create integration error
*/
const createIntegrationError = (system, details) => {
return error.create({
name: 'INTEGRATION_ERROR',
message: `External system error (${system}): ${details}`,
notifyOff: false
});
};

return {
createValidationError,
createConfigError,
createIntegrationError
};
});

Using Custom Errors

define(['./errors_lib', 'N/record'], (errors, record) => {

const beforeSubmit = (context) => {
const email = context.newRecord.getValue({ fieldId: 'email' });

if (!email || !email.includes('@')) {
throw errors.createValidationError('email', 'Valid email address required');
}
};

return { beforeSubmit };
});

Error Handling by Script Type

User Event Scripts

/**
* User Event error handling
*/
const beforeSubmit = (context) => {
try {
validateRecord(context.newRecord);
} catch (e) {
log.error('Validation Error', e.message);

// Throwing stops the save and shows error to user
throw error.create({
name: 'VALIDATION_FAILED',
message: e.message,
notifyOff: true
});
}
};

const afterSubmit = (context) => {
try {
sendNotification(context.newRecord);
} catch (e) {
// Log but don't throw - record is already saved
log.error('Notification Error', {
recordId: context.newRecord.id,
error: e.message
});
// Consider: queue for retry, alert admin, etc.
}
};

Client Scripts

/**
* Client Script error handling with user feedback
*/
const saveRecord = (context) => {
try {
const isValid = validateAllFields(context.currentRecord);

if (!isValid.success) {
alert(isValid.message);
return false; // Prevent save
}

return true; // Allow save

} catch (e) {
console.error('Validation error:', e);
alert('An error occurred during validation. Please try again.');
return false;
}
};

const fieldChanged = (context) => {
try {
handleFieldChange(context);
} catch (e) {
console.error('Field change error:', e);
// Don't alert for every field change - just log
}
};

Scheduled Scripts

/**
* Scheduled Script with batch error handling
*/
const execute = (context) => {
const errors = [];
let processed = 0;

const records = getRecordsToProcess();

records.run().each((result) => {
try {
processRecord(result.id);
processed++;
} catch (e) {
errors.push({
id: result.id,
error: e.message
});
log.error('Record Error', { id: result.id, error: e.message });
}

// Continue processing other records
return true;
});

// Log summary
log.audit('Execution Complete', {
processed,
errors: errors.length
});

// Send alert if errors occurred
if (errors.length > 0) {
sendErrorSummary(errors);
}
};

Map/Reduce Scripts

/**
* Map/Reduce with stage-specific error handling
*/
const map = (context) => {
try {
const data = JSON.parse(context.value);
const result = processData(data);

context.write({
key: data.id,
value: result
});
} catch (e) {
log.error('Map Error', {
key: context.key,
error: e.message
});
// Error is captured in summarize stage
}
};

const summarize = (summary) => {
// Handle map stage errors
summary.mapSummary.errors.iterator().each((key, error) => {
log.error('Map Error Summary', { key, error });
return true;
});

// Handle reduce stage errors
summary.reduceSummary.errors.iterator().each((key, error) => {
log.error('Reduce Error Summary', { key, error });
return true;
});

// Send notification if errors occurred
const totalErrors = summary.mapSummary.errors.length + summary.reduceSummary.errors.length;

if (totalErrors > 0) {
email.send({
author: -5,
recipients: ['admin@company.com'],
subject: 'Map/Reduce Errors',
body: `${totalErrors} errors occurred during execution`
});
}
};

RESTlets

/**
* RESTlet with HTTP-appropriate error responses
*/
const get = (params) => {
try {
if (!params.id) {
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Record ID is required'
}
};
}

const rec = record.load({
type: record.Type.CUSTOMER,
id: params.id
});

return {
success: true,
data: extractCustomerData(rec)
};

} catch (e) {
log.error('RESTlet GET Error', e.message);

if (e.name === 'RCRD_DSNT_EXIST') {
return {
success: false,
error: {
code: 'NOT_FOUND',
message: `Customer ${params.id} not found`
}
};
}

return {
success: false,
error: {
code: 'INTERNAL_ERROR',
message: 'An error occurred processing your request'
}
};
}
};

Error Recovery Patterns

Retry Pattern

/**
* Retry with exponential backoff
*/
const retryOperation = (operation, maxRetries = 3) => {
let lastError;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return operation();
} catch (e) {
lastError = e;
log.debug('Retry', `Attempt ${attempt} failed: ${e.message}`);

if (attempt < maxRetries) {
// Wait before retry (exponential backoff)
const waitMs = Math.pow(2, attempt) * 100;
const start = Date.now();
while (Date.now() - start < waitMs) {
// Busy wait (SuiteScript has no sleep)
}
}
}
}

throw lastError;
};

// Usage
const result = retryOperation(() => {
return https.get({ url: externalApiUrl });
});

Fallback Pattern

/**
* Fallback to alternative data source
*/
const getCustomerData = (customerId) => {
try {
// Try primary source
return getPrimaryData(customerId);
} catch (e) {
log.debug('Primary Failed', `Falling back: ${e.message}`);

try {
// Try secondary source
return getSecondaryData(customerId);
} catch (e2) {
log.error('All Sources Failed', {
customerId,
primaryError: e.message,
secondaryError: e2.message
});

// Return default data
return getDefaultData(customerId);
}
}
};

Queue for Later Pattern

/**
* Queue failed operations for retry
*/
const processWithQueue = (recordId) => {
try {
return processRecord(recordId);
} catch (e) {
log.error('Process Failed', { recordId, error: e.message });

// Queue for retry
const queueRecord = record.create({
type: 'customrecord_retry_queue'
});
queueRecord.setValue({ fieldId: 'custrecord_rq_record_id', value: recordId });
queueRecord.setValue({ fieldId: 'custrecord_rq_error', value: e.message });
queueRecord.setValue({ fieldId: 'custrecord_rq_attempts', value: 1 });
queueRecord.save();

return { success: false, queued: true };
}
};

Error Logging Best Practices

/**
* Comprehensive error logging
*/
const logError = (context, error, additionalData = {}) => {
const script = runtime.getCurrentScript();

const errorDetails = {
// Error information
name: error.name,
message: error.message,
stack: error.stack,

// Context
scriptId: script.id,
deploymentId: script.deploymentId,
remainingUsage: script.getRemainingUsage(),

// Custom data
...additionalData,

// Timestamp
timestamp: new Date().toISOString()
};

log.error({
title: context,
details: JSON.stringify(errorDetails, null, 2)
});

return errorDetails;
};

// Usage
try {
processOrder(orderId);
} catch (e) {
logError('Order Processing', e, {
orderId,
customerId,
step: 'validation'
});
}

User-Friendly Error Messages

/**
* Error message translator
*/
const ERROR_MESSAGES = {
'RCRD_DSNT_EXIST': 'The record you are looking for does not exist.',
'INSUFFICIENT_PERMISSION': 'You do not have permission to perform this action.',
'INVALID_KEY_OR_REF': 'Invalid reference. Please check your selection.',
'SSS_RECORD_TYPE_MISMATCH': 'Invalid record type for this operation.',
'UNIQUE_CUST_ID_REQD': 'A customer with this ID already exists.',
'DEFAULT': 'An unexpected error occurred. Please contact support.'
};

const getUserMessage = (error) => {
return ERROR_MESSAGES[error.name] || ERROR_MESSAGES.DEFAULT;
};

// Usage in Client Script
const saveRecord = (context) => {
try {
// Validation...
return true;
} catch (e) {
alert(getUserMessage(e));
return false;
}
};

Error Notification System

/**
* Error notification service
*/
const notifyError = (error, context) => {
const script = runtime.getCurrentScript();

// Get notification settings
const notifyEmail = script.getParameter({ name: 'custscript_error_email' });

if (!notifyEmail) return;

const body = `
<h2>Script Error Alert</h2>
<p><strong>Script:</strong> ${script.id}</p>
<p><strong>Deployment:</strong> ${script.deploymentId}</p>
<p><strong>Error:</strong> ${error.message}</p>
<p><strong>Time:</strong> ${new Date().toISOString()}</p>

<h3>Context</h3>
<pre>${JSON.stringify(context, null, 2)}</pre>

<h3>Stack Trace</h3>
<pre>${error.stack}</pre>
`;

email.send({
author: -5,
recipients: [notifyEmail],
subject: `Script Error: ${script.id}`,
body
});
};

Error Handling Checklist

┌─────────────────────────────────────────────────────────────────────────────┐
│ ERROR HANDLING CHECKLIST │
└─────────────────────────────────────────────────────────────────────────────┘

COVERAGE
☐ All external API calls wrapped in try/catch
☐ All record operations have error handling
☐ File operations have error handling
☐ Search operations have error handling

LOGGING
☐ Errors logged with sufficient context
☐ Error name, message, and stack captured
☐ Business context included (record IDs, etc.)
☐ Timestamps included

USER EXPERIENCE
☐ User-friendly messages shown
☐ Technical details hidden from users
☐ Clear next steps provided when possible

RECOVERY
☐ Retry logic for transient failures
☐ Fallback strategies defined
☐ Failed operations queued for retry

NOTIFICATION
☐ Critical errors trigger alerts
☐ Error summaries for batch operations
☐ Admin notification for system errors

Next Steps