Published on January 13, 2024.
One of SuiteBrowser's features that I've been exploring is its ability to support "remote apps." These are apps that run inside of NetSuite, but whose source code (and any other assets it needs) are hosted externally. There's no SuiteApp or bundle that needs to be installed, and no SuiteScripts needed.
These apps can be developed using whatever language or technologies you'd prefer. However, they're very much like Suitelets and Client Scripts in that they interact with NetSuite using standard SuiteScript modules.
The source code for the app is included at the end of this post. If you're a SuiteScript developer then I think you'll be familiar with most of the code.
The "executeInXojoSync" function is used to make calls into SuiteBrowser's "App Runner" engine and execute SuiteScript. It can be used to do other things as well, such as adjusting the name of the app's window, resizing it, etc.
In the code, you'll see that I'm using executeInXojoSync to load the SuiteScript "N/query" module, run a SuiteQL query, and return the results. I then use the results to generate a table, and load the content into the body of the page.
The code is hosted on one of my servers. When the user opens the app, the code is loaded and executed by SuiteBrowser's App Runner engine.
There's a lot more that can be done with this concept. For example, you can build and market apps that are used by multiple clients, control how the apps are licensed, how trials and upgrades are handled, and so on.
I'll write more about SuiteBrowser and "remote apps" in the future. Stay tuned!
<!DOCTYPE html> <html> <head> <title>Opportunity Tracker</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="supersimple.css"> <link rel="stylesheet" href="datatables.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js"></script> </head> <body onload="initApp();"> <div id="content"></div> </body> <script> function initApp() { executeInXojo( "setWindowTitle", "Opportunity Tracker" ); var jsCode = "require( [ 'N/query' ] );"; var jsResult = executeInXojoSync( jsCode ); jsCode = ` const query = require( 'N/query' ); let sql = \` SELECT TranID, TranDate, BUILTIN.DF( Entity ) AS Customer, Title, BUILTIN.DF( Employee ) AS SalesRep, '$' || ProjectedTotal AS ProjectedTotal, ( Probability * 100 ) || '%' AS Probability, REPLACE( BUILTIN.DF( Status ), BUILTIN.DF( Type ) || ' : ', '' ) AS Status FROM Transaction WHERE Type = 'Opprtnty' AND BUILTIN.DF( Status ) NOT LIKE '%Closed%' ORDER BY TranID \`; let queryResults = query.runSuiteQL( { query: sql, params: [] } ).asMappedResults(); JSON.stringify( queryResults, null, 5 ); `; var beginTime = new Date().getTime(); jsResult = executeInXojoSync( "runCode", jsCode ); var elapsedTime = ( new Date().getTime() - beginTime ) ; var records = JSON.parse( jsResult ); var columnNames = Object.keys( records[0] ); var thead = '<thead>'; thead += '<tr>'; for ( i = 0; i < columnNames.length; i++ ) { thead += '<th>' + columnNames[i] + '</th>'; } thead += '</tr>'; thead += '</thead>'; var tbody = '<tbody>'; for ( r = 0; r < records.length; r++ ) { tbody += '<tr>'; for ( i = 0; i < columnNames.length; i++ ) { var value = records[r][ columnNames[i] ]; if ( value === null ) { value = ''; } tbody += '<td>' + value + '</td>'; } tbody += '</tr>'; } tbody += '</tbody>'; var content = ` <h1 style="margin-bottom: 9px;">Opportunity Tracker</h1> <h5 style="margin-top: 0px; margin-bottom: 6px; color: #4d5f79; font-weight: 600; font-size: 14px;">There are currently ${records.length} open opportunities.</h5> <table id="transactionsTable" style="margin-top: 0px; margin-bottom: 12px; width: 100%;"> ${thead} ${tbody} </table> <p style="margin-top: 0px; margin-bottom: 24px; font-size: 12px; color: #4d5f79;"> Elapsed Time: ${elapsedTime}ms. </p> `; document.getElementById("content").innerHTML = content; var dataTableOptions = { "pageLength": 10, "lengthMenu": [ 10, 25, 50, 100, 250, 500, 1000 ] } $(`#transactionsTable`).DataTable( dataTableOptions ); } </script> </html>
Hello, I'm Tim Dietrich. I develop custom software for businesses that are running on NetSuite, including mobile apps, Web portals, Web APIs, and more.
I'm the developer of several popular NetSuite open source solutions, including the SuiteQL Query Tool, SuiteAPI, and more.
I founded SuiteStep, a NetSuite development studio, to provide custom software and AI solutions - and continue pushing the boundaries of what's possible on the NetSuite platform.
Copyright © 2025 Tim Dietrich.