Writing Scripts
The script editor
Open Script Engine › Scripts. The editor fills the page, with a toolbar across the top and side panels that slide in from the right. When a panel is open, the code moves over rather than being covered.
The toolbar
- Select Script...: the script picker. Click it, or press Ctrl+K, to search.
- ID: the script's ID. Click it to copy.
- Webhook: the address other systems call to run this script. See Script webhooks.
- Excel: publish the script to Excel and Power BI. See Scripts in Excel & Power BI.
- + (New Script): create a script.
- Modified: shown while the code has changes you have not saved.
- Panel buttons: Version history, Script settings, Documentation, Context browser and Execution history.
- Format Code and Validate (checks the code for syntax errors).
- Test, Debug, Save and Run. See Testing a script below.
- Git: compare the script with its file in your repository. It appears only when you have a Git connection. See Git integration.
The button in the editor's top corner, or Shift+F11, makes the editor full screen. Press Escape to leave.
Finding a script
Press Ctrl+K (Cmd+K on a Mac) or click the script picker. Type in Search scripts by name, ID, tag, or author...: every word you type must match. A number also matches a script ID, and #1363 matches the ID only. You can narrow the list by Category, Tags, type (Scripts or Reports) and Capabilities (AI Tool, Excel, OData, Write), and change the Sort. Use the arrow keys and Enter to open a script, and Escape to close the search.
Workflows appear in the list too, with a Workflow badge. Their code is generated from the canvas, so change them in the workflow editor, not here.
Problems and unsaved drafts
- Problems appears below the code when something is wrong, such as a syntax error or a name the sandbox does not have. Click a problem to go to its line.
- The editor keeps your unsaved code in this browser. If you come back to a script with changes from an earlier session, it offers Restore draft or Discard. The saved version is what runs until you save again.
Help while you type
- Autocomplete. Type a name and a dot, such as
helpers., to see what it offers, with a short description of each function and its arguments. Ctrl+Space opens the list at any point. A name the sandbox does not have, such as a misspelledodbc.excuteQuery, is underlined in red. - Names from your company. Inside the quotes of
odbc.executeQuery(",credentials.get(",tables.get(",documents.generateSync(",scripts.run("and similar calls, the list offers your real saved queries, credentials, lookup tables, document templates and scripts. - Snippets. Type one of these names and press Tab:
main,main-async,main-odbc,main-document,try-catch,retry-fetch,for-each-async,daily-windowandverify-webhook. - The Docs panel. Documentation opens the full reference beside your code, with a search box. Its Plex APIs tab searches the Plex Connect API catalog. The same reference is published as the Script API reference.
- AI assistant. If your company uses DataMagik's AI assistant, its AI Assistant button opens a chat that can read the script you are editing. It proposes code changes, which you approve before they are applied. Ask AI on the Problems list, and Ask AI to fix this on an error message, open the chat with the problem already filled in.
main(context)
Every script needs a function called main. DataMagik calls it with one argument, context, and what it returns is the run's result.
function main(context) {
const order = context.order_no;
if (!order) {
return { success: false, script_message: "No order number was given." };
}
console.log("Processing order", order);
return { success: true, script_message: "Processed " + order, data: { order_no: order } };
}
main can also be async function main(context), so you can use await. An async script is allowed its full timeout.
What is in context
Every run gets three keys from DataMagik. They replace any key with the same name in the input.
| Key | What it holds |
|---|---|
company_id | Your company. |
user_id | The person the run is for. 0 when no person is involved, such as on a schedule. |
triggered_by | What started the run, such as test-run, manual, scheduler, app, automation, assistant, email, file_gateway or script. |
The rest depends on what started the run:
- Test and Run in the editor: exactly the JSON on the Test input tab.
- A webhook or API call: the JSON body of the request, as top-level keys.
- A schedule: nothing else. There is no schedule name and no scheduled time.
- An AI assistant:
context.parameters, the arguments the assistant chose. - Another script: the parent's context with the keys it passed laid over it.
- Apps, automations, email, files, hooks and others: each sends its own keys. See the page for each, and the Triggers tab below.
The Context browser
Click Context browser to see what context will hold. It has three tabs:
- Test input: the JSON your test runs use. Type it here. Validate JSON checks it, and Clear empties it.
- Past runs: the context a real earlier run received. Pick a run from the list.
- Triggers: every way a script can be started and the keys each one sends.
Fields your code reads are marked read by this script; fields it reads that the chosen context does not have are marked read but missing. Click or drag a field into the code to insert it. Use as test input copies a past run's or a trigger's context into the Test input, which is the easiest way to rehearse a real start.
What a script returns
Return an object. Only success is required.
| Field | What it does |
|---|---|
success | true if the script did its job, false if not. A run that returns false counts as a failure on the Executions page. |
script_message | The sentence the person who started the script reads. |
script_title | A heading above that message. |
notification_level | "success", "warning", "error" or "info". Sets the message's colour. Anything else is treated as "info". |
data | Any data for whatever called the script: an app, a webhook caller, a parent script. It is not shown to anyone. |
error | Why it failed. Use it with success: false. |
documents | Files the run produced, one entry per file: { url, filename, format }. |
download_url | One file. |
_navigation | Send the browser to a page when the script finishes. |
_effects | Actions in an Application Designer app, such as showing a dialog or refreshing a component. |
Write script_message for the person who will read it on the shop floor. "No price for part 44-9912 in Part Pricing" tells them what to fix; "Failed" does not.
Where each field is shown
Different places read different fields. A script written for one place can look silent in another.
| Started from | Reads |
|---|---|
| The browser extension (Plex toolbar and automations) | script_title, script_message and notification_level as a pop-up message, plus documents, download_url and _navigation. |
| An Application Designer status indicator | status (success, warning, error or neutral), text (or message, or script_message), detail and download_url. One file only: it ignores documents. |
| An app calling the script from its own code | data only. |
| A webhook or API call | The whole object. |
A parent script, through scripts.run | The whole object. |
A script that drives a status indicator should return status and text:
return {
success: true,
status: "success",
text: "Order SO-1001 created",
detail: "3 lines"
};
A hook script returns its own shape, such as { veto: true, reason: "…" }. See the hook's page.
Two common mistakes: returning message to the browser extension, which only shows script_message; and putting a file's URL inside data, where nothing opens it. Use documents or download_url for files.
Logging
console.log, console.info, console.warn, console.error and console.debug write lines to the run's log. Objects are written as JSON. You see the log on the run's Console Logs tab in the editor, live while it runs, and on the Executions page afterwards.
- A run keeps up to 5,000 lines. After that the log says
[truncated]and further lines are dropped. - A line longer than 10,000 characters is cut short.
- Credentials are never put in
context, so loggingcontextdoes not reveal them. Do not log a credential's value yourself.
Testing a script
| Button | Runs | Notes |
|---|---|---|
| Test | The code in the editor, saved or not, with the Test input as context. | Does real work: emails are sent and Plex writes happen. |
| Debug | The code in the editor, stopping at breakpoints. | Emails are captured, not sent. The run is allowed at least 30 minutes so you can step through it. |
| Run | The last saved version, with the Test input as context. | The same as a real run started by a person. Unsaved changes are not used. |
When a run starts, the Execution history panel opens. Each run has tabs:
- Console Logs: the run's log, live while it runs.
- Return Value: what
mainreturned. - Generated Documents: files the run made, if any.
- Error: why it failed, if it did.
The message at the bottom of the screen says whether the run finished, not what it returned. A script that returns success: false still shows Test execution completed, so check Return Value.
Test runs appear on the Executions page as test-run. See Script executions.
Debugging step by step
- Click just left of a line number, or put the cursor on a line and press F9, to set a breakpoint. With no breakpoints, Debug stops on the first line.
- Click Debug. When the run stops, the line is highlighted and a panel shows its local variables.
- Use Continue (F8), Step over (F10), Step into (F11), Step out or Stop.
Saving and versions
Click Save or press Ctrl+S. Each save creates a new numbered version. The newest version is what every real run uses from then on: schedules, webhooks, apps, Excel and the rest. Test and Debug do not need a save.
Click Version history to see the versions, newest first, with the current one marked CURRENT. For each version you can:
- View Code.
- Compare with Previous: show what changed from the version before.
- Revert: save that version's code again as a new version. Nothing is deleted; the versions in between stay in the history.
Script settings
Click Script settings. Change what you need and click Save Changes, which also saves the code.
| Setting | What it does |
|---|---|
| Script Name and Description | How the script is found and called. scripts.run calls a script by its name, so renaming one breaks callers that use the old name. |
| Tags | Group scripts in the search and in Integrations. Press Enter or comma after each tag. |
| Part of | The integrations this script belongs to. Add to integration adds it to one. See Integrations. |
| Category | A free-text group, used by the search's Category filter. |
| Priority (1-20) | When work is waiting, a higher number runs sooner. Default 5. |
| Timeout (s) | How long one run may take before it is stopped. Default 30 seconds, at most 2,700 (45 minutes). |
| Memory (MB) | How much memory one run may use, from 64 to 256. Default 128. A run that needs more is stopped. |
| Schedules | The schedules that run this script. Manage opens the Schedules page. |
| Assistant Tool | Let AI assistants call the script. Turn on Expose as assistant tool, describe what it does, and optionally give a Parameters schema (Generate proposes one from the context keys your code reads). Tick Writes / mutates data if the script changes anything. Click Save tool registration. The schema also sets the inputs offered for Excel links. |
| Credentials and Allowed Domains | Shortcuts to your company's credentials and to the list of outside sites scripts may call. Both are shared by every script in the company. |
| Delete Script | Stops the script from running. Its versions and run history are kept. |
Sandbox rules and limits
- No modules. There is no
requireorimport, and no npm packages. Use the built-in tools, or share your own code withscripts.include. - No browser or Node.js objects.
window,document,processandXMLHttpRequestdo not exist. Usefetchfor web requests. - No timers and no eval.
setTimeout,setInterval,evalandnew Functionare not available. - Modern JavaScript works:
async/await, classes, destructuring,?.,??,Map,Setand regular expressions. - Outside sites must be allowed. Every host a script calls with
fetchbelongs on your company's Allowed Domains list. List each host exactly, or use*.example.comto allow every host under it. A call to a host that is not listed writes an[allowed-domains]warning to the run's console, and will be refused once enforcement is switched on; see Script credentials. DataMagik's own address is always allowed. Addresses inside a private network, and anything other thanhttpandhttps, are always refused. - Web request limits. Each
fetchwaits up to 30 seconds and reads at most 10 MB of response. - Calls are rate-limited. A script that makes calls too fast gets an error back.
- Secrets stay in credentials. Get them with
credentials.get("NAME"); never type a password or key into the code. See Script credentials.
fetch, odbc.executeQuery and scripts.run wait for their answer before the script carries on, so wrapping them in Promise.all still runs them one at a time. To run several at once, use fetchAsync, odbc.executeQueryAsync or scripts.runAsync with await Promise.all(...).
Calling other scripts and sharing code
| Call | What it does | Returns |
|---|---|---|
scripts.run(name, input) | Runs another script's saved version on its own, separate from this one. Its context is this script's context with input laid over it. | Everything the other script returned. If it could not run (not found, never saved, threw an error, timed out), { success: false, error: "…" }. |
scripts.runAsync(name, input) | The same, but several can run at once with await Promise.all(...). Each child gets its own run record. | A promise of what the other script returned. If it fails, the promise rejects, so use try/catch. |
scripts.include(name, includeData) | Loads another script into this one, then calls its main. Functions it defines become available here. It receives includeData as context.includeData. | What the included script's main returned. |
Use scripts.include for a library of shared functions, and scripts.run for a separate job whose result you need.
function main(context) {
scripts.include("Shared Formatting"); // defines formatPartNo()
const check = scripts.run("Validate Order", { order_no: context.order_no });
if (!check.success) {
return { success: false, script_message: "Validation failed: " + (check.error || check.script_message) };
}
return { success: true, data: { part: formatPartNo(context.part_no), checked: check.data } };
}
Handling errors
Some tools report a failure in what they return, and some throw an error. plex.execute, fetch and email.send return a result you check; odbc.executeQuery throws. The Script API reference says which each one does. A pattern that covers both:
function main(context) {
if (!context.part_no) {
return { success: false, script_message: "part_no is required." };
}
try {
const result = odbc.executeQuery("Part Lookup", { part_no: context.part_no });
if (result.rowCount === 0) {
return { success: false, script_message: "No part " + context.part_no + " in Plex." };
}
return { success: true, script_message: "Found " + context.part_no, data: { part: result.rows[0] } };
} catch (error) {
console.error("Part Lookup failed:", String(error));
return { success: false, script_message: "Could not read Plex: " + String(error), notification_level: "error" };
}
}
A run that throws, runs out of time or runs out of memory is recorded as failed, with the reason on its Error tab.
Keyboard shortcuts
| Keys | Does |
|---|---|
| Ctrl+K | Search scripts. |
| Ctrl+S | Save. |
| Ctrl+Enter | Run the last saved version (the same as Run, not Test). |
| Shift+F11 / Escape | Full-screen editor on / off. |
| Shift+Alt+F | Format the code. |
| Ctrl+Space | Show autocomplete. |
| Ctrl+/ | Comment or uncomment the line. |
| Ctrl+D | Select the next match of the selected text. |
| Ctrl+Shift+K | Delete the line. |
| F9 | Set or clear a breakpoint on the current line. |
| F8 / F10 / F11 | While a debug run is paused: continue / step over / step into. |
On a Mac, use Cmd in place of Ctrl.
Good habits
- Check the input first. Return a clear
script_messagewhen a required key is missing, rather than failing halfway through. - Rehearse the real start. Use Past runs or Triggers and Use as test input, so your test sees the same
contextas the real thing. Remember a scheduled run gets almost nothing. - Keep company-specific values out of the code. Use lookup tables or a workflow's constants, and credentials for anything secret.
- Use saved queries. Save SQL in the SQL Editor and call it with
odbc.executeQuery, rather than writing SQL in the script. - Send email from templates. Use
email.sendWithTemplatewith a saved email template, so the wording can change without a new script version. - Log what helps the next person. A line at each important step makes a failed run easy to read on the Executions page; logging every row of a large loop hides the useful lines.
- Test before you save. Saving changes what every schedule and caller runs next.