SSJS — Server-Side JavaScript
This skill activates when a practitioner needs to author, debug, or architect Server-Side JavaScript (SSJS) code running inside Salesforce Marketing Cloud. It provides grounded guidance on execution environments, WSProxy for SOAP access, HTTP functions for REST calls, error handling, variable scoping rules, and the SSJS/AMPscript interoperability model.
Before Starting
Gather this context before working on anything in this domain:
- Execution context: is this running in a Script Activity (Automation Studio), a Cloud Page, or a Landing Page? The timeout, available functions, and subscriber context differ across contexts.
- API target: if calling SOAP APIs (retrieve/upsert Data Extensions, send Email, etc.), WSProxy is preferred over raw HTTP+XML. If calling external REST endpoints, use Script.Util.HttpRequest.
- Most common wrong assumption: SSJS is modern JavaScript. It is not. Marketing Cloud runs an ES3-compatible engine —
let, const, arrow functions, template literals, destructuring, and Promises are unavailable.
- Limits: Script Activities time out at 30 minutes and have a 6 GB memory limit per execution. Cloud Pages have no documented long-execution window and should be treated as synchronous request/response.
Core Concepts
Execution Environments and the <script runat="server"> Tag
SSJS code must be wrapped in <script runat="server"> tags. Without the runat="server" attribute the block is treated as client-side JavaScript and does not execute on the Marketing Cloud server. SSJS can coexist with AMPscript in the same file — the Marketing Cloud rendering engine processes AMPscript substitutions first, then evaluates SSJS blocks. This allows AMPscript variables to be referenced inside SSJS if the AMPscript block is declared before the SSJS block.
WSProxy — Preferred SOAP API Client
WSProxy (Script.Util.WSProxy) is the built-in Marketing Cloud SOAP API client for SSJS. It is the correct and preferred way to interact with Marketing Cloud objects (Data Extensions, Subscribers, Sends, etc.) from SSJS because:
- It handles SOAP envelope construction, authentication token injection, and paging automatically.
- It has significantly lower overhead than constructing raw SOAP XML via
Script.Util.HttpRequest.
- Common operations:
retrieve, createItem, updateItem, upsertBatch, deleteItem.
The core pattern:
var prox = new Script.Util.WSProxy();
var cols = ["SubscriberKey", "EmailAddress", "Status"];
var filter = {
Property: "Status",
SimpleOperator: "equals",
Value: "Active"
};
var result = prox.retrieve("Subscriber", cols, filter);
Retrieve results are paged. If result.HasMoreRows is true, use prox.getNextPage() to iterate.
Script.Util.HttpRequest — Outbound REST/HTTP
For calls to external REST APIs or non-Marketing Cloud SOAP endpoints, use Script.Util.HttpRequest. This is a synchronous HTTP client that supports GET, POST, PUT, PATCH, and DELETE. Set headers and body before calling request.send().
var req = new Script.Util.HttpRequest("https://api.example.com/data");
req.emptyContentHandling = 0;
req.retryCount = 2;
req.setHeader("Content-Type", "application/json");
req.setHeader("Authorization", "Bearer " + token);
req.method = "POST";
req.postData = Stringify(payload);
var resp = req.send();
var body = Platform.Function.ParseJSON(resp.content);
Error Handling and the Write() Logging Pattern
SSJS does not surface uncaught exceptions to the user in a useful way in Script Activities — an uncaught exception causes the entire Activity step to fail with a generic error, which makes diagnosis difficult. The mandatory pattern is try/catch around all significant operations with Write() logging inside the catch block.
Write() outputs text to the Script Activity log tab in Automation Studio, making it the primary debugging mechanism. Log the error message, stack if available, and any relevant variable state before re-throwing or gracefully continuing.
ES3 Dialect Constraints
The SSJS engine is ES3-compatible. This means:
- Use
var for variable declarations — let and const cause syntax errors.
- No arrow functions (
=>); use function keyword.
- No template literals (backtick strings); use
+ concatenation.
- No destructuring, spread, or
Promise.
JSON.stringify / JSON.parse are not available — use Stringify() and Platform.Function.ParseJSON() instead.
typeof, instanceof, standard for loops, and try/catch/finally work normally.
Common Patterns
WSProxy Upsert to a Data Extension
When to use: Bulk insert or update records in a Data Extension from a Script Activity, typically after retrieving data from an external API or another system.
How it works:
<script runat="server">
Platform.Load("Core", "1.1.1");
try {
var prox = new Script.Util.WSProxy();
var rows = [
{ keys: { SubscriberKey: "abc123" }, values: { FirstName: "Ana", Score: "95" } },
{ keys: { SubscriberKey: "def456" }, values: { FirstName: "Ben", Score: "80" } }
];
var result = prox.upsertBatch("DataExtensionObject", rows, { Name: "My_DE_ExternalKey" });
Write("Upserted: " + result.Status);
} catch(e) {
Write("ERROR: " + e.message);
}
</script>
Why not the alternative: Using AMPscript UpsertDE() works for small single-record updates inside sends, but it cannot handle batch operations, does not provide programmatic status checking, and is not appropriate for Automation Studio Script Activities.
Outbound REST Call with Error Logging
When to use: Pulling data from an external REST API (CRM, ERP, custom backend) inside a Script Activity and writing results to a Data Extension.
How it works:
<script runat="server">
Platform.Load("Core", "1.1.1");
try {
var req = new Script.Util.HttpRequest("https://api.example.com/leads");
req.emptyContentHandling = 0;
req.retryCount = 1;
req.setHeader("Authorization", "Bearer MyToken");
req.method = "GET";
var resp = req.send();
if (resp.statusCode != 200) {
throw new Error("HTTP error: " + resp.statusCode);
}
var data = Platform.Function.ParseJSON(resp.content);
// process data...
Write("Retrieved " + data.length + " leads.");
} catch(e) {
Write("FAILED: " + e.message);
// log to error DE if needed
}
</script>
Decision Guidance
| Situation |
Recommended Approach |
Reason |
| Retrieve/upsert Marketing Cloud objects (Subscribers, DEs, Sends) |
WSProxy |
Handles auth, paging, SOAP envelope automatically; lower overhead than raw HTTP |
| Call external REST API from Script Activity |
Script.Util.HttpRequest |
Designed for outbound HTTP; handles headers, methods, retries |
| Per-subscriber email personalization at send time |
AMPscript |
AMPscript has subscriber context; SSJS does not run per-subscriber during sends |
| Complex data transformation, looping, conditional logic in Automation |
SSJS Script Activity |
SSJS supports full procedural logic; AMPscript is template-oriented |
| Debugging a failing Script Activity |
Write() + try/catch |
Write() outputs to Activity log; uncaught exceptions give no diagnostic info |
| Need to call a SOAP API without WSProxy |
Script.Util.HttpRequest + raw XML |
Last resort only — WSProxy is always preferred for SOAP |
Recommended Workflow
Step-by-step instructions for an AI agent or practitioner working on this task:
- Confirm execution context — identify whether this is a Script Activity, Cloud Page, or Landing Page. Script Activity: check timeout risk (30-min limit). Cloud Page: check whether code needs to be synchronous and handle subscriber context.
- Identify the API target — if interacting with Marketing Cloud objects (Data Extensions, Subscribers, Sends), plan to use WSProxy. If calling external endpoints, plan to use Script.Util.HttpRequest.
- Author the SSJS block — wrap all code in
<script runat="server"> tags. Load the Core library with Platform.Load("Core", "1.1.1"). Use only var for declarations. Use Stringify() and Platform.Function.ParseJSON() instead of JSON.stringify/JSON.parse.
- Wrap in try/catch — every meaningful operation (API call, WSProxy call, data write) must be inside a try/catch block with
Write() logging in the catch. Never allow uncaught exceptions in Script Activities.
- Implement paging if using WSProxy retrieve — check
result.HasMoreRows after every prox.retrieve() call and loop with prox.getNextPage() until false to avoid silently missing records.
- Test incrementally — run the Script Activity manually in Automation Studio, check the Activity log tab for Write() output, verify DE row counts before and after.
- Review for ES3 compatibility — scan for
let, const, arrow functions, template literals, JSON.stringify, JSON.parse, Promise, and async/await — all are unsupported and will cause syntax or runtime errors.
Review Checklist
Run through these before marking work in this area complete:
Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
Script Activity 30-minute timeout is absolute — If a Script Activity exceeds 30 minutes of execution time, it terminates without completing and the step is marked as an error. There is no graceful shutdown callback. For large-volume operations, split work across multiple Activities or use Query Activities for set-based data operations instead.
WSProxy retrieve does not return all rows by default — prox.retrieve() returns a paged result set. If you do not check result.HasMoreRows and call prox.getNextPage(), you silently process only the first page of results (typically 2,500 rows). This causes data processing gaps that are very hard to detect.
AMPscript is evaluated before SSJS — In a mixed file, AMPscript variable substitution happens before the SSJS engine runs. This means AMPscript @variables can inject values into SSJS string literals, but SSJS variables cannot be read by AMPscript in the same file. Relying on SSJS output being available to AMPscript in the same render pass will not work.
Output Artifacts
| Artifact |
Description |
| SSJS Script Activity file |
Complete <script runat="server"> block ready to paste into an Automation Studio Script Activity |
| WSProxy retrieve/upsert snippet |
Parameterized code block for reading from or writing to a Data Extension via WSProxy |
| HTTP request snippet |
Script.Util.HttpRequest pattern for outbound REST calls with header, method, and error handling |
| Error logging pattern |
try/catch + Write() template for any Script Activity block |
Related Skills
data/marketing-cloud-sql-queries — Use for set-based data transformation inside Automation Studio; SQL Query Activity is more efficient than SSJS loops for bulk DE-to-DE operations
data/marketing-cloud-data-sync — Use when the goal is syncing data between Marketing Cloud and external systems at the platform configuration level rather than via SSJS scripting
admin/consent-management-marketing — Use when SSJS is being used to read or write subscription/preference data; consent rules affect which DE fields are writable
1---2name: ssjs-server-side-javascript3description: Use this skill when writing, debugging, or reviewing Server-Side JavaScript (SSJS) in Salesforce Marketing Cloud — Script Activities, Cloud Pages, and Landing Pages. Covers WSProxy for SOAP API access, Script.Util.HttpRequest for outbound REST calls, error handling patterns, execution limits, and SSJS/AMPscript interoperability. NOT for standard Apex on the Salesforce Platform, and not for client-side JavaScript in Experience Cloud or LWC. NOT for AMPscript-only personalization logic inside Email Studio sends — use apex/ampscript-development. NOT for calling the Marketing Cloud REST/SOAP API from Salesforce — use apex/marketing-cloud-api.4---56# SSJS — Server-Side JavaScript78This skill activates when a practitioner needs to author, debug, or architect Server-Side JavaScript (SSJS) code running inside Salesforce Marketing Cloud. It provides grounded guidance on execution environments, WSProxy for SOAP access, HTTP functions for REST calls, error handling, variable scoping rules, and the SSJS/AMPscript interoperability model.910---1112## Before Starting1314Gather this context before working on anything in this domain:1516- **Execution context:** is this running in a Script Activity (Automation Studio), a Cloud Page, or a Landing Page? The timeout, available functions, and subscriber context differ across contexts.17- **API target:** if calling SOAP APIs (retrieve/upsert Data Extensions, send Email, etc.), WSProxy is preferred over raw HTTP+XML. If calling external REST endpoints, use Script.Util.HttpRequest.18- **Most common wrong assumption:** SSJS is modern JavaScript. It is not. Marketing Cloud runs an ES3-compatible engine — `let`, `const`, arrow functions, template literals, destructuring, and Promises are unavailable.19- **Limits:** Script Activities time out at 30 minutes and have a 6 GB memory limit per execution. Cloud Pages have no documented long-execution window and should be treated as synchronous request/response.2021---2223## Core Concepts2425### Execution Environments and the `<script runat="server">` Tag2627SSJS code must be wrapped in `<script runat="server">` tags. Without the `runat="server"` attribute the block is treated as client-side JavaScript and does not execute on the Marketing Cloud server. SSJS can coexist with AMPscript in the same file — the Marketing Cloud rendering engine processes AMPscript substitutions first, then evaluates SSJS blocks. This allows AMPscript variables to be referenced inside SSJS if the AMPscript block is declared before the SSJS block.2829### WSProxy — Preferred SOAP API Client3031WSProxy (`Script.Util.WSProxy`) is the built-in Marketing Cloud SOAP API client for SSJS. It is the correct and preferred way to interact with Marketing Cloud objects (Data Extensions, Subscribers, Sends, etc.) from SSJS because:3233- It handles SOAP envelope construction, authentication token injection, and paging automatically.34- It has significantly lower overhead than constructing raw SOAP XML via `Script.Util.HttpRequest`.35- Common operations: `retrieve`, `createItem`, `updateItem`, `upsertBatch`, `deleteItem`.3637The core pattern:38```javascript39var prox = new Script.Util.WSProxy();40var cols = ["SubscriberKey", "EmailAddress", "Status"];41var filter = {42 Property: "Status",43 SimpleOperator: "equals",44 Value: "Active"45};46var result = prox.retrieve("Subscriber", cols, filter);47```4849Retrieve results are paged. If `result.HasMoreRows` is true, use `prox.getNextPage()` to iterate.5051### Script.Util.HttpRequest — Outbound REST/HTTP5253For calls to external REST APIs or non-Marketing Cloud SOAP endpoints, use `Script.Util.HttpRequest`. This is a synchronous HTTP client that supports GET, POST, PUT, PATCH, and DELETE. Set headers and body before calling `request.send()`.5455```javascript56var req = new Script.Util.HttpRequest("https://api.example.com/data");57req.emptyContentHandling = 0;58req.retryCount = 2;59req.setHeader("Content-Type", "application/json");60req.setHeader("Authorization", "Bearer " + token);61req.method = "POST";62req.postData = Stringify(payload);63var resp = req.send();64var body = Platform.Function.ParseJSON(resp.content);65```6667### Error Handling and the Write() Logging Pattern6869SSJS does not surface uncaught exceptions to the user in a useful way in Script Activities — an uncaught exception causes the entire Activity step to fail with a generic error, which makes diagnosis difficult. The mandatory pattern is `try/catch` around all significant operations with `Write()` logging inside the catch block.7071`Write()` outputs text to the Script Activity log tab in Automation Studio, making it the primary debugging mechanism. Log the error message, stack if available, and any relevant variable state before re-throwing or gracefully continuing.7273### ES3 Dialect Constraints7475The SSJS engine is ES3-compatible. This means:76- Use `var` for variable declarations — `let` and `const` cause syntax errors.77- No arrow functions (`=>`); use `function` keyword.78- No template literals (backtick strings); use `+` concatenation.79- No destructuring, spread, or `Promise`.80- `JSON.stringify` / `JSON.parse` are not available — use `Stringify()` and `Platform.Function.ParseJSON()` instead.81- `typeof`, `instanceof`, standard `for` loops, and `try/catch/finally` work normally.8283---8485## Common Patterns8687### WSProxy Upsert to a Data Extension8889**When to use:** Bulk insert or update records in a Data Extension from a Script Activity, typically after retrieving data from an external API or another system.9091**How it works:**92```javascript93<script runat="server">94Platform.Load("Core", "1.1.1");95try {96 var prox = new Script.Util.WSProxy();97 var rows = [98 { keys: { SubscriberKey: "abc123" }, values: { FirstName: "Ana", Score: "95" } },99 { keys: { SubscriberKey: "def456" }, values: { FirstName: "Ben", Score: "80" } }100 ];101 var result = prox.upsertBatch("DataExtensionObject", rows, { Name: "My_DE_ExternalKey" });102 Write("Upserted: " + result.Status);103} catch(e) {104 Write("ERROR: " + e.message);105}106</script>107```108109**Why not the alternative:** Using AMPscript `UpsertDE()` works for small single-record updates inside sends, but it cannot handle batch operations, does not provide programmatic status checking, and is not appropriate for Automation Studio Script Activities.110111### Outbound REST Call with Error Logging112113**When to use:** Pulling data from an external REST API (CRM, ERP, custom backend) inside a Script Activity and writing results to a Data Extension.114115**How it works:**116```javascript117<script runat="server">118Platform.Load("Core", "1.1.1");119try {120 var req = new Script.Util.HttpRequest("https://api.example.com/leads");121 req.emptyContentHandling = 0;122 req.retryCount = 1;123 req.setHeader("Authorization", "Bearer MyToken");124 req.method = "GET";125 var resp = req.send();126 if (resp.statusCode != 200) {127 throw new Error("HTTP error: " + resp.statusCode);128 }129 var data = Platform.Function.ParseJSON(resp.content);130 // process data...131 Write("Retrieved " + data.length + " leads.");132} catch(e) {133 Write("FAILED: " + e.message);134 // log to error DE if needed135}136</script>137```138139---140141## Decision Guidance142143| Situation | Recommended Approach | Reason |144|---|---|---|145| Retrieve/upsert Marketing Cloud objects (Subscribers, DEs, Sends) | WSProxy | Handles auth, paging, SOAP envelope automatically; lower overhead than raw HTTP |146| Call external REST API from Script Activity | Script.Util.HttpRequest | Designed for outbound HTTP; handles headers, methods, retries |147| Per-subscriber email personalization at send time | AMPscript | AMPscript has subscriber context; SSJS does not run per-subscriber during sends |148| Complex data transformation, looping, conditional logic in Automation | SSJS Script Activity | SSJS supports full procedural logic; AMPscript is template-oriented |149| Debugging a failing Script Activity | Write() + try/catch | Write() outputs to Activity log; uncaught exceptions give no diagnostic info |150| Need to call a SOAP API without WSProxy | Script.Util.HttpRequest + raw XML | Last resort only — WSProxy is always preferred for SOAP |151152---153154## Recommended Workflow155156Step-by-step instructions for an AI agent or practitioner working on this task:1571581. **Confirm execution context** — identify whether this is a Script Activity, Cloud Page, or Landing Page. Script Activity: check timeout risk (30-min limit). Cloud Page: check whether code needs to be synchronous and handle subscriber context.1592. **Identify the API target** — if interacting with Marketing Cloud objects (Data Extensions, Subscribers, Sends), plan to use WSProxy. If calling external endpoints, plan to use Script.Util.HttpRequest.1603. **Author the SSJS block** — wrap all code in `<script runat="server">` tags. Load the Core library with `Platform.Load("Core", "1.1.1")`. Use only `var` for declarations. Use `Stringify()` and `Platform.Function.ParseJSON()` instead of `JSON.stringify`/`JSON.parse`.1614. **Wrap in try/catch** — every meaningful operation (API call, WSProxy call, data write) must be inside a try/catch block with `Write()` logging in the catch. Never allow uncaught exceptions in Script Activities.1625. **Implement paging if using WSProxy retrieve** — check `result.HasMoreRows` after every `prox.retrieve()` call and loop with `prox.getNextPage()` until false to avoid silently missing records.1636. **Test incrementally** — run the Script Activity manually in Automation Studio, check the Activity log tab for Write() output, verify DE row counts before and after.1647. **Review for ES3 compatibility** — scan for `let`, `const`, arrow functions, template literals, `JSON.stringify`, `JSON.parse`, `Promise`, and `async/await` — all are unsupported and will cause syntax or runtime errors.165166---167168## Review Checklist169170Run through these before marking work in this area complete:171172- [ ] All code is inside `<script runat="server">` tags173- [ ] `Platform.Load("Core", "1.1.1")` is present at the top of the block174- [ ] No `let`, `const`, arrow functions, template literals, or modern JS syntax175- [ ] All API calls and data writes are wrapped in try/catch with Write() in the catch block176- [ ] WSProxy retrieve loops check `HasMoreRows` and call `getNextPage()` if paging is possible177- [ ] Script.Util.HttpRequest calls check `resp.statusCode` before consuming `resp.content`178- [ ] Sensitive values (tokens, passwords) are stored in Data Extensions or Content Builder, not hardcoded179180---181182## Salesforce-Specific Gotchas183184Non-obvious platform behaviors that cause real production problems:1851861. **Script Activity 30-minute timeout is absolute** — If a Script Activity exceeds 30 minutes of execution time, it terminates without completing and the step is marked as an error. There is no graceful shutdown callback. For large-volume operations, split work across multiple Activities or use Query Activities for set-based data operations instead.1871882. **WSProxy retrieve does not return all rows by default** — `prox.retrieve()` returns a paged result set. If you do not check `result.HasMoreRows` and call `prox.getNextPage()`, you silently process only the first page of results (typically 2,500 rows). This causes data processing gaps that are very hard to detect.1891903. **AMPscript is evaluated before SSJS** — In a mixed file, AMPscript variable substitution happens before the SSJS engine runs. This means AMPscript `@variables` can inject values into SSJS string literals, but SSJS variables cannot be read by AMPscript in the same file. Relying on SSJS output being available to AMPscript in the same render pass will not work.191192---193194## Output Artifacts195196| Artifact | Description |197|---|---|198| SSJS Script Activity file | Complete `<script runat="server">` block ready to paste into an Automation Studio Script Activity |199| WSProxy retrieve/upsert snippet | Parameterized code block for reading from or writing to a Data Extension via WSProxy |200| HTTP request snippet | Script.Util.HttpRequest pattern for outbound REST calls with header, method, and error handling |201| Error logging pattern | try/catch + Write() template for any Script Activity block |202203---204205## Related Skills206207- `data/marketing-cloud-sql-queries` — Use for set-based data transformation inside Automation Studio; SQL Query Activity is more efficient than SSJS loops for bulk DE-to-DE operations208- `data/marketing-cloud-data-sync` — Use when the goal is syncing data between Marketing Cloud and external systems at the platform configuration level rather than via SSJS scripting209- `admin/consent-management-marketing` — Use when SSJS is being used to read or write subscription/preference data; consent rules affect which DE fields are writable