🔹 How to Triage Quickly (Checklist)
- Read the full stack trace in the Execution Log → note script type, line number, and module.
- Reproduce with minimal input (one record or a small date range).
- Log inputs & external responses (sanitized) right before failing lines.
- Check role/permissions (record/field level, subsidiary, features).
- Check governance/time (remaining usage, expected volume).
- Validate assumptions (field types, list values, nulls, date/timezones).
🔹 Top Errors & How to Fix Them
1) SSS_USAGE_LIMIT_EXCEEDED
Why it happens: Too many expensive calls (e.g., record.load/save
, large search.run()
loops).
Fixes:
- Use Map/Reduce instead of Scheduled/User Event for big jobs.
- Replace
record.load()
withsearch.lookupFields
or SuiteQL when you only need a few fields. - Batch writes; avoid saving inside tight loops.
- Add resumability checkpoints.
if (runtime.getCurrentScript().getRemainingUsage() < 200) return; // Let MR auto-resume
2) SCRIPT_EXECUTION_USAGE_LIMIT_EXCEEDED
/ SCRIPT_TIMED_OUT
Why: Long loops, network waits in Suitelet, heavy processing on User Event.
Fixes:
- Offload work to Map/Reduce; return quickly from Suitelet.
- Reduce result set via Saved Search filters or SuiteQL
LIMIT
. - Use pagination and partial processing.
3) YOU_DO_NOT_HAVE_PERMISSION_TO_EDIT_THIS_RECORD
(or View)
Why: Role lacks record/field permission, subsidiary mismatch, or segment restriction.
Fixes:
- Verify role permissions + feature enablement (e.g., Multi-Subsidiary, Departments/Classes/Locations).
- Ensure script deployment audience includes the executing role.
- For scheduled/MR, check Script Owner role and Execute As Role.
4) INVALID_FLD_VALUE
/ FIELD_CHANGED
Errors
Why: Setting a value not allowed (wrong type, not in list, inactive, subsidiary mismatch).
Fixes:
- For select fields, set internal IDs (
anyof
) not text. - Validate with a search to map external value → internalId.
- Ensure the value is active and visible to the role.
// Good: map external to internalId before setValue
const deptId = getInternalId('department','name','Sales');
rec.setValue({ fieldId:'department', value: deptId });
5) RCRD_DSNT_EXIST
/ REC_TYP_REQD
Why: Loading/saving a record with wrong type or deleted ID.
Fixes:
- Confirm record type matches actual (e.g.,
record.Type.SALES_ORDER
). - Guard against empty/null IDs from searches.
6) Search Errors: SSS_INVALID_SRCH_COL
, Empty Results, or Slow Queries
Why: Wrong column/alias; fetching too much; using run().each
over thousands.
Fixes:
- Prefer Saved Search +
runPaged
, or SuiteQL. - Fetch only needed columns.
- Test search UI first, then port to code.
const count = search.load({ id:'customsearch_x' }).runPaged({ pageSize: 1000 }).count;
7) JSON Parse / “Unexpected token <”
Why: Parsing HTML error page as JSON (failed HTTP call).
Fixes:
- Log
response.code
and first 300 chars ofresponse.body
. - Add try/catch; validate
Content-Type
. - Handle 3xx/4xx/5xx explicitly and retry with backoff if transient.
let res = https.request(opts);
if (String(res.code).startsWith('2')) { JSON.parse(res.body) } else { log.error('HTTP', res.body.slice(0,300)) }
8) SFTP: Host key mismatch
/ Permission denied
/ Empty Listing
Why: Wrong host key, wrong directory, missing permissions or key/pass.
Fixes:
- Use exact Host Key fingerprint from the SFTP server.
- Verify directory path and user access.
- Use Secrets for passwords; avoid hardcoding.
- Test with a tiny upload/download first.
const conn = sftp.createConnection({ url:'sftp.partner.com', hostKey:'AAAAB3NzaC1yc2E...', username:'u', passwordGuid:'custsecret_pwd' });
9) REST (HMAC/OAuth): INVALID_LOGIN
, Signature Invalid
, 401/403
Why: Wrong auth header, timestamp drift, missing scope.
Fixes:
- Recreate signature using the exact method, path, query, and body.
- Ensure UTC timestamps and acceptable clock skew.
- Verify API user has required permissions/scopes.
10) Map/Reduce: No input data
, Too many keys written
Why: getInputData returns nothing; writing unique keys per line explodes memory.
Fixes:
- Validate the Saved Search (run in UI).
- Use a stable grouping key in
map
and aggregate inreduce
. - Avoid writing thousands of distinct keys for one logical doc.
11) Advanced PDF/FreeMarker: Blank Output / ${field}
not showing
Why: Wrong field ID, null value, incorrect FreeMarker syntax, or #list
over empty sequence.
Fixes:
- Inspect XML in Preview → “View Template XML”.
- Use null-safe:
${field!''}
; check sublist paths. - Guard loops:
<#if items?has_content> ... </#if>
.
12) Custom GL Plug-in: Lines Not Posting / Double Posting
Why: Not neutralizing standard lines; wrong account; missing amounts.
Fixes:
- If replacing, set original line amount to 0 before adding custom lines.
- Ensure Debit/Credit totals balance.
- Add logging for
getAccountId
,creditAmount
,debitAmount
.
13) CANNOT_DETERMINE_DEPLOYMENT
/ “Page not available”
Why: Wrong script/deployment IDs or audience, or missing permission.
Fixes:
- Confirm
scriptId
/deploymentId
, set audience correctly, check URL tokens. - For Suitelet, verify Available without Login if needed.
14) Date/Timezone: Wrong Date Saved / Off by Hours
Why: Using local time strings; formatting mismatch.
Fixes:
- Use
N/format
to parse/format; storeDate
objects, not strings. - Normalize to account timezone when comparing.
const dt = format.parse({ value:'09/25/2025', type: format.Type.DATE });
🔹 Defensive Coding Patterns
Safe Getters & Null Guards
const get = (obj, path, dflt='') => path.split('.').reduce((o,k)=> (o && o[k]!==undefined)? o[k] : dflt, obj);
Try/Catch with Context
try {
// risky call
} catch (e) {
log.error({ title:'Step X failed', details:{ msg: e.message, stack: e.stack, input: JSON.stringify(input).slice(0,500) }});
throw e; // or map to user-friendly message
}
Backoff for Transient Errors
async function backoff(fn, max=5, base=300){
let i=0; while(i<max){ try{ return await fn(); } catch(e){ await wait(base*Math.pow(2,i++)); } }
throw new Error('Max retries');
}
Validate Before Set
function safeSet(rec, fieldId, value){
if (value === null || value === undefined || value === '') return;
rec.setValue({ fieldId, value });
}
🔹 Logging & Debugging Tips
- Always log identifiers (record id, type, partner id) and the count/size of arrays.
- Truncate payload logs (first 500–1000 chars) to avoid size limits.
- Add a “debug mode” script parameter to enable verbose logs safely.
- For MR, log phase (getInputData/map/reduce/summary) and totals processed.
🔹 Preventing Recurrence (Design Tips)
- Configurable Saved Searches instead of hardcoded filters.
- Custom Records for partner mappings (IDs, doc versions, accounts).
- Idempotency keys for inbound EDI/REST to avoid duplicates.
- Feature flags via script params to toggle behaviors without redeploys.
✅ Quick Reference: Choose the Right Tool
- Large data → Map/Reduce
- User interaction → Suitelet (+ Client Script)
- One-off nightly tasks → Scheduled Script
- Real-time validations → User Event (lightweight) / Client Script
- Integrations → RESTlet/Suitelet JSON endpoints + external translator/iPaaS as needed