# Netsuite Suitescript Learning

> 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.

- Skill: `oracle/netsuite-suitescript-learning` (Agent Skill)
- Install (CLI): `npx skillmds add oracle/netsuite-suitescript-learning`
- Raw SKILL.md: https://api.skillmd.com/api/skills/oracle/netsuite-suitescript-learning/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: The Universal Permissive License (UPL), Version 1.0
- Author: Oracle (https://skillmd.com/u/oracle)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/oracle/netsuite-suitescript-learning

---


# NetSuite SuiteScript Learning Skill

**Created by:** Oracle NetSuite

## Description
Interactive learning system for NetSuite SuiteScript and SDF projects with **SAFE Guide integration**. This skill provides:

- **Learn Mode**: Topic-based learning from SAFE Guide principles (14 topics including governance, performance, security, N/cache, concurrency)
- **Review Mode**: Analyze code files and identify key learning concepts
- **Explain Mode**: Deep-dive explanations with automatic SAFE Guide references
- **Annotate Mode**: Embed educational comments directly into code
- **Quiz Mode**: Generate quizzes from user code (`--source=code`), SAFE Guide principles (`--source=safe`), or both
- **Final Mode**: Comprehensive learning documentation with SAFE Guide compliance checklist

Covers all 14 script types, deployment configurations, performance patterns, defensive coding practices, and governance limits.

## How to Use This Skill

### Manual Invocation (Slash Command)
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
```

### Optional Coding Assistant Activation Example
For Claude Code, add this to your project's `.claude/settings.local.json`:

```json
{
  "permissions": {
    "allow": [
      "Skill(netsuite-sdf-safe-guide)",
      "Skill(netsuite-suitescript-learning)"
    ]
  }
}
```

With both skills enabled, an assistant will:
- Follow SDF best practices for all SDF components (scripts, custom records, workflows, etc.)
- Automatically embed educational annotations in code as it's written
- Generate quizzes and learning materials on request
- Provide guidance on Suitelets, RESTlets, User Event Scripts, and all 14 script types

---

## When to Use This Skill

### Proactive Invocation (Recommended)
- Invoke this skill **during development** as code is being written, not just at the end.
- Call after each major component is created (script file, object XML, etc.).
- Use for real-time learning reinforcement.
- **IMPORTANT**: When creating NetSuite SuiteScript code, automatically embed learning annotations (CONCEPT comments, LEARNING NOTES) directly into the code as it's written.

### Automatic Annotation During Code Creation

When this skill is active and code is being created (not just reviewed), the following annotations should be automatically included:

1. **JSDoc Headers**: Include LEARNING NOTES explaining the script type and purpose.
2. **Entry Points**: Add LEARNING NOTES blocks explaining when/how the function runs.
3. **API Calls**: Add CONCEPT comments before each N/* module usage.
4. **Complex Logic**: Add step-by-step comments for multi-step operations.
5. **Return Statements**: Explain what's being exposed and why.

This ensures educational content is embedded as code is written, not added as an afterthought.

### Manual Invocation
- User explicitly requests educational content: "explain this code", "create a quiz", "review for learning"
- Commands like: "/suitescript-learning", "/quiz", "/explain-suitescript"
- After completing a NetSuite SDF project to generate final learning materials

### Invocation Modes

| 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 |

---

## Usage Syntax

```
/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
```

---

## Core Functionality

### 1. Code Review Mode (`review`)

When reviewing a SuiteScript file, identify and document:

#### Script Type Analysis
Detect the script type from JSDoc annotations and provide:
- Purpose of this script type
- When it executes (server-side vs client-side)
- Common use cases
- Entry points specific to this type

#### Key Concepts Extraction
For each code pattern found, document:
- **Concept Name**: Brief identifier
- **What It Does**: Plain English explanation
- **Why It's Used**: Business/technical rationale
- **Code Location**: Line number reference
- **Common Pitfalls**: What could go wrong
- **Best Practice**: Recommended approach

#### Output Format for Review Mode
```markdown
## 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]
...
```

---

### 2. Explain Mode (`explain`)

Provide deep-dive explanations for specific concepts:

#### Supported Concepts (Auto-Detect from User Query)

**User Event Script Concepts:**
- `beforeLoad` – Form modification before render
- `beforeSubmit` – Validation before save
- `afterSubmit` – Post-save processing
- `context.type` – Record access modes
- `form.addButton()` – Custom buttons
- `form.clientScriptModulePath` – Linking client scripts

**Client Script Concepts:**
- `pageInit` – Page load initialization
- `saveRecord` – Save validation
- `validateField` – Field-level validation
- `fieldChanged` – Reactive field handling
- `currentRecord.get()` – Accessing record data
- `selectNewLine/commitLine` – Sublist manipulation
- `window.opener` – Parent window communication

**Suitelet Concepts:**
- `onRequest` – HTTP request handling
- `GET vs POST` – HTTP method routing
- `response.write()` – Sending responses
- `serverWidget.Form` – NetSuite forms
- Custom HTML rendering

**RESTlet Concepts:**
- `get/post/put/delete` handlers
- JSON responses
- URL parameters vs body
- CORS and authentication

**Search & Query Concepts:**
- `N/search` – Saved searches
- `N/query` – SuiteQL
- Search filters and columns
- `search.run().each()` – Result iteration
- Performance optimization

**Deployment Concepts:**
- Script IDs and naming
- Deployment XML structure
- `<runasrole>` and `<allroles>` support by type
- `<recordtype>` requirements
- Status values (RELEASED vs NOTSCHEDULED)
- manifest.xml dependencies

**Defensive Coding Concepts:**
- `runtime.executionContext` – Check how script was triggered
- Override vs. Wait patterns – When to defer to other scripts
- Idempotent operations – Safe to re-run without side effects
- Script coordination – Using flag fields for ordering
- Graceful error handling – try/catch in afterSubmit
- Sanity checks – Verify before acting
- `search.lookupFields()` – Check record exists before loading

**Governance Concepts:**
- `Script.getRemainingUsage()` – Monitor remaining usage units
- Usage unit limits by script type – 1,000 for UE/Suitelet, 10,000 for Scheduled
- API governance costs – Different costs for custom vs transaction records
- `SSS_USAGE_LIMIT_EXCEEDED` – Script exceeded usage units
- `SSS_TIME_LIMIT_EXCEEDED` – Script exceeded time limit
- Search result limits – 1,000 standard, 4,000 saved search
- Yielding in Scheduled Scripts – Reschedule before hitting limits
- Client Script logging – `log.*` ignored, use `console.log()`

#### Output Format for Explain Mode
````markdown
## 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]`
````

#### Concept-to-Principle Mapping

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` |

---

### 3. Annotate Mode (`annotate`)

Add inline learning comments directly into code files. This embeds educational content where developers will see it as they work with the code.

#### Comment Styles

**CONCEPT Comments** – Brief inline explanations
```javascript
// CONCEPT: context.type tells us how the record is being accessed
if (context.type !== context.UserEventType.VIEW)
```

**LEARNING NOTES Comments** – Block explanations in JSDoc

```javascript
/**
 * 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
```javascript
// 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'
});
```

#### Annotation Rules

1. **JSDoc Header Annotations**
   - Add LEARNING NOTES block to every entry point function.
   - Include what the function does, when it runs, and key parameters.
   - Reference related concepts and common pitfalls.

2. **Inline CONCEPT Comments**
   - Add before any non-obvious code pattern.
   - Keep to single line when possible.
   - Focus on the "why" not just the "what".

3. **Code Block Explanations**
   - Add before complex logic blocks.
   - Use numbered steps for multi-step operations.
   - Include expected outcomes.

4. **Return Statement Comments**
   - Explain what the return object exposes.
   - Note which functions are entry points vs custom.

#### Where to Add Annotations

| 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 |

#### Example: Fully Annotated User Event Script

```javascript
/**
 * @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
    };
});
```

#### Annotation Density Guidelines

| 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.

#### Output Format for Annotate Mode

When annotating a file, provide:
1. Summary of annotations added
2. Count of each annotation type
3. Any areas that couldn't be annotated (and why)

```markdown
## 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]
```

---

### 4. Learn Mode (`learn`)

Topic-based learning from SAFE Guide references. This mode generates educational content summarized from the SAFE Guide principles and appendices.

#### Supported Topics

| 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 |

#### How Learn Mode Works

1. **Read SAFE Guide Reference**: Load the `netsuite-sdf-safe-guide` skill first, then read the relevant reference file from `../netsuite-sdf-safe-guide/references/`.
2. **Extract Key Concepts**: Identify the main principles, patterns, and examples.
3. **Generate Summary**: Create a structured learning document with examples.
4. **Include Quiz Questions**: Generate 2–3 quick questions to reinforce learning.

#### Output Format for Learn Mode

````markdown
## 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]`
````

#### Example: `/suitescript-learning learn ncache`

````markdown
## 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`
````

---

### 5. Quiz Mode (`quiz`)

Generate quiz questions with answers based on written code.

#### Question Types

**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()
```

#### SAFE Guide Question Types (--source=safe or --source=both)

**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)] })
```

#### Quiz Sources

| 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 |

#### Output Format for Quiz Mode
```markdown
## 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]
```

---

### 6. Final Mode (`final`)

Generate comprehensive learning documentation for the entire project, including a SAFE Guide compliance review.

#### Final Document Structure

````markdown
# [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]
````

---

## Script Type Reference

### Concepts by Script Type

#### UserEventScript
| 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. |

#### ClientScript
| 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 |

#### Suitelet
| 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 |

#### RESTlet
| 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 |

---

## Deployment XML Reference

### Element Support by Script Type

| 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 |

---

## Quiz Generation Rules

### Per-File Minimums
- UserEventScript: 5 questions
- ClientScript: 5 questions
- Suitelet: 5 questions
- RESTlet: 5 questions
- Object XML: 3 questions per file
- manifest.xml: 2 questions

### Question Distribution
- 40% Conceptual Understanding
- 25% Code Prediction
- 15% Error Identification
- 15% Best Practice
- 5% Fill in the Blank

### Difficulty Levels
- **Basic**: What does X do?
- **Intermediate**: When would you use X vs Y?
- **Advanced**: What happens if X fails? How would you optimize?

---

## Integration with Development Workflow

### Recommended Invocation Points

1. **After creating a new script file:**
   ```
   /suitescript-learning review [filename]
   ```

2. **After completing a logical section:**
   ```
   /suitescript-learning quiz [section-name]
   ```

3. **When encountering a new concept:**
   ```
   /suitescript-learning explain [concept]
   ```

4. **At project completion:**
   ```
   /suitescript-learning final
   ```

### Automated Triggers (Proactive Mode)

When this skill is active, automatically:
1. Detect when a new `.js` file is created in a SuiteScript directory.
2. Identify the script type from JSDoc annotations.
3. Flag key concepts as code is written.
4. Suggest quiz questions based on patterns used.
5. Maintain a running list of concepts for final review.

---

## Example Output

### Example: Review of a User Event Script

```markdown
## 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 @NScript

…(truncated)
