[{"data":1,"prerenderedAt":5296},["ShallowReactive",2],{"blog-building-custom-suitecommerce-extensions-developer-guide":3,"all-blog-posts":5100},{"id":4,"title":5,"author":6,"body":7,"categories":5081,"date":5084,"description":5085,"extension":5086,"heroImage":5087,"meta":5088,"navigation":303,"path":5089,"relatedArticles":5090,"seo":5091,"stem":5092,"tags":5093,"__hash__":5099},"content\u002Fblog\u002Fbuilding-custom-suitecommerce-extensions-developer-guide.md","Building Custom SuiteCommerce Extensions: A Developer's Start-to-Finish Guide","Stenbase Team",{"type":8,"value":9,"toc":5022},"minimark",[10,14,22,25,28,33,98,100,103,106,113,118,121,157,161,164,224,227,231,234,244,247,249,252,255,259,264,320,325,328,352,357,360,372,376,379,441,444,448,451,482,485,489,496,650,653,655,658,661,665,668,826,837,841,848,997,1002,1006,1009,1066,1069,1071,1074,1077,1083,1087,1090,1457,1461,1464,1707,1710,1734,1738,1741,2065,2069,2072,2247,2249,2252,2255,2259,2262,2825,2829,2832,3323,3327,3330,3382,3384,3387,3390,3394,3397,3593,3597,3600,3731,3733,3736,3739,3743,3746,4037,4041,4044,4223,4227,4230,4287,4289,4292,4295,4299,4302,4331,4334,4351,4355,4358,4427,4431,4434,4504,4508,4511,4565,4567,4570,4573,4577,4580,4626,4630,4633,4718,4722,4725,4884,4888,4891,4939,4941,4944,4948,4951,4955,4958,4962,4969,4973,4980,4984,4987,4989,4993,4996,5004,5012,5018],[11,12,5],"h1",{"id":13},"building-custom-suitecommerce-extensions-a-developers-start-to-finish-guide",[15,16,17,21],"p",{},[18,19,20],"strong",{},"73% of SuiteCommerce implementations require custom extensions to meet business requirements."," Yet most developers spend their first extension project fighting the framework instead of building features. After developing dozens of extensions across SuiteCommerce Advanced implementations, we've distilled the process into a repeatable methodology that eliminates the guesswork.",[15,23,24],{},"This guide takes you from zero to deployed extension, covering architecture decisions, file structure, frontend and backend integration, testing strategies, and deployment workflows. Whether you're building a custom product configurator, a specialized checkout flow, or a B2B portal feature, the patterns here apply universally.",[26,27],"hr",{},[29,30,32],"h2",{"id":31},"table-of-contents","Table of Contents",[34,35,36,44,50,56,62,68,74,80,86,92],"ol",{},[37,38,39],"li",{},[40,41,43],"a",{"href":42},"#extension-architecture-overview","Extension Architecture Overview",[37,45,46],{},[40,47,49],{"href":48},"#setting-up-your-development-environment","Setting Up Your Development Environment",[37,51,52],{},[40,53,55],{"href":54},"#module-structure-and-configuration","Module Structure and Configuration",[37,57,58],{},[40,59,61],{"href":60},"#frontend-development-templates-views-and-models","Frontend Development: Templates, Views, and Models",[37,63,64],{},[40,65,67],{"href":66},"#backend-integration-suitescript-services","Backend Integration: SuiteScript Services",[37,69,70],{},[40,71,73],{"href":72},"#state-management-and-event-handling","State Management and Event Handling",[37,75,76],{},[40,77,79],{"href":78},"#testing-your-extension","Testing Your Extension",[37,81,82],{},[40,83,85],{"href":84},"#deployment-and-version-management","Deployment and Version Management",[37,87,88],{},[40,89,91],{"href":90},"#performance-considerations","Performance Considerations",[37,93,94],{},[40,95,97],{"href":96},"#faq","FAQ",[26,99],{},[29,101,43],{"id":102},"extension-architecture-overview",[15,104,105],{},"SuiteCommerce extensions follow a modular architecture built on Backbone.js with a custom AMD loader. Understanding this architecture before writing code saves hours of debugging.",[15,107,108],{},[109,110],"img",{"alt":111,"src":112},"JavaScript code editor showing SuiteCommerce extension development","\u002Fimages\u002Fblog\u002Fextensions-guide-code-editor.webp",[114,115,117],"h3",{"id":116},"the-extension-lifecycle","The Extension Lifecycle",[15,119,120],{},"Every extension goes through these phases:",[34,122,123,133,139,145,151],{},[37,124,125,128,129],{},[18,126,127],{},"Module Registration",": The extension declares its modules in ",[130,131,132],"code",{},"ns.package.json",[37,134,135,138],{},[18,136,137],{},"Dependency Resolution",": SCA's module loader resolves and loads dependencies",[37,140,141,144],{},[18,142,143],{},"Entry Point Execution",": Your main JavaScript file runs, registering components",[37,146,147,150],{},[18,148,149],{},"Mount Point Integration",": Views attach to predefined mount points in the theme",[37,152,153,156],{},[18,154,155],{},"Runtime Execution",": Event handlers, models, and services execute as users interact",[114,158,160],{"id":159},"extension-vs-theme-customization","Extension vs. Theme Customization",[15,162,163],{},"Before building an extension, ask whether your requirement actually needs one:",[165,166,167,180],"table",{},[168,169,170],"thead",{},[171,172,173,177],"tr",{},[174,175,176],"th",{},"Use an Extension When",[174,178,179],{},"Use Theme Customization When",[181,182,183,192,200,208,216],"tbody",{},[171,184,185,189],{},[186,187,188],"td",{},"Adding new functionality",[186,190,191],{},"Modifying existing layouts",[171,193,194,197],{},[186,195,196],{},"Integrating third-party APIs",[186,198,199],{},"Changing styles and colors",[171,201,202,205],{},[186,203,204],{},"Creating reusable components",[186,206,207],{},"Adjusting template markup",[171,209,210,213],{},[186,211,212],{},"Building complex business logic",[186,214,215],{},"Minor text or image changes",[171,217,218,221],{},[186,219,220],{},"Needing backend SuiteScript",[186,222,223],{},"Frontend-only changes",[15,225,226],{},"Extensions live separately from themes, making them portable across theme updates. This isolation is their primary advantage.",[114,228,230],{"id":229},"anatomy-of-an-extension","Anatomy of an Extension",[15,232,233],{},"A typical extension contains:",[235,236,241],"pre",{"className":237,"code":239,"language":240},[238],"language-text","MyExtension\u002F\n├── Modules\u002F\n│   └── MyModule\u002F\n│       ├── JavaScript\u002F\n│       │   ├── MyModule.js\n│       │   ├── MyModule.View.js\n│       │   ├── MyModule.Model.js\n│       │   └── MyModule.Router.js\n│       ├── Templates\u002F\n│       │   └── my_module.tpl\n│       ├── Sass\u002F\n│       │   └── _my-module.scss\n│       ├── SuiteScript\u002F\n│       │   └── MyModule.ServiceController.js\n│       └── Configuration\u002F\n│           └── MyModule.json\n├── ns.package.json\n├── manifest.json\n└── README.md\n","text",[130,242,239],{"__ignoreMap":243},"",[15,245,246],{},"Every file has a purpose. Skipping or misplacing files causes silent failures that waste debugging time.",[26,248],{},[29,250,49],{"id":251},"setting-up-your-development-environment",[15,253,254],{},"A proper development environment reduces iteration time from minutes to seconds.",[114,256,258],{"id":257},"required-tools","Required Tools",[15,260,261],{},[18,262,263],{},"SuiteCommerce Developer Tools (SCC DevTools)",[235,265,269],{"className":266,"code":267,"language":268,"meta":243,"style":243},"language-bash shiki shiki-themes github-light github-dark","# Install globally\nnpm install -g @anthropic\u002Fsuitecommerce-devtools\n\n# Verify installation\nscc --version\n","bash",[130,270,271,280,298,305,311],{"__ignoreMap":243},[272,273,276],"span",{"class":274,"line":275},"line",1,[272,277,279],{"class":278},"sJ8bj","# Install globally\n",[272,281,283,287,291,295],{"class":274,"line":282},2,[272,284,286],{"class":285},"sScJk","npm",[272,288,290],{"class":289},"sZZnC"," install",[272,292,294],{"class":293},"sj4cs"," -g",[272,296,297],{"class":289}," @anthropic\u002Fsuitecommerce-devtools\n",[272,299,301],{"class":274,"line":300},3,[272,302,304],{"emptyLinePlaceholder":303},true,"\n",[272,306,308],{"class":274,"line":307},4,[272,309,310],{"class":278},"# Verify installation\n",[272,312,314,317],{"class":274,"line":313},5,[272,315,316],{"class":285},"scc",[272,318,319],{"class":293}," --version\n",[15,321,322],{},[18,323,324],{},"Node.js and npm",[15,326,327],{},"SuiteCommerce requires Node.js 16+ for local development. We recommend using nvm to manage versions:",[235,329,331],{"className":266,"code":330,"language":268,"meta":243,"style":243},"nvm install 18\nnvm use 18\n",[130,332,333,343],{"__ignoreMap":243},[272,334,335,338,340],{"class":274,"line":275},[272,336,337],{"class":285},"nvm",[272,339,290],{"class":289},[272,341,342],{"class":293}," 18\n",[272,344,345,347,350],{"class":274,"line":282},[272,346,337],{"class":285},[272,348,349],{"class":289}," use",[272,351,342],{"class":293},[15,353,354],{},[18,355,356],{},"NetSuite Account Configuration",[15,358,359],{},"Your account needs these permissions enabled:",[361,362,363,366,369],"ul",{},[37,364,365],{},"SuiteScript deployment",[37,367,368],{},"SuiteCommerce file access",[37,370,371],{},"Customization permission",[114,373,375],{"id":374},"project-initialization","Project Initialization",[15,377,378],{},"Create a new extension project:",[235,380,382],{"className":266,"code":381,"language":268,"meta":243,"style":243},"# Create extension scaffolding\nscc extension:create MyExtension --vendor \"YourCompany\"\n\n# Navigate to the extension\ncd MyExtension\n\n# Install dependencies\nnpm install\n",[130,383,384,389,405,409,414,422,427,433],{"__ignoreMap":243},[272,385,386],{"class":274,"line":275},[272,387,388],{"class":278},"# Create extension scaffolding\n",[272,390,391,393,396,399,402],{"class":274,"line":282},[272,392,316],{"class":285},[272,394,395],{"class":289}," extension:create",[272,397,398],{"class":289}," MyExtension",[272,400,401],{"class":293}," --vendor",[272,403,404],{"class":289}," \"YourCompany\"\n",[272,406,407],{"class":274,"line":300},[272,408,304],{"emptyLinePlaceholder":303},[272,410,411],{"class":274,"line":307},[272,412,413],{"class":278},"# Navigate to the extension\n",[272,415,416,419],{"class":274,"line":313},[272,417,418],{"class":293},"cd",[272,420,421],{"class":289}," MyExtension\n",[272,423,425],{"class":274,"line":424},6,[272,426,304],{"emptyLinePlaceholder":303},[272,428,430],{"class":274,"line":429},7,[272,431,432],{"class":278},"# Install dependencies\n",[272,434,436,438],{"class":274,"line":435},8,[272,437,286],{"class":285},[272,439,440],{"class":289}," install\n",[15,442,443],{},"The scaffolding generates a working extension structure. Resist the urge to deviate from this structure—SuiteCommerce's build process expects specific paths.",[114,445,447],{"id":446},"local-development-server","Local Development Server",[15,449,450],{},"Run the local development server to test changes without deploying:",[235,452,454],{"className":266,"code":453,"language":268,"meta":243,"style":243},"# Start local development\nscc extension:local --source MyExtension\n\n# This proxies your local extension files against your live SuiteCommerce instance\n",[130,455,456,461,473,477],{"__ignoreMap":243},[272,457,458],{"class":274,"line":275},[272,459,460],{"class":278},"# Start local development\n",[272,462,463,465,468,471],{"class":274,"line":282},[272,464,316],{"class":285},[272,466,467],{"class":289}," extension:local",[272,469,470],{"class":293}," --source",[272,472,421],{"class":289},[272,474,475],{"class":274,"line":300},[272,476,304],{"emptyLinePlaceholder":303},[272,478,479],{"class":274,"line":307},[272,480,481],{"class":278},"# This proxies your local extension files against your live SuiteCommerce instance\n",[15,483,484],{},"The local server injects your extension files into the live site, letting you test changes instantly. This workflow is essential—deploying to test minor changes wastes 5-10 minutes per iteration.",[114,486,488],{"id":487},"configuring-your-development-domain","Configuring Your Development Domain",[15,490,491,492,495],{},"Create a ",[130,493,494],{},"gulp\u002Fconfig\u002Fconfig.json"," file:",[235,497,501],{"className":498,"code":499,"language":500,"meta":243,"style":243},"language-json shiki shiki-themes github-light github-dark","{\n  \"credentials\": {\n    \"molecule\": \"your-molecule-id\",\n    \"nsVersion\": \"2024.1\",\n    \"applicationId\": \"YOUR_APP_ID\"\n  },\n  \"folders\": {\n    \"source\": \"Modules\",\n    \"distribution\": \"deploy\"\n  },\n  \"identity\": {\n    \"vendor\": \"YourCompany\",\n    \"name\": \"MyExtension\",\n    \"version\": \"1.0.0\"\n  }\n}\n","json",[130,502,503,509,517,531,543,553,558,565,577,588,593,601,614,627,638,644],{"__ignoreMap":243},[272,504,505],{"class":274,"line":275},[272,506,508],{"class":507},"sVt8B","{\n",[272,510,511,514],{"class":274,"line":282},[272,512,513],{"class":293},"  \"credentials\"",[272,515,516],{"class":507},": {\n",[272,518,519,522,525,528],{"class":274,"line":300},[272,520,521],{"class":293},"    \"molecule\"",[272,523,524],{"class":507},": ",[272,526,527],{"class":289},"\"your-molecule-id\"",[272,529,530],{"class":507},",\n",[272,532,533,536,538,541],{"class":274,"line":307},[272,534,535],{"class":293},"    \"nsVersion\"",[272,537,524],{"class":507},[272,539,540],{"class":289},"\"2024.1\"",[272,542,530],{"class":507},[272,544,545,548,550],{"class":274,"line":313},[272,546,547],{"class":293},"    \"applicationId\"",[272,549,524],{"class":507},[272,551,552],{"class":289},"\"YOUR_APP_ID\"\n",[272,554,555],{"class":274,"line":424},[272,556,557],{"class":507},"  },\n",[272,559,560,563],{"class":274,"line":429},[272,561,562],{"class":293},"  \"folders\"",[272,564,516],{"class":507},[272,566,567,570,572,575],{"class":274,"line":435},[272,568,569],{"class":293},"    \"source\"",[272,571,524],{"class":507},[272,573,574],{"class":289},"\"Modules\"",[272,576,530],{"class":507},[272,578,580,583,585],{"class":274,"line":579},9,[272,581,582],{"class":293},"    \"distribution\"",[272,584,524],{"class":507},[272,586,587],{"class":289},"\"deploy\"\n",[272,589,591],{"class":274,"line":590},10,[272,592,557],{"class":507},[272,594,596,599],{"class":274,"line":595},11,[272,597,598],{"class":293},"  \"identity\"",[272,600,516],{"class":507},[272,602,604,607,609,612],{"class":274,"line":603},12,[272,605,606],{"class":293},"    \"vendor\"",[272,608,524],{"class":507},[272,610,611],{"class":289},"\"YourCompany\"",[272,613,530],{"class":507},[272,615,617,620,622,625],{"class":274,"line":616},13,[272,618,619],{"class":293},"    \"name\"",[272,621,524],{"class":507},[272,623,624],{"class":289},"\"MyExtension\"",[272,626,530],{"class":507},[272,628,630,633,635],{"class":274,"line":629},14,[272,631,632],{"class":293},"    \"version\"",[272,634,524],{"class":507},[272,636,637],{"class":289},"\"1.0.0\"\n",[272,639,641],{"class":274,"line":640},15,[272,642,643],{"class":507},"  }\n",[272,645,647],{"class":274,"line":646},16,[272,648,649],{"class":507},"}\n",[15,651,652],{},"Replace placeholder values with your actual NetSuite credentials.",[26,654],{},[29,656,55],{"id":657},"module-structure-and-configuration",[15,659,660],{},"Modules are the building blocks of extensions. Each module encapsulates a specific feature.",[114,662,664],{"id":663},"the-nspackagejson-file","The ns.package.json File",[15,666,667],{},"This file tells SuiteCommerce what your extension contains:",[235,669,671],{"className":498,"code":670,"language":500,"meta":243,"style":243},"{\n  \"gulp\": {\n    \"javascript\": [\n      \"JavaScript\u002F*.js\"\n    ],\n    \"templates\": [\n      \"Templates\u002F*.tpl\"\n    ],\n    \"sass\": [\n      \"Sass\u002F**\u002F*.scss\"\n    ],\n    \"ssp-libraries\": [\n      \"SuiteScript\u002F*.js\"\n    ]\n  },\n  \"overrides\": {},\n  \"dependencies\": [],\n  \"application\": {\n    \"myaccount\": true,\n    \"checkout\": true,\n    \"shopping\": true\n  }\n}\n",[130,672,673,677,684,692,697,702,709,714,718,725,730,734,741,746,751,755,763,772,780,793,805,816,821],{"__ignoreMap":243},[272,674,675],{"class":274,"line":275},[272,676,508],{"class":507},[272,678,679,682],{"class":274,"line":282},[272,680,681],{"class":293},"  \"gulp\"",[272,683,516],{"class":507},[272,685,686,689],{"class":274,"line":300},[272,687,688],{"class":293},"    \"javascript\"",[272,690,691],{"class":507},": [\n",[272,693,694],{"class":274,"line":307},[272,695,696],{"class":289},"      \"JavaScript\u002F*.js\"\n",[272,698,699],{"class":274,"line":313},[272,700,701],{"class":507},"    ],\n",[272,703,704,707],{"class":274,"line":424},[272,705,706],{"class":293},"    \"templates\"",[272,708,691],{"class":507},[272,710,711],{"class":274,"line":429},[272,712,713],{"class":289},"      \"Templates\u002F*.tpl\"\n",[272,715,716],{"class":274,"line":435},[272,717,701],{"class":507},[272,719,720,723],{"class":274,"line":579},[272,721,722],{"class":293},"    \"sass\"",[272,724,691],{"class":507},[272,726,727],{"class":274,"line":590},[272,728,729],{"class":289},"      \"Sass\u002F**\u002F*.scss\"\n",[272,731,732],{"class":274,"line":595},[272,733,701],{"class":507},[272,735,736,739],{"class":274,"line":603},[272,737,738],{"class":293},"    \"ssp-libraries\"",[272,740,691],{"class":507},[272,742,743],{"class":274,"line":616},[272,744,745],{"class":289},"      \"SuiteScript\u002F*.js\"\n",[272,747,748],{"class":274,"line":629},[272,749,750],{"class":507},"    ]\n",[272,752,753],{"class":274,"line":640},[272,754,557],{"class":507},[272,756,757,760],{"class":274,"line":646},[272,758,759],{"class":293},"  \"overrides\"",[272,761,762],{"class":507},": {},\n",[272,764,766,769],{"class":274,"line":765},17,[272,767,768],{"class":293},"  \"dependencies\"",[272,770,771],{"class":507},": [],\n",[272,773,775,778],{"class":274,"line":774},18,[272,776,777],{"class":293},"  \"application\"",[272,779,516],{"class":507},[272,781,783,786,788,791],{"class":274,"line":782},19,[272,784,785],{"class":293},"    \"myaccount\"",[272,787,524],{"class":507},[272,789,790],{"class":293},"true",[272,792,530],{"class":507},[272,794,796,799,801,803],{"class":274,"line":795},20,[272,797,798],{"class":293},"    \"checkout\"",[272,800,524],{"class":507},[272,802,790],{"class":293},[272,804,530],{"class":507},[272,806,808,811,813],{"class":274,"line":807},21,[272,809,810],{"class":293},"    \"shopping\"",[272,812,524],{"class":507},[272,814,815],{"class":293},"true\n",[272,817,819],{"class":274,"line":818},22,[272,820,643],{"class":507},[272,822,824],{"class":274,"line":823},23,[272,825,649],{"class":507},[15,827,828,829,832,833,836],{},"The ",[130,830,831],{},"application"," object controls which SuiteCommerce applications load your module. Set ",[130,834,835],{},"false"," for applications where your module shouldn't appear.",[114,838,840],{"id":839},"entry-point-configuration","Entry Point Configuration",[15,842,843,844,847],{},"Your module's entry point JavaScript file must export a ",[130,845,846],{},"mountToApp"," function:",[235,849,853],{"className":850,"code":851,"language":852,"meta":243,"style":243},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F MyModule.js\ndefine('MyModule', [\n    'MyModule.View',\n    'MyModule.Router'\n], function(\n    MyModuleView,\n    MyModuleRouter\n) {\n    'use strict';\n\n    return {\n        mountToApp: function(application) {\n            \u002F\u002F Register router\n            var router = new MyModuleRouter(application);\n            \n            \u002F\u002F Register child views to mount points\n            var PageType = application.getComponent('PageType');\n            \n            PageType.registerPageType({\n                name: 'my-module-page',\n                routes: ['my-custom-route'],\n                view: MyModuleView\n            });\n            \n            return router;\n        }\n    };\n});\n","javascript",[130,854,855,860,865,870,875,880,885,890,895,900,904,909,914,919,924,929,934,939,943,948,953,958,963,968,973,979,985,991],{"__ignoreMap":243},[272,856,857],{"class":274,"line":275},[272,858,859],{},"\u002F\u002F MyModule.js\n",[272,861,862],{"class":274,"line":282},[272,863,864],{},"define('MyModule', [\n",[272,866,867],{"class":274,"line":300},[272,868,869],{},"    'MyModule.View',\n",[272,871,872],{"class":274,"line":307},[272,873,874],{},"    'MyModule.Router'\n",[272,876,877],{"class":274,"line":313},[272,878,879],{},"], function(\n",[272,881,882],{"class":274,"line":424},[272,883,884],{},"    MyModuleView,\n",[272,886,887],{"class":274,"line":429},[272,888,889],{},"    MyModuleRouter\n",[272,891,892],{"class":274,"line":435},[272,893,894],{},") {\n",[272,896,897],{"class":274,"line":579},[272,898,899],{},"    'use strict';\n",[272,901,902],{"class":274,"line":590},[272,903,304],{"emptyLinePlaceholder":303},[272,905,906],{"class":274,"line":595},[272,907,908],{},"    return {\n",[272,910,911],{"class":274,"line":603},[272,912,913],{},"        mountToApp: function(application) {\n",[272,915,916],{"class":274,"line":616},[272,917,918],{},"            \u002F\u002F Register router\n",[272,920,921],{"class":274,"line":629},[272,922,923],{},"            var router = new MyModuleRouter(application);\n",[272,925,926],{"class":274,"line":640},[272,927,928],{},"            \n",[272,930,931],{"class":274,"line":646},[272,932,933],{},"            \u002F\u002F Register child views to mount points\n",[272,935,936],{"class":274,"line":765},[272,937,938],{},"            var PageType = application.getComponent('PageType');\n",[272,940,941],{"class":274,"line":774},[272,942,928],{},[272,944,945],{"class":274,"line":782},[272,946,947],{},"            PageType.registerPageType({\n",[272,949,950],{"class":274,"line":795},[272,951,952],{},"                name: 'my-module-page',\n",[272,954,955],{"class":274,"line":807},[272,956,957],{},"                routes: ['my-custom-route'],\n",[272,959,960],{"class":274,"line":818},[272,961,962],{},"                view: MyModuleView\n",[272,964,965],{"class":274,"line":823},[272,966,967],{},"            });\n",[272,969,971],{"class":274,"line":970},24,[272,972,928],{},[272,974,976],{"class":274,"line":975},25,[272,977,978],{},"            return router;\n",[272,980,982],{"class":274,"line":981},26,[272,983,984],{},"        }\n",[272,986,988],{"class":274,"line":987},27,[272,989,990],{},"    };\n",[272,992,994],{"class":274,"line":993},28,[272,995,996],{},"});\n",[15,998,828,999,1001],{},[130,1000,846],{}," function receives the application instance, giving you access to all SuiteCommerce components and services.",[114,1003,1005],{"id":1004},"dependency-declaration","Dependency Declaration",[15,1007,1008],{},"Declare module dependencies in the module's configuration:",[235,1010,1012],{"className":498,"code":1011,"language":500,"meta":243,"style":243},"{\n  \"dependencies\": [\n    \"Backbone\",\n    \"underscore\",\n    \"jQuery\",\n    \"SC.Configuration\",\n    \"Utils\"\n  ]\n}\n",[130,1013,1014,1018,1024,1031,1038,1045,1052,1057,1062],{"__ignoreMap":243},[272,1015,1016],{"class":274,"line":275},[272,1017,508],{"class":507},[272,1019,1020,1022],{"class":274,"line":282},[272,1021,768],{"class":293},[272,1023,691],{"class":507},[272,1025,1026,1029],{"class":274,"line":300},[272,1027,1028],{"class":289},"    \"Backbone\"",[272,1030,530],{"class":507},[272,1032,1033,1036],{"class":274,"line":307},[272,1034,1035],{"class":289},"    \"underscore\"",[272,1037,530],{"class":507},[272,1039,1040,1043],{"class":274,"line":313},[272,1041,1042],{"class":289},"    \"jQuery\"",[272,1044,530],{"class":507},[272,1046,1047,1050],{"class":274,"line":424},[272,1048,1049],{"class":289},"    \"SC.Configuration\"",[272,1051,530],{"class":507},[272,1053,1054],{"class":274,"line":429},[272,1055,1056],{"class":289},"    \"Utils\"\n",[272,1058,1059],{"class":274,"line":435},[272,1060,1061],{"class":507},"  ]\n",[272,1063,1064],{"class":274,"line":579},[272,1065,649],{"class":507},[15,1067,1068],{},"Only declare dependencies you actually use. Each dependency adds to your bundle size and load time.",[26,1070],{},[29,1072,61],{"id":1073},"frontend-development-templates-views-and-models",[15,1075,1076],{},"The frontend follows Backbone.js patterns with SuiteCommerce-specific conventions.",[15,1078,1079],{},[109,1080],{"alt":1081,"src":1082},"Software architecture and module structure diagram","\u002Fimages\u002Fblog\u002Fextensions-guide-architecture.webp",[114,1084,1086],{"id":1085},"creating-views","Creating Views",[15,1088,1089],{},"Views manage DOM rendering and user interactions:",[235,1091,1093],{"className":850,"code":1092,"language":852,"meta":243,"style":243},"\u002F\u002F MyModule.View.js\ndefine('MyModule.View', [\n    'Backbone',\n    'my_module.tpl',\n    'Utils'\n], function(\n    Backbone,\n    myModuleTemplate,\n    Utils\n) {\n    'use strict';\n\n    return Backbone.View.extend({\n        \n        template: myModuleTemplate,\n        \n        title: Utils.translate('My Module Page'),\n        \n        page_header: Utils.translate('My Module'),\n        \n        events: {\n            'click [data-action=\"submit\"]': 'handleSubmit',\n            'change [data-input=\"quantity\"]': 'handleQuantityChange'\n        },\n        \n        initialize: function(options) {\n            this.application = options.application;\n            this.model = options.model;\n            \n            \u002F\u002F Listen to model changes\n            this.model.on('change', this.render, this);\n        },\n        \n        \u002F\u002F Data passed to the template\n        getContext: function() {\n            return {\n                pageTitle: this.title,\n                items: this.model.get('items') || [],\n                isLoading: this.model.get('isLoading'),\n                errorMessage: this.model.get('errorMessage')\n            };\n        },\n        \n        handleSubmit: function(event) {\n            event.preventDefault();\n            \n            var self = this;\n            \n            this.model.save().done(function() {\n                self.showConfirmation();\n            }).fail(function(error) {\n                self.showError(error);\n            });\n        },\n        \n        handleQuantityChange: function(event) {\n            var quantity = parseInt(event.target.value, 10);\n            this.model.set('quantity', quantity);\n        },\n        \n        showConfirmation: function() {\n            \u002F\u002F Show success message\n        },\n        \n        showError: function(error) {\n            this.model.set('errorMessage', error.message);\n        }\n    });\n});\n",[130,1094,1095,1100,1105,1110,1115,1120,1124,1129,1134,1139,1143,1147,1151,1156,1161,1166,1170,1175,1179,1184,1188,1193,1198,1203,1208,1212,1217,1222,1227,1232,1238,1244,1249,1254,1260,1266,1272,1278,1284,1290,1296,1302,1307,1312,1318,1324,1329,1335,1340,1346,1352,1358,1364,1369,1374,1379,1385,1391,1397,1402,1407,1413,1419,1424,1429,1435,1441,1446,1452],{"__ignoreMap":243},[272,1096,1097],{"class":274,"line":275},[272,1098,1099],{},"\u002F\u002F MyModule.View.js\n",[272,1101,1102],{"class":274,"line":282},[272,1103,1104],{},"define('MyModule.View', [\n",[272,1106,1107],{"class":274,"line":300},[272,1108,1109],{},"    'Backbone',\n",[272,1111,1112],{"class":274,"line":307},[272,1113,1114],{},"    'my_module.tpl',\n",[272,1116,1117],{"class":274,"line":313},[272,1118,1119],{},"    'Utils'\n",[272,1121,1122],{"class":274,"line":424},[272,1123,879],{},[272,1125,1126],{"class":274,"line":429},[272,1127,1128],{},"    Backbone,\n",[272,1130,1131],{"class":274,"line":435},[272,1132,1133],{},"    myModuleTemplate,\n",[272,1135,1136],{"class":274,"line":579},[272,1137,1138],{},"    Utils\n",[272,1140,1141],{"class":274,"line":590},[272,1142,894],{},[272,1144,1145],{"class":274,"line":595},[272,1146,899],{},[272,1148,1149],{"class":274,"line":603},[272,1150,304],{"emptyLinePlaceholder":303},[272,1152,1153],{"class":274,"line":616},[272,1154,1155],{},"    return Backbone.View.extend({\n",[272,1157,1158],{"class":274,"line":629},[272,1159,1160],{},"        \n",[272,1162,1163],{"class":274,"line":640},[272,1164,1165],{},"        template: myModuleTemplate,\n",[272,1167,1168],{"class":274,"line":646},[272,1169,1160],{},[272,1171,1172],{"class":274,"line":765},[272,1173,1174],{},"        title: Utils.translate('My Module Page'),\n",[272,1176,1177],{"class":274,"line":774},[272,1178,1160],{},[272,1180,1181],{"class":274,"line":782},[272,1182,1183],{},"        page_header: Utils.translate('My Module'),\n",[272,1185,1186],{"class":274,"line":795},[272,1187,1160],{},[272,1189,1190],{"class":274,"line":807},[272,1191,1192],{},"        events: {\n",[272,1194,1195],{"class":274,"line":818},[272,1196,1197],{},"            'click [data-action=\"submit\"]': 'handleSubmit',\n",[272,1199,1200],{"class":274,"line":823},[272,1201,1202],{},"            'change [data-input=\"quantity\"]': 'handleQuantityChange'\n",[272,1204,1205],{"class":274,"line":970},[272,1206,1207],{},"        },\n",[272,1209,1210],{"class":274,"line":975},[272,1211,1160],{},[272,1213,1214],{"class":274,"line":981},[272,1215,1216],{},"        initialize: function(options) {\n",[272,1218,1219],{"class":274,"line":987},[272,1220,1221],{},"            this.application = options.application;\n",[272,1223,1224],{"class":274,"line":993},[272,1225,1226],{},"            this.model = options.model;\n",[272,1228,1230],{"class":274,"line":1229},29,[272,1231,928],{},[272,1233,1235],{"class":274,"line":1234},30,[272,1236,1237],{},"            \u002F\u002F Listen to model changes\n",[272,1239,1241],{"class":274,"line":1240},31,[272,1242,1243],{},"            this.model.on('change', this.render, this);\n",[272,1245,1247],{"class":274,"line":1246},32,[272,1248,1207],{},[272,1250,1252],{"class":274,"line":1251},33,[272,1253,1160],{},[272,1255,1257],{"class":274,"line":1256},34,[272,1258,1259],{},"        \u002F\u002F Data passed to the template\n",[272,1261,1263],{"class":274,"line":1262},35,[272,1264,1265],{},"        getContext: function() {\n",[272,1267,1269],{"class":274,"line":1268},36,[272,1270,1271],{},"            return {\n",[272,1273,1275],{"class":274,"line":1274},37,[272,1276,1277],{},"                pageTitle: this.title,\n",[272,1279,1281],{"class":274,"line":1280},38,[272,1282,1283],{},"                items: this.model.get('items') || [],\n",[272,1285,1287],{"class":274,"line":1286},39,[272,1288,1289],{},"                isLoading: this.model.get('isLoading'),\n",[272,1291,1293],{"class":274,"line":1292},40,[272,1294,1295],{},"                errorMessage: this.model.get('errorMessage')\n",[272,1297,1299],{"class":274,"line":1298},41,[272,1300,1301],{},"            };\n",[272,1303,1305],{"class":274,"line":1304},42,[272,1306,1207],{},[272,1308,1310],{"class":274,"line":1309},43,[272,1311,1160],{},[272,1313,1315],{"class":274,"line":1314},44,[272,1316,1317],{},"        handleSubmit: function(event) {\n",[272,1319,1321],{"class":274,"line":1320},45,[272,1322,1323],{},"            event.preventDefault();\n",[272,1325,1327],{"class":274,"line":1326},46,[272,1328,928],{},[272,1330,1332],{"class":274,"line":1331},47,[272,1333,1334],{},"            var self = this;\n",[272,1336,1338],{"class":274,"line":1337},48,[272,1339,928],{},[272,1341,1343],{"class":274,"line":1342},49,[272,1344,1345],{},"            this.model.save().done(function() {\n",[272,1347,1349],{"class":274,"line":1348},50,[272,1350,1351],{},"                self.showConfirmation();\n",[272,1353,1355],{"class":274,"line":1354},51,[272,1356,1357],{},"            }).fail(function(error) {\n",[272,1359,1361],{"class":274,"line":1360},52,[272,1362,1363],{},"                self.showError(error);\n",[272,1365,1367],{"class":274,"line":1366},53,[272,1368,967],{},[272,1370,1372],{"class":274,"line":1371},54,[272,1373,1207],{},[272,1375,1377],{"class":274,"line":1376},55,[272,1378,1160],{},[272,1380,1382],{"class":274,"line":1381},56,[272,1383,1384],{},"        handleQuantityChange: function(event) {\n",[272,1386,1388],{"class":274,"line":1387},57,[272,1389,1390],{},"            var quantity = parseInt(event.target.value, 10);\n",[272,1392,1394],{"class":274,"line":1393},58,[272,1395,1396],{},"            this.model.set('quantity', quantity);\n",[272,1398,1400],{"class":274,"line":1399},59,[272,1401,1207],{},[272,1403,1405],{"class":274,"line":1404},60,[272,1406,1160],{},[272,1408,1410],{"class":274,"line":1409},61,[272,1411,1412],{},"        showConfirmation: function() {\n",[272,1414,1416],{"class":274,"line":1415},62,[272,1417,1418],{},"            \u002F\u002F Show success message\n",[272,1420,1422],{"class":274,"line":1421},63,[272,1423,1207],{},[272,1425,1427],{"class":274,"line":1426},64,[272,1428,1160],{},[272,1430,1432],{"class":274,"line":1431},65,[272,1433,1434],{},"        showError: function(error) {\n",[272,1436,1438],{"class":274,"line":1437},66,[272,1439,1440],{},"            this.model.set('errorMessage', error.message);\n",[272,1442,1444],{"class":274,"line":1443},67,[272,1445,984],{},[272,1447,1449],{"class":274,"line":1448},68,[272,1450,1451],{},"    });\n",[272,1453,1455],{"class":274,"line":1454},69,[272,1456,996],{},[114,1458,1460],{"id":1459},"template-syntax","Template Syntax",[15,1462,1463],{},"SuiteCommerce uses Handlebars templates with custom helpers:",[235,1465,1469],{"className":1466,"code":1467,"language":1468,"meta":243,"style":243},"language-handlebars shiki shiki-themes github-light github-dark","{{!-- my_module.tpl --}}\n\u003Cdiv class=\"my-module\" data-view=\"MyModule\">\n    \u003Ch1 class=\"my-module-title\">{{pageTitle}}\u003C\u002Fh1>\n    \n    {{#if isLoading}}\n        \u003Cdiv class=\"my-module-loading\">\n            \u003Cspan class=\"my-module-loading-icon\">\u003C\u002Fspan>\n            {{translate 'Loading...'}}\n        \u003C\u002Fdiv>\n    {{else}}\n        {{#if errorMessage}}\n            \u003Cdiv class=\"my-module-error\" data-type=\"alert-error\">\n                {{errorMessage}}\n            \u003C\u002Fdiv>\n        {{\u002Fif}}\n        \n        {{#if items.length}}\n            \u003Cul class=\"my-module-items\">\n                {{#each items}}\n                    \u003Cli class=\"my-module-item\" data-item-id=\"{{internalid}}\">\n                        \u003Cspan class=\"my-module-item-name\">{{name}}\u003C\u002Fspan>\n                        \u003Cspan class=\"my-module-item-price\">\n                            {{formatCurrency price}}\n                        \u003C\u002Fspan>\n                        \u003Cinput \n                            type=\"number\" \n                            data-input=\"quantity\"\n                            value=\"{{quantity}}\"\n                            min=\"1\"\n                            max=\"{{maxQuantity}}\"\n                        \u002F>\n                    \u003C\u002Fli>\n                {{\u002Feach}}\n            \u003C\u002Ful>\n        {{else}}\n            \u003Cp class=\"my-module-empty\">\n                {{translate 'No items found.'}}\n            \u003C\u002Fp>\n        {{\u002Fif}}\n        \n        \u003Cbutton data-action=\"submit\" class=\"my-module-submit-button\">\n            {{translate 'Submit'}}\n        \u003C\u002Fbutton>\n    {{\u002Fif}}\n    \n    {{!-- Child view mount point --}}\n    \u003Cdiv data-view=\"MyModule.ChildView\">\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n","handlebars",[130,1470,1471,1476,1481,1486,1491,1496,1501,1506,1511,1516,1521,1526,1531,1536,1541,1546,1550,1555,1560,1565,1570,1575,1580,1585,1590,1595,1600,1605,1610,1615,1620,1625,1630,1635,1640,1645,1650,1655,1660,1664,1668,1673,1678,1683,1688,1692,1697,1702],{"__ignoreMap":243},[272,1472,1473],{"class":274,"line":275},[272,1474,1475],{},"{{!-- my_module.tpl --}}\n",[272,1477,1478],{"class":274,"line":282},[272,1479,1480],{},"\u003Cdiv class=\"my-module\" data-view=\"MyModule\">\n",[272,1482,1483],{"class":274,"line":300},[272,1484,1485],{},"    \u003Ch1 class=\"my-module-title\">{{pageTitle}}\u003C\u002Fh1>\n",[272,1487,1488],{"class":274,"line":307},[272,1489,1490],{},"    \n",[272,1492,1493],{"class":274,"line":313},[272,1494,1495],{},"    {{#if isLoading}}\n",[272,1497,1498],{"class":274,"line":424},[272,1499,1500],{},"        \u003Cdiv class=\"my-module-loading\">\n",[272,1502,1503],{"class":274,"line":429},[272,1504,1505],{},"            \u003Cspan class=\"my-module-loading-icon\">\u003C\u002Fspan>\n",[272,1507,1508],{"class":274,"line":435},[272,1509,1510],{},"            {{translate 'Loading...'}}\n",[272,1512,1513],{"class":274,"line":579},[272,1514,1515],{},"        \u003C\u002Fdiv>\n",[272,1517,1518],{"class":274,"line":590},[272,1519,1520],{},"    {{else}}\n",[272,1522,1523],{"class":274,"line":595},[272,1524,1525],{},"        {{#if errorMessage}}\n",[272,1527,1528],{"class":274,"line":603},[272,1529,1530],{},"            \u003Cdiv class=\"my-module-error\" data-type=\"alert-error\">\n",[272,1532,1533],{"class":274,"line":616},[272,1534,1535],{},"                {{errorMessage}}\n",[272,1537,1538],{"class":274,"line":629},[272,1539,1540],{},"            \u003C\u002Fdiv>\n",[272,1542,1543],{"class":274,"line":640},[272,1544,1545],{},"        {{\u002Fif}}\n",[272,1547,1548],{"class":274,"line":646},[272,1549,1160],{},[272,1551,1552],{"class":274,"line":765},[272,1553,1554],{},"        {{#if items.length}}\n",[272,1556,1557],{"class":274,"line":774},[272,1558,1559],{},"            \u003Cul class=\"my-module-items\">\n",[272,1561,1562],{"class":274,"line":782},[272,1563,1564],{},"                {{#each items}}\n",[272,1566,1567],{"class":274,"line":795},[272,1568,1569],{},"                    \u003Cli class=\"my-module-item\" data-item-id=\"{{internalid}}\">\n",[272,1571,1572],{"class":274,"line":807},[272,1573,1574],{},"                        \u003Cspan class=\"my-module-item-name\">{{name}}\u003C\u002Fspan>\n",[272,1576,1577],{"class":274,"line":818},[272,1578,1579],{},"                        \u003Cspan class=\"my-module-item-price\">\n",[272,1581,1582],{"class":274,"line":823},[272,1583,1584],{},"                            {{formatCurrency price}}\n",[272,1586,1587],{"class":274,"line":970},[272,1588,1589],{},"                        \u003C\u002Fspan>\n",[272,1591,1592],{"class":274,"line":975},[272,1593,1594],{},"                        \u003Cinput \n",[272,1596,1597],{"class":274,"line":981},[272,1598,1599],{},"                            type=\"number\" \n",[272,1601,1602],{"class":274,"line":987},[272,1603,1604],{},"                            data-input=\"quantity\"\n",[272,1606,1607],{"class":274,"line":993},[272,1608,1609],{},"                            value=\"{{quantity}}\"\n",[272,1611,1612],{"class":274,"line":1229},[272,1613,1614],{},"                            min=\"1\"\n",[272,1616,1617],{"class":274,"line":1234},[272,1618,1619],{},"                            max=\"{{maxQuantity}}\"\n",[272,1621,1622],{"class":274,"line":1240},[272,1623,1624],{},"                        \u002F>\n",[272,1626,1627],{"class":274,"line":1246},[272,1628,1629],{},"                    \u003C\u002Fli>\n",[272,1631,1632],{"class":274,"line":1251},[272,1633,1634],{},"                {{\u002Feach}}\n",[272,1636,1637],{"class":274,"line":1256},[272,1638,1639],{},"            \u003C\u002Ful>\n",[272,1641,1642],{"class":274,"line":1262},[272,1643,1644],{},"        {{else}}\n",[272,1646,1647],{"class":274,"line":1268},[272,1648,1649],{},"            \u003Cp class=\"my-module-empty\">\n",[272,1651,1652],{"class":274,"line":1274},[272,1653,1654],{},"                {{translate 'No items found.'}}\n",[272,1656,1657],{"class":274,"line":1280},[272,1658,1659],{},"            \u003C\u002Fp>\n",[272,1661,1662],{"class":274,"line":1286},[272,1663,1545],{},[272,1665,1666],{"class":274,"line":1292},[272,1667,1160],{},[272,1669,1670],{"class":274,"line":1298},[272,1671,1672],{},"        \u003Cbutton data-action=\"submit\" class=\"my-module-submit-button\">\n",[272,1674,1675],{"class":274,"line":1304},[272,1676,1677],{},"            {{translate 'Submit'}}\n",[272,1679,1680],{"class":274,"line":1309},[272,1681,1682],{},"        \u003C\u002Fbutton>\n",[272,1684,1685],{"class":274,"line":1314},[272,1686,1687],{},"    {{\u002Fif}}\n",[272,1689,1690],{"class":274,"line":1320},[272,1691,1490],{},[272,1693,1694],{"class":274,"line":1326},[272,1695,1696],{},"    {{!-- Child view mount point --}}\n",[272,1698,1699],{"class":274,"line":1331},[272,1700,1701],{},"    \u003Cdiv data-view=\"MyModule.ChildView\">\u003C\u002Fdiv>\n",[272,1703,1704],{"class":274,"line":1337},[272,1705,1706],{},"\u003C\u002Fdiv>\n",[15,1708,1709],{},"Key template conventions:",[361,1711,1712,1719,1725,1728],{},[37,1713,1714,1715,1718],{},"Use ",[130,1716,1717],{},"{{translate 'text'}}"," for all user-facing strings (enables i18n)",[37,1720,1714,1721,1724],{},[130,1722,1723],{},"data-"," attributes for JavaScript hooks, not classes",[37,1726,1727],{},"Use semantic class names following BEM conventions",[37,1729,1730,1731],{},"Create mount points for child views with ",[130,1732,1733],{},"data-view",[114,1735,1737],{"id":1736},"models-and-data-fetching","Models and Data Fetching",[15,1739,1740],{},"Models handle data and communicate with backend services:",[235,1742,1744],{"className":850,"code":1743,"language":852,"meta":243,"style":243},"\u002F\u002F MyModule.Model.js\ndefine('MyModule.Model', [\n    'Backbone',\n    'Utils'\n], function(\n    Backbone,\n    Utils\n) {\n    'use strict';\n\n    return Backbone.Model.extend({\n        \n        urlRoot: Utils.getAbsoluteUrl(\n            'services\u002FMyModule.Service.ss'\n        ),\n        \n        defaults: {\n            items: [],\n            isLoading: false,\n            errorMessage: null\n        },\n        \n        initialize: function() {\n            \u002F\u002F Model initialization\n        },\n        \n        \u002F\u002F Parse server response\n        parse: function(response) {\n            if (response.error) {\n                return {\n                    errorMessage: response.error.message\n                };\n            }\n            \n            return {\n                items: response.items || [],\n                total: response.total || 0\n            };\n        },\n        \n        \u002F\u002F Validate before saving\n        validate: function(attributes) {\n            var errors = [];\n            \n            if (!attributes.items || attributes.items.length === 0) {\n                errors.push({\n                    field: 'items',\n                    message: Utils.translate('At least one item is required.')\n                });\n            }\n            \n            if (errors.length) {\n                return errors;\n            }\n        },\n        \n        \u002F\u002F Custom fetch with loading state\n        fetch: function(options) {\n            var self = this;\n            \n            this.set('isLoading', true);\n            this.set('errorMessage', null);\n            \n            return Backbone.Model.prototype.fetch.call(this, options)\n                .always(function() {\n                    self.set('isLoading', false);\n                });\n        }\n    });\n});\n",[130,1745,1746,1751,1756,1760,1764,1768,1772,1776,1780,1784,1788,1793,1797,1802,1807,1812,1816,1821,1826,1831,1836,1840,1844,1849,1854,1858,1862,1867,1872,1877,1882,1887,1892,1897,1901,1905,1910,1915,1919,1923,1927,1932,1937,1942,1946,1951,1956,1961,1966,1971,1975,1979,1984,1989,1993,1997,2001,2006,2011,2015,2019,2024,2029,2033,2038,2043,2048,2052,2056,2060],{"__ignoreMap":243},[272,1747,1748],{"class":274,"line":275},[272,1749,1750],{},"\u002F\u002F MyModule.Model.js\n",[272,1752,1753],{"class":274,"line":282},[272,1754,1755],{},"define('MyModule.Model', [\n",[272,1757,1758],{"class":274,"line":300},[272,1759,1109],{},[272,1761,1762],{"class":274,"line":307},[272,1763,1119],{},[272,1765,1766],{"class":274,"line":313},[272,1767,879],{},[272,1769,1770],{"class":274,"line":424},[272,1771,1128],{},[272,1773,1774],{"class":274,"line":429},[272,1775,1138],{},[272,1777,1778],{"class":274,"line":435},[272,1779,894],{},[272,1781,1782],{"class":274,"line":579},[272,1783,899],{},[272,1785,1786],{"class":274,"line":590},[272,1787,304],{"emptyLinePlaceholder":303},[272,1789,1790],{"class":274,"line":595},[272,1791,1792],{},"    return Backbone.Model.extend({\n",[272,1794,1795],{"class":274,"line":603},[272,1796,1160],{},[272,1798,1799],{"class":274,"line":616},[272,1800,1801],{},"        urlRoot: Utils.getAbsoluteUrl(\n",[272,1803,1804],{"class":274,"line":629},[272,1805,1806],{},"            'services\u002FMyModule.Service.ss'\n",[272,1808,1809],{"class":274,"line":640},[272,1810,1811],{},"        ),\n",[272,1813,1814],{"class":274,"line":646},[272,1815,1160],{},[272,1817,1818],{"class":274,"line":765},[272,1819,1820],{},"        defaults: {\n",[272,1822,1823],{"class":274,"line":774},[272,1824,1825],{},"            items: [],\n",[272,1827,1828],{"class":274,"line":782},[272,1829,1830],{},"            isLoading: false,\n",[272,1832,1833],{"class":274,"line":795},[272,1834,1835],{},"            errorMessage: null\n",[272,1837,1838],{"class":274,"line":807},[272,1839,1207],{},[272,1841,1842],{"class":274,"line":818},[272,1843,1160],{},[272,1845,1846],{"class":274,"line":823},[272,1847,1848],{},"        initialize: function() {\n",[272,1850,1851],{"class":274,"line":970},[272,1852,1853],{},"            \u002F\u002F Model initialization\n",[272,1855,1856],{"class":274,"line":975},[272,1857,1207],{},[272,1859,1860],{"class":274,"line":981},[272,1861,1160],{},[272,1863,1864],{"class":274,"line":987},[272,1865,1866],{},"        \u002F\u002F Parse server response\n",[272,1868,1869],{"class":274,"line":993},[272,1870,1871],{},"        parse: function(response) {\n",[272,1873,1874],{"class":274,"line":1229},[272,1875,1876],{},"            if (response.error) {\n",[272,1878,1879],{"class":274,"line":1234},[272,1880,1881],{},"                return {\n",[272,1883,1884],{"class":274,"line":1240},[272,1885,1886],{},"                    errorMessage: response.error.message\n",[272,1888,1889],{"class":274,"line":1246},[272,1890,1891],{},"                };\n",[272,1893,1894],{"class":274,"line":1251},[272,1895,1896],{},"            }\n",[272,1898,1899],{"class":274,"line":1256},[272,1900,928],{},[272,1902,1903],{"class":274,"line":1262},[272,1904,1271],{},[272,1906,1907],{"class":274,"line":1268},[272,1908,1909],{},"                items: response.items || [],\n",[272,1911,1912],{"class":274,"line":1274},[272,1913,1914],{},"                total: response.total || 0\n",[272,1916,1917],{"class":274,"line":1280},[272,1918,1301],{},[272,1920,1921],{"class":274,"line":1286},[272,1922,1207],{},[272,1924,1925],{"class":274,"line":1292},[272,1926,1160],{},[272,1928,1929],{"class":274,"line":1298},[272,1930,1931],{},"        \u002F\u002F Validate before saving\n",[272,1933,1934],{"class":274,"line":1304},[272,1935,1936],{},"        validate: function(attributes) {\n",[272,1938,1939],{"class":274,"line":1309},[272,1940,1941],{},"            var errors = [];\n",[272,1943,1944],{"class":274,"line":1314},[272,1945,928],{},[272,1947,1948],{"class":274,"line":1320},[272,1949,1950],{},"            if (!attributes.items || attributes.items.length === 0) {\n",[272,1952,1953],{"class":274,"line":1326},[272,1954,1955],{},"                errors.push({\n",[272,1957,1958],{"class":274,"line":1331},[272,1959,1960],{},"                    field: 'items',\n",[272,1962,1963],{"class":274,"line":1337},[272,1964,1965],{},"                    message: Utils.translate('At least one item is required.')\n",[272,1967,1968],{"class":274,"line":1342},[272,1969,1970],{},"                });\n",[272,1972,1973],{"class":274,"line":1348},[272,1974,1896],{},[272,1976,1977],{"class":274,"line":1354},[272,1978,928],{},[272,1980,1981],{"class":274,"line":1360},[272,1982,1983],{},"            if (errors.length) {\n",[272,1985,1986],{"class":274,"line":1366},[272,1987,1988],{},"                return errors;\n",[272,1990,1991],{"class":274,"line":1371},[272,1992,1896],{},[272,1994,1995],{"class":274,"line":1376},[272,1996,1207],{},[272,1998,1999],{"class":274,"line":1381},[272,2000,1160],{},[272,2002,2003],{"class":274,"line":1387},[272,2004,2005],{},"        \u002F\u002F Custom fetch with loading state\n",[272,2007,2008],{"class":274,"line":1393},[272,2009,2010],{},"        fetch: function(options) {\n",[272,2012,2013],{"class":274,"line":1399},[272,2014,1334],{},[272,2016,2017],{"class":274,"line":1404},[272,2018,928],{},[272,2020,2021],{"class":274,"line":1409},[272,2022,2023],{},"            this.set('isLoading', true);\n",[272,2025,2026],{"class":274,"line":1415},[272,2027,2028],{},"            this.set('errorMessage', null);\n",[272,2030,2031],{"class":274,"line":1421},[272,2032,928],{},[272,2034,2035],{"class":274,"line":1426},[272,2036,2037],{},"            return Backbone.Model.prototype.fetch.call(this, options)\n",[272,2039,2040],{"class":274,"line":1431},[272,2041,2042],{},"                .always(function() {\n",[272,2044,2045],{"class":274,"line":1437},[272,2046,2047],{},"                    self.set('isLoading', false);\n",[272,2049,2050],{"class":274,"line":1443},[272,2051,1970],{},[272,2053,2054],{"class":274,"line":1448},[272,2055,984],{},[272,2057,2058],{"class":274,"line":1454},[272,2059,1451],{},[272,2061,2063],{"class":274,"line":2062},70,[272,2064,996],{},[114,2066,2068],{"id":2067},"child-views-and-composition","Child Views and Composition",[15,2070,2071],{},"Build complex UIs by composing child views:",[235,2073,2075],{"className":850,"code":2074,"language":852,"meta":243,"style":243},"\u002F\u002F MyModule.View.js (with child views)\ndefine('MyModule.View', [\n    'Backbone',\n    'Backbone.CompositeView',\n    'MyModule.ChildView',\n    'my_module.tpl'\n], function(\n    Backbone,\n    BackboneCompositeView,\n    MyModuleChildView,\n    myModuleTemplate\n) {\n    'use strict';\n\n    return Backbone.View.extend({\n        \n        template: myModuleTemplate,\n        \n        initialize: function(options) {\n            BackboneCompositeView.add(this);\n            this.application = options.application;\n        },\n        \n        childViews: {\n            'MyModule.ChildView': function() {\n                return new MyModuleChildView({\n                    model: this.model,\n                    application: this.application\n                });\n            }\n        },\n        \n        getContext: function() {\n            return {\n                \u002F\u002F context data\n            };\n        }\n    });\n});\n",[130,2076,2077,2082,2086,2090,2095,2100,2105,2109,2113,2118,2123,2128,2132,2136,2140,2144,2148,2152,2156,2160,2165,2169,2173,2177,2182,2187,2192,2197,2202,2206,2210,2214,2218,2222,2226,2231,2235,2239,2243],{"__ignoreMap":243},[272,2078,2079],{"class":274,"line":275},[272,2080,2081],{},"\u002F\u002F MyModule.View.js (with child views)\n",[272,2083,2084],{"class":274,"line":282},[272,2085,1104],{},[272,2087,2088],{"class":274,"line":300},[272,2089,1109],{},[272,2091,2092],{"class":274,"line":307},[272,2093,2094],{},"    'Backbone.CompositeView',\n",[272,2096,2097],{"class":274,"line":313},[272,2098,2099],{},"    'MyModule.ChildView',\n",[272,2101,2102],{"class":274,"line":424},[272,2103,2104],{},"    'my_module.tpl'\n",[272,2106,2107],{"class":274,"line":429},[272,2108,879],{},[272,2110,2111],{"class":274,"line":435},[272,2112,1128],{},[272,2114,2115],{"class":274,"line":579},[272,2116,2117],{},"    BackboneCompositeView,\n",[272,2119,2120],{"class":274,"line":590},[272,2121,2122],{},"    MyModuleChildView,\n",[272,2124,2125],{"class":274,"line":595},[272,2126,2127],{},"    myModuleTemplate\n",[272,2129,2130],{"class":274,"line":603},[272,2131,894],{},[272,2133,2134],{"class":274,"line":616},[272,2135,899],{},[272,2137,2138],{"class":274,"line":629},[272,2139,304],{"emptyLinePlaceholder":303},[272,2141,2142],{"class":274,"line":640},[272,2143,1155],{},[272,2145,2146],{"class":274,"line":646},[272,2147,1160],{},[272,2149,2150],{"class":274,"line":765},[272,2151,1165],{},[272,2153,2154],{"class":274,"line":774},[272,2155,1160],{},[272,2157,2158],{"class":274,"line":782},[272,2159,1216],{},[272,2161,2162],{"class":274,"line":795},[272,2163,2164],{},"            BackboneCompositeView.add(this);\n",[272,2166,2167],{"class":274,"line":807},[272,2168,1221],{},[272,2170,2171],{"class":274,"line":818},[272,2172,1207],{},[272,2174,2175],{"class":274,"line":823},[272,2176,1160],{},[272,2178,2179],{"class":274,"line":970},[272,2180,2181],{},"        childViews: {\n",[272,2183,2184],{"class":274,"line":975},[272,2185,2186],{},"            'MyModule.ChildView': function() {\n",[272,2188,2189],{"class":274,"line":981},[272,2190,2191],{},"                return new MyModuleChildView({\n",[272,2193,2194],{"class":274,"line":987},[272,2195,2196],{},"                    model: this.model,\n",[272,2198,2199],{"class":274,"line":993},[272,2200,2201],{},"                    application: this.application\n",[272,2203,2204],{"class":274,"line":1229},[272,2205,1970],{},[272,2207,2208],{"class":274,"line":1234},[272,2209,1896],{},[272,2211,2212],{"class":274,"line":1240},[272,2213,1207],{},[272,2215,2216],{"class":274,"line":1246},[272,2217,1160],{},[272,2219,2220],{"class":274,"line":1251},[272,2221,1265],{},[272,2223,2224],{"class":274,"line":1256},[272,2225,1271],{},[272,2227,2228],{"class":274,"line":1262},[272,2229,2230],{},"                \u002F\u002F context data\n",[272,2232,2233],{"class":274,"line":1268},[272,2234,1301],{},[272,2236,2237],{"class":274,"line":1274},[272,2238,984],{},[272,2240,2241],{"class":274,"line":1280},[272,2242,1451],{},[272,2244,2245],{"class":274,"line":1286},[272,2246,996],{},[26,2248],{},[29,2250,67],{"id":2251},"backend-integration-suitescript-services",[15,2253,2254],{},"Most extensions need backend services to interact with NetSuite data.",[114,2256,2258],{"id":2257},"service-controller-pattern","Service Controller Pattern",[15,2260,2261],{},"Create a SuiteScript service controller:",[235,2263,2265],{"className":850,"code":2264,"language":852,"meta":243,"style":243},"\u002F\u002F MyModule.ServiceController.js\ndefine('MyModule.ServiceController', [\n    'ServiceController',\n    'MyModule.Model'\n], function(\n    ServiceController,\n    MyModuleModel\n) {\n    'use strict';\n\n    return ServiceController.extend({\n        \n        name: 'MyModule.ServiceController',\n        \n        \u002F\u002F Handle GET requests\n        get: function() {\n            var filters = this.request.getParameter('filters');\n            var page = parseInt(this.request.getParameter('page'), 10) || 1;\n            var pageSize = parseInt(this.request.getParameter('pageSize'), 10) || 20;\n            \n            try {\n                var model = new MyModuleModel();\n                var results = model.list({\n                    filters: filters ? JSON.parse(filters) : {},\n                    page: page,\n                    pageSize: pageSize\n                });\n                \n                return results;\n            } catch (error) {\n                return {\n                    error: {\n                        code: 'FETCH_ERROR',\n                        message: error.message\n                    }\n                };\n            }\n        },\n        \n        \u002F\u002F Handle POST requests\n        post: function() {\n            var data = this.data;\n            \n            \u002F\u002F Validate required fields\n            if (!data.items || !data.items.length) {\n                return {\n                    error: {\n                        code: 'VALIDATION_ERROR',\n                        message: 'Items are required.'\n                    }\n                };\n            }\n            \n            try {\n                var model = new MyModuleModel();\n                var result = model.create(data);\n                \n                return {\n                    success: true,\n                    id: result.id\n                };\n            } catch (error) {\n                return {\n                    error: {\n                        code: 'CREATE_ERROR',\n                        message: error.message\n                    }\n                };\n            }\n        },\n        \n        \u002F\u002F Handle PUT requests\n        put: function() {\n            var id = this.request.getParameter('internalid');\n            var data = this.data;\n            \n            try {\n                var model = new MyModuleModel();\n                model.update(id, data);\n                \n                return {\n                    success: true,\n                    id: id\n                };\n            } catch (error) {\n                return {\n                    error: {\n                        code: 'UPDATE_ERROR',\n                        message: error.message\n                    }\n                };\n            }\n        },\n        \n        \u002F\u002F Handle DELETE requests\n        delete: function() {\n            var id = this.request.getParameter('internalid');\n            \n            try {\n                var model = new MyModuleModel();\n                model.remove(id);\n                \n                return {\n                    success: true\n                };\n            } catch (error) {\n                return {\n                    error: {\n                        code: 'DELETE_ERROR',\n                        message: error.message\n                    }\n                };\n            }\n        }\n    });\n});\n",[130,2266,2267,2272,2277,2282,2287,2291,2296,2301,2305,2309,2313,2318,2322,2327,2331,2336,2341,2346,2351,2356,2360,2365,2370,2375,2380,2385,2390,2394,2399,2404,2409,2413,2418,2423,2428,2433,2437,2441,2445,2449,2454,2459,2464,2468,2473,2478,2482,2486,2491,2496,2500,2504,2508,2512,2516,2520,2525,2529,2533,2538,2543,2547,2551,2555,2559,2564,2568,2572,2576,2580,2584,2589,2595,2601,2607,2612,2617,2622,2627,2633,2638,2643,2648,2654,2659,2664,2669,2674,2680,2685,2690,2695,2700,2705,2710,2716,2722,2727,2732,2737,2742,2748,2753,2758,2764,2769,2774,2779,2784,2790,2795,2800,2805,2810,2815,2820],{"__ignoreMap":243},[272,2268,2269],{"class":274,"line":275},[272,2270,2271],{},"\u002F\u002F MyModule.ServiceController.js\n",[272,2273,2274],{"class":274,"line":282},[272,2275,2276],{},"define('MyModule.ServiceController', [\n",[272,2278,2279],{"class":274,"line":300},[272,2280,2281],{},"    'ServiceController',\n",[272,2283,2284],{"class":274,"line":307},[272,2285,2286],{},"    'MyModule.Model'\n",[272,2288,2289],{"class":274,"line":313},[272,2290,879],{},[272,2292,2293],{"class":274,"line":424},[272,2294,2295],{},"    ServiceController,\n",[272,2297,2298],{"class":274,"line":429},[272,2299,2300],{},"    MyModuleModel\n",[272,2302,2303],{"class":274,"line":435},[272,2304,894],{},[272,2306,2307],{"class":274,"line":579},[272,2308,899],{},[272,2310,2311],{"class":274,"line":590},[272,2312,304],{"emptyLinePlaceholder":303},[272,2314,2315],{"class":274,"line":595},[272,2316,2317],{},"    return ServiceController.extend({\n",[272,2319,2320],{"class":274,"line":603},[272,2321,1160],{},[272,2323,2324],{"class":274,"line":616},[272,2325,2326],{},"        name: 'MyModule.ServiceController',\n",[272,2328,2329],{"class":274,"line":629},[272,2330,1160],{},[272,2332,2333],{"class":274,"line":640},[272,2334,2335],{},"        \u002F\u002F Handle GET requests\n",[272,2337,2338],{"class":274,"line":646},[272,2339,2340],{},"        get: function() {\n",[272,2342,2343],{"class":274,"line":765},[272,2344,2345],{},"            var filters = this.request.getParameter('filters');\n",[272,2347,2348],{"class":274,"line":774},[272,2349,2350],{},"            var page = parseInt(this.request.getParameter('page'), 10) || 1;\n",[272,2352,2353],{"class":274,"line":782},[272,2354,2355],{},"            var pageSize = parseInt(this.request.getParameter('pageSize'), 10) || 20;\n",[272,2357,2358],{"class":274,"line":795},[272,2359,928],{},[272,2361,2362],{"class":274,"line":807},[272,2363,2364],{},"            try {\n",[272,2366,2367],{"class":274,"line":818},[272,2368,2369],{},"                var model = new MyModuleModel();\n",[272,2371,2372],{"class":274,"line":823},[272,2373,2374],{},"                var results = model.list({\n",[272,2376,2377],{"class":274,"line":970},[272,2378,2379],{},"                    filters: filters ? JSON.parse(filters) : {},\n",[272,2381,2382],{"class":274,"line":975},[272,2383,2384],{},"                    page: page,\n",[272,2386,2387],{"class":274,"line":981},[272,2388,2389],{},"                    pageSize: pageSize\n",[272,2391,2392],{"class":274,"line":987},[272,2393,1970],{},[272,2395,2396],{"class":274,"line":993},[272,2397,2398],{},"                \n",[272,2400,2401],{"class":274,"line":1229},[272,2402,2403],{},"                return results;\n",[272,2405,2406],{"class":274,"line":1234},[272,2407,2408],{},"            } catch (error) {\n",[272,2410,2411],{"class":274,"line":1240},[272,2412,1881],{},[272,2414,2415],{"class":274,"line":1246},[272,2416,2417],{},"                    error: {\n",[272,2419,2420],{"class":274,"line":1251},[272,2421,2422],{},"                        code: 'FETCH_ERROR',\n",[272,2424,2425],{"class":274,"line":1256},[272,2426,2427],{},"                        message: error.message\n",[272,2429,2430],{"class":274,"line":1262},[272,2431,2432],{},"                    }\n",[272,2434,2435],{"class":274,"line":1268},[272,2436,1891],{},[272,2438,2439],{"class":274,"line":1274},[272,2440,1896],{},[272,2442,2443],{"class":274,"line":1280},[272,2444,1207],{},[272,2446,2447],{"class":274,"line":1286},[272,2448,1160],{},[272,2450,2451],{"class":274,"line":1292},[272,2452,2453],{},"        \u002F\u002F Handle POST requests\n",[272,2455,2456],{"class":274,"line":1298},[272,2457,2458],{},"        post: function() {\n",[272,2460,2461],{"class":274,"line":1304},[272,2462,2463],{},"            var data = this.data;\n",[272,2465,2466],{"class":274,"line":1309},[272,2467,928],{},[272,2469,2470],{"class":274,"line":1314},[272,2471,2472],{},"            \u002F\u002F Validate required fields\n",[272,2474,2475],{"class":274,"line":1320},[272,2476,2477],{},"            if (!data.items || !data.items.length) {\n",[272,2479,2480],{"class":274,"line":1326},[272,2481,1881],{},[272,2483,2484],{"class":274,"line":1331},[272,2485,2417],{},[272,2487,2488],{"class":274,"line":1337},[272,2489,2490],{},"                        code: 'VALIDATION_ERROR',\n",[272,2492,2493],{"class":274,"line":1342},[272,2494,2495],{},"                        message: 'Items are required.'\n",[272,2497,2498],{"class":274,"line":1348},[272,2499,2432],{},[272,2501,2502],{"class":274,"line":1354},[272,2503,1891],{},[272,2505,2506],{"class":274,"line":1360},[272,2507,1896],{},[272,2509,2510],{"class":274,"line":1366},[272,2511,928],{},[272,2513,2514],{"class":274,"line":1371},[272,2515,2364],{},[272,2517,2518],{"class":274,"line":1376},[272,2519,2369],{},[272,2521,2522],{"class":274,"line":1381},[272,2523,2524],{},"                var result = model.create(data);\n",[272,2526,2527],{"class":274,"line":1387},[272,2528,2398],{},[272,2530,2531],{"class":274,"line":1393},[272,2532,1881],{},[272,2534,2535],{"class":274,"line":1399},[272,2536,2537],{},"                    success: true,\n",[272,2539,2540],{"class":274,"line":1404},[272,2541,2542],{},"                    id: result.id\n",[272,2544,2545],{"class":274,"line":1409},[272,2546,1891],{},[272,2548,2549],{"class":274,"line":1415},[272,2550,2408],{},[272,2552,2553],{"class":274,"line":1421},[272,2554,1881],{},[272,2556,2557],{"class":274,"line":1426},[272,2558,2417],{},[272,2560,2561],{"class":274,"line":1431},[272,2562,2563],{},"                        code: 'CREATE_ERROR',\n",[272,2565,2566],{"class":274,"line":1437},[272,2567,2427],{},[272,2569,2570],{"class":274,"line":1443},[272,2571,2432],{},[272,2573,2574],{"class":274,"line":1448},[272,2575,1891],{},[272,2577,2578],{"class":274,"line":1454},[272,2579,1896],{},[272,2581,2582],{"class":274,"line":2062},[272,2583,1207],{},[272,2585,2587],{"class":274,"line":2586},71,[272,2588,1160],{},[272,2590,2592],{"class":274,"line":2591},72,[272,2593,2594],{},"        \u002F\u002F Handle PUT requests\n",[272,2596,2598],{"class":274,"line":2597},73,[272,2599,2600],{},"        put: function() {\n",[272,2602,2604],{"class":274,"line":2603},74,[272,2605,2606],{},"            var id = this.request.getParameter('internalid');\n",[272,2608,2610],{"class":274,"line":2609},75,[272,2611,2463],{},[272,2613,2615],{"class":274,"line":2614},76,[272,2616,928],{},[272,2618,2620],{"class":274,"line":2619},77,[272,2621,2364],{},[272,2623,2625],{"class":274,"line":2624},78,[272,2626,2369],{},[272,2628,2630],{"class":274,"line":2629},79,[272,2631,2632],{},"                model.update(id, data);\n",[272,2634,2636],{"class":274,"line":2635},80,[272,2637,2398],{},[272,2639,2641],{"class":274,"line":2640},81,[272,2642,1881],{},[272,2644,2646],{"class":274,"line":2645},82,[272,2647,2537],{},[272,2649,2651],{"class":274,"line":2650},83,[272,2652,2653],{},"                    id: id\n",[272,2655,2657],{"class":274,"line":2656},84,[272,2658,1891],{},[272,2660,2662],{"class":274,"line":2661},85,[272,2663,2408],{},[272,2665,2667],{"class":274,"line":2666},86,[272,2668,1881],{},[272,2670,2672],{"class":274,"line":2671},87,[272,2673,2417],{},[272,2675,2677],{"class":274,"line":2676},88,[272,2678,2679],{},"                        code: 'UPDATE_ERROR',\n",[272,2681,2683],{"class":274,"line":2682},89,[272,2684,2427],{},[272,2686,2688],{"class":274,"line":2687},90,[272,2689,2432],{},[272,2691,2693],{"class":274,"line":2692},91,[272,2694,1891],{},[272,2696,2698],{"class":274,"line":2697},92,[272,2699,1896],{},[272,2701,2703],{"class":274,"line":2702},93,[272,2704,1207],{},[272,2706,2708],{"class":274,"line":2707},94,[272,2709,1160],{},[272,2711,2713],{"class":274,"line":2712},95,[272,2714,2715],{},"        \u002F\u002F Handle DELETE requests\n",[272,2717,2719],{"class":274,"line":2718},96,[272,2720,2721],{},"        delete: function() {\n",[272,2723,2725],{"class":274,"line":2724},97,[272,2726,2606],{},[272,2728,2730],{"class":274,"line":2729},98,[272,2731,928],{},[272,2733,2735],{"class":274,"line":2734},99,[272,2736,2364],{},[272,2738,2740],{"class":274,"line":2739},100,[272,2741,2369],{},[272,2743,2745],{"class":274,"line":2744},101,[272,2746,2747],{},"                model.remove(id);\n",[272,2749,2751],{"class":274,"line":2750},102,[272,2752,2398],{},[272,2754,2756],{"class":274,"line":2755},103,[272,2757,1881],{},[272,2759,2761],{"class":274,"line":2760},104,[272,2762,2763],{},"                    success: true\n",[272,2765,2767],{"class":274,"line":2766},105,[272,2768,1891],{},[272,2770,2772],{"class":274,"line":2771},106,[272,2773,2408],{},[272,2775,2777],{"class":274,"line":2776},107,[272,2778,1881],{},[272,2780,2782],{"class":274,"line":2781},108,[272,2783,2417],{},[272,2785,2787],{"class":274,"line":2786},109,[272,2788,2789],{},"                        code: 'DELETE_ERROR',\n",[272,2791,2793],{"class":274,"line":2792},110,[272,2794,2427],{},[272,2796,2798],{"class":274,"line":2797},111,[272,2799,2432],{},[272,2801,2803],{"class":274,"line":2802},112,[272,2804,1891],{},[272,2806,2808],{"class":274,"line":2807},113,[272,2809,1896],{},[272,2811,2813],{"class":274,"line":2812},114,[272,2814,984],{},[272,2816,2818],{"class":274,"line":2817},115,[272,2819,1451],{},[272,2821,2823],{"class":274,"line":2822},116,[272,2824,996],{},[114,2826,2828],{"id":2827},"backend-model-pattern","Backend Model Pattern",[15,2830,2831],{},"Separate data access from the controller:",[235,2833,2835],{"className":850,"code":2834,"language":852,"meta":243,"style":243},"\u002F\u002F MyModule.Model.ss.js (Backend model)\ndefine('MyModule.Model', [\n    'SC.Model',\n    'Application',\n    'Utils'\n], function(\n    SCModel,\n    Application,\n    Utils\n) {\n    'use strict';\n\n    return SCModel.extend({\n        \n        name: 'MyModule.Model',\n        \n        list: function(options) {\n            var filters = options.filters || {};\n            var page = options.page || 1;\n            var pageSize = options.pageSize || 20;\n            \n            var search = nlapiCreateSearch('customrecord_my_record', [\n                ['isinactive', 'is', 'F']\n            ], [\n                new nlobjSearchColumn('name'),\n                new nlobjSearchColumn('custrecord_field1'),\n                new nlobjSearchColumn('custrecord_field2')\n            ]);\n            \n            var searchResults = search.runSearch();\n            var startIndex = (page - 1) * pageSize;\n            var results = searchResults.getResults(startIndex, startIndex + pageSize);\n            \n            var items = results.map(function(result) {\n                return {\n                    internalid: result.getId(),\n                    name: result.getValue('name'),\n                    field1: result.getValue('custrecord_field1'),\n                    field2: result.getValue('custrecord_field2')\n                };\n            });\n            \n            return {\n                items: items,\n                page: page,\n                pageSize: pageSize,\n                total: this.getCount(filters)\n            };\n        },\n        \n        get: function(id) {\n            var record = nlapiLoadRecord('customrecord_my_record', id);\n            \n            return {\n                internalid: record.getId(),\n                name: record.getFieldValue('name'),\n                field1: record.getFieldValue('custrecord_field1'),\n                field2: record.getFieldValue('custrecord_field2')\n            };\n        },\n        \n        create: function(data) {\n            var record = nlapiCreateRecord('customrecord_my_record');\n            \n            record.setFieldValue('name', data.name);\n            record.setFieldValue('custrecord_field1', data.field1);\n            record.setFieldValue('custrecord_field2', data.field2);\n            \n            var id = nlapiSubmitRecord(record);\n            \n            return {\n                id: id\n            };\n        },\n        \n        update: function(id, data) {\n            var record = nlapiLoadRecord('customrecord_my_record', id);\n            \n            if (data.name !== undefined) {\n                record.setFieldValue('name', data.name);\n            }\n            if (data.field1 !== undefined) {\n                record.setFieldValue('custrecord_field1', data.field1);\n            }\n            if (data.field2 !== undefined) {\n                record.setFieldValue('custrecord_field2', data.field2);\n            }\n            \n            nlapiSubmitRecord(record);\n        },\n        \n        remove: function(id) {\n            nlapiDeleteRecord('customrecord_my_record', id);\n        },\n        \n        getCount: function(filters) {\n            var countSearch = nlapiCreateSearch('customrecord_my_record', [\n                ['isinactive', 'is', 'F']\n            ], [\n                new nlobjSearchColumn('internalid', null, 'count')\n            ]);\n            \n            var results = countSearch.runSearch().getResults(0, 1);\n            return parseInt(results[0].getValue('internalid', null, 'count'), 10);\n        }\n    });\n});\n",[130,2836,2837,2842,2846,2851,2856,2860,2864,2869,2874,2878,2882,2886,2890,2895,2899,2904,2908,2913,2918,2923,2928,2932,2937,2942,2947,2952,2957,2962,2967,2971,2976,2981,2986,2990,2995,2999,3004,3009,3014,3019,3023,3027,3031,3035,3040,3045,3050,3055,3059,3063,3067,3072,3077,3081,3085,3090,3095,3100,3105,3109,3113,3117,3122,3127,3131,3136,3141,3146,3150,3155,3159,3163,3168,3172,3176,3180,3185,3189,3193,3198,3203,3207,3212,3217,3221,3226,3231,3235,3239,3244,3248,3252,3257,3262,3266,3270,3275,3280,3284,3288,3293,3297,3301,3306,3311,3315,3319],{"__ignoreMap":243},[272,2838,2839],{"class":274,"line":275},[272,2840,2841],{},"\u002F\u002F MyModule.Model.ss.js (Backend model)\n",[272,2843,2844],{"class":274,"line":282},[272,2845,1755],{},[272,2847,2848],{"class":274,"line":300},[272,2849,2850],{},"    'SC.Model',\n",[272,2852,2853],{"class":274,"line":307},[272,2854,2855],{},"    'Application',\n",[272,2857,2858],{"class":274,"line":313},[272,2859,1119],{},[272,2861,2862],{"class":274,"line":424},[272,2863,879],{},[272,2865,2866],{"class":274,"line":429},[272,2867,2868],{},"    SCModel,\n",[272,2870,2871],{"class":274,"line":435},[272,2872,2873],{},"    Application,\n",[272,2875,2876],{"class":274,"line":579},[272,2877,1138],{},[272,2879,2880],{"class":274,"line":590},[272,2881,894],{},[272,2883,2884],{"class":274,"line":595},[272,2885,899],{},[272,2887,2888],{"class":274,"line":603},[272,2889,304],{"emptyLinePlaceholder":303},[272,2891,2892],{"class":274,"line":616},[272,2893,2894],{},"    return SCModel.extend({\n",[272,2896,2897],{"class":274,"line":629},[272,2898,1160],{},[272,2900,2901],{"class":274,"line":640},[272,2902,2903],{},"        name: 'MyModule.Model',\n",[272,2905,2906],{"class":274,"line":646},[272,2907,1160],{},[272,2909,2910],{"class":274,"line":765},[272,2911,2912],{},"        list: function(options) {\n",[272,2914,2915],{"class":274,"line":774},[272,2916,2917],{},"            var filters = options.filters || {};\n",[272,2919,2920],{"class":274,"line":782},[272,2921,2922],{},"            var page = options.page || 1;\n",[272,2924,2925],{"class":274,"line":795},[272,2926,2927],{},"            var pageSize = options.pageSize || 20;\n",[272,2929,2930],{"class":274,"line":807},[272,2931,928],{},[272,2933,2934],{"class":274,"line":818},[272,2935,2936],{},"            var search = nlapiCreateSearch('customrecord_my_record', [\n",[272,2938,2939],{"class":274,"line":823},[272,2940,2941],{},"                ['isinactive', 'is', 'F']\n",[272,2943,2944],{"class":274,"line":970},[272,2945,2946],{},"            ], [\n",[272,2948,2949],{"class":274,"line":975},[272,2950,2951],{},"                new nlobjSearchColumn('name'),\n",[272,2953,2954],{"class":274,"line":981},[272,2955,2956],{},"                new nlobjSearchColumn('custrecord_field1'),\n",[272,2958,2959],{"class":274,"line":987},[272,2960,2961],{},"                new nlobjSearchColumn('custrecord_field2')\n",[272,2963,2964],{"class":274,"line":993},[272,2965,2966],{},"            ]);\n",[272,2968,2969],{"class":274,"line":1229},[272,2970,928],{},[272,2972,2973],{"class":274,"line":1234},[272,2974,2975],{},"            var searchResults = search.runSearch();\n",[272,2977,2978],{"class":274,"line":1240},[272,2979,2980],{},"            var startIndex = (page - 1) * pageSize;\n",[272,2982,2983],{"class":274,"line":1246},[272,2984,2985],{},"            var results = searchResults.getResults(startIndex, startIndex + pageSize);\n",[272,2987,2988],{"class":274,"line":1251},[272,2989,928],{},[272,2991,2992],{"class":274,"line":1256},[272,2993,2994],{},"            var items = results.map(function(result) {\n",[272,2996,2997],{"class":274,"line":1262},[272,2998,1881],{},[272,3000,3001],{"class":274,"line":1268},[272,3002,3003],{},"                    internalid: result.getId(),\n",[272,3005,3006],{"class":274,"line":1274},[272,3007,3008],{},"                    name: result.getValue('name'),\n",[272,3010,3011],{"class":274,"line":1280},[272,3012,3013],{},"                    field1: result.getValue('custrecord_field1'),\n",[272,3015,3016],{"class":274,"line":1286},[272,3017,3018],{},"                    field2: result.getValue('custrecord_field2')\n",[272,3020,3021],{"class":274,"line":1292},[272,3022,1891],{},[272,3024,3025],{"class":274,"line":1298},[272,3026,967],{},[272,3028,3029],{"class":274,"line":1304},[272,3030,928],{},[272,3032,3033],{"class":274,"line":1309},[272,3034,1271],{},[272,3036,3037],{"class":274,"line":1314},[272,3038,3039],{},"                items: items,\n",[272,3041,3042],{"class":274,"line":1320},[272,3043,3044],{},"                page: page,\n",[272,3046,3047],{"class":274,"line":1326},[272,3048,3049],{},"                pageSize: pageSize,\n",[272,3051,3052],{"class":274,"line":1331},[272,3053,3054],{},"                total: this.getCount(filters)\n",[272,3056,3057],{"class":274,"line":1337},[272,3058,1301],{},[272,3060,3061],{"class":274,"line":1342},[272,3062,1207],{},[272,3064,3065],{"class":274,"line":1348},[272,3066,1160],{},[272,3068,3069],{"class":274,"line":1354},[272,3070,3071],{},"        get: function(id) {\n",[272,3073,3074],{"class":274,"line":1360},[272,3075,3076],{},"            var record = nlapiLoadRecord('customrecord_my_record', id);\n",[272,3078,3079],{"class":274,"line":1366},[272,3080,928],{},[272,3082,3083],{"class":274,"line":1371},[272,3084,1271],{},[272,3086,3087],{"class":274,"line":1376},[272,3088,3089],{},"                internalid: record.getId(),\n",[272,3091,3092],{"class":274,"line":1381},[272,3093,3094],{},"                name: record.getFieldValue('name'),\n",[272,3096,3097],{"class":274,"line":1387},[272,3098,3099],{},"                field1: record.getFieldValue('custrecord_field1'),\n",[272,3101,3102],{"class":274,"line":1393},[272,3103,3104],{},"                field2: record.getFieldValue('custrecord_field2')\n",[272,3106,3107],{"class":274,"line":1399},[272,3108,1301],{},[272,3110,3111],{"class":274,"line":1404},[272,3112,1207],{},[272,3114,3115],{"class":274,"line":1409},[272,3116,1160],{},[272,3118,3119],{"class":274,"line":1415},[272,3120,3121],{},"        create: function(data) {\n",[272,3123,3124],{"class":274,"line":1421},[272,3125,3126],{},"            var record = nlapiCreateRecord('customrecord_my_record');\n",[272,3128,3129],{"class":274,"line":1426},[272,3130,928],{},[272,3132,3133],{"class":274,"line":1431},[272,3134,3135],{},"            record.setFieldValue('name', data.name);\n",[272,3137,3138],{"class":274,"line":1437},[272,3139,3140],{},"            record.setFieldValue('custrecord_field1', data.field1);\n",[272,3142,3143],{"class":274,"line":1443},[272,3144,3145],{},"            record.setFieldValue('custrecord_field2', data.field2);\n",[272,3147,3148],{"class":274,"line":1448},[272,3149,928],{},[272,3151,3152],{"class":274,"line":1454},[272,3153,3154],{},"            var id = nlapiSubmitRecord(record);\n",[272,3156,3157],{"class":274,"line":2062},[272,3158,928],{},[272,3160,3161],{"class":274,"line":2586},[272,3162,1271],{},[272,3164,3165],{"class":274,"line":2591},[272,3166,3167],{},"                id: id\n",[272,3169,3170],{"class":274,"line":2597},[272,3171,1301],{},[272,3173,3174],{"class":274,"line":2603},[272,3175,1207],{},[272,3177,3178],{"class":274,"line":2609},[272,3179,1160],{},[272,3181,3182],{"class":274,"line":2614},[272,3183,3184],{},"        update: function(id, data) {\n",[272,3186,3187],{"class":274,"line":2619},[272,3188,3076],{},[272,3190,3191],{"class":274,"line":2624},[272,3192,928],{},[272,3194,3195],{"class":274,"line":2629},[272,3196,3197],{},"            if (data.name !== undefined) {\n",[272,3199,3200],{"class":274,"line":2635},[272,3201,3202],{},"                record.setFieldValue('name', data.name);\n",[272,3204,3205],{"class":274,"line":2640},[272,3206,1896],{},[272,3208,3209],{"class":274,"line":2645},[272,3210,3211],{},"            if (data.field1 !== undefined) {\n",[272,3213,3214],{"class":274,"line":2650},[272,3215,3216],{},"                record.setFieldValue('custrecord_field1', data.field1);\n",[272,3218,3219],{"class":274,"line":2656},[272,3220,1896],{},[272,3222,3223],{"class":274,"line":2661},[272,3224,3225],{},"            if (data.field2 !== undefined) {\n",[272,3227,3228],{"class":274,"line":2666},[272,3229,3230],{},"                record.setFieldValue('custrecord_field2', data.field2);\n",[272,3232,3233],{"class":274,"line":2671},[272,3234,1896],{},[272,3236,3237],{"class":274,"line":2676},[272,3238,928],{},[272,3240,3241],{"class":274,"line":2682},[272,3242,3243],{},"            nlapiSubmitRecord(record);\n",[272,3245,3246],{"class":274,"line":2687},[272,3247,1207],{},[272,3249,3250],{"class":274,"line":2692},[272,3251,1160],{},[272,3253,3254],{"class":274,"line":2697},[272,3255,3256],{},"        remove: function(id) {\n",[272,3258,3259],{"class":274,"line":2702},[272,3260,3261],{},"            nlapiDeleteRecord('customrecord_my_record', id);\n",[272,3263,3264],{"class":274,"line":2707},[272,3265,1207],{},[272,3267,3268],{"class":274,"line":2712},[272,3269,1160],{},[272,3271,3272],{"class":274,"line":2718},[272,3273,3274],{},"        getCount: function(filters) {\n",[272,3276,3277],{"class":274,"line":2724},[272,3278,3279],{},"            var countSearch = nlapiCreateSearch('customrecord_my_record', [\n",[272,3281,3282],{"class":274,"line":2729},[272,3283,2941],{},[272,3285,3286],{"class":274,"line":2734},[272,3287,2946],{},[272,3289,3290],{"class":274,"line":2739},[272,3291,3292],{},"                new nlobjSearchColumn('internalid', null, 'count')\n",[272,3294,3295],{"class":274,"line":2744},[272,3296,2966],{},[272,3298,3299],{"class":274,"line":2750},[272,3300,928],{},[272,3302,3303],{"class":274,"line":2755},[272,3304,3305],{},"            var results = countSearch.runSearch().getResults(0, 1);\n",[272,3307,3308],{"class":274,"line":2760},[272,3309,3310],{},"            return parseInt(results[0].getValue('internalid', null, 'count'), 10);\n",[272,3312,3313],{"class":274,"line":2766},[272,3314,984],{},[272,3316,3317],{"class":274,"line":2771},[272,3318,1451],{},[272,3320,3321],{"class":274,"line":2776},[272,3322,996],{},[114,3324,3326],{"id":3325},"registering-the-service","Registering the Service",[15,3328,3329],{},"Add your service to the SSP application's service controller registration:",[235,3331,3333],{"className":498,"code":3332,"language":500,"meta":243,"style":243},"{\n  \"ssp-libraries\": {\n    \"entry_point\": \"SuiteScript\u002FMyModule.ServiceController.js\",\n    \"dependencies\": [\n      \"SuiteScript\u002FMyModule.Model.ss.js\"\n    ]\n  }\n}\n",[130,3334,3335,3339,3346,3358,3365,3370,3374,3378],{"__ignoreMap":243},[272,3336,3337],{"class":274,"line":275},[272,3338,508],{"class":507},[272,3340,3341,3344],{"class":274,"line":282},[272,3342,3343],{"class":293},"  \"ssp-libraries\"",[272,3345,516],{"class":507},[272,3347,3348,3351,3353,3356],{"class":274,"line":300},[272,3349,3350],{"class":293},"    \"entry_point\"",[272,3352,524],{"class":507},[272,3354,3355],{"class":289},"\"SuiteScript\u002FMyModule.ServiceController.js\"",[272,3357,530],{"class":507},[272,3359,3360,3363],{"class":274,"line":307},[272,3361,3362],{"class":293},"    \"dependencies\"",[272,3364,691],{"class":507},[272,3366,3367],{"class":274,"line":313},[272,3368,3369],{"class":289},"      \"SuiteScript\u002FMyModule.Model.ss.js\"\n",[272,3371,3372],{"class":274,"line":424},[272,3373,750],{"class":507},[272,3375,3376],{"class":274,"line":429},[272,3377,643],{"class":507},[272,3379,3380],{"class":274,"line":435},[272,3381,649],{"class":507},[26,3383],{},[29,3385,73],{"id":3386},"state-management-and-event-handling",[15,3388,3389],{},"Complex extensions need proper state management.",[114,3391,3393],{"id":3392},"application-events","Application Events",[15,3395,3396],{},"Use SuiteCommerce's event system for cross-module communication:",[235,3398,3400],{"className":850,"code":3399,"language":852,"meta":243,"style":243},"\u002F\u002F Publishing events\ndefine('MyModule.Publisher', [\n    'Backbone'\n], function(Backbone) {\n    'use strict';\n\n    return {\n        notifyItemAdded: function(item) {\n            Backbone.trigger('MyModule.ItemAdded', item);\n        },\n        \n        notifyCartUpdated: function(cart) {\n            Backbone.trigger('MyModule.CartUpdated', cart);\n        }\n    };\n});\n\n\u002F\u002F Subscribing to events\ndefine('MyModule.Subscriber', [\n    'Backbone'\n], function(Backbone) {\n    'use strict';\n\n    return {\n        initialize: function() {\n            Backbone.on('MyModule.ItemAdded', this.handleItemAdded, this);\n            Backbone.on('cart:updated', this.handleCartUpdate, this);\n        },\n        \n        handleItemAdded: function(item) {\n            console.log('Item added:', item);\n        },\n        \n        handleCartUpdate: function(cart) {\n            console.log('Cart updated:', cart);\n        },\n        \n        destroy: function() {\n            Backbone.off('MyModule.ItemAdded', this.handleItemAdded, this);\n            Backbone.off('cart:updated', this.handleCartUpdate, this);\n        }\n    };\n});\n",[130,3401,3402,3407,3412,3417,3422,3426,3430,3434,3439,3444,3448,3452,3457,3462,3466,3470,3474,3478,3483,3488,3492,3496,3500,3504,3508,3512,3517,3522,3526,3530,3535,3540,3544,3548,3553,3558,3562,3566,3571,3576,3581,3585,3589],{"__ignoreMap":243},[272,3403,3404],{"class":274,"line":275},[272,3405,3406],{},"\u002F\u002F Publishing events\n",[272,3408,3409],{"class":274,"line":282},[272,3410,3411],{},"define('MyModule.Publisher', [\n",[272,3413,3414],{"class":274,"line":300},[272,3415,3416],{},"    'Backbone'\n",[272,3418,3419],{"class":274,"line":307},[272,3420,3421],{},"], function(Backbone) {\n",[272,3423,3424],{"class":274,"line":313},[272,3425,899],{},[272,3427,3428],{"class":274,"line":424},[272,3429,304],{"emptyLinePlaceholder":303},[272,3431,3432],{"class":274,"line":429},[272,3433,908],{},[272,3435,3436],{"class":274,"line":435},[272,3437,3438],{},"        notifyItemAdded: function(item) {\n",[272,3440,3441],{"class":274,"line":579},[272,3442,3443],{},"            Backbone.trigger('MyModule.ItemAdded', item);\n",[272,3445,3446],{"class":274,"line":590},[272,3447,1207],{},[272,3449,3450],{"class":274,"line":595},[272,3451,1160],{},[272,3453,3454],{"class":274,"line":603},[272,3455,3456],{},"        notifyCartUpdated: function(cart) {\n",[272,3458,3459],{"class":274,"line":616},[272,3460,3461],{},"            Backbone.trigger('MyModule.CartUpdated', cart);\n",[272,3463,3464],{"class":274,"line":629},[272,3465,984],{},[272,3467,3468],{"class":274,"line":640},[272,3469,990],{},[272,3471,3472],{"class":274,"line":646},[272,3473,996],{},[272,3475,3476],{"class":274,"line":765},[272,3477,304],{"emptyLinePlaceholder":303},[272,3479,3480],{"class":274,"line":774},[272,3481,3482],{},"\u002F\u002F Subscribing to events\n",[272,3484,3485],{"class":274,"line":782},[272,3486,3487],{},"define('MyModule.Subscriber', [\n",[272,3489,3490],{"class":274,"line":795},[272,3491,3416],{},[272,3493,3494],{"class":274,"line":807},[272,3495,3421],{},[272,3497,3498],{"class":274,"line":818},[272,3499,899],{},[272,3501,3502],{"class":274,"line":823},[272,3503,304],{"emptyLinePlaceholder":303},[272,3505,3506],{"class":274,"line":970},[272,3507,908],{},[272,3509,3510],{"class":274,"line":975},[272,3511,1848],{},[272,3513,3514],{"class":274,"line":981},[272,3515,3516],{},"            Backbone.on('MyModule.ItemAdded', this.handleItemAdded, this);\n",[272,3518,3519],{"class":274,"line":987},[272,3520,3521],{},"            Backbone.on('cart:updated', this.handleCartUpdate, this);\n",[272,3523,3524],{"class":274,"line":993},[272,3525,1207],{},[272,3527,3528],{"class":274,"line":1229},[272,3529,1160],{},[272,3531,3532],{"class":274,"line":1234},[272,3533,3534],{},"        handleItemAdded: function(item) {\n",[272,3536,3537],{"class":274,"line":1240},[272,3538,3539],{},"            console.log('Item added:', item);\n",[272,3541,3542],{"class":274,"line":1246},[272,3543,1207],{},[272,3545,3546],{"class":274,"line":1251},[272,3547,1160],{},[272,3549,3550],{"class":274,"line":1256},[272,3551,3552],{},"        handleCartUpdate: function(cart) {\n",[272,3554,3555],{"class":274,"line":1262},[272,3556,3557],{},"            console.log('Cart updated:', cart);\n",[272,3559,3560],{"class":274,"line":1268},[272,3561,1207],{},[272,3563,3564],{"class":274,"line":1274},[272,3565,1160],{},[272,3567,3568],{"class":274,"line":1280},[272,3569,3570],{},"        destroy: function() {\n",[272,3572,3573],{"class":274,"line":1286},[272,3574,3575],{},"            Backbone.off('MyModule.ItemAdded', this.handleItemAdded, this);\n",[272,3577,3578],{"class":274,"line":1292},[272,3579,3580],{},"            Backbone.off('cart:updated', this.handleCartUpdate, this);\n",[272,3582,3583],{"class":274,"line":1298},[272,3584,984],{},[272,3586,3587],{"class":274,"line":1304},[272,3588,990],{},[272,3590,3591],{"class":274,"line":1309},[272,3592,996],{},[114,3594,3596],{"id":3595},"integrating-with-existing-components","Integrating with Existing Components",[15,3598,3599],{},"Access and extend existing SuiteCommerce components:",[235,3601,3603],{"className":850,"code":3602,"language":852,"meta":243,"style":243},"\u002F\u002F Adding items to cart programmatically\ndefine('MyModule.CartIntegration', [\n    'LiveOrder.Model'\n], function(LiveOrderModel) {\n    'use strict';\n\n    return {\n        addToCart: function(item, quantity) {\n            var cart = LiveOrderModel.getInstance();\n            \n            return cart.addItem({\n                item: {\n                    internalid: item.internalid\n                },\n                quantity: quantity\n            });\n        },\n        \n        getCartSummary: function() {\n            var cart = LiveOrderModel.getInstance();\n            \n            return {\n                itemCount: cart.get('lines').length,\n                subtotal: cart.get('summary').subtotal\n            };\n        }\n    };\n});\n",[130,3604,3605,3610,3615,3620,3625,3629,3633,3637,3642,3647,3651,3656,3661,3666,3671,3676,3680,3684,3688,3693,3697,3701,3705,3710,3715,3719,3723,3727],{"__ignoreMap":243},[272,3606,3607],{"class":274,"line":275},[272,3608,3609],{},"\u002F\u002F Adding items to cart programmatically\n",[272,3611,3612],{"class":274,"line":282},[272,3613,3614],{},"define('MyModule.CartIntegration', [\n",[272,3616,3617],{"class":274,"line":300},[272,3618,3619],{},"    'LiveOrder.Model'\n",[272,3621,3622],{"class":274,"line":307},[272,3623,3624],{},"], function(LiveOrderModel) {\n",[272,3626,3627],{"class":274,"line":313},[272,3628,899],{},[272,3630,3631],{"class":274,"line":424},[272,3632,304],{"emptyLinePlaceholder":303},[272,3634,3635],{"class":274,"line":429},[272,3636,908],{},[272,3638,3639],{"class":274,"line":435},[272,3640,3641],{},"        addToCart: function(item, quantity) {\n",[272,3643,3644],{"class":274,"line":579},[272,3645,3646],{},"            var cart = LiveOrderModel.getInstance();\n",[272,3648,3649],{"class":274,"line":590},[272,3650,928],{},[272,3652,3653],{"class":274,"line":595},[272,3654,3655],{},"            return cart.addItem({\n",[272,3657,3658],{"class":274,"line":603},[272,3659,3660],{},"                item: {\n",[272,3662,3663],{"class":274,"line":616},[272,3664,3665],{},"                    internalid: item.internalid\n",[272,3667,3668],{"class":274,"line":629},[272,3669,3670],{},"                },\n",[272,3672,3673],{"class":274,"line":640},[272,3674,3675],{},"                quantity: quantity\n",[272,3677,3678],{"class":274,"line":646},[272,3679,967],{},[272,3681,3682],{"class":274,"line":765},[272,3683,1207],{},[272,3685,3686],{"class":274,"line":774},[272,3687,1160],{},[272,3689,3690],{"class":274,"line":782},[272,3691,3692],{},"        getCartSummary: function() {\n",[272,3694,3695],{"class":274,"line":795},[272,3696,3646],{},[272,3698,3699],{"class":274,"line":807},[272,3700,928],{},[272,3702,3703],{"class":274,"line":818},[272,3704,1271],{},[272,3706,3707],{"class":274,"line":823},[272,3708,3709],{},"                itemCount: cart.get('lines').length,\n",[272,3711,3712],{"class":274,"line":970},[272,3713,3714],{},"                subtotal: cart.get('summary').subtotal\n",[272,3716,3717],{"class":274,"line":975},[272,3718,1301],{},[272,3720,3721],{"class":274,"line":981},[272,3722,984],{},[272,3724,3725],{"class":274,"line":987},[272,3726,990],{},[272,3728,3729],{"class":274,"line":993},[272,3730,996],{},[26,3732],{},[29,3734,79],{"id":3735},"testing-your-extension",[15,3737,3738],{},"Testing prevents production bugs and regression issues.",[114,3740,3742],{"id":3741},"unit-testing-with-jasmine","Unit Testing with Jasmine",[15,3744,3745],{},"Set up Jasmine tests for your modules:",[235,3747,3749],{"className":850,"code":3748,"language":852,"meta":243,"style":243},"\u002F\u002F tests\u002FMyModule.View.spec.js\ndefine([\n    'MyModule.View',\n    'MyModule.Model',\n    'Backbone'\n], function(MyModuleView, MyModuleModel, Backbone) {\n    'use strict';\n\n    describe('MyModule.View', function() {\n        \n        var view;\n        var model;\n        \n        beforeEach(function() {\n            model = new MyModuleModel({\n                items: [\n                    { internalid: '1', name: 'Item 1', price: 10.00 },\n                    { internalid: '2', name: 'Item 2', price: 20.00 }\n                ]\n            });\n            \n            view = new MyModuleView({\n                model: model,\n                application: {\n                    getComponent: function() { return {}; }\n                }\n            });\n        });\n        \n        afterEach(function() {\n            view.destroy();\n        });\n        \n        it('should render with items', function() {\n            view.render();\n            \n            expect(view.$('.my-module-item').length).toBe(2);\n        });\n        \n        it('should show loading state', function() {\n            model.set('isLoading', true);\n            view.render();\n            \n            expect(view.$('.my-module-loading').length).toBe(1);\n        });\n        \n        it('should handle submit click', function() {\n            spyOn(model, 'save').and.returnValue($.Deferred().resolve());\n            \n            view.render();\n            view.$('[data-action=\"submit\"]').click();\n            \n            expect(model.save).toHaveBeenCalled();\n        });\n        \n        it('should display error message', function() {\n            model.set('errorMessage', 'Something went wrong');\n            view.render();\n            \n            expect(view.$('.my-module-error').text()).toContain('Something went wrong');\n        });\n    });\n});\n",[130,3750,3751,3756,3761,3765,3770,3774,3779,3783,3787,3792,3796,3801,3806,3810,3815,3820,3825,3830,3835,3840,3844,3848,3853,3858,3863,3868,3873,3877,3882,3886,3891,3896,3900,3904,3909,3914,3918,3923,3927,3931,3936,3941,3945,3949,3954,3958,3962,3967,3972,3976,3980,3985,3989,3994,3998,4002,4007,4012,4016,4020,4025,4029,4033],{"__ignoreMap":243},[272,3752,3753],{"class":274,"line":275},[272,3754,3755],{},"\u002F\u002F tests\u002FMyModule.View.spec.js\n",[272,3757,3758],{"class":274,"line":282},[272,3759,3760],{},"define([\n",[272,3762,3763],{"class":274,"line":300},[272,3764,869],{},[272,3766,3767],{"class":274,"line":307},[272,3768,3769],{},"    'MyModule.Model',\n",[272,3771,3772],{"class":274,"line":313},[272,3773,3416],{},[272,3775,3776],{"class":274,"line":424},[272,3777,3778],{},"], function(MyModuleView, MyModuleModel, Backbone) {\n",[272,3780,3781],{"class":274,"line":429},[272,3782,899],{},[272,3784,3785],{"class":274,"line":435},[272,3786,304],{"emptyLinePlaceholder":303},[272,3788,3789],{"class":274,"line":579},[272,3790,3791],{},"    describe('MyModule.View', function() {\n",[272,3793,3794],{"class":274,"line":590},[272,3795,1160],{},[272,3797,3798],{"class":274,"line":595},[272,3799,3800],{},"        var view;\n",[272,3802,3803],{"class":274,"line":603},[272,3804,3805],{},"        var model;\n",[272,3807,3808],{"class":274,"line":616},[272,3809,1160],{},[272,3811,3812],{"class":274,"line":629},[272,3813,3814],{},"        beforeEach(function() {\n",[272,3816,3817],{"class":274,"line":640},[272,3818,3819],{},"            model = new MyModuleModel({\n",[272,3821,3822],{"class":274,"line":646},[272,3823,3824],{},"                items: [\n",[272,3826,3827],{"class":274,"line":765},[272,3828,3829],{},"                    { internalid: '1', name: 'Item 1', price: 10.00 },\n",[272,3831,3832],{"class":274,"line":774},[272,3833,3834],{},"                    { internalid: '2', name: 'Item 2', price: 20.00 }\n",[272,3836,3837],{"class":274,"line":782},[272,3838,3839],{},"                ]\n",[272,3841,3842],{"class":274,"line":795},[272,3843,967],{},[272,3845,3846],{"class":274,"line":807},[272,3847,928],{},[272,3849,3850],{"class":274,"line":818},[272,3851,3852],{},"            view = new MyModuleView({\n",[272,3854,3855],{"class":274,"line":823},[272,3856,3857],{},"                model: model,\n",[272,3859,3860],{"class":274,"line":970},[272,3861,3862],{},"                application: {\n",[272,3864,3865],{"class":274,"line":975},[272,3866,3867],{},"                    getComponent: function() { return {}; }\n",[272,3869,3870],{"class":274,"line":981},[272,3871,3872],{},"                }\n",[272,3874,3875],{"class":274,"line":987},[272,3876,967],{},[272,3878,3879],{"class":274,"line":993},[272,3880,3881],{},"        });\n",[272,3883,3884],{"class":274,"line":1229},[272,3885,1160],{},[272,3887,3888],{"class":274,"line":1234},[272,3889,3890],{},"        afterEach(function() {\n",[272,3892,3893],{"class":274,"line":1240},[272,3894,3895],{},"            view.destroy();\n",[272,3897,3898],{"class":274,"line":1246},[272,3899,3881],{},[272,3901,3902],{"class":274,"line":1251},[272,3903,1160],{},[272,3905,3906],{"class":274,"line":1256},[272,3907,3908],{},"        it('should render with items', function() {\n",[272,3910,3911],{"class":274,"line":1262},[272,3912,3913],{},"            view.render();\n",[272,3915,3916],{"class":274,"line":1268},[272,3917,928],{},[272,3919,3920],{"class":274,"line":1274},[272,3921,3922],{},"            expect(view.$('.my-module-item').length).toBe(2);\n",[272,3924,3925],{"class":274,"line":1280},[272,3926,3881],{},[272,3928,3929],{"class":274,"line":1286},[272,3930,1160],{},[272,3932,3933],{"class":274,"line":1292},[272,3934,3935],{},"        it('should show loading state', function() {\n",[272,3937,3938],{"class":274,"line":1298},[272,3939,3940],{},"            model.set('isLoading', true);\n",[272,3942,3943],{"class":274,"line":1304},[272,3944,3913],{},[272,3946,3947],{"class":274,"line":1309},[272,3948,928],{},[272,3950,3951],{"class":274,"line":1314},[272,3952,3953],{},"            expect(view.$('.my-module-loading').length).toBe(1);\n",[272,3955,3956],{"class":274,"line":1320},[272,3957,3881],{},[272,3959,3960],{"class":274,"line":1326},[272,3961,1160],{},[272,3963,3964],{"class":274,"line":1331},[272,3965,3966],{},"        it('should handle submit click', function() {\n",[272,3968,3969],{"class":274,"line":1337},[272,3970,3971],{},"            spyOn(model, 'save').and.returnValue($.Deferred().resolve());\n",[272,3973,3974],{"class":274,"line":1342},[272,3975,928],{},[272,3977,3978],{"class":274,"line":1348},[272,3979,3913],{},[272,3981,3982],{"class":274,"line":1354},[272,3983,3984],{},"            view.$('[data-action=\"submit\"]').click();\n",[272,3986,3987],{"class":274,"line":1360},[272,3988,928],{},[272,3990,3991],{"class":274,"line":1366},[272,3992,3993],{},"            expect(model.save).toHaveBeenCalled();\n",[272,3995,3996],{"class":274,"line":1371},[272,3997,3881],{},[272,3999,4000],{"class":274,"line":1376},[272,4001,1160],{},[272,4003,4004],{"class":274,"line":1381},[272,4005,4006],{},"        it('should display error message', function() {\n",[272,4008,4009],{"class":274,"line":1387},[272,4010,4011],{},"            model.set('errorMessage', 'Something went wrong');\n",[272,4013,4014],{"class":274,"line":1393},[272,4015,3913],{},[272,4017,4018],{"class":274,"line":1399},[272,4019,928],{},[272,4021,4022],{"class":274,"line":1404},[272,4023,4024],{},"            expect(view.$('.my-module-error').text()).toContain('Something went wrong');\n",[272,4026,4027],{"class":274,"line":1409},[272,4028,3881],{},[272,4030,4031],{"class":274,"line":1415},[272,4032,1451],{},[272,4034,4035],{"class":274,"line":1421},[272,4036,996],{},[114,4038,4040],{"id":4039},"integration-testing","Integration Testing",[15,4042,4043],{},"Test the full flow from frontend to backend:",[235,4045,4047],{"className":850,"code":4046,"language":852,"meta":243,"style":243},"\u002F\u002F tests\u002FMyModule.Integration.spec.js\ndefine([\n    'MyModule.Model',\n    'jQuery'\n], function(MyModuleModel, $) {\n    'use strict';\n\n    describe('MyModule Integration', function() {\n        \n        var model;\n        \n        beforeEach(function() {\n            model = new MyModuleModel();\n        });\n        \n        it('should fetch items from the server', function(done) {\n            model.fetch().done(function() {\n                expect(model.get('items')).toBeDefined();\n                expect(Array.isArray(model.get('items'))).toBe(true);\n                done();\n            }).fail(function(error) {\n                fail('Fetch should not fail: ' + error);\n                done();\n            });\n        });\n        \n        it('should handle server errors gracefully', function(done) {\n            \u002F\u002F Mock a server error\n            spyOn($, 'ajax').and.returnValue(\n                $.Deferred().reject({ status: 500 })\n            );\n            \n            model.fetch().always(function() {\n                expect(model.get('isLoading')).toBe(false);\n                done();\n            });\n        });\n    });\n});\n",[130,4048,4049,4054,4058,4062,4067,4072,4076,4080,4085,4089,4093,4097,4101,4106,4110,4114,4119,4124,4129,4134,4139,4143,4148,4152,4156,4160,4164,4169,4174,4179,4184,4189,4193,4198,4203,4207,4211,4215,4219],{"__ignoreMap":243},[272,4050,4051],{"class":274,"line":275},[272,4052,4053],{},"\u002F\u002F tests\u002FMyModule.Integration.spec.js\n",[272,4055,4056],{"class":274,"line":282},[272,4057,3760],{},[272,4059,4060],{"class":274,"line":300},[272,4061,3769],{},[272,4063,4064],{"class":274,"line":307},[272,4065,4066],{},"    'jQuery'\n",[272,4068,4069],{"class":274,"line":313},[272,4070,4071],{},"], function(MyModuleModel, $) {\n",[272,4073,4074],{"class":274,"line":424},[272,4075,899],{},[272,4077,4078],{"class":274,"line":429},[272,4079,304],{"emptyLinePlaceholder":303},[272,4081,4082],{"class":274,"line":435},[272,4083,4084],{},"    describe('MyModule Integration', function() {\n",[272,4086,4087],{"class":274,"line":579},[272,4088,1160],{},[272,4090,4091],{"class":274,"line":590},[272,4092,3805],{},[272,4094,4095],{"class":274,"line":595},[272,4096,1160],{},[272,4098,4099],{"class":274,"line":603},[272,4100,3814],{},[272,4102,4103],{"class":274,"line":616},[272,4104,4105],{},"            model = new MyModuleModel();\n",[272,4107,4108],{"class":274,"line":629},[272,4109,3881],{},[272,4111,4112],{"class":274,"line":640},[272,4113,1160],{},[272,4115,4116],{"class":274,"line":646},[272,4117,4118],{},"        it('should fetch items from the server', function(done) {\n",[272,4120,4121],{"class":274,"line":765},[272,4122,4123],{},"            model.fetch().done(function() {\n",[272,4125,4126],{"class":274,"line":774},[272,4127,4128],{},"                expect(model.get('items')).toBeDefined();\n",[272,4130,4131],{"class":274,"line":782},[272,4132,4133],{},"                expect(Array.isArray(model.get('items'))).toBe(true);\n",[272,4135,4136],{"class":274,"line":795},[272,4137,4138],{},"                done();\n",[272,4140,4141],{"class":274,"line":807},[272,4142,1357],{},[272,4144,4145],{"class":274,"line":818},[272,4146,4147],{},"                fail('Fetch should not fail: ' + error);\n",[272,4149,4150],{"class":274,"line":823},[272,4151,4138],{},[272,4153,4154],{"class":274,"line":970},[272,4155,967],{},[272,4157,4158],{"class":274,"line":975},[272,4159,3881],{},[272,4161,4162],{"class":274,"line":981},[272,4163,1160],{},[272,4165,4166],{"class":274,"line":987},[272,4167,4168],{},"        it('should handle server errors gracefully', function(done) {\n",[272,4170,4171],{"class":274,"line":993},[272,4172,4173],{},"            \u002F\u002F Mock a server error\n",[272,4175,4176],{"class":274,"line":1229},[272,4177,4178],{},"            spyOn($, 'ajax').and.returnValue(\n",[272,4180,4181],{"class":274,"line":1234},[272,4182,4183],{},"                $.Deferred().reject({ status: 500 })\n",[272,4185,4186],{"class":274,"line":1240},[272,4187,4188],{},"            );\n",[272,4190,4191],{"class":274,"line":1246},[272,4192,928],{},[272,4194,4195],{"class":274,"line":1251},[272,4196,4197],{},"            model.fetch().always(function() {\n",[272,4199,4200],{"class":274,"line":1256},[272,4201,4202],{},"                expect(model.get('isLoading')).toBe(false);\n",[272,4204,4205],{"class":274,"line":1262},[272,4206,4138],{},[272,4208,4209],{"class":274,"line":1268},[272,4210,967],{},[272,4212,4213],{"class":274,"line":1274},[272,4214,3881],{},[272,4216,4217],{"class":274,"line":1280},[272,4218,1451],{},[272,4220,4221],{"class":274,"line":1286},[272,4222,996],{},[114,4224,4226],{"id":4225},"running-tests","Running Tests",[15,4228,4229],{},"Execute tests using the SuiteCommerce test runner:",[235,4231,4233],{"className":266,"code":4232,"language":268,"meta":243,"style":243},"# Run all tests\nscc extension:test\n\n# Run specific test file\nscc extension:test --spec tests\u002FMyModule.View.spec.js\n\n# Run with coverage\nscc extension:test --coverage\n",[130,4234,4235,4240,4247,4251,4256,4269,4273,4278],{"__ignoreMap":243},[272,4236,4237],{"class":274,"line":275},[272,4238,4239],{"class":278},"# Run all tests\n",[272,4241,4242,4244],{"class":274,"line":282},[272,4243,316],{"class":285},[272,4245,4246],{"class":289}," extension:test\n",[272,4248,4249],{"class":274,"line":300},[272,4250,304],{"emptyLinePlaceholder":303},[272,4252,4253],{"class":274,"line":307},[272,4254,4255],{"class":278},"# Run specific test file\n",[272,4257,4258,4260,4263,4266],{"class":274,"line":313},[272,4259,316],{"class":285},[272,4261,4262],{"class":289}," extension:test",[272,4264,4265],{"class":293}," --spec",[272,4267,4268],{"class":289}," tests\u002FMyModule.View.spec.js\n",[272,4270,4271],{"class":274,"line":424},[272,4272,304],{"emptyLinePlaceholder":303},[272,4274,4275],{"class":274,"line":429},[272,4276,4277],{"class":278},"# Run with coverage\n",[272,4279,4280,4282,4284],{"class":274,"line":435},[272,4281,316],{"class":285},[272,4283,4262],{"class":289},[272,4285,4286],{"class":293}," --coverage\n",[26,4288],{},[29,4290,85],{"id":4291},"deployment-and-version-management",[15,4293,4294],{},"A reliable deployment process prevents production incidents.",[114,4296,4298],{"id":4297},"build-process","Build Process",[15,4300,4301],{},"Build your extension for deployment:",[235,4303,4305],{"className":266,"code":4304,"language":268,"meta":243,"style":243},"# Build for production\nscc extension:build --production\n\n# This creates optimized bundles in the deploy\u002F directory\n",[130,4306,4307,4312,4322,4326],{"__ignoreMap":243},[272,4308,4309],{"class":274,"line":275},[272,4310,4311],{"class":278},"# Build for production\n",[272,4313,4314,4316,4319],{"class":274,"line":282},[272,4315,316],{"class":285},[272,4317,4318],{"class":289}," extension:build",[272,4320,4321],{"class":293}," --production\n",[272,4323,4324],{"class":274,"line":300},[272,4325,304],{"emptyLinePlaceholder":303},[272,4327,4328],{"class":274,"line":307},[272,4329,4330],{"class":278},"# This creates optimized bundles in the deploy\u002F directory\n",[15,4332,4333],{},"The build process:",[361,4335,4336,4339,4342,4345,4348],{},[37,4337,4338],{},"Compiles Sass to CSS",[37,4340,4341],{},"Minifies JavaScript",[37,4343,4344],{},"Compiles Handlebars templates",[37,4346,4347],{},"Generates source maps (optional)",[37,4349,4350],{},"Creates the deployment manifest",[114,4352,4354],{"id":4353},"deployment-to-netsuite","Deployment to NetSuite",[15,4356,4357],{},"Deploy using the SuiteCommerce developer tools:",[235,4359,4361],{"className":266,"code":4360,"language":268,"meta":243,"style":243},"# Deploy to sandbox\nscc extension:deploy --target sandbox\n\n# Deploy to production (requires confirmation)\nscc extension:deploy --target production\n\n# Deploy specific version\nscc extension:deploy --target production --version 1.2.0\n",[130,4362,4363,4368,4381,4385,4390,4401,4405,4410],{"__ignoreMap":243},[272,4364,4365],{"class":274,"line":275},[272,4366,4367],{"class":278},"# Deploy to sandbox\n",[272,4369,4370,4372,4375,4378],{"class":274,"line":282},[272,4371,316],{"class":285},[272,4373,4374],{"class":289}," extension:deploy",[272,4376,4377],{"class":293}," --target",[272,4379,4380],{"class":289}," sandbox\n",[272,4382,4383],{"class":274,"line":300},[272,4384,304],{"emptyLinePlaceholder":303},[272,4386,4387],{"class":274,"line":307},[272,4388,4389],{"class":278},"# Deploy to production (requires confirmation)\n",[272,4391,4392,4394,4396,4398],{"class":274,"line":313},[272,4393,316],{"class":285},[272,4395,4374],{"class":289},[272,4397,4377],{"class":293},[272,4399,4400],{"class":289}," production\n",[272,4402,4403],{"class":274,"line":424},[272,4404,304],{"emptyLinePlaceholder":303},[272,4406,4407],{"class":274,"line":429},[272,4408,4409],{"class":278},"# Deploy specific version\n",[272,4411,4412,4414,4416,4418,4421,4424],{"class":274,"line":435},[272,4413,316],{"class":285},[272,4415,4374],{"class":289},[272,4417,4377],{"class":293},[272,4419,4420],{"class":289}," production",[272,4422,4423],{"class":293}," --version",[272,4425,4426],{"class":293}," 1.2.0\n",[114,4428,4430],{"id":4429},"version-control-best-practices","Version Control Best Practices",[15,4432,4433],{},"Structure your git workflow for extensions:",[235,4435,4437],{"className":266,"code":4436,"language":268,"meta":243,"style":243},"# Branch naming\nfeature\u002Fmy-new-feature\nbugfix\u002Ffix-cart-calculation\nrelease\u002F1.2.0\n\n# Commit message format\n[MODULE] Brief description\n\n# Examples\n[MyModule] Add quantity validation to cart\n[MyModule] Fix price calculation for bulk orders\n[Config] Update deployment configuration\n",[130,4438,4439,4444,4449,4454,4459,4463,4468,4473,4477,4482,4487,4499],{"__ignoreMap":243},[272,4440,4441],{"class":274,"line":275},[272,4442,4443],{"class":278},"# Branch naming\n",[272,4445,4446],{"class":274,"line":282},[272,4447,4448],{"class":285},"feature\u002Fmy-new-feature\n",[272,4450,4451],{"class":274,"line":300},[272,4452,4453],{"class":285},"bugfix\u002Ffix-cart-calculation\n",[272,4455,4456],{"class":274,"line":307},[272,4457,4458],{"class":285},"release\u002F1.2.0\n",[272,4460,4461],{"class":274,"line":313},[272,4462,304],{"emptyLinePlaceholder":303},[272,4464,4465],{"class":274,"line":424},[272,4466,4467],{"class":278},"# Commit message format\n",[272,4469,4470],{"class":274,"line":429},[272,4471,4472],{"class":507},"[MODULE] Brief description\n",[272,4474,4475],{"class":274,"line":435},[272,4476,304],{"emptyLinePlaceholder":303},[272,4478,4479],{"class":274,"line":579},[272,4480,4481],{"class":278},"# Examples\n",[272,4483,4484],{"class":274,"line":590},[272,4485,4486],{"class":507},"[MyModule] Add quantity validation to cart\n",[272,4488,4489,4492,4496],{"class":274,"line":595},[272,4490,4491],{"class":507},"[MyModule] Fix price calculation ",[272,4493,4495],{"class":4494},"szBVR","for",[272,4497,4498],{"class":507}," bulk orders\n",[272,4500,4501],{"class":274,"line":603},[272,4502,4503],{"class":507},"[Config] Update deployment configuration\n",[114,4505,4507],{"id":4506},"rollback-strategy","Rollback Strategy",[15,4509,4510],{},"Always maintain rollback capability:",[235,4512,4514],{"className":266,"code":4513,"language":268,"meta":243,"style":243},"# Tag releases\ngit tag -a v1.2.0 -m \"Release 1.2.0 - Added bulk pricing\"\n\n# Deploy previous version if needed\nscc extension:deploy --target production --version 1.1.0\n",[130,4515,4516,4521,4541,4545,4550],{"__ignoreMap":243},[272,4517,4518],{"class":274,"line":275},[272,4519,4520],{"class":278},"# Tag releases\n",[272,4522,4523,4526,4529,4532,4535,4538],{"class":274,"line":282},[272,4524,4525],{"class":285},"git",[272,4527,4528],{"class":289}," tag",[272,4530,4531],{"class":293}," -a",[272,4533,4534],{"class":289}," v1.2.0",[272,4536,4537],{"class":293}," -m",[272,4539,4540],{"class":289}," \"Release 1.2.0 - Added bulk pricing\"\n",[272,4542,4543],{"class":274,"line":300},[272,4544,304],{"emptyLinePlaceholder":303},[272,4546,4547],{"class":274,"line":307},[272,4548,4549],{"class":278},"# Deploy previous version if needed\n",[272,4551,4552,4554,4556,4558,4560,4562],{"class":274,"line":313},[272,4553,316],{"class":285},[272,4555,4374],{"class":289},[272,4557,4377],{"class":293},[272,4559,4420],{"class":289},[272,4561,4423],{"class":293},[272,4563,4564],{"class":293}," 1.1.0\n",[26,4566],{},[29,4568,91],{"id":4569},"performance-considerations",[15,4571,4572],{},"Extensions directly impact site performance. Build with performance in mind.",[114,4574,4576],{"id":4575},"bundle-size-optimization","Bundle Size Optimization",[15,4578,4579],{},"Keep your JavaScript bundles small:",[235,4581,4583],{"className":850,"code":4582,"language":852,"meta":243,"style":243},"\u002F\u002F Bad: Import entire library\ndefine(['lodash'], function(_) {\n    return _.map(items, transform);\n});\n\n\u002F\u002F Good: Import only what you need\ndefine(['underscore'], function(_) {\n    return _.map(items, transform);\n});\n",[130,4584,4585,4590,4595,4600,4604,4608,4613,4618,4622],{"__ignoreMap":243},[272,4586,4587],{"class":274,"line":275},[272,4588,4589],{},"\u002F\u002F Bad: Import entire library\n",[272,4591,4592],{"class":274,"line":282},[272,4593,4594],{},"define(['lodash'], function(_) {\n",[272,4596,4597],{"class":274,"line":300},[272,4598,4599],{},"    return _.map(items, transform);\n",[272,4601,4602],{"class":274,"line":307},[272,4603,996],{},[272,4605,4606],{"class":274,"line":313},[272,4607,304],{"emptyLinePlaceholder":303},[272,4609,4610],{"class":274,"line":424},[272,4611,4612],{},"\u002F\u002F Good: Import only what you need\n",[272,4614,4615],{"class":274,"line":429},[272,4616,4617],{},"define(['underscore'], function(_) {\n",[272,4619,4620],{"class":274,"line":435},[272,4621,4599],{},[272,4623,4624],{"class":274,"line":579},[272,4625,996],{},[114,4627,4629],{"id":4628},"lazy-loading","Lazy Loading",[15,4631,4632],{},"Load extension features only when needed:",[235,4634,4636],{"className":850,"code":4635,"language":852,"meta":243,"style":243},"\u002F\u002F Lazy load heavy modules\ndefine('MyModule', [], function() {\n    'use strict';\n\n    return {\n        mountToApp: function(application) {\n            var router = application.getComponent('Router');\n            \n            router.route('my-heavy-feature', function() {\n                \u002F\u002F Load the heavy view only when the route is accessed\n                require(['MyModule.HeavyView'], function(HeavyView) {\n                    var view = new HeavyView({ application: application });\n                    view.showInModal();\n                });\n            });\n        }\n    };\n});\n",[130,4637,4638,4643,4648,4652,4656,4660,4664,4669,4673,4678,4683,4688,4693,4698,4702,4706,4710,4714],{"__ignoreMap":243},[272,4639,4640],{"class":274,"line":275},[272,4641,4642],{},"\u002F\u002F Lazy load heavy modules\n",[272,4644,4645],{"class":274,"line":282},[272,4646,4647],{},"define('MyModule', [], function() {\n",[272,4649,4650],{"class":274,"line":300},[272,4651,899],{},[272,4653,4654],{"class":274,"line":307},[272,4655,304],{"emptyLinePlaceholder":303},[272,4657,4658],{"class":274,"line":313},[272,4659,908],{},[272,4661,4662],{"class":274,"line":424},[272,4663,913],{},[272,4665,4666],{"class":274,"line":429},[272,4667,4668],{},"            var router = application.getComponent('Router');\n",[272,4670,4671],{"class":274,"line":435},[272,4672,928],{},[272,4674,4675],{"class":274,"line":579},[272,4676,4677],{},"            router.route('my-heavy-feature', function() {\n",[272,4679,4680],{"class":274,"line":590},[272,4681,4682],{},"                \u002F\u002F Load the heavy view only when the route is accessed\n",[272,4684,4685],{"class":274,"line":595},[272,4686,4687],{},"                require(['MyModule.HeavyView'], function(HeavyView) {\n",[272,4689,4690],{"class":274,"line":603},[272,4691,4692],{},"                    var view = new HeavyView({ application: application });\n",[272,4694,4695],{"class":274,"line":616},[272,4696,4697],{},"                    view.showInModal();\n",[272,4699,4700],{"class":274,"line":629},[272,4701,1970],{},[272,4703,4704],{"class":274,"line":640},[272,4705,967],{},[272,4707,4708],{"class":274,"line":646},[272,4709,984],{},[272,4711,4712],{"class":274,"line":765},[272,4713,990],{},[272,4715,4716],{"class":274,"line":774},[272,4717,996],{},[114,4719,4721],{"id":4720},"caching-strategies","Caching Strategies",[15,4723,4724],{},"Cache expensive computations and API responses:",[235,4726,4728],{"className":850,"code":4727,"language":852,"meta":243,"style":243},"\u002F\u002F Client-side caching\ndefine('MyModule.Cache', [], function() {\n    'use strict';\n\n    var cache = {};\n    var CACHE_TTL = 5 * 60 * 1000; \u002F\u002F 5 minutes\n    \n    return {\n        get: function(key) {\n            var entry = cache[key];\n            \n            if (entry && Date.now() \u003C entry.expiry) {\n                return entry.value;\n            }\n            \n            return null;\n        },\n        \n        set: function(key, value, ttl) {\n            cache[key] = {\n                value: value,\n                expiry: Date.now() + (ttl || CACHE_TTL)\n            };\n        },\n        \n        clear: function(key) {\n            if (key) {\n                delete cache[key];\n            } else {\n                cache = {};\n            }\n        }\n    };\n});\n",[130,4729,4730,4735,4740,4744,4748,4753,4758,4762,4766,4771,4776,4780,4785,4790,4794,4798,4803,4807,4811,4816,4821,4826,4831,4835,4839,4843,4848,4853,4858,4863,4868,4872,4876,4880],{"__ignoreMap":243},[272,4731,4732],{"class":274,"line":275},[272,4733,4734],{},"\u002F\u002F Client-side caching\n",[272,4736,4737],{"class":274,"line":282},[272,4738,4739],{},"define('MyModule.Cache', [], function() {\n",[272,4741,4742],{"class":274,"line":300},[272,4743,899],{},[272,4745,4746],{"class":274,"line":307},[272,4747,304],{"emptyLinePlaceholder":303},[272,4749,4750],{"class":274,"line":313},[272,4751,4752],{},"    var cache = {};\n",[272,4754,4755],{"class":274,"line":424},[272,4756,4757],{},"    var CACHE_TTL = 5 * 60 * 1000; \u002F\u002F 5 minutes\n",[272,4759,4760],{"class":274,"line":429},[272,4761,1490],{},[272,4763,4764],{"class":274,"line":435},[272,4765,908],{},[272,4767,4768],{"class":274,"line":579},[272,4769,4770],{},"        get: function(key) {\n",[272,4772,4773],{"class":274,"line":590},[272,4774,4775],{},"            var entry = cache[key];\n",[272,4777,4778],{"class":274,"line":595},[272,4779,928],{},[272,4781,4782],{"class":274,"line":603},[272,4783,4784],{},"            if (entry && Date.now() \u003C entry.expiry) {\n",[272,4786,4787],{"class":274,"line":616},[272,4788,4789],{},"                return entry.value;\n",[272,4791,4792],{"class":274,"line":629},[272,4793,1896],{},[272,4795,4796],{"class":274,"line":640},[272,4797,928],{},[272,4799,4800],{"class":274,"line":646},[272,4801,4802],{},"            return null;\n",[272,4804,4805],{"class":274,"line":765},[272,4806,1207],{},[272,4808,4809],{"class":274,"line":774},[272,4810,1160],{},[272,4812,4813],{"class":274,"line":782},[272,4814,4815],{},"        set: function(key, value, ttl) {\n",[272,4817,4818],{"class":274,"line":795},[272,4819,4820],{},"            cache[key] = {\n",[272,4822,4823],{"class":274,"line":807},[272,4824,4825],{},"                value: value,\n",[272,4827,4828],{"class":274,"line":818},[272,4829,4830],{},"                expiry: Date.now() + (ttl || CACHE_TTL)\n",[272,4832,4833],{"class":274,"line":823},[272,4834,1301],{},[272,4836,4837],{"class":274,"line":970},[272,4838,1207],{},[272,4840,4841],{"class":274,"line":975},[272,4842,1160],{},[272,4844,4845],{"class":274,"line":981},[272,4846,4847],{},"        clear: function(key) {\n",[272,4849,4850],{"class":274,"line":987},[272,4851,4852],{},"            if (key) {\n",[272,4854,4855],{"class":274,"line":993},[272,4856,4857],{},"                delete cache[key];\n",[272,4859,4860],{"class":274,"line":1229},[272,4861,4862],{},"            } else {\n",[272,4864,4865],{"class":274,"line":1234},[272,4866,4867],{},"                cache = {};\n",[272,4869,4870],{"class":274,"line":1240},[272,4871,1896],{},[272,4873,4874],{"class":274,"line":1246},[272,4875,984],{},[272,4877,4878],{"class":274,"line":1251},[272,4879,990],{},[272,4881,4882],{"class":274,"line":1256},[272,4883,996],{},[114,4885,4887],{"id":4886},"minimizing-netsuite-api-calls","Minimizing NetSuite API Calls",[15,4889,4890],{},"Batch requests where possible:",[235,4892,4894],{"className":850,"code":4893,"language":852,"meta":243,"style":243},"\u002F\u002F Bad: Multiple separate requests\nitems.forEach(function(item) {\n    model.fetchItem(item.id);\n});\n\n\u002F\u002F Good: Single batched request\nmodel.fetchItems(items.map(function(item) {\n    return item.id;\n}));\n",[130,4895,4896,4901,4906,4911,4915,4919,4924,4929,4934],{"__ignoreMap":243},[272,4897,4898],{"class":274,"line":275},[272,4899,4900],{},"\u002F\u002F Bad: Multiple separate requests\n",[272,4902,4903],{"class":274,"line":282},[272,4904,4905],{},"items.forEach(function(item) {\n",[272,4907,4908],{"class":274,"line":300},[272,4909,4910],{},"    model.fetchItem(item.id);\n",[272,4912,4913],{"class":274,"line":307},[272,4914,996],{},[272,4916,4917],{"class":274,"line":313},[272,4918,304],{"emptyLinePlaceholder":303},[272,4920,4921],{"class":274,"line":424},[272,4922,4923],{},"\u002F\u002F Good: Single batched request\n",[272,4925,4926],{"class":274,"line":429},[272,4927,4928],{},"model.fetchItems(items.map(function(item) {\n",[272,4930,4931],{"class":274,"line":435},[272,4932,4933],{},"    return item.id;\n",[272,4935,4936],{"class":274,"line":579},[272,4937,4938],{},"}));\n",[26,4940],{},[29,4942,97],{"id":4943},"faq",[114,4945,4947],{"id":4946},"how-long-does-it-take-to-build-a-typical-extension","How long does it take to build a typical extension?",[15,4949,4950],{},"Simple extensions (UI widgets, display modifications) take 2-4 days. Medium complexity (custom checkout steps, B2B features) take 1-2 weeks. Complex extensions (product configurators, custom integrations) take 3-6 weeks. Factor in testing and deployment time.",[114,4952,4954],{"id":4953},"can-i-use-typescript-for-suitecommerce-extensions","Can I use TypeScript for SuiteCommerce extensions?",[15,4956,4957],{},"Not natively. SuiteCommerce's build system expects JavaScript with AMD modules. You can compile TypeScript to JavaScript, but this adds build complexity. Most teams stick with JavaScript and JSDoc for type hints.",[114,4959,4961],{"id":4960},"how-do-i-debug-extensions-in-production","How do I debug extensions in production?",[15,4963,4964,4965,4968],{},"Enable verbose logging during development, then use browser DevTools and the Network tab to trace issues. For backend issues, use SuiteScript's ",[130,4966,4967],{},"nlapiLogExecution()"," and check the Execution Log in NetSuite.",[114,4970,4972],{"id":4971},"whats-the-best-way-to-handle-extension-configuration","What's the best way to handle extension configuration?",[15,4974,4975,4976,4979],{},"Use SuiteCommerce's Configuration component. Create a JSON schema for your settings and access them via ",[130,4977,4978],{},"SC.Configuration.get('mymodule.setting')",". This allows settings changes without code deployments.",[114,4981,4983],{"id":4982},"how-do-i-make-extensions-theme-agnostic","How do I make extensions theme-agnostic?",[15,4985,4986],{},"Avoid hard-coding CSS selectors from specific themes. Use data attributes for JavaScript hooks. Provide your own CSS classes and let theme developers override styles. Test with multiple themes before release.",[26,4988],{},[29,4990,4992],{"id":4991},"start-building","Start Building",[15,4994,4995],{},"You now have the architecture knowledge and code patterns to build production-quality SuiteCommerce extensions. The patterns in this guide have been refined across dozens of implementations—they work.",[15,4997,4998,4999,5003],{},"If you're facing a complex extension requirement or need to accelerate development, ",[40,5000,5002],{"href":5001},"\u002Fsuitecommerce-services\u002Fsuitecommerce-implementation","our SuiteCommerce team"," has built extensions ranging from simple UI enhancements to full custom checkout experiences. We're happy to review your requirements.",[15,5005,5006,5007,5011],{},"For simpler customizations that don't require extensions, check our guide on ",[40,5008,5010],{"href":5009},"\u002Fblog\u002Fsuitecommerce-theme-development","SuiteCommerce theme development"," (coming soon).",[15,5013,5014,5017],{},[18,5015,5016],{},"The best extension is one that's maintainable, performant, and solves a real business problem."," Build with those goals, and you'll succeed.",[5019,5020,5021],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":243,"searchDepth":282,"depth":282,"links":5023},[5024,5025,5030,5036,5041,5047,5052,5056,5061,5067,5073,5080],{"id":31,"depth":282,"text":32},{"id":102,"depth":282,"text":43,"children":5026},[5027,5028,5029],{"id":116,"depth":300,"text":117},{"id":159,"depth":300,"text":160},{"id":229,"depth":300,"text":230},{"id":251,"depth":282,"text":49,"children":5031},[5032,5033,5034,5035],{"id":257,"depth":300,"text":258},{"id":374,"depth":300,"text":375},{"id":446,"depth":300,"text":447},{"id":487,"depth":300,"text":488},{"id":657,"depth":282,"text":55,"children":5037},[5038,5039,5040],{"id":663,"depth":300,"text":664},{"id":839,"depth":300,"text":840},{"id":1004,"depth":300,"text":1005},{"id":1073,"depth":282,"text":61,"children":5042},[5043,5044,5045,5046],{"id":1085,"depth":300,"text":1086},{"id":1459,"depth":300,"text":1460},{"id":1736,"depth":300,"text":1737},{"id":2067,"depth":300,"text":2068},{"id":2251,"depth":282,"text":67,"children":5048},[5049,5050,5051],{"id":2257,"depth":300,"text":2258},{"id":2827,"depth":300,"text":2828},{"id":3325,"depth":300,"text":3326},{"id":3386,"depth":282,"text":73,"children":5053},[5054,5055],{"id":3392,"depth":300,"text":3393},{"id":3595,"depth":300,"text":3596},{"id":3735,"depth":282,"text":79,"children":5057},[5058,5059,5060],{"id":3741,"depth":300,"text":3742},{"id":4039,"depth":300,"text":4040},{"id":4225,"depth":300,"text":4226},{"id":4291,"depth":282,"text":85,"children":5062},[5063,5064,5065,5066],{"id":4297,"depth":300,"text":4298},{"id":4353,"depth":300,"text":4354},{"id":4429,"depth":300,"text":4430},{"id":4506,"depth":300,"text":4507},{"id":4569,"depth":282,"text":91,"children":5068},[5069,5070,5071,5072],{"id":4575,"depth":300,"text":4576},{"id":4628,"depth":300,"text":4629},{"id":4720,"depth":300,"text":4721},{"id":4886,"depth":300,"text":4887},{"id":4943,"depth":282,"text":97,"children":5074},[5075,5076,5077,5078,5079],{"id":4946,"depth":300,"text":4947},{"id":4953,"depth":300,"text":4954},{"id":4960,"depth":300,"text":4961},{"id":4971,"depth":300,"text":4972},{"id":4982,"depth":300,"text":4983},{"id":4991,"depth":282,"text":4992},[5082,5083],"SuiteCommerce","Development","2026-02-03","Complete developer guide for building custom SuiteCommerce extensions. Covers architecture, file structure, frontend\u002Fbackend integration, testing, and deployment.","md","\u002Fimages\u002Fblog\u002Fextensions-guide-hero.webp",{},"\u002Fblog\u002Fbuilding-custom-suitecommerce-extensions-developer-guide",null,{"title":5,"description":5085},"blog\u002Fbuilding-custom-suitecommerce-extensions-developer-guide",[5094,5095,5096,5097,5098],"SuiteCommerce Extensions","Custom Development","Backbone.js","SuiteScript","Frontend Development","kbZtMB_5J7532iCzNcu3TbYKn7L28SdHtuqbhSGGRMA",[5101,5104,5116,5128,5137,5147,5159,5168,5177,5186,5195,5205,5213,5223,5231,5242,5254,5263,5272,5281,5289],{"path":5089,"title":5,"categories":5102,"tags":5103,"heroImage":5087},[5082,5083],[5094,5095,5096,5097,5098],{"path":5105,"title":5106,"categories":5107,"tags":5109,"heroImage":5115},"\u002Fblog\u002Fcore-web-vitals-suitecommerce-optimization-checklist","Core Web Vitals for SuiteCommerce: The Complete 2026 Optimization Checklist",[5108,5082],"Performance",[5110,5111,5112,5113,5114,5082],"Core Web Vitals","LCP","INP","CLS","Performance Optimization","\u002Fimages\u002Fblog\u002Fcore-web-vitals-hero.webp",{"path":5117,"title":5118,"categories":5119,"tags":5121,"heroImage":5127},"\u002Fblog\u002Ffixing-duplicate-content-suitecommerce-faceted-navigation","Fixing Duplicate Content in SuiteCommerce Faceted Navigation",[5120,5082],"SEO",[5122,5123,5124,5125,5082,5126],"Duplicate Content","Faceted Navigation","Canonical Tags","Technical SEO","URL Parameters","\u002Fimages\u002Fblog\u002Fduplicate-content-seo-hero.webp",{"path":5129,"title":5130,"categories":5131,"tags":5132,"heroImage":5136},"\u002Fblog\u002Fheadless-suitecommerce-when-does-it-make-sense","Headless SuiteCommerce: When Does It Make Sense?",[5082,5083],[5082,5133,5134,5135,5108],"Headless Commerce","Architecture","Implementation","\u002Fimages\u002Fblog\u002Fheadless-suitecommerce-hero.webp",{"path":5138,"title":5139,"categories":5140,"tags":5142,"heroImage":5146},"\u002Fblog\u002Fnetsuite-ecommerce-integration-architecture-how-suitecommerce-works","NetSuite E-commerce Integration Architecture: How SuiteCommerce Actually Works",[5082,5083,5141],"Integration",[5134,5141,5097,5143,5144,5145],"API","Backend","Frontend","\u002Fimages\u002Fblog\u002Fnetsuite-ecommerce-integration-hero.webp",{"path":5148,"title":5149,"categories":5150,"tags":5152,"heroImage":5158},"\u002Fblog\u002Fnetsuite-integration-without-celigo-when-custom-beats-off-the-shelf","NetSuite Integration Without Celigo: When Custom Beats Off-the-Shelf",[5151,5141],"NetSuite",[5153,5154,5155,5156,5097,5157],"NetSuite Integration","Celigo","Custom Integration","RESTlet","API Development","\u002Fimages\u002Fblog\u002Fnetsuite-integration-hero.webp",{"path":5160,"title":5161,"categories":5162,"tags":5163,"heroImage":5167},"\u002Fblog\u002Fsuitecommerce-checkout-optimization-fixing-abandonment","SuiteCommerce Checkout Optimization: Fixing Abandonment at the Technical Level",[5082,5108],[5082,5164,5165,5166,5108],"Checkout Optimization","Cart Abandonment","Conversions","\u002Fimages\u002Fblog\u002Fsuitecommerce-checkout-hero.webp",{"path":5169,"title":5170,"categories":5171,"tags":5172,"heroImage":5176},"\u002Fblog\u002Fsuitecommerce-image-optimization-developer-guide","SuiteCommerce Image Optimization: A Developer's Guide",[5108,5082],[5173,5174,4629,5175,5108,5082],"Image Optimization","WebP","CDN","\u002Fimages\u002Fblog\u002Fimage-optimization-hero.webp",{"path":5178,"title":5179,"categories":5180,"tags":5181,"heroImage":5185},"\u002Fblog\u002Fsuitecommerce-implementation-cost-guide-2026","SuiteCommerce Implementation Cost Guide: What to Expect in 2026",[5082,5135],[5182,5135,5151,5183,5184],"SuiteCommerce Cost","Budget Planning","E-commerce","\u002Fimages\u002Fblog\u002Fimplementation-cost-hero.webp",{"path":5187,"title":5188,"categories":5189,"tags":5190,"heroImage":5194},"\u002Fblog\u002Fsuitecommerce-migration-checklist-upgrading-without-downtime","The SuiteCommerce Migration Checklist: Upgrading Without Downtime",[5082,5083],[5082,5191,5192,5193,5135],"Migration","Upgrade","Zero Downtime","\u002Fimages\u002Fblog\u002Fmigration-checklist-hero.webp",{"path":5196,"title":5197,"categories":5198,"tags":5199,"heroImage":5204},"\u002Fblog\u002Fsuitecommerce-myaccount-customization-b2b-features","SuiteCommerce MyAccount Customization: 10 Features B2B Customers Need",[5082,5083],[5082,5200,5201,5202,5203],"MyAccount","B2B","Customization","Portal","\u002Fimages\u002Fblog\u002Fsuitecommerce-myaccount-hero.webp",{"path":5206,"title":5207,"categories":5208,"tags":5210,"heroImage":5212},"\u002Fblog\u002Fsuitecommerce-performance-audit-286-stores","We Audited 286 Live SuiteCommerce Stores. Here's What We Found",[5108,5209,5082],"Research",[5108,5082,5110,5209,5211],"Benchmarks","\u002Fimages\u002Fblog\u002Fsuitecommerce-audit-hero.webp",{"path":5214,"title":5215,"categories":5216,"tags":5217,"heroImage":5222},"\u002Fblog\u002Fsuitecommerce-product-page-optimization-conversions-seo","How to Optimize SuiteCommerce Product Pages for Conversions and SEO",[5120,5108,5082],[5218,5219,5220,5125,5221],"Product Pages","Conversion Optimization","Schema Markup","CRO","\u002Fimages\u002Fblog\u002Fsuitecommerce-product-page-hero.webp",{"path":5224,"title":5225,"categories":5226,"tags":5227,"heroImage":5230},"\u002Fblog\u002Fsuitecommerce-seo-schema-markup-technical-guide","SuiteCommerce SEO: Schema Markup, Technical SEO, and What Actually Works",[5120,5082],[5220,5125,5228,5229,5082,5151],"JSON-LD","Structured Data","\u002Fimages\u002Fblog\u002Fseo-schema-markup-hero.webp",{"path":5232,"title":5233,"categories":5234,"tags":5236,"heroImage":5241},"\u002Fblog\u002Fsuitecommerce-theme-development-design-to-deployment","SuiteCommerce Theme Development: From Design to Deployment",[5083,5082,5235],"Themes",[5237,5238,5239,5240,5098,5202],"Theme Development","SASS","CSS","Templates","\u002Fimages\u002Fblog\u002Fsuitecommerce-theme-hero.webp",{"path":5243,"title":5244,"categories":5245,"tags":5248,"heroImage":5253},"\u002Fblog\u002Fsuitecommerce-version-upgrade-guide-2024","SuiteCommerce Version Upgrade Guide: 2023.x to 2024.x",[5082,5246,5247],"Maintenance","Upgrades",[5249,5191,5250,5251,5252],"Version Upgrade","2024 Release","SCA","Deployment","\u002Fimages\u002Fblog\u002Fsuitecommerce-upgrade-hero.webp",{"path":5255,"title":5256,"categories":5257,"tags":5258,"heroImage":5262},"\u002Fblog\u002Fsuitecommerce-vs-bigcommerce-netsuite-users","SuiteCommerce vs. BigCommerce for NetSuite Users: Which Platform Wins?",[5082,5184],[5259,5082,5151,5260,5261,5141],"BigCommerce","Platform Comparison","E-commerce Platform","\u002Fimages\u002Fblog\u002Fsuitecommerce-vs-bigcommerce-hero.webp",{"path":5264,"title":5265,"categories":5266,"tags":5267,"heroImage":5271},"\u002Fblog\u002Fsuitescript-performance-optimization-writing-efficient-scripts","SuiteScript Performance Optimization: Writing Efficient Scripts",[5151,5097],[5097,5114,5268,5269,5270,5143],"Governance","Map\u002FReduce","NetSuite Development","\u002Fimages\u002Fblog\u002Fsuitescript-performance-hero.webp",{"path":5273,"title":5274,"categories":5275,"tags":5276,"heroImage":5280},"\u002Fblog\u002Ftroubleshooting-suitecommerce-15-common-errors-how-to-fix","Troubleshooting SuiteCommerce: 15 Common Errors and How to Fix Them",[5082,5083],[5082,5277,5278,5279,5083],"Troubleshooting","Errors","Debugging","\u002Fimages\u002Fblog\u002Ftroubleshooting-errors-hero.webp",{"path":5282,"title":5283,"categories":5284,"tags":5285,"heroImage":5288},"\u002Fblog\u002Ftrue-cost-suitecommerce-maintenance-annual-budget-guide","The True Cost of SuiteCommerce Maintenance: Annual Budget Planning Guide",[5082,5108],[5082,5246,5183,5286,5287],"TCO","E-commerce Operations","\u002Fimages\u002Fblog\u002Fsuitecommerce-maintenance-cost-hero.webp",{"path":5290,"title":5291,"categories":5292,"tags":5293,"heroImage":5295},"\u002Fblog\u002Fwhy-suitecommerce-site-slow-how-to-fix","Why Your SuiteCommerce Site is Slow (And How to Fix It)",[5108,5082],[5108,5082,5294,5277],"Speed Optimization","\u002Fimages\u002Fblog\u002Fslow-site-fix-hero.webp",1773773965814]