String Methods
The String() constructor/conversion function and String prototype methods in SSJS — which work natively, which are partial, and which are missing, with ES3/ES5-safe alternatives and polyfill links.
Each member below is tagged with the ECMAScript edition that standardized it: (ES3), (ES5), or (ES6). Methods that need a polyfill link to Polyfills.
The String() constructor
(ES3) — ✅ Works. String(value) is the native constructor called as a conversion function: it converts any value to its string representation. Called with no argument, String() returns "". In SFMC SSJS it has a critical extra use case: converting CLR/.NET objects — most importantly the .content of a Script.Util.HttpRequest response — into real JavaScript strings.
String(42); // "42"
String(true); // "true"
String(); // ""
Converting CLR HTTP response content
resp.content is a .NET object, not a JS string — it cannot be passed directly to Platform.Function.ParseJSON(). Convert it first:
var req = new Script.Util.HttpRequest("https://api.example.com/data");
req.method = "GET";
var resp = req.send();
// resp.content is a CLR object — convert before parsing
var bodyStr = String(resp.content); // CLR → JS string
var data = Platform.Function.ParseJSON(bodyStr + ""); // JS string → object
String() vs Stringify()
String(value)— converts primitives and CLR/engine values to a plain JS string. It does not render a plain object:String({})throwsObject reference not set to an instance of an object.Stringify(value)— serializes a JavaScript value to a JSON string. This is the reliable way to render an object.
var obj = { a: 1, b: 2 };
String(obj); // THROWS "Object reference not set to an instance of an object."
("" + obj); // "" — the empty string, NOT "[object Object]"
Stringify(obj); // '{"a":1,"b":2}' — JSON serialization
Standard JavaScript coerces a plain object to "[object Object]" — in the SFMC engine neither coercion form does. String({}) throws Object reference not set to an instance of an object. (the throw is catchable and does not abort the page), and ("" + {}) yields the empty string. Runtime-proven; String([]) and ("" + []) are also both "". To render an object, read its fields individually or serialize it with Stringify(). An explicit obj.toString() call still returns "[object Object]" — see toString. See Differs from Official Docs.
The same throw applies to a caught plain object — do not call String(e) on one; see Error().
For engine/CLR values, prefer ("" + value) over String(value). Both produce a real JS string that satisfies === and supports string methods, but String(value) throws Object reference not set to an instance of an object. on .NET-null-backed CLR properties (resp.contentType, resp.encoding, resp.headers) and on plain objects, where ("" + value) safely yields "".
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: The String() constructor
*
* Proves:
* 1. String(value) converts primitives to their string representation:
* String(42) -> "42", String(true) -> "true".
* 2. String() with no argument returns "".
* 3. Stringify(value) produces JSON, unlike String(value).
*
* NOT ASSERTED HERE (settled by an isolated probe, not re-run in this bundle):
* - Plain-object coercion. Probe clr-coerce-3 (2026-08-08, recorded in the
* verification DB entry for String) settled it: String({}) THROWS "Object
* reference not set to an instance of an object." — catchable, and it
* does NOT abort the page — while "" + {} yields the EMPTY STRING, not
* the spec's "[object Object]". String([]) and "" + [] are also both "".
* The page and ssjs-data now state this. No assertion for it has been
* observed green in THIS bundle yet, so none is added here.
* - String(resp.content) converting CLR HTTP response content. That needs
* a live outbound HTTP call, which is non-deterministic and risks the
* CloudPage timeout; it is covered on the Script.Util.HttpRequest page.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. Primitive conversion. */
assert("String(42) is '42'", function () { return String(42); }, "42");
assert("String(true) is 'true'", function () { return String(true); }, "true");
/* 2. No argument returns the empty string. */
assert("String() is ''", function () { return String(); }, "");
/* 3. Stringify() serializes to JSON instead. */
var obj = { a: 1, b: 2 };
assert("Stringify({a:1,b:2}) is JSON", function () { return String(Stringify(obj)); }, '{"a":1,"b":2}');
</script>
Status legend
| Icon | Meaning |
|---|---|
| ✅ Works | Available and behaves as expected |
| ⚠️ Partial | Available but with a documented caveat or bug |
| ❌ Missing | Not available — use the workaround / polyfill |
Members
| Member | ES | Status | Notes |
|---|---|---|---|
length |
ES3 | ✅ Works | |
charAt(index) |
ES3 | ⚠️ Partial | Out-of-range index returns the last char, not "" |
charCodeAt(index) |
ES3 | ✅ Works | |
indexOf(search, fromIndex) |
ES3 | ✅ Works | |
lastIndexOf(search, fromIndex) |
ES3 | ✅ Works | |
toUpperCase() |
ES3 | ✅ Works | |
toLowerCase() |
ES3 | ✅ Works | |
toLocaleLowerCase() |
ES3 | ✅ Works | |
substring(start, end) |
ES3 | ✅ Works | |
slice(start, end) |
ES3 | ✅ Works | |
concat(...strings) |
ES3 | ✅ Works | |
replace(pattern, replacement) |
ES3 | ✅ Works | |
localeCompare(other) |
ES3 | ✅ Works | |
match(regexp) |
ES3 | ⚠️ Partial | Returns [] (not null) on no match; no .index |
search(regexp) |
ES3 | ⚠️ Partial | Returns 0 (not -1) on no match; unreliable — see Polyfills |
split(separator, limit) |
ES3 | ⚠️ Partial | Empty-separator form does not split into chars — see Polyfills |
trim() |
ES5 | ❌ Missing | See Polyfills |
substr(start, length) |
ES3 | ❌ Missing | Throws at runtime — use substring/slice or polyfill |
startsWith(prefix) |
ES6 | ❌ Missing | Use indexOf(prefix) === 0 or polyfill |
endsWith(suffix) |
ES6 | ❌ Missing | Use lastIndexOf check or polyfill |
includes(substr) |
ES6 | ❌ Missing | Use indexOf(substr) !== -1 |
trimStart() |
ES6 | ❌ Missing | Use a /^\s+/ replace |
trimEnd() |
ES6 | ❌ Missing | Use a /\s+$/ replace |
padStart(targetLen, pad) |
ES6 | ❌ Missing | Prepend pad characters in a loop |
padEnd(targetLen, pad) |
ES6 | ❌ Missing | Append pad characters in a loop |
repeat(count) |
ES6 | ❌ Missing | Concatenate in a loop |
codePointAt(index) |
ES6 | ❌ Missing | Use charCodeAt for BMP characters |
length
(ES3) — ✅ Works. The number of UTF-16 code units in the string.
"Hello".length; // 5
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: length
*
* Proves:
* 1. "Hello".length is 5 — the number of UTF-16 code units.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.length is 5", function () { return String("Hello".length); }, "5");
</script>
charAt
(ES3) — ⚠️ Partial. Returns the character at the given index. In-range indices behave normally, but out-of-range indices are broken: instead of the spec-mandated empty string "", SFMC returns the last character of the string. Guard the index against .length before calling.
"Hello".charAt(0); // "H"
"Hello"[0]; // "H" (bracket access works in range)
"Hello".charAt(99); // "o" — ❌ SFMC returns the last char, not ""
"Hello".charAt(5); // "o" — ❌ same bug (spec says "")
"Hello"[99]; // throws "Index was outside the bounds of the array"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: charAt
*
* Proves:
* 1. In-range charAt() returns the character at that index.
* 2. Bracket access works for an in-range index.
* 3. DEVIATION marked "DEV": an out-of-range index returns the LAST
* character of the string (spec: the empty string ""), both for an
* index far past the end and for index === length.
* 4. Bracket access with an out-of-range index throws instead.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. + 2. In-range access. */
assert("'Hello'.charAt(0) is 'H'", function () { return String("Hello".charAt(0)); }, "H");
assert("'Hello'[0] is 'H'", function () { return String("Hello"[0]); }, "H");
/* 3. DEVIATION — out of range returns the last character. */
assert("DEV 'Hello'.charAt(99) is 'o' (spec: '')", function () { return String("Hello".charAt(99)); }, "o");
assert("DEV 'Hello'.charAt(5) is 'o' (spec: '')", function () { return String("Hello".charAt(5)); }, "o");
/* 4. Bracket access is not forgiving. */
assertThrows("'Hello'[99] throws", function () { return "Hello"[99]; });
</script>
charCodeAt
(ES3) — ✅ Works. Returns the UTF-16 code unit at the given index.
"Hello".charCodeAt(0); // 72
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: charCodeAt
*
* Proves:
* 1. "Hello".charCodeAt(0) is 72 — the UTF-16 code unit at that index.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.charCodeAt(0) is 72", function () { return String("Hello".charCodeAt(0)); }, "72");
</script>
indexOf
(ES3) — ✅ Works. Returns the index of the first occurrence, or -1.
"Hello World".indexOf("World"); // 6
"Hello World".indexOf("o", 5); // 7
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: indexOf
*
* Proves:
* 1. indexOf(search) returns the index of the first occurrence.
* 2. indexOf(search, fromIndex) starts searching at fromIndex.
* 3. A missing needle returns -1.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello World'.indexOf('World') is 6", function () { return String("Hello World".indexOf("World")); }, "6");
assert("'Hello World'.indexOf('o', 5) is 7", function () { return String("Hello World".indexOf("o", 5)); }, "7");
assert("no match returns -1", function () { return String("Hello World".indexOf("zzz")); }, "-1");
</script>
lastIndexOf
(ES3) — ✅ Works. Returns the index of the last occurrence, or -1.
"Hello World Hello".lastIndexOf("Hello"); // 12
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: lastIndexOf
*
* Proves:
* 1. lastIndexOf(search) returns the index of the LAST occurrence.
* 2. A missing needle returns -1.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello World Hello'.lastIndexOf('Hello') is 12", function () { return String("Hello World Hello".lastIndexOf("Hello")); }, "12");
assert("no match returns -1", function () { return String("Hello World Hello".lastIndexOf("zzz")); }, "-1");
</script>
toUpperCase
(ES3) — ✅ Works.
"Hello".toUpperCase(); // "HELLO"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: toUpperCase
*
* Proves:
* 1. "Hello".toUpperCase() is "HELLO".
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.toUpperCase() is 'HELLO'", function () { return String("Hello".toUpperCase()); }, "HELLO");
</script>
toLowerCase
(ES3) — ✅ Works.
"Hello".toLowerCase(); // "hello"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: toLowerCase
*
* Proves:
* 1. "Hello".toLowerCase() is "hello".
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.toLowerCase() is 'hello'", function () { return String("Hello".toLowerCase()); }, "hello");
</script>
toLocaleLowerCase
(ES3) — ✅ Works. Locale-aware lowercase.
"Hello".toLocaleLowerCase(); // "hello"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: toLocaleLowerCase
*
* Proves:
* 1. "Hello".toLocaleLowerCase() is "hello" — the locale-aware lowercase
* method exists and works.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.toLocaleLowerCase() is 'hello'", function () { return String("Hello".toLocaleLowerCase()); }, "hello");
</script>
substring
(ES3) — ✅ Works. Returns the part of the string between two indices.
"Hello World".substring(6, 11); // "World"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: substring
*
* Proves:
* 1. "Hello World".substring(6, 11) is "World" — the part between the two
* indices.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello World'.substring(6, 11) is 'World'", function () { return String("Hello World".substring(6, 11)); }, "World");
</script>
slice
(ES3) — ✅ Works. Like substring, but supports negative indices.
"Hello World".slice(0, 5); // "Hello"
"Hello World".slice(-5); // "World"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: slice
*
* Proves:
* 1. slice(start, end) behaves like substring.
* 2. slice() supports NEGATIVE indices, which is what distinguishes it
* from substring: "Hello World".slice(-5) is "World".
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello World'.slice(0, 5) is 'Hello'", function () { return String("Hello World".slice(0, 5)); }, "Hello");
assert("'Hello World'.slice(-5) is 'World'", function () { return String("Hello World".slice(-5)); }, "World");
</script>
concat
(ES3) — ✅ Works. Concatenates strings. + is usually preferred.
"Hello".concat(" ", "World"); // "Hello World"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: concat
*
* Proves:
* 1. concat() joins several strings: "Hello".concat(" ", "World").
* 2. The + operator produces the same result, which is why the chapter
* recommends it.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'Hello'.concat(' ', 'World') is 'Hello World'", function () { return String("Hello".concat(" ", "World")); }, "Hello World");
assert("+ produces the same result", function () { return String("Hello" + " " + "World"); }, "Hello World");
</script>
replace
(ES3) — ✅ Works. Replaces matches of a string or RegExp. Use the /g flag to replace all.
"aabbcc".replace("b", "X"); // "aaXbcc"
"aabbcc".replace(/b/g, "X"); // "aaXXcc"
"hello world".replace(/\b\w/g, function (c) { return c.toUpperCase(); }); // "Hello World"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: replace
*
* Proves:
* 1. A string pattern replaces only the FIRST occurrence:
* "aabbcc".replace("b", "X") -> "aaXbcc".
* 2. A RegExp with the /g flag replaces ALL occurrences:
* "aabbcc".replace(/b/g, "X") -> "aaXXcc".
* 3. A function replacement receives the match and its return value is
* substituted: "hello world".replace(/\b\w/g, upper) -> "Hello World".
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. String pattern — first match only. */
assert("'aabbcc'.replace('b', 'X') is 'aaXbcc'", function () { return String("aabbcc".replace("b", "X")); }, "aaXbcc");
/* 2. RegExp with /g — all matches. */
assert("'aabbcc'.replace(/b/g, 'X') is 'aaXXcc'", function () { return String("aabbcc".replace(/b/g, "X")); }, "aaXXcc");
/* 3. Function replacement. */
assert("function replacement capitalizes each word", function () {
return String("hello world".replace(/\b\w/g, function (c) { return c.toUpperCase(); }));
}, "Hello World");
</script>
localeCompare
(ES3) — ✅ Works. Compares two strings in the current locale.
"apple".localeCompare("banana"); // negative
"apple".localeCompare("apple"); // 0
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: localeCompare
*
* Proves:
* 1. "apple".localeCompare("banana") is NEGATIVE (asserted as a sign test,
* because the spec only fixes the sign, not the magnitude).
* 2. "apple".localeCompare("apple") is 0.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
assert("'apple'.localeCompare('banana') is negative", function () {
return "apple".localeCompare("banana") < 0 ? "negative" : "not-negative";
}, "negative");
assert("'apple'.localeCompare('apple') is 0", function () { return String("apple".localeCompare("apple")); }, "0");
</script>
match
(ES3) — ⚠️ Partial. In SFMC, String.match returns an empty array [] (not null) when there is no match, and matched results do not carry an .index property.
var str = "Call 555-1234 or 555-5678";
str.match(/\d{3}-\d{4}/g); // ["555-1234", "555-5678"]
str.match(/zzz/); // [] (empty array in SFMC, not null)
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: match
*
* Proves:
* 1. A /g match returns every match: "Call 555-1234 or 555-5678"
* matched with /\d{3}-\d{4}/g yields ["555-1234", "555-5678"].
* 2. DEVIATION marked "DEV": a no-match returns an EMPTY ARRAY (spec:
* null) — asserted via its length, and by proving the result is not
* null.
* 3. DEVIATION marked "DEV": the result carries NO .index property
* (spec: a non-global match result has .index).
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
var str = "Call 555-1234 or 555-5678";
/* 1. Global match returns all matches. */
assert("match(/\\d{3}-\\d{4}/g) finds 2 matches", function () { return String(str.match(/\d{3}-\d{4}/g).length); }, "2");
assert("match(/\\d{3}-\\d{4}/g) joined", function () { return String(str.match(/\d{3}-\d{4}/g).join("|")); }, "555-1234|555-5678");
/* 2. DEVIATION — no match yields [] rather than null. */
assert("DEV no-match result is not null (spec: null)", function () { return str.match(/zzz/) === null ? "null" : "not-null"; }, "not-null");
assert("DEV no-match result is an empty array (spec: null)", function () { return String(str.match(/zzz/).length); }, "0");
/* 3. DEVIATION — no .index on the result. */
assert("DEV match result has no .index (spec: number)", function () { return String(typeof str.match(/555-1234/).index); }, "undefined");
</script>
search
(ES3) — ⚠️ Partial. Unreliable in SFMC: returns 0 instead of -1 for a no-match, and some real matches return the wrong index. Use match or RegExp.test to detect a match, or apply the polyfill.
"abc123".search(/\d/); // unreliable — prefer match() or RegExp.test()
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: search
*
* Proves:
* 1. DEVIATION marked "DEV": a no-match returns 0 (spec: -1).
* 2. The documented workarounds detect a match reliably where search()
* cannot: String.match() and RegExp.test().
*
* NOT ASSERTABLE (documented on the page, deliberately not asserted here):
* - "some real matches return the wrong index". The page states the
* returned index is unreliable without pinning it to a specific wrong
* value, so there is no deterministic expected value to assert. The
* actionable part — do not trust the index, use match()/test() instead —
* IS asserted, in point 2.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. DEVIATION — no match reports 0, which is indistinguishable from a
match at index 0, so search() cannot be used to detect a match. */
assert("DEV 'abc'.search(/\\d/) is 0 for a NO match (spec: -1)", function () { return String("abc".search(/\d/)); }, "0");
/* 2. The recommended replacements. */
assert("workaround: match() finds the digit", function () { return String("abc123".match(/\d/).length > 0 ? "found" : "not-found"); }, "found");
assert("workaround: match() reports no digit", function () { return String("abc".match(/\d/).length > 0 ? "found" : "not-found"); }, "not-found");
assert("workaround: RegExp.test() finds the digit", function () { return /\d/.test("abc123") ? "true" : "false"; }, "true");
assert("workaround: RegExp.test() reports no digit", function () { return /\d/.test("abc") ? "true" : "false"; }, "false");
</script>
split
(ES3) — ⚠️ Partial. The empty-separator form str.split("") does not split into characters in SFMC — it returns the whole string as a single element. Loop with charAt, or apply the polyfill.
"a,b,c".split(","); // ["a", "b", "c"]
"a b c".split(/\s+/); // ["a", "b", "c"]
// "hello".split("") — ❌ does NOT split into chars in SFMC. Loop instead:
var chars = [];
var s = "hello";
for (var i = 0; i < s.length; i++) { chars.push(s.charAt(i)); }
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: split
*
* Proves:
* 1. A string separator splits normally: "a,b,c".split(",").
* 2. A RegExp separator splits normally: "a b c".split(/\s+/).
* 3. DEVIATION marked "DEV": the empty-separator form "hello".split("")
* does NOT split into characters (spec: one element per character) —
* it returns the whole string as a single element.
* 4. The documented workaround: a charAt() loop does produce one element
* per character.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. String separator. */
assert("'a,b,c'.split(',') has 3 parts", function () { return String("a,b,c".split(",").length); }, "3");
assert("'a,b,c'.split(',') joined", function () { return String("a,b,c".split(",").join("|")); }, "a|b|c");
/* 2. RegExp separator. */
assert("'a b c'.split(/\\s+/) has 3 parts", function () { return String("a b c".split(/\s+/).length); }, "3");
assert("'a b c'.split(/\\s+/) joined", function () { return String("a b c".split(/\s+/).join("|")); }, "a|b|c");
/* 3. DEVIATION — empty separator does not split into characters. */
assert("DEV 'hello'.split('') has 1 element (spec: 5)", function () { return String("hello".split("").length); }, "1");
assert("DEV 'hello'.split('')[0] is the whole string (spec: 'h')", function () { return String("hello".split("")[0]); }, "hello");
/* 4. The documented charAt() loop workaround. */
assert("workaround: charAt loop yields 5 chars", function () {
var chars = [], s = "hello";
for (var i = 0; i < s.length; i++) { chars.push(s.charAt(i)); }
return String(chars.length);
}, "5");
assert("workaround: charAt loop joined", function () {
var chars = [], s = "hello";
for (var i = 0; i < s.length; i++) { chars.push(s.charAt(i)); }
return String(chars.join("|"));
}, "h|e|l|l|o");
</script>
trim
(ES5) — ❌ Missing. Apply the polyfill. There is no Platform.Function.Trim in SSJS — reading it reports clrmethodinfo (a phantom member), but calling it throws Unable to retrieve security descriptor for this frame.
function trim(str) {
return String(str).replace(/^\s+/, "").replace(/\s+$/, "");
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: trim
*
* Proves:
* 1. String.prototype.trim is MISSING: reading it yields undefined and
* calling it throws.
* 2. The documented polyfill (two replace() calls) strips leading and
* trailing whitespace.
* 3. There is NO Platform.Function.Trim: reading it reports the phantom
* "clrmethodinfo" type, but calling it throws.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof ' x '.trim is undefined", function () { return String(typeof " x ".trim); }, "undefined");
assertThrows("' x '.trim() throws", function () { return " x ".trim(); });
/* 2. The documented polyfill. */
function trim(str) {
return String(str).replace(/^\s+/, "").replace(/\s+$/, "");
}
assert("polyfill trim(' hi ') is 'hi'", function () { return String(trim(" hi ")); }, "hi");
assert("polyfill trim leaves inner spaces", function () { return String(trim(" a b ")); }, "a b");
/* 3. Platform.Function.Trim does not exist. "clrmethodinfo" is reported for
any member name under Platform.Function, existing or not — it is not
evidence that the member is real. */
assert("typeof Platform.Function.Trim is the phantom clrmethodinfo", function () { return String(typeof Platform.Function.Trim); }, "clrmethodinfo");
assertThrows("Platform.Function.Trim(' hi ') throws", function () { return Platform.Function.Trim(" hi "); });
</script>
substr
(ES3) — ❌ Missing. String.prototype.substr throws at runtime in SFMC. Use substring or slice, or apply the polyfill.
"Hello World".substring(6, 11); // "World" (instead of substr(6, 5))
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: substr
*
* Proves:
* 1. String.prototype.substr is MISSING: calling it throws at runtime.
* 2. The documented replacement substring(6, 11) produces "World", the
* value substr(6, 5) would have produced.
* 3. slice(6, 11) produces the same replacement value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Calling substr throws. */
assertThrows("'Hello World'.substr(6, 5) throws", function () { return "Hello World".substr(6, 5); });
/* 2. + 3. The documented replacements. */
assert("substring(6, 11) is 'World'", function () { return String("Hello World".substring(6, 11)); }, "World");
assert("slice(6, 11) is 'World'", function () { return String("Hello World".slice(6, 11)); }, "World");
</script>
startsWith
(ES6) — ❌ Missing. Use indexOf(prefix) === 0 or the polyfill.
function startsWith(str, prefix) {
return str.indexOf(prefix) === 0;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: startsWith
*
* Proves:
* 1. String.prototype.startsWith is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented workaround indexOf(prefix) === 0 returns true for a
* real prefix and false for a non-prefix (including a substring that
* occurs later in the string).
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof 'Hello'.startsWith is undefined", function () { return String(typeof "Hello".startsWith); }, "undefined");
assertThrows("'Hello'.startsWith('He') throws", function () { return "Hello".startsWith("He"); });
/* 2. The documented workaround. */
function startsWith(str, prefix) {
return str.indexOf(prefix) === 0;
}
assert("workaround startsWith('Hello','He') is true", function () { return startsWith("Hello", "He") ? "true" : "false"; }, "true");
assert("workaround startsWith('Hello','llo') is false", function () { return startsWith("Hello", "llo") ? "true" : "false"; }, "false");
assert("workaround startsWith('Hello','zzz') is false", function () { return startsWith("Hello", "zzz") ? "true" : "false"; }, "false");
</script>
endsWith
(ES6) — ❌ Missing. Use a lastIndexOf check or the polyfill.
function endsWith(str, suffix) {
return str.lastIndexOf(suffix) === str.length - suffix.length;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: endsWith
*
* Proves:
* 1. String.prototype.endsWith is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented lastIndexOf workaround returns true for a real suffix
* and false for a non-suffix.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof 'Hello'.endsWith is undefined", function () { return String(typeof "Hello".endsWith); }, "undefined");
assertThrows("'Hello'.endsWith('llo') throws", function () { return "Hello".endsWith("llo"); });
/* 2. The documented workaround. */
function endsWith(str, suffix) {
return str.lastIndexOf(suffix) === str.length - suffix.length;
}
assert("workaround endsWith('Hello','llo') is true", function () { return endsWith("Hello", "llo") ? "true" : "false"; }, "true");
assert("workaround endsWith('Hello','He') is false", function () { return endsWith("Hello", "He") ? "true" : "false"; }, "false");
</script>
includes
(ES6) — ❌ Missing. Use indexOf(substr) !== -1.
function includes(str, sub) {
return str.indexOf(sub) !== -1;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: includes
*
* Proves:
* 1. String.prototype.includes is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented workaround indexOf(sub) !== -1 is true for a present
* substring and false for an absent one.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof 'Hello'.includes is undefined", function () { return String(typeof "Hello".includes); }, "undefined");
assertThrows("'Hello'.includes('ell') throws", function () { return "Hello".includes("ell"); });
/* 2. The documented workaround. */
function includes(str, sub) {
return str.indexOf(sub) !== -1;
}
assert("workaround includes('Hello','ell') is true", function () { return includes("Hello", "ell") ? "true" : "false"; }, "true");
assert("workaround includes('Hello','zzz') is false", function () { return includes("Hello", "zzz") ? "true" : "false"; }, "false");
</script>
trimStart
(ES6) — ❌ Missing. Use a /^\s+/ replace.
function trimStart(str) {
return String(str).replace(/^\s+/, "");
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: trimStart
*
* Proves:
* 1. String.prototype.trimStart is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented /^\s+/ replace workaround removes only LEADING
* whitespace and leaves trailing whitespace intact.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof ' x '.trimStart is undefined", function () { return String(typeof " x ".trimStart); }, "undefined");
assertThrows("' x '.trimStart() throws", function () { return " x ".trimStart(); });
/* 2. The documented workaround. */
function trimStart(str) {
return String(str).replace(/^\s+/, "");
}
assert("workaround trimStart(' hi ') keeps trailing spaces", function () { return String(trimStart(" hi ")); }, "hi ");
assert("workaround trimStart(' hi ').length is 4", function () { return String(trimStart(" hi ").length); }, "4");
</script>
trimEnd
(ES6) — ❌ Missing. Use a /\s+$/ replace.
function trimEnd(str) {
return String(str).replace(/\s+$/, "");
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: trimEnd
*
* Proves:
* 1. String.prototype.trimEnd is MISSING: reading it yields undefined and
* calling it throws.
* 2. The documented /\s+$/ replace workaround removes only TRAILING
* whitespace and leaves leading whitespace intact.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof ' x '.trimEnd is undefined", function () { return String(typeof " x ".trimEnd); }, "undefined");
assertThrows("' x '.trimEnd() throws", function () { return " x ".trimEnd(); });
/* 2. The documented workaround. */
function trimEnd(str) {
return String(str).replace(/\s+$/, "");
}
assert("workaround trimEnd(' hi ') keeps leading spaces", function () { return String(trimEnd(" hi ")); }, " hi");
assert("workaround trimEnd(' hi ').length is 4", function () { return String(trimEnd(" hi ").length); }, "4");
</script>
padStart
(ES6) — ❌ Missing. Prepend pad characters in a loop.
function padStart(str, targetLen, padChar) {
str = String(str);
padChar = padChar || " ";
while (str.length < targetLen) { str = padChar + str; }
return str;
}
padStart("7", 3, "0"); // "007"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: padStart
*
* Proves:
* 1. String.prototype.padStart is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented loop workaround pads on the LEFT:
* padStart("7", 3, "0") -> "007".
* 3. The workaround leaves an already-long-enough string untouched.
* 4. Its padChar defaults to a space.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof '7'.padStart is undefined", function () { return String(typeof "7".padStart); }, "undefined");
assertThrows("'7'.padStart(3, '0') throws", function () { return "7".padStart(3, "0"); });
/* 2. - 4. The documented workaround. */
function padStart(str, targetLen, padChar) {
str = String(str);
padChar = padChar || " ";
while (str.length < targetLen) { str = padChar + str; }
return str;
}
assert("workaround padStart('7', 3, '0') is '007'", function () { return String(padStart("7", 3, "0")); }, "007");
assert("workaround padStart('1234', 3, '0') is unchanged", function () { return String(padStart("1234", 3, "0")); }, "1234");
assert("workaround padStart('7', 3) pads with spaces", function () { return String(padStart("7", 3)); }, " 7");
</script>
padEnd
(ES6) — ❌ Missing. Append pad characters in a loop.
function padEnd(str, targetLen, padChar) {
str = String(str);
padChar = padChar || " ";
while (str.length < targetLen) { str = str + padChar; }
return str;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: padEnd
*
* Proves:
* 1. String.prototype.padEnd is MISSING: reading it yields undefined and
* calling it throws.
* 2. The documented loop workaround pads on the RIGHT.
* 3. The workaround leaves an already-long-enough string untouched.
* 4. Its padChar defaults to a space.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof '7'.padEnd is undefined", function () { return String(typeof "7".padEnd); }, "undefined");
assertThrows("'7'.padEnd(3, '0') throws", function () { return "7".padEnd(3, "0"); });
/* 2. - 4. The documented workaround. */
function padEnd(str, targetLen, padChar) {
str = String(str);
padChar = padChar || " ";
while (str.length < targetLen) { str = str + padChar; }
return str;
}
assert("workaround padEnd('7', 3, '0') is '700'", function () { return String(padEnd("7", 3, "0")); }, "700");
assert("workaround padEnd('1234', 3, '0') is unchanged", function () { return String(padEnd("1234", 3, "0")); }, "1234");
assert("workaround padEnd('7', 3) pads with spaces", function () { return String(padEnd("7", 3)); }, "7 ");
</script>
repeat
(ES6) — ❌ Missing. Concatenate in a loop.
function repeat(str, n) {
var result = "";
for (var i = 0; i < n; i++) { result += str; }
return result;
}
repeat("ab", 3); // "ababab"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: repeat
*
* Proves:
* 1. String.prototype.repeat is MISSING: reading it yields undefined and
* calling it throws.
* 2. The documented loop workaround produces the repeated string:
* repeat("ab", 3) -> "ababab".
* 3. A count of 0 yields the empty string.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof 'ab'.repeat is undefined", function () { return String(typeof "ab".repeat); }, "undefined");
assertThrows("'ab'.repeat(3) throws", function () { return "ab".repeat(3); });
/* 2. + 3. The documented workaround. */
function repeat(str, n) {
var result = "";
for (var i = 0; i < n; i++) { result += str; }
return result;
}
assert("workaround repeat('ab', 3) is 'ababab'", function () { return String(repeat("ab", 3)); }, "ababab");
assert("workaround repeat('ab', 0) is ''", function () { return String(repeat("ab", 0)); }, "");
</script>
codePointAt
(ES6) — ❌ Missing. Use charCodeAt for Basic Multilingual Plane characters.
"A".charCodeAt(0); // 65 (codePointAt(0) for BMP chars)
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: codePointAt
*
* Proves:
* 1. String.prototype.codePointAt is MISSING: reading it yields undefined
* and calling it throws.
* 2. The documented replacement charCodeAt() returns 65 for "A", the
* value codePointAt(0) would return for a Basic Multilingual Plane
* character.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, fn, expected) {
var got;
try { got = fn(); } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. The member does not exist. */
assert("typeof 'A'.codePointAt is undefined", function () { return String(typeof "A".codePointAt); }, "undefined");
assertThrows("'A'.codePointAt(0) throws", function () { return "A".codePointAt(0); });
/* 2. The documented replacement. */
assert("'A'.charCodeAt(0) is 65", function () { return String("A".charCodeAt(0)); }, "65");
</script>