A drop-in NetSuite RESTlet that closes the gap Chartstone’s
/script endpoint can’t reach: file-cabinet ops,
roles & permissions, custom records and fields of every type,
and generic record CRUD — 29 discoverable functions, all behind a
single /restlet call.
Chartstone’s /script endpoint is a fantastic
ad-hoc shell — but it runs in NetSuite’s “anonymous”
execution context where most NS modules aren’t pre-loaded.
That’s fine for SuiteQL-style work, but it can’t do
customization-record CRUD: creating a custom field with a
TEXT, DATE, CURRENCY type
(or any of the other 19 field types), creating a custom record
type, assigning role permissions — these all depend on validator
and formatter functions that NS lazy-loads from modules
/script can’t reach. Same operations from
/script either crash with
field.validateAndFormatFieldValue is not a function
or never get past the create step.
The Toolkit RESTlet runs in a real
define([…], cb) context with all the modules
pre-declared. Same operations work cleanly. Once installed, an
AI agent calls it through Chartstone’s
/restlet endpoint and gets a single, discoverable
dispatch surface for the whole SuiteScript dev workflow.
meta — service info + function cataloghelp — per-function or full docsrequestEcho — debugfileCreate, fileGet, fileDelete, fileEnumerationsGetfolderCreate, folderGet, folderDelete, folderList
fileDelete is unique to the Toolkit — Tim Dietrich’s
original File Cabinet RESTlet doesn’t expose it.
roleCreate, roleGet, rolePermissionSet (auto-routes by prefix)employeeRoleAssign (idempotent), employeeRoleRemovecustomRecordTypeCreate, customRecordTypeGetcustomListCreate, customListGetcustomFieldCreate — works for
all 22 NS field types, all 6 field families
(entity / body / column / item / other / crm)
customFieldGet, customFieldUpdatecustomSegmentCreaterecordCreate, recordLoad, recordUpdate, recordSubmitFields, recordDelete1. Download the RESTlet
Grab chartstone-toolkit.restlet.js — single file, ~1,000 lines, no dependencies.
2. Upload to your NetSuite File Cabinet
You can drag-and-drop the file into Documents → Files → SuiteScripts via the NetSuite UI, or — if you’ve already got Tim Dietrich’s File Cabinet RESTlet deployed — upload it programmatically:
curl -sS -X POST "http://127.0.0.1:<port>/restlet" \
-H "Authorization: Bearer <your-secret>" \
-H "Content-Type: application/json" \
-d '{
"scriptId": "<your-file-cabinet-restlet-script-id>",
"deploymentId": "<your-file-cabinet-restlet-deploy-id>",
"method": "POST",
"payload": {
"function": "fileCreate",
"name": "chartstone-toolkit.restlet.js",
"fileType": "JAVASCRIPT",
"encoding": "UTF-8",
"folderID": -15,
"isOnline": false,
"overwrite": true,
"contents": "<file contents>"
}
}'
3. Register as a Script + Deployment
NS 2026.1 dropped script-record creation via SuiteScript, so this step is manual (~30 seconds):
@NScriptType Restlet from the JSDoc.Chartstone Toolkit and
ID: _chartstone_toolkit_rl.
Chartstone Toolkit — Deployment 1;
ID = _chartstone_toolkit_dep1;
Status = Released; Audience
= Roles → Administrator only (recommended);
Log Level = Debug (move to Error after stable).
script=NNNN&deploy=N.4. Tell your AI agent the IDs
Drop the two IDs into your CLAUDE.md (or whatever
memory your agent reads). After that, calls go through
Chartstone’s /restlet endpoint:
curl -sS -X POST "http://127.0.0.1:<port>/restlet" \
-H "Authorization: Bearer <your-secret>" \
-H "Content-Type: application/json" \
-d '{
"scriptId": "<toolkit-script-id>",
"deploymentId": "<toolkit-deploy-id>",
"method": "POST",
"payload": { "function": "<name>", ...args... }
}'
Create a TEXT field on Customer
{
"function": "customFieldCreate",
"fieldFamily": "entity",
"label": "Loyalty Tier",
"scriptid": "loyalty_tier",
"fieldtype": "TEXT",
"appliesTo": ["customer"]
}
Create a CURRENCY field on Sales Orders + Invoices
{
"function": "customFieldCreate",
"fieldFamily": "body",
"label": "Negotiated Discount",
"scriptid": "neg_discount",
"fieldtype": "CURRENCY",
"appliesTo": ["salesorder", "invoice"]
}
Set a role permission
{
"function": "rolePermissionSet",
"roleId": 2088,
"permkey": "TRAN_SALESORD",
"level": "FULL"
}
Create a custom list with values
{
"function": "customListCreate",
"name": "Priority Levels",
"scriptid": "priority_levels",
"values": [
{ "value": "Low" },
{ "value": "Medium" },
{ "value": "High" }
]
}
The toolkit is self-documenting. Three discovery calls let an agent reflect on what’s available without you naming functions in advance:
# Service info + full function catalog
{ "function": "meta" }
# Per-function documentation
{ "function": "help", "name": "customFieldCreate" }
# All function docs at once
{ "function": "help" }
Success and error responses follow a stable envelope so callers can branch on a single key:
// success
{ "function": "<name>", "version": "0.1.0", ...result fields... }
// error
{
"function": "<name>",
"version": "0.1.0",
"error": "human-readable message",
"errorCode": "MACHINE_CODE",
"detail": { /* optional context */ }
}
NetSuite auto-prefixes script IDs without a separator. The Toolkit normalizes whatever the caller provides so agents don’t have to remember the rules:
| You pass | Stored as |
|---|---|
customrecord_my_thing | customrecord_my_thing |
_my_thing | customrecord_my_thing |
my_thing | customrecord_my_thing |
Same convention for customlist_*, custentity_*,
custbody_*, custcol_*, custitem_*,
custevent_*, custrecord_* (Other Fields).
The caller must have, at minimum:
Administrator covers all of them. The recommended deployment audience is Administrator only — this is a power tool.
INVALID_RCRD_TYPE.
Workaround: split into two requests.
record.create({ type: 'suitelet' })
errors with RECORD_CANNOT_BE_CREATED_OR_DELETED.
Use the UI or SDF.
submitFields
are not supported by NS. The Toolkit’s customFieldUpdate
falls back to load+save for that family.