Performance is one of the biggest concerns when building SuiteScript solutions at scale. Every time your script looks up the same configuration record, calls an external API, or re-runs an expensive search, you burn governance units and add latency. The N/cache module gives you a simple, built-in way to store the results of expensive operations in memory and reuse them across script executions, dramatically reducing redundant work.
In this guide we will walk through what the N/cache module is, how to create and use a cache, how the loader pattern keeps your code clean, a real-world example, and the best practices that keep your cached data fresh and reliable.
What Is the N/cache Module?
The N/cache module lets you store key-value data in a named, in-memory cache that NetSuite manages for you. Instead of repeating a costly lookup on every execution, you store the result the first time and read it from the cache on subsequent runs. Caches are scoped so you can control how widely the data is shared, and each entry can have a time-to-live (TTL) so stale data automatically expires.
There are three scopes to understand. PRIVATE limits the cache to the current script, PROTECTED shares it across scripts in the same bundle, and PUBLIC makes it available to any script in the account. Choosing the right scope matters for both correctness and security.
Creating and Using a Cache
You load a cache with cache.getCache(), passing a name and a scope. The cache object exposes three core methods: get() to retrieve a value, put() to store one, and remove() to delete one. The real power is in get(), which accepts a loader function that runs only when the key is missing from the cache. This loader pattern means you never have to write the if-present-else-fetch logic yourself.
Code Examples
Example 1 stores and retrieves a value using the loader pattern, which is the recommended approach for most use cases.
/**
* @NApiVersion 2.1
* @NScriptType Suitelet
*/
define(['N/cache', 'N/search'], (cache, search) => {
const onRequest = (context) => {
// Load (or create) a private cache named 'configCache'
const configCache = cache.getCache({
name: 'configCache',
scope: cache.Scope.PRIVATE
});
// get() runs the loader ONLY if the key is missing
const settings = configCache.get({
key: 'companyPrefs',
ttl: 3600, // seconds the value stays cached
loader: () => {
const lookup = search.lookupFields({
type: search.Type.COMPANY_INFORMATION,
id: '1',
columns: ['companyname', 'legalname']
});
return JSON.stringify(lookup);
}
});
context.response.write(settings);
};
return { onRequest };
});
Example 2 caches the result of an expensive saved search so repeated executions do not re-run the query.
define(['N/cache', 'N/search'], (cache, search) => {
const getActiveItems = () => {
const itemCache = cache.getCache({
name: 'itemListCache',
scope: cache.Scope.PUBLIC
});
const data = itemCache.get({
key: 'activeItems',
ttl: 1800,
loader: () => {
const results = [];
search.create({
type: search.Type.INVENTORY_ITEM,
filters: [['isinactive', 'is', 'F']],
columns: ['itemid', 'baseprice']
}).run().each((r) => {
results.push({
id: r.id,
name: r.getValue('itemid'),
price: r.getValue('baseprice')
});
return true;
});
return JSON.stringify(results);
}
});
return JSON.parse(data);
};
return { getActiveItems };
});
Example 3 shows how to invalidate a cache entry with remove() when the underlying data changes, so consumers fetch fresh data on the next call.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/cache'], (cache) => {
// After a config record is edited, clear the cached copy
const afterSubmit = (context) => {
if (context.type === context.UserEventType.EDIT) {
const configCache = cache.getCache({
name: 'configCache',
scope: cache.Scope.PRIVATE
});
configCache.remove({ key: 'companyPrefs' });
}
};
return { afterSubmit };
});
A Real-World Use Case
Imagine a Suitelet that renders a product catalog page and is hit hundreds of times per minute during a sale. Without caching, every request runs the same inventory search, quickly exhausting governance and slowing the page. By wrapping that search in a PUBLIC cache with a 30-minute TTL, the search runs at most once every half hour while every other request reads instantly from memory. When a price changes, a User Event script calls remove() to clear the entry so customers always see accurate pricing.
When to Use the N/cache Module
Reach for N/cache when you repeatedly read data that is expensive to compute but changes infrequently: configuration records, currency or tax tables, mapping data, or the results of slow saved searches. It is not a database, so do not store large objects, anything that must be guaranteed durable, or per-user transactional data. Cache entries can be evicted at any time, so your loader must always be able to rebuild the value.
Best Practices
Always set a sensible TTL so data refreshes automatically and you never serve indefinitely stale values. Keep cache values small and serialize them as strings with JSON.stringify, since caches store text. Use the narrowest scope that works: prefer PRIVATE unless other scripts genuinely need the data. Treat a cache miss as normal by writing loaders that can always rebuild the value. Finally, invalidate proactively with remove() whenever the source data changes, rather than relying on TTL alone.
The N/cache module is one of the simplest ways to make your SuiteScript faster and more governance-friendly. By caching expensive lookups and slow searches behind a clean loader pattern, you cut redundant work while keeping your code readable. Start small with a single PRIVATE cache around your slowest lookup, measure the difference, and expand from there.
Discover more from The NetSuite Pro
Subscribe to get the latest posts sent to your email.
Leave a Reply