CloudPage Apps
Patterns for building CloudPage applications — request routing, session cookies, multi-step forms, and JSON API endpoints.
Claims checked
Test scripts included
Basic Request Router
<script runat="server">
Platform.Load("core", "1.1.5");
// String() converts the CLR value so === against a literal works
var method = String(Platform.Request.Method);
var rawBody = method === "POST" ? Platform.Request.GetPostData() : "";
if (method === "GET") {
handleGet();
} else if (method === "POST") {
handlePost(Platform.Function.ParseJSON(rawBody + ""));
} else {
Write(Stringify({ status: 405, statusMessage: "Method Not Allowed", error: "Method not allowed" }));
}
function handleGet() {
var id = Platform.Request.GetQueryStringParameter("id");
if (!id) {
Platform.Response.ContentType = "application/json";
Write(Stringify({ message: "Welcome to the API" }));
return;
}
// String() first — a Lookup result throws on a truthiness test when the field is empty
var record = String(Platform.Function.Lookup("Records", "data", "id", id));
Platform.Response.ContentType = "application/json";
if (record === "" || record === "null") {
Write(Stringify({ status: 404, statusMessage: "Not Found", error: "Record not found" }));
} else {
Write(Stringify({ id: id, data: record }));
}
}
function handlePost(body) {
if (!body || !body.email) {
Write(Stringify({ status: 400, statusMessage: "Bad Request", error: "email is required" }));
return;
}
if (!Platform.Function.IsEmailAddress(body.email)) {
Write(Stringify({ status: 400, statusMessage: "Bad Request", error: "Invalid email format" }));
return;
}
Platform.Function.InsertData("Submissions",
["Email", "Name", "Timestamp"],
[body.email, body.name || "", Platform.Function.Now()]
);
Platform.Response.ContentType = "application/json";
Write(Stringify({ status: "ok", email: body.email }));
}
</script>
Show test script
<script runat="server">
/*
* Chapter: Basic Request Router
* Proves:
* 1. Method coerce; 405 JSON shape for wrong method.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\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");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
Platform.Load("core", "1.1.5");
var method = String(Platform.Request.Method);
assert("method GET", method, "GET");
var body405 = Platform.Function.Stringify({ status: 405, statusMessage: "Method Not Allowed", error: "Method not allowed" });
assert("405 json", body405.indexOf("405") >= 0 ? "true" : "false", "true");
</script>
Session Management
<script runat="server">
var SESSION_COOKIE = "sfmc_session";
var SESSION_DE = "Sessions";
function dateAdd(timestamp,intervalToAdd,intervalType) {
Platform.Variable.SetValue("@dateAdd_ts",timestamp);
Platform.Variable.SetValue("@dateAdd_add",intervalToAdd);
Platform.Variable.SetValue("@dateAdd_type",intervalType);
return Platform.Function.TreatAsContent("%%=DateAdd(@dateAdd_ts, @dateAdd_add, @dateAdd_type)=%%");
}
function formatDate(dateString,dateFormat,timeFormat,isoLocale) {
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)=%%");
}
function createSession(userId, data) {
var token = Platform.Function.GUID();
var expires = formatDate(
dateAdd(Platform.Function.Now(), 30, "D"),
"MM/DD/YYYY","HH:mm:ss"
);
Platform.Function.InsertData(SESSION_DE,
["token", "userId", "data", "expires"],
[token, userId, Stringify(data), expires]
);
Platform.Response.SetCookie(SESSION_COOKIE, token, expires, true);
return token;
}
function getSession() {
var token = Platform.Request.GetCookieValue(SESSION_COOKIE);
if (!token) return null;
var row = Platform.Function.LookupRows(SESSION_DE, "token", token);
if (!row || row.length === 0) return null;
var session = row[0];
// Check expiry
if (new Date(session.expires) < new Date()) {
return null;
}
return {
userId: session.userId,
data: Platform.Function.ParseJSON(session.data + "")
};
}
function destroySession() {
var token = Platform.Request.GetCookieValue(SESSION_COOKIE);
if (token) {
Platform.Function.DeleteData(SESSION_DE, ["token"], [token]);
Platform.Response.SetCookie(SESSION_COOKIE, "",
"Thu, 01 Jan 1970 00:00:00 GMT", true);
}
}
// Usage
var session = getSession();
if (!session) {
Platform.Response.Redirect("/login", false);
}
Write("Hello, user " + session.userId);
</script>
Show test script
<script runat="server">
/*
* Chapter: Session Management
* Proves:
* 1. GUID session token; SetCookie returns null.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\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");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var token = Platform.Function.GUID();
assert("session token", ("" + token).length > 10 ? "true" : "false", "true");
var ret = Platform.Response.SetCookie("gsSessionProbe", token);
assert("SetCookie null", ret === null ? "null" : "other", "null");
</script>
Multi-Step Form
var step = parseInt(Platform.Request.GetQueryStringParameter("step") || "1", 10);
var sessionToken = Platform.Request.GetCookieValue("formSession");
// Initialize form session
if (!sessionToken) {
sessionToken = Platform.Function.GUID();
Platform.Response.SetCookie("formSession", sessionToken);
Platform.Function.InsertData("FormSessions",
["token", "step", "data", "created"],
[sessionToken, "1", "{}", Platform.Function.Now()]
);
}
if (String(Platform.Request.Method) === "POST") {
var fieldName = Platform.Request.GetFormField("fieldName");
// Save step data
var existing = Platform.Function.Lookup("FormSessions", "data", "token", sessionToken);
var saved = Platform.Function.ParseJSON(existing + "");
saved["step" + step] = { name: fieldName };
Platform.Function.UpdateData("FormSessions",
["token"], [sessionToken],
["data", "step"],
[Platform.Function.Stringify(saved), String(step + 1)]
);
if (step < 3) {
Platform.Response.Redirect("?step=" + (step + 1), false);
} else {
// Final submission
// process complete form data...
Write("Form complete!");
}
}
Show test script
<script runat="server">
/*
* Chapter: Multi-Step Form
* Proves:
* 1. Step counter + Stringify saved state.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\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");
}
function typeOfThunk(fn) {
try { return "" + fn(); } catch (ex) { return "THREW:" + ("" + ex.message); }
}
var step = 1;
var saved = { name: "Ada" };
var next = String(step + 1);
assert("next step", next, "2");
assert("saved json", Platform.Function.Stringify(saved).indexOf("Ada") >= 0 ? "true" : "false", "true");
</script>