Embedding SSJS
The script block syntax, language attribute, multiple blocks, and how SSJS output is injected into the page.
The <script runat="server"> Block
SSJS is embedded using an HTML <script> tag with the runat="server" attribute:
<html>
<body>
<h1>My Page</h1>
<script runat="server">
Write("<p>This text comes from SSJS.</p>");
</script>
<p>This is regular HTML.</p>
</body>
</html>
The Write() output replaces the script block in the final rendered page. The subscriber sees:
<html>
<body>
<h1>My Page</h1>
<p>This text comes from SSJS.</p>
<p>This is regular HTML.</p>
</body>
</html>
Show test script
<script runat="server">
/*
* Chapter: script runat=server
* Proves:
* 1. Script body executes (this assert is proof).
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
assert("script executed", "true", "true");
</script>
The language Attribute
You can optionally specify the language:
<script runat="server" language="JavaScript">
// Explicitly sets JavaScript mode
</script>
This is usually omitted — runat="server" alone is sufficient and implies JavaScript. The language attribute is relevant when a page mixes AMPscript script blocks:
<script runat="server" language="ampscript">
/* AMPscript block */
Set @name = "World"
</script>
<script runat="server" language="JavaScript">
/* SSJS block */
var name = Variable.GetValue("@name");
Write("Hello, " + name);
</script>
Show test script
<script runat="server">
/*
* Chapter: language attribute
* Proves:
* 1. NON-ASSERTABLE: attribute is markup, not SSJS.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
assert("markup-only", "documented", "documented");
</script>
Multiple Script Blocks
A single page can have multiple <script runat="server"> blocks. They all share the same execution scope — variables declared in one block are visible in subsequent blocks.
<script runat="server">
// Block 1: Load libraries and declare helpers
Platform.Load("core", "1.1.5");
function formatDate(dateString,dateFormat,timeFormat,isoLocale) {
if(!dateFormat) {
dateFormat = "MM/DD/YYYY";
}
Platform.Variable.SetValue("@formatDate_string",dateString);
Platform.Variable.SetValue("@formatDate_date",dateFormat);
Platform.Variable.SetValue("@formatDate_time",timeFormat);
Platform.Variable.SetValue("@formatDate_iso",isoLocale);
return Platform.Function.TreatAsContent("%%=FormatDate(@formatDate_string, @formatDate_date, @formatDate_time, @formatDate_iso)=%%");
}
</script>
<h1>Welcome</h1>
<script runat="server">
// Block 2: Use helpers defined in Block 1
var today = formatDate(Platform.Function.Now());
Write("<p>Today is " + today + "</p>");
</script>
All blocks execute in order before the page is assembled. The output of each block appears at that block’s position in the HTML.
Show test script
<script runat="server">
/*
* Chapter: Multiple Script Blocks
* Proves:
* 1. Top-level var persists within one script (stand-in for shared scope).
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var shared = "block1";
assert("shared var", shared, "block1");
</script>
Hoisting Behavior
Function declarations are hoisted within their script block but not across blocks. Blocks execute top-to-bottom in page order and share one global scope, so a later block can call a function an earlier block defined — but an earlier block cannot reach forward to a function only defined later. Calling a not-yet-defined function throws Object expected: <name>.
<script runat="server">
// ❌ This will error — greet() is defined in the next block
Write(greet("World"));
</script>
<script runat="server">
function greet(name) {
return "Hello, " + name + "!";
}
</script>
The forward reference throws Object expected: greet at the point of the call. The page itself still returns HTTP 200 — the error surfaces in the rendered body where the failing block would have written, not as a server error. Everything the earlier block wrote before the failing line is still emitted.
Safe pattern: Put all function declarations in the first script block:
<script runat="server">
Platform.Load("core", "1.1.5");
// All function declarations at the top
function greet(name) {
return "Hello, " + name + "!";
}
function getSubscriberData(sk) {
return Platform.Function.Lookup("Subscribers", "Email", "SubscriberKey", sk);
}
</script>
<h1>%%=v(@salutation)=%%</h1>
<script runat="server">
// Usage in later blocks is fine
var sk = Platform.Request.GetQueryStringParameter("sk");
Write(greet(getSubscriberData(sk)));
</script>
Show test script
<script runat="server">
/*
* Chapter: Hoisting
* Proves:
* 1. Function declaration hoisted within the block.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var ok = false; try { hoist(); ok = true; } catch (e) { ok = false; }
function hoist() {}
assert("fn hoisted", ok ? "true" : "false", "true");
</script>
AMPscript Inside SSJS
AMPscript and SSJS run in the same document and can share data through the Variable namespace:
%%[
SET @subscriberKey = _subscriberKey
]%%
<script runat="server">
// Read the AMPscript variable in SSJS
var sk = Variable.GetValue("@subscriberKey");
// Write back to AMPscript
var email = Platform.Function.Lookup("Subscribers", "Email", "SubscriberKey", sk);
Variable.SetValue("@email", email);
</script>
<!-- Use the SSJS-set variable in AMPscript -->
<p>Email: %%=v(@email)=%%</p>
Or use Platform.Variable.GetValue / Platform.Variable.SetValue for the same purpose.
Show test script
<script runat="server">
/*
* Chapter: AMPscript Inside SSJS
* Proves:
* 1. Variable bridge works without embedding AMPscript delimiters in SSJS.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
Platform.Variable.SetValue("@embed", "via-ssjs");
assert("bridge", Platform.Variable.GetValue("@embed"), "via-ssjs");
</script>
Ctrl: Tags (Alternative Syntax)
SFMC also supports older ctrl:field, ctrl:var, and ctrl:eval tags for inline SSJS output. These are legacy and rarely used in new development:
<ctrl:eval>Platform.Function.Now()</ctrl:eval>
<ctrl:var name="myVariable" />
<ctrl:field name="FirstName" />
Stick to <script runat="server"> + Write() for all new work.
→ Next: Execution Contexts
Show test script
<script runat="server">
/*
* Chapter: Ctrl Tags
* Proves:
* 1. NON-ASSERTABLE: Ctrl: markup is not executable inside this HTML script harness.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
assert("markup-only", "documented", "documented");
</script>