Skip to main content

SSJS Debugging & Error Handling

To catch, or not to catch, that is debugging.

When you start working with programmatic languages in Salesforce Marketing Cloud, you will quickly become close friends with the Error 500 page. And if you beat it and go forward with the code and official documentation, you will fall in love with unexpected errors in functions and API responses. How to survive those?


SSJS Testing Ground

Before diving deep into errors, let's talk about where to write the SSJS. The language is useful in many places within the Marketing Cloud ecosystem. Among else in:

  • Cloud Pages (Web Studio)
  • Code Resources (Web Studio -> Content Builder)
  • Content Blocks (Content Builder)
  • Emails and other communication (Email Studio / Mobile Studio / Content Builder)
  • Script Activities (Automation Studio)

In all of the above cases, you might write more complex logic that will be error-prone and in need of debugging. However, only a few of those are really good to test your code, as only some of them provide access to methods mentioned later in the article.

Cloud Page

The most popular recommendation is Cloud Page - write or paste the SSJS code there, publish and check whether it is working correctly. It is a great way, as it allows you to easily leverage SSJS for the backend and HTML/CSS/JavaScript for the frontend.

It has, however, two flaws that, in some scenarios, might guide you to a different solution.

  1. Cost. Each view of the Cloud Page costs one Super Message. During debugging, you might hit quite a lot of those. And those tests - across your team and over time - stack up.
  2. Speed and context. Each time you want to republish the updated Cloud Page, you have to go through the Preview window (that load the whole code in POST method context), confirm it and go to the page via URL to see the GET method context.
You Should Know

Whenever you are writing an SSJS script on the Cloud Page, be sure to check your code on the published page, not the Web Studio Preview. Many SSJS functions do not work correctly in the Preview, so you might see errors that won't happen on the Cloud Page.

JSON Code Resource

When I want to go around those issues, I work in JSON Code Resource (in the past stored in Web Studio, now in Content Builder). It generates a link you can use to check whether everything works, just like a Cloud Page. However, page views are free. There is also no preview allowing for a faster save-reload cycle.

The cons? You won't be able to use any frontend (neither HTML nor JavaScript), which might be a deal-breaker in some scenarios.

So where?

If you needCloud PageJSON Code Resource
SSJS
HTML/CSS/JS
No Cost
Quick Save

500 - Internal Server Error

The first type of error you might encounter is the dreaded 500 Error you see right after trying to run your code Reason? The backend code (SSJS/AMPScript) is invalid. There might be a few reasons for this. The most popular are:

  1. Typo in SSJS Function Name
  2. Unclosed or wrongly closed Bracket
  3. Use of JavaScript feature that is not available in SSJS (there is quite a lot of those...)
  4. Lack of declaration (in most cases either missing variable declaration, Platform.Load("Core","1"); or var soap = new Script.Util.WSProxy(); while using it later in the code)

How to deal with those issues? Apart from just reading through your code line by line, there are two faster solutions.

Divide and Conquer

The first one can be done directly in the Marketing Cloud but is a bit of a brute-force approach. You block half of your code from running by enclosing it in a multiline comment (/* here goes the code */) and check again. If it works, you have half of the SSJS validated (from the 500 error perspective). If not, you split the remaining code in half and comment it out. Rinse, repeat. This way, you can quickly find the few lines that are the source of the issue and focus on validating them.

SSJS Linting

For many issues leading to 500 error, there is an even faster solution, but it requires some pre-work:

  1. You need to have Visual Studio Code with ESLint extension installed.
  2. You need to have NodeJS installed.
  3. You need to have a folder where you will store your SSJS code in files with the .ssjs file extension.
  4. You need to install the excellent ESLing configuration for SSJS made by Joern Berkefeld. To do this, run in terminal npm i -D eslint eslint-config-ssjs for the SSJS folder.

Once done, you have automatic SSJS code validation (again, only from the perspective of 500 error - but it is still worth it).

There is also a much more basic and less helpful but built-in solution to validate your SSJS code directly in the Marketing Cloud. Script Activity in Automation Studio. After adding your code there and clicking Validate Syntax button, it will check it against some basic rules. However, I recommend going with the ESLint. It provides much more value and is faster once configured.

What about Try/Catch?

There will be a separate section about Try/Catch. It does not work for issues leading to 500 Error.


Write the Error down

You got past the 500 error. Great! The script is still not outputting what you wanted? It is time to check what else might be wrong. The easiest way is to use the Write function. It shows whatever you pass in it to the frontend view. It is handy as SSJS does not support console.log due to its server-side nature (a little trick to make it work is below).

To use this function just add Write('This will be visible on the website') between <script runat="server"> Platform.Load('Core','1'); and </script>.

Of course, in real life, you will probably want to use it for variables:

<script runat="server">
Platform.Load('Core','1');
var response = HTTP.Get('http://www.example.com');
Write('Response from example.com: ' + response + '<br><br>');
</script>

As you can see in the code snippet above, I have more than just a response variable within my Write. The string before describes what is printed (useful mainly if you use multiple Writes in your code). The one after - <br><br> - will separate it from the rest of the website content (however, as it is HTML, it won't work in JSON Code Resource. You can use \n\n instead for the same outcome). I highly recommend this approach.

Few things to remember
  1. Write is Core Library function, so you need to load it first in your script with Platform.Load('Core','1');.
  2. If the variable you want to output is an object, you need to parse it to a string using Stringify(response).
  3. If you don't want to load Core Library, use Platform versions of those functions: Platform.Response.Write() and Platform.Function.Stringify().

Debugging Variable

Whenever you create a script for the long-term, it is good to keep your debugging Write functions. There might be new requirements. Data sources change. Marketing Cloud too. And sometimes, those things might break your code.

Of course, keeping the Writes as above is a no-go. You don't want your customers to see those. But there is a neat little trick to eat the cake and have it too:

<script runat="server">
Platform.Load('Core','1');
var debugging = true;

var response = HTTP.Get('http://www.example.com?q=1234');

if (debugging) {
Write('Response from example.com: ' + response + '<br><br>');
};
</script>

I added two things:

  1. There is var debugging = true; near the top. When debugging, keep it on true. When you publish it for production - change the value to false.
  2. The Write is within the if block. It now works only if the debugging variable equals true.
You Should Know

Make your life easier with a nice clean function that will save you a lot of time in the long run:

/**
* @function debugValue
* @description Outputs provided description and SSJS value to front-end in a type-safe & consistent way
* @param {string} description - Describes meaning of the second parameter in the output
* @param {*} value - The value that needs to be debugged
*/
function debugValue(description, value) {
Write(description + ': ' + (typeof value == 'object' ? Stringify(value) : value) + '<br><br>');
};

var response = HTTP.Get('http://www.example.com?q=1234');
if (debugging) debugValue('Response from example.com', response);

With this approach, once you copy-paste the function, you have cleaner debugging lines, less code to write, no worry whether you need to Stringify or not and automatic new line addition for easier reading. Just pass debug description and the value you want to check and see the magic happen.

Console Logging

I mentioned before that SSJS does not support console.log as it executes on a server. There is, however, a workaround that might bring the SSJS values to your console. SSJS personalization strings passed to your JavaScript can help:

console.log('Response from example.com' + '<ctrl:var name=response />')
Passing Objects from SSJS to JS

If the variable you want to pass to JavaScript is an SSJS Object, you will need to create a parsed variable to make it work:

  1. In SSJS create var parsedResponse = Stringify(response);
  2. In JS change the personalization to console.log(<ctrl:var name=parsedResponse />) (notice different name value and lack of quotes around personalization string)

Alternatively use ternary to cover all scenarios:

var parsedResponse = typeof response == 'object' ? Stringify(response) : response

Try to Catch the Error

Using Write is a great and simple solution, but it will work only if the code runs correctly. And sometimes it won't. For those cases, add a Try-Catch block. Check the Response tab below to compare the difference between standard Write(response) and writing the caught error.

GET request to invalid URL within Try/Catch block
<script runat="server">
Platform.Load('Core','1');
var debugging = true;

/**
* @function debugValue
* @description Outputs provided description and SSJS value to front-end in a type-safe & consistent way
* @param {string} description - Describes meaning of the second parameter in the output
* @param {*} value - The value that needs to be debugged
*/
function debugValue(description, value) {
Write(description + ': ' + (typeof value == 'object' ? Stringify(value) : value) + '<br><br>');
};

// Try/Catch Block with error Write
try {
var response = HTTP.Get('http://www.example.c');
} catch (error) {
if (debugging) debugValue('1. Error caught', error);
};

// Standard Write
if (debugging) debugValue('2. Response from example.com', response);
</script>

Try/Catch is especially useful for SSJS Functions that call out-of-page data. Marketing Cloud Data Extensions, Salesforce Objects or even data sources from outside the Salesforce environment. It also will help when you are handling responses from such sources. For example, trying to get value from a nested object.

In most cases, the simple Try/Catch will be all you need, but there is also a third element that might be very helpful: finally. Its purpose is to store a code that will execute, no matter what. Great for a cleanup code that must run regardless whether the previous logic was successful or not.

try {
// Risky code
} catch (error) {
// Code that will be executed if the risky code crashes
} finally {
// Code that will be executed always
};

Out of the three, SSJS requires only Try. You can use it with Catch, Finally or both, depending on your use case.

You should know

Try/Catch block in SSJS has separate scope. It means that any variables declared within the block won't be accessible from outside. Neither in the other parts of SSJS nor by personalization strings.

There is a way to overcome this limitation. Declare the variables you want to use globally before the try and modify their values within the block:

<script runat="server">
Platform.Load('Core','1');

var response;
try {
response = HTTP.Get('http://www.example.com');
} catch (error) {
// Error handling logic
};
</script>

Throw Error

You can leverage the Try/Catch block further by implementing your custom errors with throw. Why would you break your own code on purpose? It will help you control the execution of the code - especially if it uses external data. It can also be great for debugging and streamlining user experience.

try {
if (submissionData.greCaptcha) {
var secret = "XXX";
var payload = "secret=" + secret + "&response=" + submissionData.greCaptcha;
var contentType = "application/x-www-form-urlencoded";
var endpoint = "https://www.google.com/recaptcha/api/siteverify";
var response = HTTP.Post(endpoint, contentType, payload);
if (response.StatusCode == 200) {
var parsedResponse = Platform.Function.ParseJSON(String(response.Response));
if (!parsedResponse.success) throw 'Wrong reCAPTCHA';
} else {
throw 'reCAPTCHA API error';
};
};
} catch (error) {
debugValue('reCaptcha Error', error);
};

In the above example, you can see that we are checking whether the response from our reCaptcha validation to know whether we reached the Google server (else we throw reCAPTCHA API error) and if yes - was the Captcha solved.

Here I'm using the string passed to throw just for debugging purposes, but you can use it for an overlay with feedback to your client or log the error.

You Should Know

You are not limited to strings with the throw. You can also pass objects to mimic the Marketing Cloud errors. It will make error handling much easier:

if (response.StatusCode != 200) {
throw {'message': 'HTTP.Get Error', 'description': 'Could not connect to example.com'};
};

Error Logging

Once you debug your code using the above methods, it is still good to program defensively and leverage the Try/Catch block in the production environment. It will allow you to handle the errors for the customers and log the errors to a data extension (it will provide you with a history of problems along with a description of what happened). You can even build something more sophisticated, like automatically creating a JIRA bug ticket.

Logging to Data Extension

The easiest way to log your errors is to leverage Marketing Cloud Data Extensions.

My recommendation is to create one Data Extension that will capture all the errors from the whole instance. It will make it much easier and faster to check any new issues.

Here you can find a sample setup:

Column NamePrimary KeyLengthTypeNullableDefault Value
idYes36TextNo
errorSource100TextNo
errorMessage2000TextYes
errorDescription2000TextYes
errorDateDateYesCurrent Date

If you have multiple Business Units on your instance, add it as a Shared Data Extension.

It shouldn't be sendable or testable, but consider adding a retention period. Align it with the internal process for checking it for new issues. Seven days per record is a good starting point.

Global Error Catching

Instead of writing Try/Catch blocks for every potentially risky function, you might create just one block that will capture the whole code. You can even use it to catch errors in your AMPScript, as described by Zuzanna Jarczyńska.

This approach might make your code shorter and more comfortable to read but limit your control over specific handling for various scenarios.

You Should Know

Any redirect within your try block is recognized as an error and caught. If you need to use a redirect, don't use the global Try/Catch approach and instead leverage it only for potentially problematic code.

You can also leverage one of the approaches mentioned by Gortonington in his article on Try/Catch. It might be instrumental when you want to leverage redirect to handle the customer's error.


Error Handler Function

Once you start implementing the solutions mentioned above, it might get quite repetitive to add the same lines for conditional Write, log to Data Extension, et cetera.

You can solve it by creating a single Error Handler function and just calling it wherever it is needed:

<script runat="server">
Platform.Load('Core','1');
var debugging = true;

/**
* @function debugValue
* @description Outputs provided description and SSJS value to front-end in a type-safe & consistent way
* @param {string} description - Describes meaning of the second parameter in the output
* @param {*} value - The value that needs to be debugged
*/
function debugValue(description, value) {
Write(description + ': ' + (typeof value == 'object' ? Stringify(value) : value) + '<br><br>');
};

/**
* @function handleError
* @description If debuging is enabled, outputs the error value. Else, adds the error with context to error logging Data Extension.
* @param {Object} error - The caught error object. Can come from the try/catch block or be manually created.
* @param {string} error.message - First error key stores short error message describing the issue.
* @param {string} error.description - Second error key stores detailed error path helping with root cause analysis
*/
function handleError(error) {
if (debugging) {
debugValue('Found error', error);
} else {
// Remember that if your Logging Data Extension is in Shared Folder, you need to add the "ENT." prefix to name
Platform.Function.InsertData(
'Data_Extension_Name',
['id', 'errorSource', 'errorMessage', 'errorDescription'],
[GUID(), 'Name of the asset where the script runs', Stringify(error.message), Stringify(error.description)]
);
};
};

try {
var response = HTTP.Get('http://www.example.c');
} catch (error) {
handleError(error);
};
</script>

Whenever the Try/Catch block finds an error, the above snippet will check the value of debugging variable. If it is equal to true, it will just Write the error to the page to make it easier to debug quickly. If it is false, it will add the error to the Data Extension instead.

Most of the Marketing Cloud Errors are objects with two keys: message and description. Putting those elements into separate columns of the Data Extension make it easier to read the log.

Using the Error Handler Function is useful, especially when you are triggering it multiple times or when you have elaborate logic for handling (for example, API calls to external systems for bug tracking).

You Should Know

You can also create your custom error, by passing a custom object to the handleErrror function:

handleError({message: 'Custom Error Message', description: 'Custom Error Description'});

There is also a new solution - SSJS Lib - created by email360. Among other tools, they support console logging and simplified debugging. You can read more about the implementation here.


Sum Up

If you have any problems with your SSJS code:

  1. Test it in Cloud Page or JSON Code Resource 🔗
  2. Overcome 500 Error with linting or divide & conquer methodology 🔗
  3. Use the Write function to understand what happens with your variables during execution 🔗
  4. Use the Try/Catch block to understand better what is the reason for errors you encounter 🔗
  5. Save errors from your production code to easily track and solve the problems 🔗
  6. Simplify the approach with custom Error Handler Function 🔗