🔹 Introduction
Scheduled Scripts run automatically in the background at set intervals or when triggered manually. Unlike Client or User Event scripts, they don’t depend on a user opening or editing a record.
They are perfect for:
- Running batch jobs (e.g., nightly updates)
- Cleaning up data
- Sending reminders or notifications
- Processing records that don’t need to be handled in real-time
🔹 How They Work
- Written in SuiteScript 2.1
- Deployed under Customization → Scripting → Script Deployments
- Can be scheduled daily, weekly, monthly, or triggered on-demand
- Can process thousands of records (but heavy jobs may require Map/Reduce)
🔹 Example 1: Simple Log Message
What this script does:
- Runs in the background and logs a message (useful as a template to test your first deployment).
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define([], () => {
const execute = () => {
try {
log.debug('Scheduled Script', 'Hello! This is my first scheduled script.');
} catch (e) {
log.error('Error in scheduled script', e.message);
}
};
return { execute };
});
🔹 Example 2: Update a Field on Multiple Records
What this script does:
- Finds inactive customers and reactivates them.
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search', 'N/record'], (search, record) => {
const execute = () => {
try {
const customerSearch = search.create({
type: search.Type.CUSTOMER,
filters: [['isinactive', 'is', 'T']],
columns: ['entityid']
});
const results = customerSearch.run().getRange({ start: 0, end: 10 });
results.forEach(result => {
try {
const custId = result.id;
record.submitFields({
type: record.Type.CUSTOMER,
id: custId,
values: { isinactive: false }
});
log.debug('Updated', `Customer ${custId} reactivated.`);
} catch (innerErr) {
log.error('Error updating customer', innerErr.message);
}
});
} catch (e) {
log.error('Error in scheduled script', e.message);
}
};
return { execute };
});
🔹 Example 3: Send Reminder Emails
What this script does:
- Finds open Sales Orders and emails an admin summary.
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search', 'N/email', 'N/runtime'], (search, email, runtime) => {
const execute = () => {
try {
const soSearch = search.create({
type: search.Type.SALES_ORDER,
filters: [['status', 'anyof', 'SalesOrd:A']], // Pending Approval
columns: ['tranid', 'entity']
});
const results = soSearch.run().getRange({ start: 0, end: 5 });
let body = 'Open Sales Orders:\n';
results.forEach(result => {
body += `SO# ${result.getValue('tranid')} - Customer: ${result.getText('entity')}\n`;
});
if (results.length > 0) {
email.send({
author: runtime.getCurrentUser().id,
recipients: 'admin@company.com',
subject: 'Pending Sales Orders Reminder',
body: body
});
log.debug('Success', 'Reminder email sent.');
}
} catch (e) {
log.error('Error in scheduled script', e.message);
}
};
return { execute };
});
🔹 Example 4: Chunk Processing with Governance Handling
What this script does:
- Loops through results and checks usage limits, yielding control if running low.
/**
*@NApiVersion 2.1
*@NScriptType ScheduledScript
*/
define(['N/search', 'N/record', 'N/runtime'], (search, record, runtime) => {
const execute = () => {
try {
const script = runtime.getCurrentScript();
const results = search.create({
type: search.Type.CUSTOMER,
filters: [['isinactive', 'is', 'F']],
columns: ['entityid']
}).run().getRange({ start: 0, end: 100 });
for (let i = 0; i < results.length; i++) {
try {
const custId = results[i].id;
log.debug('Processing', `Customer ID: ${custId}`);
// Example operation: update phone field
record.submitFields({
type: record.Type.CUSTOMER,
id: custId,
values: { phone: '555-123-0000' }
});
// Check governance usage
if (script.getRemainingUsage() < 100) {
log.debug('Governance', 'Rescheduling due to low usage.');
return; // exit early, will be rescheduled automatically
}
} catch (innerErr) {
log.error('Error updating record', innerErr.message);
}
}
} catch (e) {
log.error('Error in scheduled script', e.message);
}
};
return { execute };
});
🔹 Best Practices
- Use
submitFields()
for lightweight updates (faster thanrecord.load()
+save()
). - Always check remaining usage (
runtime.getCurrentScript().getRemainingUsage()
). - Split large jobs into batches or use Map/Reduce for big data.
- Wrap each operation in try–catch to prevent one bad record from stopping the script.
- Schedule during off-hours to reduce performance impact.
✅ Key Takeaway
Scheduled Scripts are the backbone of background automation in NetSuite. Use them for batch jobs, cleanup, reminders, and lightweight data updates. For heavy-lifting (thousands of records), move to Map/Reduce.