'An Unexpected SuiteScript Error Has Occurred': Diagnostic Guide
Diagnose NetSuite UNEXPECTED_ERROR failures by isolating deployment, imports, permissions, field IDs, N/search, N/query, serialization and governance problems.
Few NetSuite messages are less helpful than:
{
"error": {
"code": "UNEXPECTED_ERROR",
"message": "An unexpected SuiteScript error has occurred"
}
}
The message identifies the symptom, not the cause. The reliable response is to reduce the execution to the smallest working path and then restore one boundary at a time.
Preserve the error ID first
If NetSuite provides an error ID, record it with:
- The timestamp and account time zone
- Script and deployment IDs
- Executing role and user
- Script type and entry point
- The operation being attempted
The ID is useful when correlating an external failure with NetSuite’s execution logs or a support case. Do not expose it publicly with customer data or credentials.
Check the execution log
Open the script or deployment record and inspect its Execution Log subtab. NetSuite records platform errors and messages written with N/log there.
Log boundaries rather than entire payloads:
log.audit({
title: 'Create customer: start',
details: {
hasExternalId: Boolean(input.externalId),
fieldCount: Object.keys(input.fields ?? {}).length,
},
});
Avoid logging access tokens, personal information, full request bodies or confidential record values.
Reduce the entry point to a known response
For a RESTlet, temporarily replace the business logic with:
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define([], () => {
const get = () => ({ ok: true });
return { get };
});
If this still fails, focus on the URL, deployment, HTTP method, authentication and role. If it works, the failure is inside the removed application path.
Use the same principle for other script types: keep the required entry point, return or log a constant, and reintroduce dependencies progressively.
Check deployment configuration
Confirm:
- The script record points to the current compiled file.
- The deployment is released or correctly set to testing.
- The deployment audience includes the executing role where applicable.
- The requested RESTlet script and deployment IDs are correct.
- The HTTP method matches an exported RESTlet entry point.
- The script and deployment are not inactive.
For an entry-point-interface error, use the dedicated SuiteScript 2.1 entry-point diagnostic.
Check module imports and exports
An omitted module can surface far from the import declaration:
define(['N/search'], (search) => {
const get = () => search.create({
type: search.Type.CUSTOMER,
columns: ['internalid'],
}).run().getRange({ start: 0, end: 1 });
return { get };
});
Verify that:
- Every callback parameter aligns with the corresponding dependency.
- Relative module paths are correct from the deployed file.
- The compiled output exports the expected entry point.
- A circular dependency has not produced an incomplete module.
Isolate N/search and N/query
When the error appears after changing a search or query:
- Select only an internal ID.
- Remove formulas and joined columns.
- Remove filters.
- Run a one-row or tightly filtered request.
- Add fields, joins and formulas back one at a time.
For saved-search filters, confirm operators and special values. For SuiteQL, confirm analytical record and field IDs in the Records Catalog.
An invalid field, unavailable join or role-restricted custom field can be hidden behind the generic error.
Test permissions with the execution role
Administrator success does not prove that an integration or employee role can perform the same work.
Check permission at each boundary:
- Script and deployment audience
- Target record type
- Custom fields and custom records
- Saved search audience
- File Cabinet folder
- SuiteAnalytics or query access
- Subsidiary, employee or data-access restrictions
Use the same role and token as the failing request. Broadening permissions permanently is not a diagnostic strategy; add only the permission demonstrated to be necessary.
Validate input before using it
Reject malformed input with a named error before it reaches a NetSuite API:
import error = require('N/error');
function requireInteger(value, name) {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw error.create({
name: 'INVALID_ARGUMENT',
message: `${name} must be a positive integer`,
notifyOff: true,
});
}
return parsed;
}
Validate dates, record IDs, enumerated values and required object shapes. Do not let an undefined or incorrectly typed value travel several calls before it fails.
Check serialization at the boundary
RESTlets and Suitelets must return serializable data. Do not return a loaded record, search object, error object with circular references or another complex NetSuite object directly.
Map the result to plain data:
return {
ok: true,
id: String(recordId),
warnings: [],
};
If a RESTlet returns HTTP 200, still inspect the response body. Oracle notes that a caught SuiteScript error can be returned within an otherwise successful HTTP response.
Check governance and volume
The real failure may be the amount of work rather than one bad record.
Log remaining usage around expensive boundaries:
log.debug({
title: 'Remaining governance',
details: runtime.getCurrentScript().getRemainingUsage(),
});
Look for:
- Record loads or saves inside large loops
- Unpaged searches or queries
- Large files or response bodies
- Repeated calls that should be cached
- Recursive user-event behaviour
- Map/Reduce values that exceed stage limits
Move bulk work to a scheduled or Map/Reduce process instead of making a RESTlet request wait for it.
Catch errors without erasing the evidence
Add context and rethrow a safe error, but keep the original details in restricted logs:
try {
return processRequest(input);
} catch (caught) {
log.error({
title: 'Request processing failed',
details: {
name: caught?.name,
message: caught?.message,
stack: caught?.stack,
},
});
throw error.create({
name: 'REQUEST_FAILED',
message: 'The request could not be processed.',
notifyOff: true,
});
}
Do not return stack traces, internal record data or permission details to an external caller.
A practical isolation order
- Record the error ID and timestamp.
- Inspect the execution log.
- Replace the entry point with a constant response.
- Verify deployment, audience, method and authentication.
- Restore imports.
- Validate and normalise input.
- Restore searches or queries one field at a time.
- Test with the actual execution role.
- Restore record writes and file operations.
- Measure governance and payload size.
This turns an opaque UNEXPECTED_ERROR into a sequence of small, testable boundaries.