NetSuite SDF開発用のインタラクティブ(双方向の)学習システムです。 学習、復習、説明、コメント追加、クイズ、最終テストの6つのモード、およびSAFE Guide(開発ガイドラインに関する包括的な参考資料)との連携を備えています。コンプライアンス(法令や規定への適合)を確認した学習資料を生成します。 SAFE Guide の原則に基づいて、ガバナンス(組織運営の方針)、N/cache(キャッシュ機構)、セキュリティ(情報保護)といったトピックを直接学習できます。また、コードやSAFE Guideの内容からクイズを自動生成することも可能です。 **次のような場合に使用:** - NetSuite SDF開発スキルを体系的に習得したいとき - コンプライアンス対応の学習資料が必要なとき - 開発知識の定着度をクイズで確認したいとき
Interactive learning system for NetSuite SDF development. Features six modes (learn, review, explain, annotate, quiz, and final) with SAFE Guide integration. Produces compliance-reviewed learning documentation. Learn topics like governance, N/cache, and security directly from SAFE Guide principles. Generate quizzes from code or SAFE Guide content.
Created by: Oracle NetSuite
Interactive learning system for NetSuite SuiteScript and SDF projects with SAFE Guide integration. This skill provides:
--source=code), SAFE Guide principles (--source=safe), or bothCovers all 14 script types, deployment configurations, performance patterns, defensive coding practices, and governance limits.
Invoke this skill at any time by typing:
/netsuite-suitescript-learning
Note: /netsuite-suitescript-learning is the full skill command, while /suitescript-learning may be available as a coding assistant alias where supported.
Or use specific mode commands:
/netsuite-suitescript-learning learn [topic] # Learn a SAFE Guide topic
/netsuite-suitescript-learning review [filename] # Review a file and identify learning concepts
/netsuite-suitescript-learning explain [concept] # Deep dive into a specific concept
/netsuite-suitescript-learning annotate [filename] # Add inline learning comments to code
/netsuite-suitescript-learning quiz [section] # Generate quiz questions
/netsuite-suitescript-learning final # Generate a comprehensive learning document
For Claude Code, add this to your project's .claude/settings.local.json:
{
"permissions": {
"allow": [
"Skill(netsuite-sdf-safe-guide)",
"Skill(netsuite-suitescript-learning)"
]
}
}
With both skills enabled, an assistant will:
When this skill is active and code is being created (not just reviewed), the following annotations should be automatically included:
This ensures educational content is embedded as code is written, not added as an afterthought.
| Mode | Trigger | Purpose |
|---|---|---|
learn |
When learning a SAFE Guide topic | Topic-based learning from SAFE Guide principles |
review |
After creating a script file | Generate concepts and questions for that specific file |
explain |
When user asks for explanation | Deep-dive into specific code patterns |
annotate |
When the user asks to annotate code with inline comments | Add inline learning comments to existing code |
quiz |
After completing a section | Generate quiz questions with answers |
final |
At project completion | Consolidate all learning into comprehensive documentation |
/netsuite-suitescript-learning [mode] [target] [options]
Modes:
learn – Topic-based learning from SAFE Guide principles
review – Review a specific file and identify learning concepts
explain – Explain a specific concept or code pattern
annotate – Add inline learning comments to existing code
quiz – Generate quiz questions for recent code
final – Generate final comprehensive learning document
Target:
- File path for review/annotate mode
- Concept name for explain mode
- "all" or section name for quiz mode
- Topic keyword for learn mode (see SAFE Guide Learning Topics)
Options:
--source=code Quiz questions from user's code only (quiz mode)
--source=safe Quiz questions from SAFE Guide principles only (quiz mode)
--source=owasp Quiz questions from OWASP secure coding practices only (quiz mode)
--source=both Quiz questions from both sources (default for quiz mode)
Examples:
/suitescript-learning learn ncache
/suitescript-learning learn governance
/suitescript-learning review quick_add_ue.js
/suitescript-learning explain beforeLoad
/suitescript-learning annotate quick_add_cs.js
/suitescript-learning quiz user-event-scripts
/suitescript-learning quiz --source=safe
/suitescript-learning final
review)When reviewing a SuiteScript file, identify and document:
Detect the script type from JSDoc annotations and provide:
For each code pattern found, document:
## Code Review: [filename]
### Script Overview
- **Type**: [UserEventScript/ClientScript/Suitelet/RESTlet/etc.]
- **Execution Context**: [Server/Client]
- **Entry Points**: [list of entry points used]
### Key Concepts Identified
#### 1. [Concept Name]
**Lines**: [X–Y]
**What**: [explanation]
**Why**: [rationale]
**Pitfall**: [common mistake]
**Best Practice**: [recommendation]
#### 2. [Next Concept]
...
### Quiz Questions for This File
1. [Question about concept 1]
2. [Question about concept 2]
...
explain)Provide deep-dive explanations for specific concepts:
User Event Script Concepts:
beforeLoad – Form modification before renderbeforeSubmit – Validation before saveafterSubmit – Post-save processingcontext.type – Record access modesform.addButton() – Custom buttonsform.clientScriptModulePath – Linking client scriptsClient Script Concepts:
pageInit – Page load initializationsaveRecord – Save validationvalidateField – Field-level validationfieldChanged – Reactive field handlingcurrentRecord.get() – Accessing record dataselectNewLine/commitLine – Sublist manipulationwindow.opener – Parent window communicationSuitelet Concepts:
onRequest – HTTP request handlingGET vs POST – HTTP method routingresponse.write() – Sending responsesserverWidget.Form – NetSuite formsRESTlet Concepts:
get/post/put/delete handlersSearch & Query Concepts:
N/search – Saved searchesN/query – SuiteQLsearch.run().each() – Result iterationDeployment Concepts:
<runasrole> and <allroles> support by type<recordtype> requirementsDefensive Coding Concepts:
runtime.executionContext – Check how script was triggeredsearch.lookupFields() – Check record exists before loadingGovernance Concepts:
Script.getRemainingUsage() – Monitor remaining usage unitsSSS_USAGE_LIMIT_EXCEEDED – Script exceeded usage unitsSSS_TIME_LIMIT_EXCEEDED – Script exceeded time limitlog.* ignored, use console.log()## Concept: [Name]
### What It Is
[Clear explanation]
### When to Use It
[Use cases and scenarios]
### How It Works
[Technical details with code examples]
### Example Code
```javascript
[Relevant code snippet]
```
### Common Mistakes
1. [Mistake 1]
2. [Mistake 2]
### Best Practices
1. [Practice 1]
2. [Practice 2]
### Related Concepts
- [Link to related concept 1]
- [Link to related concept 2]
### SAFE Guide Reference
This concept relates to **Principle [X]: [Name]**
Key points from the SAFE Guide:
- [Point 1 from SAFE Guide]
- [Point 2 from SAFE Guide]
See: `../netsuite-sdf-safe-guide/references/[XX-filename.md]`
When explaining concepts, automatically reference relevant SAFE Guide principles:
| Concept Category | SAFE Guide Principle | Reference File |
|---|---|---|
| Governance, Usage Units | Principle 2 | 02-governance-usage-units.md |
| N/cache, Performance | Principle 3 | 03-performance-optimization.md |
| N/query, SuiteQL | Principle 3 | 03-performance-optimization.md |
| Script Coexistence | Principle 4 | 04-multi-suiteapp-environment.md |
| Security, Permissions | Principle 5 & 11 | 05-security-privacy.md, 11-security-best-practices.md |
| Testing, SDN | Principle 6 | 06-testing-suiteapps.md |
| Map/Reduce, Scheduled | Principle 2 & 3 | 02-governance-usage-units.md |
| RESTlet vs Suitelet | Appendix | appendices/appendix-concurrency-cheatsheet.md |
| Legacy TBA Exceptions | Principle 5 | 05-security-privacy.md |
| OWASP, XSS, Injection | OWASP Skill | netsuite-owasp-secure-coding/SKILL.md |
annotate)Add inline learning comments directly into code files. This embeds educational content where developers will see it as they work with the code.
CONCEPT Comments – Brief inline explanations
// CONCEPT: context.type tells us how the record is being accessed
if (context.type !== context.UserEventType.VIEW)
LEARNING NOTES Comments – Block explanations in JSDoc
/**
* beforeLoad Entry Point
*
* LEARNING NOTES:
* - This function executes BEFORE the form is sent to the browser
* - Perfect place to modify the form (add fields, buttons, sublists)
* - context.form gives access to the N/ui/serverWidget.Form object
*
* @param {Object} context – Contains form, record, type (view/edit/create)
*/
const beforeLoad = (context) => {
const form = context.form;
};
Parameters Comments – Explain function parameters
// Parameters:
// id: Unique identifier (prefix with custpage_ for custom buttons)
// label: What the user sees
// functionName: Client Script function to call when clicked
form.addButton({
id: 'custpage_quick_add_items',
label: 'Quick Add Items',
functionName: 'openQuickAddDialog'
});
JSDoc Header Annotations
Inline CONCEPT Comments
Code Block Explanations
Return Statement Comments
| Location | Comment Type | Purpose |
|---|---|---|
| File header | LEARNING NOTES in JSDoc | Overall script purpose and type |
| Entry point functions | LEARNING NOTES block | Function behavior and parameters |
| Module imports | CONCEPT comment | Why each module is needed |
| Conditionals | CONCEPT comment | Why this check is performed |
| API calls | Parameters comment | What each parameter does |
| Complex logic | Numbered steps | Break down multi-step operations |
| Return statements | CONCEPT comment | What's being exposed and why |
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
* @NModuleScope SameAccount
*
* @description User Event Script to add "Quick Add Items" button to Sales Orders
*
* LEARNING NOTES:
* - @NApiVersion 2.1 tells NetSuite to use SuiteScript 2.1 (modern JS support)
* - @NScriptType UserEventScript identifies this as a UE script
* - @NModuleScope SameAccount restricts execution to same NetSuite account
* - User Event Scripts run on the SERVER, not in the browser
*/
// CONCEPT: define() is the AMD module pattern; loads dependencies
define(['N/ui/serverWidget', 'N/runtime'], (serverWidget, runtime) => {
/**
* beforeLoad Entry Point
*
* LEARNING NOTES:
* - Executes BEFORE the form is sent to the browser
* - Perfect place to modify the form (add fields, buttons, sublists)
* - context.form gives access to the N/ui/serverWidget.Form object
* - Changes made here appear when the page loads
*
* @param {Object} context – Contains form, record, type (view/edit/create)
*/
const beforeLoad = (context) => {
try {
// CONCEPT: context.type tells us how the record is being accessed
// We only want the button in edit or create mode, not view-only
if (context.type !== context.UserEventType.VIEW) {
// CONCEPT: context.form is the N/ui/serverWidget.Form object
// This gives us access to modify the form before rendering
const form = context.form;
// CONCEPT: addButton() adds a button to the form's toolbar
// Parameters:
// id: Unique identifier (prefix with custpage_ for custom buttons)
// label: What the user sees
// functionName: Client Script function to call when clicked
form.addButton({
id: 'custpage_quick_add_items',
label: 'Quick Add Items',
functionName: 'openQuickAddDialog'
});
// CONCEPT: clientScriptModulePath links a Client Script to this form
// The Client Script will contain our openQuickAddDialog function
// Path is relative to this script's location in the File Cabinet
form.clientScriptModulePath = './quick_add_cs.js';
}
} catch (error) {
log.error('beforeLoad Error', error.message);
}
};
// CONCEPT: Return object exposes entry points to NetSuite
// Only functions returned here are recognized as entry points
// Custom helper functions inside the module stay private
return {
beforeLoad: beforeLoad
};
});
| Script Complexity | Annotations Per 10 Lines |
|---|---|
| Simple/Short | 2–3 annotations |
| Medium | 3–5 annotations |
| Complex | 5–7 annotations |
Too Few: Code is hard to understand for learners. Too Many: Code becomes cluttered and hard to read.
When annotating a file, provide:
## Annotation Summary: [filename]
### Annotations Added
- LEARNING NOTES blocks: [X]
- CONCEPT comments: [Y]
- Parameters comments: [Z]
### Coverage
- Entry points annotated: [X/Y]
- Complex logic blocks annotated: [X/Y]
- API calls annotated: [X/Y]
### Notes
[Any areas skipped or needing manual review]
learn)Topic-based learning from SAFE Guide references. This mode generates educational content summarized from the SAFE Guide principles and appendices.
| Topic Keyword | SAFE Guide Reference | Description |
|---|---|---|
features |
Principle 1 | NetSuite features, REST vs SOAP, SuiteScript 2.1 |
governance |
Principle 2 | Usage units, script type limits, optimization |
performance |
Principle 3 | N/cache, Map/Reduce, N/query, SuiteQL |
multi-suiteapp |
Principle 4 | Script coexistence, execution order |
security |
Principle 5 & 11 | Roles, permissions, OWASP, secure coding |
testing |
Principle 6 | Jest testing, SDN environments, phased releases |
distribution |
Principle 7 | Managed SuiteApps, SuiteApp Control Center |
maintenance |
Principle 8 | Versioning, deployment, publishing |
licensing |
Principle 9 | IP protection, click-through agreements |
open-source |
Principle 10 | License compliance, prohibited licenses |
tba |
Appendix | Token-Based Authentication headers |
concurrency |
Appendix | Concurrency limits, error handling |
nquery |
Appendix | Multi-level joins with N/query |
ncache |
Appendix | Caching for concurrent processing |
owasp |
OWASP Skill | OWASP Top 10, injection, XSS, access control, secure coding |
netsuite-sdf-safe-guide skill first, then read the relevant reference file from ../netsuite-sdf-safe-guide/references/.## Learning Topic: [Topic Name]
### Overview
[Brief summary of the topic; 2–3 sentences explaining what this covers and why it matters]
### Key Concepts
1. **[Concept 1]**: [Explanation with code example if applicable]
2. **[Concept 2]**: [Explanation]
3. **[Concept 3]**: [Explanation]
### Best Practices
- [Practice 1]
- [Practice 2]
- [Practice 3]
### Common Pitfalls
| Pitfall | Consequence | Solution |
|---------|-------------|----------|
| [Issue 1] | [What happens] | [How to fix] |
| [Issue 2] | [What happens] | [How to fix] |
### Code Example
```javascript
[Relevant code snippet from SAFE Guide demonstrating the concept]
```
### Quick Quiz
1. [Question about this topic]
2. [Question about this topic]
### Related Topics
- [Link to related SAFE Guide principle or topic]
- [Another related topic]
### Source
Summarized from SAFE Guide Principle [X]: [Name]
Reference file: `../netsuite-sdf-safe-guide/references/[XX-filename.md]`
/suitescript-learning learn ncache## Learning Topic: N/cache for Concurrent Processing
### Overview
The N/cache module provides server-side caching to reduce redundant API calls and improve
performance in high-concurrency scenarios. Essential for Map/Reduce scripts and any
situation where multiple script executions need to share computed data.
### Key Concepts
1. **Cache Scopes**: PRIVATE (single script), PROTECTED (same bundle), PUBLIC (all scripts)
2. **Cache Loaders**: Functions that compute values on cache miss
3. **TTL (Time-To-Live)**: How long cached data remains valid
### Best Practices
- Use Scope.PROTECTED for SuiteApp-internal caching
- Keep cached data serializable (no functions, circular references)
- Set appropriate TTL based on data volatility
### Common Pitfalls
| Pitfall | Consequence | Solution |
|---------|-------------|----------|
| Using Scope.PUBLIC | Data visible to all scripts in account | Use PROTECTED for SuiteApps |
| Caching non-serializable data | Runtime errors | Only cache JSON-safe objects |
| No TTL consideration | Stale data served | Set TTL based on data freshness needs |
### Code Example
```javascript
define(['N/cache'], (cache) => {
const configCache = cache.getCache({
name: 'myAppConfig',
scope: cache.Scope.PROTECTED
});
const getConfig = () => {
return configCache.get({
key: 'settings',
loader: () => {
// This runs only on cache miss
return loadConfigFromRecord();
},
ttl: 300 // 5 minutes
});
};
});
```
### Quick Quiz
1. When should you use Scope.PROTECTED vs Scope.PUBLIC?
2. What happens when the cache loader function is called?
### Related Topics
- Performance optimization (Principle 3)
- Map/Reduce scripts
- Governance limits
### Source
Summarized from SAFE Guide Appendix: N/cache Sample Implementation
Reference file: `../netsuite-sdf-safe-guide/references/appendices/appendix-ncache-sample.md`
quiz)Generate quiz questions with answers based on written code.
Type 1: Conceptual Understanding
Q: What is the difference between beforeLoad and beforeSubmit entry points?
A: beforeLoad runs when the form is being built (before render), while beforeSubmit
runs when the user clicks Save (before the record is written to the database).
Type 2: Code Prediction
Q: What will happen if you call form.addButton() in afterSubmit instead of beforeLoad?
A: Nothing visible; the form has already been rendered and submitted. The button
would never appear because afterSubmit runs after the save operation completes.
Type 3: Error Identification
Q: This RESTlet deployment XML will fail. Why?
<scriptdeployment>
<runasrole>ADMINISTRATOR</runasrole>
<allroles>T</allroles>
</scriptdeployment>
A: RESTlets do not support <runasrole> or <allroles> elements. These must be removed.
Type 4: Best Practice
Q: Why do we use url.resolveScript() instead of hardcoding a Suitelet URL?
A: resolveScript() dynamically generates the correct URL for the current environment
(sandbox vs production), handles URL encoding, and includes necessary parameters
like company ID and deployment ID.
Type 5: Fill in the Blank
Q: To add a line to a sublist, you must call three methods in order:
_______, setCurrentSublistValue(), and _______.
A: selectNewLine(), commitLine()
Type 6: SAFE Guide Principle Application
Q: According to the SAFE Guide, why should you use N/cache with Scope.PROTECTED
when multiple scripts need to share cached data?
A: Scope.PROTECTED allows cache sharing across all scripts in the same SuiteApp
bundle while isolating data from other SuiteApps. This provides data privacy
between different publishers' SuiteApps.
Type 7: Governance Scenario
Q: A User Event Script is taking too long. According to SAFE Guide Principle 2,
what's the recommended approach when you need to process 500+ records?
A: Offload heavy processing to a Map/Reduce script using N/task. User Event
Scripts have a 1,000 unit limit; Map/Reduce has 10,000 units per stage.
This pattern is called "async offloading."
Type 8: Architecture Decision
Q: You need to make AJAX calls from a popup Suitelet. According to Principle 3,
why should you not use a RESTlet for this?
A: RESTlets count against the Web Services concurrent user limit (typically 5).
Use the Suitelet-as-API pattern instead, which uses the user's existing session
and doesn't consume web services slots.
Type 9: OWASP Security Application (--source=owasp or --source=both)
Q: This RESTlet accepts a customer ID from the URL and uses it in a SuiteQL query.
What OWASP vulnerability is present in this code?
const id = context.request.parameters.custId;
const sql = "SELECT * FROM Customer WHERE id = " + id;
A: SQL Injection (OWASP A03:2021). The customer ID is concatenated directly into
the query string without validation or parameterization. Fix: use parameterized
query with ? placeholder: query.runSuiteQL({ query: 'SELECT * FROM Customer WHERE id = ?', params: [parseInt(id, 10)] })
| Source | Flag | Description |
|---|---|---|
| Code Only | --source=code |
Questions from user's code patterns (Types 1–5) |
| SAFE Guide | --source=safe |
Questions from SAFE Guide principles (Types 6–8) |
| OWASP | --source=owasp |
Questions from OWASP secure coding practices (Type 9) |
| Combined | --source=both |
All code patterns, SAFE Guide, AND OWASP (default) |
Question Distribution by Source:
| Source | Type Distribution |
|---|---|
--source=code |
40% Conceptual, 25% Code Prediction, 15% Error ID, 15% Best Practice, 5% Fill-in |
--source=safe |
40% Principle Application, 35% Governance Scenario, 25% Architecture Decision |
--source=owasp |
100% OWASP Security Application (Type 9) |
--source=both |
Mix of all 9 types, weighted toward user's code patterns |
## Quiz: [Section/Topic Name]
### Questions
**1. [Question text]**
**2. [Question text]**
**3. [Question text]**
**4. [Question text]**
**5. [Question text]**
---
### Answer Key
**1.** [Full answer with explanation]
**2.** [Full answer with explanation]
**3.** [Full answer with explanation]
**4.** [Full answer with explanation]
**5.** [Full answer with explanation]
final)Generate comprehensive learning documentation for the entire project, including a SAFE Guide compliance review.
# [Project Name] – Learning Guide
## Project Overview
[Description of what was built and why]
## Architecture Diagram
[ASCII or text-based architecture visualization]
## Learning Objectives
By completing this project, you should understand:
- [ ] Objective 1
- [ ] Objective 2
- [ ] Objective 3
---
## Section 1: [Component Name]
### What This Section Accomplishes
[Plain English description]
### Key Concepts
[List of concepts with brief explanations]
### Code Walkthrough
[Annotated code with LEARNING NOTES comments]
### Section Quiz
[5 questions specific to this section]
---
## Section 2: [Next Component]
...
---
## Comprehensive Quiz
### All Questions (Combined)
[All questions from all sections]
### Answer Key
[All answers with detailed explanations]
---
## SAFE Guide Compliance Review
### Principles Applied
| Principle | Status | Notes |
|-----------|--------|-------|
| 1. Use NetSuite Features | ✅ Applied | Using native SuiteScript modules |
| 2. Governance | ✅ Applied | Script uses getRemainingUsage() checks |
| 3. Performance | ⚠️ Review | Consider N/cache for repeated lookups |
| 4. Multi-SuiteApp | ✅ Applied | Defensive coding patterns used |
| 5. Security | ✅ Applied | Input validation implemented |
| 6. Testing | ⏳ Pending | Add Jest unit tests |
| 11. Secure Coding | ✅ Applied | No eval(), proper escaping |
### Recommendations
Based on SAFE Guide principles, consider:
1. [Specific recommendation based on code analysis]
2. [Another recommendation referencing SAFE Guide principle]
3. [Performance optimization suggestion from Principle 3]
### Reference Files Consulted
- `../netsuite-sdf-safe-guide/references/[relevant-files.md]`
---
## Common Pitfalls Reference
| Pitfall | Symptom | Solution |
|---------|---------|----------|
| **Missing N/log import** | Script fails silently, no errors logged | Add `'N/log'` to define() and `log` to callback parameters. |
| **Relative clientScriptModulePath in SuiteApp** | Button appears but click does nothing | Use full path: `/SuiteApps/com.publisher.appid/scripts/my_cs.js`. |
| **Using log.debug() without N/log** | Script throws error or fails silently | Import N/log module - it's not globally available in SS 2.x. |
| **RESTlet with runasrole/allroles** | Deployment fails with validation error | Remove `<runasrole>` and `<allroles>` from RESTlet XML. |
| **Missing SERVERSIDESCRIPTING feature** | Deployment fails | Add feature to manifest.xml dependencies. |
| **Wrong status value** | Script doesn't execute | Use RELEASED for most scripts, NOTSCHEDULED for MapReduce/Scheduled. |
| **Bracket notation missing in scriptfile** | Deployment fails, file not found | Wrap paths: `[/SuiteApps/path/file.js]`. |
| **Custom button without custpage_ prefix** | May conflict with native buttons | Always prefix custom element IDs with `custpage_`. |
| **RESTlet for user-facing AJAX** | Fails when 6+ users concurrent | Use Suitelet-as-API pattern instead (see below). |
| **window.opener not finding function** | "Could not communicate with parent window" | Use postMessage API + module-level listener (see below). |
| **pageInit not firing with clientScriptModulePath** | Event listeners never set up, code never runs | Put critical setup code at MODULE LEVEL, outside any function. |
| **Search includes salesdescription** | Returns unrelated items (false positives) | Search only `itemid` and `displayname` - descriptions often contain unexpected terms. |
| **Per-item pricing lookups (N+1)** | Search is very slow (~5 seconds) | Use batch lookup with `anyof` filter: `['item', 'anyof', itemIds]`. |
| **DEBUG logging in production** | Excessive log volume, performance impact | Change `<loglevel>` to `AUDIT` or `ERROR` in deployment XML. |
## Performance Optimization
### N+1 Query Problem
**CRITICAL:** Avoid running queries inside loops. This is the most common performance killer in SuiteScript.
**Bad Pattern (N+1):**
```javascript
// 1 search + 50 pricing lookups = 51 queries!
itemSearch.run().each((result) => {
const price = getItemPrice(result.id); // ← Separate query per item!
});
```
**Good Pattern (Batch):**
```javascript
// Collect IDs first, then ONE batch query
const itemIds = [];
itemSearch.run().each((result) => {
itemIds.push(result.id);
});
// Single batch lookup for ALL items
const prices = getBatchPricing(itemIds); // Uses ['item', 'anyof', itemIds]
```
**Performance Comparison:**
| Approach | Queries | Time (50 items) |
|----------|---------|-----------------|
| N+1 | 51 | ~5 seconds |
| Batch | 2 | ~200ms |
## Popup Communication in NetSuite
### The Problem with window.opener
NetSuite uses **frames/iframes** for its UI. When you open a popup (Suitelet), `window.opener` points to the **top-level window**, not the frame where your Client Script runs.
```javascript
// This FAILS in NetSuite:
window.opener.myFunction(data); // window.opener exists but myFunction is undefined
```
### The Solution: postMessage API
Use `postMessage` to broadcast messages to all frames:
**Client Script (module-level, NOT in pageInit):**
```javascript
define(['N/currentRecord'], (currentRecord) => {
// CRITICAL: Module-level code, not in pageInit
// pageInit may not fire with clientScriptModulePath
window.addEventListener('message', (event) => {
// SECURITY: Use anchored regex to prevent origin spoofing
// For example, "evil-netsuite.com" would pass .includes() but fails this check
if (!/^https:\/\/([a-z0-9-]+\.)*netsuite\.com$/.test(event.origin)) return;
if (event.data?.action === 'addItems') {
handleAddItems(event.data.items);
}
});
});
```
**Popup (Suitelet HTML):**
```javascript
function sendToParent(items) {
const message = { action: 'addItems', items };
// SECURITY: Use specific origin, never wildcard '*'
const targetOrigin = window.location.origin;
window.opener.postMessage(message, targetOrigin);
// Also post to all frames
for (let i = 0; i < window.opener.frames.length; i++) {
window.opener.frames[i].postMessage(message, targetOrigin);
}
window.close();
}
```
### Why Module-Level, Not pageInit?
When using `clientScriptModulePath` (set in User Event Script), `pageInit` **may not fire reliably**. Always put critical initialization at the module level:
```javascript
define(['N/currentRecord'], (currentRecord) => {
// ✅ GOOD: Module-level; always runs when script loads
console.log('Script loaded');
window.addEventListener('message', handler);
// ❌ BAD: pageInit; may not fire with clientScriptModulePath
const pageInit = (context) => {
window.addEventListener('message', handler); // May never execute!
};
});
```
## Concurrency Considerations
### RESTlet vs Suitelet for AJAX Calls
**CRITICAL:** RESTlets count against the Web Services Concurrent User Limit (typically 5). This is a major scalability concern.
| Script Type | Concurrency Model | Best For |
|-------------|-------------------|----------|
| **RESTlet** | Web Services slots (limited to 5) | External integrations, APIs |
| **Suitelet** | User sessions (unlimited) | User-facing features, AJAX |
### Suitelet-as-API Pattern
For popups, modals, and interactive features that need AJAX calls:
```javascript
// Single Suitelet handles both UI and API
const onRequest = (context) => {
const action = context.request.parameters.action;
if (action === 'search') {
// Return JSON for AJAX calls
context.response.setHeader({ name: 'Content-Type', value: 'application/json' });
context.response.write(JSON.stringify({ items: searchResults }));
} else {
// Return HTML for page load
context.response.write(generateHtmlPage());
}
};
```
**Benefits:**
- No Web Services concurrency limits
- Single script to maintain
- Uses existing user session (no extra auth)
- Scales with user base
## Quick Reference Card
### Module Import Pattern
```javascript
define(['N/search', 'N/record', 'N/log'], (search, record, log) => {
// Module names in array must match parameter order
});
```
### Client Script Path (SuiteApp)
```javascript
// CORRECT for SuiteApps:
form.clientScriptModulePath = '/SuiteApps/com.publisher.appid/scripts/my_cs.js';
// WRONG for SuiteApps (works in Account Customization only):
form.clientScriptModulePath = './my_cs.js';
```
### Sublist Line Addition Pattern
```javascript
record.selectNewLine({ sublistId: 'item' });
record.setCurrentSublistValue({ sublistId: 'item', fieldId: 'item', value: itemId });
record.setCurrentSublistValue({ sublistId: 'item', fieldId: 'quantity', value: qty });
record.commitLine({ sublistId: 'item' });
```
## Next Steps
[Suggestions for extending the project or learning more]
| Concept | Entry Point | Server/Client | Key Learning |
|---|---|---|---|
| Form Modification | beforeLoad | Server | Adding buttons, fields, sublists |
| Pre-Save Validation | beforeSubmit | Server | Data validation, field manipulation |
| Post-Save Actions | afterSubmit | Server | Triggered workflows, integrations |
| Context Types | context.type | Server | VIEW, EDIT, CREATE, COPY, etc. |
| Concept | Entry Point | Server/Client | Key Learning |
|---|---|---|---|
| Page Initialization | pageInit | Client | Initial state setup |
| Save Validation | saveRecord | Client | Preventing invalid saves |
| Field Validation | validateField | Client | Real-time field checking |
| Field Changes | fieldChanged | Client | Reactive UI updates |
| Sublist Operations | lineInit, validateLine | Client | Line-level handling |
| Custom Functions | (exported) | Client | Button handlers, utilities |
| Concept | Entry Point | Server/Client | Key Learning |
|---|---|---|---|
| Request Handling | onRequest | Server | GET/POST routing |
| Form Building | serverWidget | Server | NetSuite native forms |
| Custom HTML | response.write | Server | Custom UI rendering |
| URL Resolution | N/url | Server | Dynamic URL generation |
| Concept | Entry Point | Server/Client | Key Learning |
|---|---|---|---|
| GET Handler | get | Server | Data retrieval |
| POST Handler | post | Server | Data creation |
| PUT Handler | put | Server | Data updates |
| DELETE Handler | delete | Server | Data removal |
| JSON Responses | return object | Server | API response format |
| Element | UE | CS | Suitelet | RESTlet | Scheduled | MapReduce |
|---|---|---|---|---|---|---|
<runasrole> |
YES | NO | YES | NO | NO | NO |
<allroles> |
YES | NO | YES | NO | NO | NO |
<title> |
NO | NO | YES | YES | YES | YES |
<recordtype> |
REQ | REQ | NO | NO | NO | NO |
| Status | RELEASED | RELEASED | RELEASED | RELEASED | NOTSCHEDULED | NOTSCHEDULED |
After creating a new script file:
/suitescript-learning review [filename]
After completing a logical section:
/suitescript-learning quiz [section-name]
When encountering a new concept:
/suitescript-learning explain [concept]
At project completion:
/suitescript-learning final
When this skill is active, automatically:
.js file is created in a SuiteScript directory.## Code Review: quick_add_ue.js
### Script Overview
- **Type**: UserEventScript
- **Execution Context**: Server-side
- **Entry Points Used**: beforeLoad
### Key Concepts Identified
#### 1. JSDoc Annotations (@NApiVersion, @NScriptType)
**Lines**: 1–8
**What**: Metadata comments that tell NetSuite how to interpret the script
**Why**: NetSuite requires these to properly deploy and execute the script
**Pitfall**: Forgetting @NScriptType will cause deployment to fail
**Best Practice**: Always include @NApiVersion 2.1 for modern JavaScript support
#### 2. Context Type Checking (context.UserEventType.VIEW)
**Lines**: 28–29
**What**: Checking how the record is being accessed before modifying the form
**Why**: Buttons shouldn't appear in view-only mode where users can't take action
**Pitfall**: Adding buttons in all modes causes confusion in view mode
**Best Practice**: Always check context.type before form modifications
#### 3. Custom Button Addition (form.addButton)
**Lines**: 36–40
**What**: Adding a clickable button to the form's toolbar
**Why**: Provides user interface for triggering custom functionality
**Pitfall**: Button ID without 'custpage_' prefix may conflict with native buttons
**Best Practice**: Always prefix custom element IDs with 'custpage_'
#### 4. Client Script Linking (clientScriptModulePath)
**Lines**: 44
**What**: Connecting a Client Script to handle the button click
**Why**: Button's functionName must be defined in an attached Client Script
**Pitfall**: Relative path must be correct or button click will fail silently
**Best Practice**: Use relative path from current script location (./)
### Quiz Questions for This File
1. What is the difference between beforeLoad and afterSubmit entry points?
2. Why do we check context.type before adding the button?
3. What is the purpose of the 'custpage_' prefix on button IDs?
4. What happens if clientScriptModulePath points to a non-existent file?
5. Could we add this button in beforeSubmit instead? Why or why not?
Unable to detect script type. Please ensure the file contains:
- @NScriptType annotation in JSDoc comment
- Valid script type value (UserEventScript, ClientScript, Suitelet, etc.)
No recognizable SuiteScript patterns found in this file.
This may be a utility module rather than a script entry point.
Unable to generate quiz questions. Possible reasons:
- File is too short or lacks distinct concepts
- Script type not supported for quiz generation
- Code patterns are too generic to quiz
This section is a quick reference for all available learning topics in Learn Mode.
| # | Topic | Command | Description |
|---|---|---|---|
| 1 | Features | /suitescript-learning learn features |
NetSuite features, REST vs SOAP, SuiteScript 2.1 |
| 2 | Governance | /suitescript-learning learn governance |
Usage units, script type limits, optimization |
| 3 | Performance | /suitescript-learning learn performance |
N/cache, Map/Reduce, N/query, SuiteQL |
| 4 | Multi-SuiteApp | /suitescript-learning learn multi-suiteapp |
Script coexistence, execution order |
| 5 | Security | /suitescript-learning learn security |
Roles, permissions, OWASP principles |
| 6 | Testing | /suitescript-learning learn testing |
Jest testing, SDN environments, phased releases |
| 7 | Distribution | /suitescript-learning learn distribution |
Managed SuiteApps, SuiteApp Control Center |
| 8 | Maintenance | /suitescript-learning learn maintenance |
Versioning, deployment, publishing |
| 9 | Licensing | /suitescript-learning learn licensing |
IP protection, click-through agreements |
| 10 | Open Source | /suitescript-learning learn open-source |
License compliance, prohibited licenses |
| Topic | Command | Description |
|---|---|---|
| Legacy TBA Exceptions | /suitescript-learning learn tba |
Legacy-only exceptions; new integrations should use OAuth 2.0 |
| Concurrency | /suitescript-learning learn concurrency |
Concurrency limits, RESTlet vs Suitelet |
| N/query Joins | /suitescript-learning learn nquery |
Multi-level joins with N/query module |
| N/cache Sample | /suitescript-learning learn ncache |
Caching patterns for concurrent processing |
All SAFE Guide reference files are located at:
../netsuite-sdf-safe-guide/references/
Load the netsuite-sdf-safe-guide skill first, then read files from its references/ directory using sibling-relative paths.
These files are automatically consulted when generating learning content, quizzes, and compliance reviews.
learn mode for topic-based learning from SAFE Guide principles.quiz mode with --source=safe flag for SAFE Guide questions.explain mode to reference relevant SAFE Guide principles.final mode with SAFE Guide compliance checklist.原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。