DeleteData
→ numberRemoves rows from a Data Extension matching the specified filter criteria. Returns the number of rows deleted.
Runtime verified
Differs from official docs
Test scripts included
Syntax
Platform.Function.DeleteData(deName, whereFieldNames, whereFieldValues)
3 arguments
Differs from official Salesforce docs
DeleteData resolves the Data Extension by its Name only — passing the external key / CustomerKey throws “A Data Extension of this name does not exist.” (runtime-verified).
Show test script — the Data Extension is resolved by Name, not by external key
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Differs-from-docs claim: DeleteData resolves the data extension by its
* NAME only. Passing the external key / CustomerKey throws
* "A Data Extension of this name does not exist."
*
* The control that makes this discriminating: the throwaway data extension
* built here has a Name that is DIFFERENT from its CustomerKey. If the
* engine accepted either identifier, the CustomerKey call would delete the
* row — it does not, and the subsequent Name call proves the row was still
* there by returning 1.
*
* Proves:
* 1. DEV — DeleteData(<CustomerKey>, …) throws instead of deleting
* (docs: deName is described as the data extension identifier without
* restricting it to the Name).
* 2. The row targeted by that failed call is still present — the throw is
* a genuine no-op. Proven by the following Name call returning 1
* rather than 0.
* 3. DeleteData(<Name>, …) deletes the very same row, returns the number
* 1, and the row is gone on read-back.
* 4. The Name and the CustomerKey really are different strings, so the
* test discriminates between the two identifiers.
*
* SCOPE: CloudPage only (MCDEV_Training_QA business unit).
*
* 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 addField(de, name, len, isKey) {
var f = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(f, "Name", name);
Platform.Function.SetObjectProperty(f, "FieldType", "Text");
Platform.Function.SetObjectProperty(f, "MaxLength", len);
Platform.Function.SetObjectProperty(f, "IsPrimaryKey", isKey);
Platform.Function.SetObjectProperty(f, "IsRequired", isKey);
Platform.Function.AddObjectArrayItem(de, "Fields", f);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Email", "100", "true");
addField(de, "Status", "50", "false");
var st = [0, 0];
return String(Platform.Function.InvokeCreate(de, st, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var st = [0, 0];
return String(Platform.Function.InvokeDelete(de, st, null));
}
function rowCount(deName, field, value) {
var rows = Platform.Function.LookupRows(deName, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsguide_deletedata_byname_name";
var deKey = "ssjsguide_deletedata_byname_key";
/* 4. The control: Name and CustomerKey are different strings. */
assert("control: the Name and the CustomerKey differ", deName === deKey ? "same" : "different", "different");
assert("setup: the throwaway data extension is created", createDE(deName, deKey), "OK");
assert("setup: the target row is inserted", Platform.Function.InsertData(deName, ["Email", "Status"], ["keytest@example.com", "keep"]), 1);
/* 1. DEV — the CustomerKey is rejected. */
assertThrows("DEV DeleteData(<CustomerKey>, ...) throws (docs: deName is not restricted to the Name)", function () {
return Platform.Function.DeleteData(deKey, ["Email"], ["keytest@example.com"]);
});
/* 2 + 3. The same call with the Name works — and returning 1 proves the
CustomerKey attempt deleted nothing. */
var deleted = Platform.Function.DeleteData(deName, ["Email"], ["keytest@example.com"]);
assert("DeleteData(<Name>, ...) deletes the row the CustomerKey call could not touch", deleted, 1);
assert("the row is gone after the Name call", rowCount(deName, "Email", "keytest@example.com"), 0);
assert("cleanup: the throwaway data extension is deleted", dropDE(deKey), "OK");
</script>
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
deName |
string | Yes | Data Extension Name (the external key / CustomerKey is not accepted — runtime-verified) |
whereFieldNames |
string[] | Yes | Array of column names to match for deletion |
whereFieldValues |
array | Yes | Array of values aligned to whereFieldNames that identify rows to delete |
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Parameters —
* Platform.Function.DeleteData(deName, whereFieldNames, whereFieldValues)
*
* Proves:
* 1. The member exists and the documented 3-argument call succeeds against
* a real data extension that this script creates itself.
* 2. The return value is a genuine JavaScript NUMBER (typeof "number") —
* the count of rows deleted, asserted precisely (=== 1, === 2),
* never loosely.
* 3. deName resolves the data extension by its NAME. The data extension
* built here deliberately has a Name that DIFFERS from its
* CustomerKey, so the two are distinguishable.
* 4. whereFieldNames is an array of column names and whereFieldValues an
* array of values aligned to it — a single-column filter and a
* two-column (AND) filter are both exercised, and the AND filter is
* proven to spare rows that match only one of the two columns.
* 5. Exactly three arguments are required: arity 0, 1, 2 and 4 all throw.
* 6. THE DELETE REALLY COMMITS: every row is read back afterwards with
* Platform.Function.LookupRows and the surviving / removed rows match
* the returned counts exactly.
* 7. A filter naming a column that does not exist throws, and a data
* extension name that does not exist throws — and neither removes
* anything.
*
* QUERY-CACHING CONSTRAINT: identical data-extension queries are cached
* within one request, so each verification query below is issued only once.
* The setup state is asserted through InsertData's own return count rather
* than through a read that would later have to be repeated.
*
* SCOPE: CloudPage only (MCDEV_Training_QA business unit). The page also
* lists email, automation and triggered-send availability; those execution
* contexts were NOT exercised here.
*
* 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, 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");
}
/* Build a throwaway data extension whose Name differs from its CustomerKey. */
function addField(de, name, len, isKey) {
var f = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(f, "Name", name);
Platform.Function.SetObjectProperty(f, "FieldType", "Text");
Platform.Function.SetObjectProperty(f, "MaxLength", len);
Platform.Function.SetObjectProperty(f, "IsPrimaryKey", isKey);
Platform.Function.SetObjectProperty(f, "IsRequired", isKey);
Platform.Function.AddObjectArrayItem(de, "Fields", f);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Email", "100", "true");
addField(de, "Status", "50", "false");
addField(de, "Active", "10", "false");
var st = [0, 0];
return String(Platform.Function.InvokeCreate(de, st, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var st = [0, 0];
return String(Platform.Function.InvokeDelete(de, st, null));
}
function rowCount(deName, field, value) {
var rows = Platform.Function.LookupRows(deName, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsguide_deletedata_params_name";
var deKey = "ssjsguide_deletedata_params_key";
assert("setup: the throwaway data extension is created", createDE(deName, deKey), "OK");
assert("control: the Name and the CustomerKey differ", deName === deKey ? "same" : "different", "different");
/* Seed four rows: a(expired,0) b(expired,1) c(active,1) d(active,1). */
assert("setup: row a inserted", Platform.Function.InsertData(deName, ["Email", "Status", "Active"], ["a@example.com", "expired", "0"]), 1);
assert("setup: row b inserted", Platform.Function.InsertData(deName, ["Email", "Status", "Active"], ["b@example.com", "expired", "1"]), 1);
assert("setup: row c inserted", Platform.Function.InsertData(deName, ["Email", "Status", "Active"], ["c@example.com", "active", "1"]), 1);
assert("setup: row d inserted", Platform.Function.InsertData(deName, ["Email", "Status", "Active"], ["d@example.com", "active", "1"]), 1);
/* 1 + 2 + 3 + 4. The documented 3-argument call, single-column filter. */
var deleted = Platform.Function.DeleteData(deName, ["Email"], ["a@example.com"]);
assert("typeof DeleteData(...) is number", String(typeof deleted), "number");
assert("DeleteData deletes exactly 1 matching row and returns 1", deleted, 1);
/* 4. Two-column (AND) filter — arrays aligned by position. */
var deleted2 = Platform.Function.DeleteData(deName, ["Status", "Active"], ["active", "1"]);
assert("a two-column AND filter deletes both matching rows and returns 2", deleted2, 2);
/* 5. Exactly three arguments. */
assertThrows("arity 0 throws", function () { return Platform.Function.DeleteData(); });
assertThrows("arity 1 throws", function () { return Platform.Function.DeleteData(deName); });
assertThrows("arity 2 throws", function () { return Platform.Function.DeleteData(deName, ["Email"]); });
assertThrows("arity 4 throws", function () { return Platform.Function.DeleteData(deName, ["Email"], ["b@example.com"], "extra"); });
/* 7. Bad filter column and bad data extension name both throw. */
assertThrows("a filter column that does not exist throws", function () {
return Platform.Function.DeleteData(deName, ["NoSuchColumn"], ["x"]);
});
assertThrows("a data extension name that does not exist throws", function () {
return Platform.Function.DeleteData("ssjsguide_deletedata_no_such_de", ["Email"], ["x"]);
});
/* 6. Read every row back — each query issued exactly once. */
assert("the single-filter delete really committed: row a is gone", rowCount(deName, "Email", "a@example.com"), 0);
assert("the AND filter spared the 'expired' row: row b survived", rowCount(deName, "Email", "b@example.com"), 1);
assert("the AND filter removed row c", rowCount(deName, "Email", "c@example.com"), 0);
assert("the AND filter removed row d", rowCount(deName, "Email", "d@example.com"), 0);
assert("no 'active' row is left", rowCount(deName, "Status", "active"), 0);
assert("exactly 1 'expired' row is left", rowCount(deName, "Status", "expired"), 1);
assert("the failed negative-case calls removed nothing", rowCount(deName, "Active", "1"), 1);
/* Cleanup. */
assert("cleanup: the throwaway data extension is deleted", dropDE(deKey), "OK");
</script>
Examples
Basic delete
var deleted = Platform.Function.DeleteData(
"TempSessions",
["SessionToken"], [token]
);
Write("Deleted " + deleted + " session(s).");
Multi-filter delete
// Delete expired AND inactive records
Platform.Function.DeleteData(
"TempData",
["Status", "Active"],
["expired", "0"]
);
Safe delete pattern
// Verify the record exists before deleting.
// Coerce first — a truthiness test on a raw Lookup result throws on a NULL field.
var found = String(Platform.Function.Lookup("Orders", "OrderID", "OrderID", orderId));
if (found !== "" && found !== "null") {
var count = Platform.Function.DeleteData("Orders", ["OrderID"], [orderId]);
Write("Deleted " + count + " order(s).");
} else {
Write("Order not found.");
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Examples — basic delete, multi-filter delete, safe delete pattern
*
* Each of the three examples on the page is executed verbatim in shape
* against a throwaway data extension this script creates and deletes.
*
* Proves:
* 1. BASIC DELETE — DeleteData(de, ["SessionToken"], [token]) returns the
* number of session rows removed (exactly 1 here) and the row is
* really gone.
* 2. MULTI-FILTER DELETE — DeleteData(de, ["Status", "Active"],
* ["expired", "0"]) removes only the rows matching BOTH columns; rows
* matching just one of them survive.
* 3. SAFE DELETE PATTERN — the example guards with the String()-first
* idiom, because truthiness on a raw Lookup result throws when the
* matched row's field is NULL (see platform-functions/lookup). For an
* existing row String() yields the stored value, so the
* `found !== "" && found !== "null"` branch is taken and the delete
* returns 1; for a missing row Lookup returns a genuine JavaScript null
* (=== null is true) which String() turns into "null", so the `else`
* branch is taken and DeleteData is never called.
* 4. The rows are read back after the deletes, so each example is proven
* by its EFFECT, not merely by its return value.
*
* QUERY-CACHING CONSTRAINT: identical data-extension queries are cached
* within one request, so every verification query below is issued only once
* and setup state is asserted through InsertData's own return count.
*
* SCOPE: CloudPage only (MCDEV_Training_QA business unit).
*
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function addField(de, name, len, isKey) {
var f = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(f, "Name", name);
Platform.Function.SetObjectProperty(f, "FieldType", "Text");
Platform.Function.SetObjectProperty(f, "MaxLength", len);
Platform.Function.SetObjectProperty(f, "IsPrimaryKey", isKey);
Platform.Function.SetObjectProperty(f, "IsRequired", isKey);
Platform.Function.AddObjectArrayItem(de, "Fields", f);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "SessionToken", "100", "true");
addField(de, "Status", "50", "false");
addField(de, "Active", "10", "false");
var st = [0, 0];
return String(Platform.Function.InvokeCreate(de, st, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var st = [0, 0];
return String(Platform.Function.InvokeDelete(de, st, null));
}
function rowCount(deName, field, value) {
var rows = Platform.Function.LookupRows(deName, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsguide_deletedata_examples_name";
var deKey = "ssjsguide_deletedata_examples_key";
assert("setup: the throwaway data extension is created", createDE(deName, deKey), "OK");
assert("setup: tok-1 (expired, 0) inserted", Platform.Function.InsertData(deName, ["SessionToken", "Status", "Active"], ["tok-1", "expired", "0"]), 1);
assert("setup: tok-2 (expired, 0) inserted", Platform.Function.InsertData(deName, ["SessionToken", "Status", "Active"], ["tok-2", "expired", "0"]), 1);
assert("setup: tok-3 (expired, 1) inserted", Platform.Function.InsertData(deName, ["SessionToken", "Status", "Active"], ["tok-3", "expired", "1"]), 1);
assert("setup: tok-4 (current, 0) inserted", Platform.Function.InsertData(deName, ["SessionToken", "Status", "Active"], ["tok-4", "current", "0"]), 1);
assert("setup: tok-5 (current, 1) inserted", Platform.Function.InsertData(deName, ["SessionToken", "Status", "Active"], ["tok-5", "current", "1"]), 1);
/* 1. Basic delete — the page's first example. */
var token = "tok-1";
var deleted = Platform.Function.DeleteData(deName, ["SessionToken"], [token]);
assert("basic delete: DeleteData returns the number of sessions removed", deleted, 1);
/* 2. Multi-filter delete — the page's second example. Only tok-2 now
matches BOTH expired AND 0, because tok-1 is already gone. */
var deletedMulti = Platform.Function.DeleteData(deName, ["Status", "Active"], ["expired", "0"]);
assert("multi-filter delete: only the rows matching BOTH columns are removed", deletedMulti, 1);
/* 3. Safe delete pattern — the page's third example, both branches. The
example guards with the String()-first idiom, never with truthiness on
the raw Lookup result. */
var found = String(Platform.Function.Lookup(deName, "Status", "SessionToken", "tok-5"));
assert("safe delete: String() of an existing row's value is that value", found, "current");
var count = 0, branch = "";
if (found !== "" && found !== "null") {
branch = "deleted";
count = Platform.Function.DeleteData(deName, ["SessionToken"], ["tok-5"]);
} else {
branch = "not found";
}
assert("safe delete: the existing row takes the delete branch", branch, "deleted");
assert("safe delete: the delete returns 1", count, 1);
var missingRaw = Platform.Function.Lookup(deName, "Status", "SessionToken", "tok-does-not-exist");
assert("safe delete: Lookup returns a genuine JavaScript null for a missing row", missingRaw === null ? "null" : "not null", "null");
var missing = String(missingRaw);
assert("safe delete: String() turns that no-match null into the string \"null\", which is what the guard tests", missing, "null");
var branch2 = "", count2 = 0;
if (missing !== "" && missing !== "null") {
branch2 = "deleted";
count2 = Platform.Function.DeleteData(deName, ["SessionToken"], ["tok-does-not-exist"]);
} else {
branch2 = "not found";
}
assert("safe delete: the missing row takes the else branch", branch2, "not found");
assert("safe delete: DeleteData was never called in the else branch", count2, 0);
/* 4. Read the surviving rows back — each query issued exactly once. */
assert("basic delete: the tok-1 session row is really gone", rowCount(deName, "SessionToken", "tok-1"), 0);
assert("multi-filter delete: tok-2 matched both columns and is gone", rowCount(deName, "SessionToken", "tok-2"), 0);
assert("multi-filter delete: the expired-but-active row tok-3 survived", rowCount(deName, "SessionToken", "tok-3"), 1);
assert("multi-filter delete: the inactive-but-current row tok-4 survived", rowCount(deName, "SessionToken", "tok-4"), 1);
assert("safe delete: the tok-5 row is gone", rowCount(deName, "SessionToken", "tok-5"), 0);
assert("exactly 1 'expired' row is left", rowCount(deName, "Status", "expired"), 1);
assert("exactly 1 'current' row is left", rowCount(deName, "Status", "current"), 1);
assert("cleanup: the throwaway data extension is deleted", dropDE(deKey), "OK");
</script>
Notes
- Returns
0when no rows match (not an error) - Resolves the DE by Name, not external key / CustomerKey
DeleteDEperforms the same delete but returnsnullinstead of a row count. The official docs describeDeleteDEas email-only, yet it was runtime-verified to run and commit on a CloudPage too — preferDeleteDataoutside email for the affected-row count.- Irreversible — SFMC DEs have no built-in undo. Always verify the filter before deleting.
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Notes
*
* Proves every bullet the Notes chapter states:
* 1. A filter that matches nothing returns the NUMBER 0 — not null, not
* undefined, and it does not throw. Asserted precisely (=== 0 and
* typeof "number").
* 2. The data extension is resolved by NAME, not by external key /
* CustomerKey — the key form throws, and the following Name call
* returning 1 proves the failed call removed nothing.
* 3. DeleteDE performs the same delete but returns a genuine JavaScript
* null (=== null is true, and it is NOT undefined) instead of a row
* count — and, although the official docs describe DeleteDE as
* email-only, it runs and COMMITS on a CloudPage: the row it targets is
* proven gone afterwards. Marked DEV.
* 4. DeleteData is the one to prefer outside email because its return
* value really is a number.
* 5. IRREVERSIBLE — after a delete the row does not come back:
* Platform.Function.Lookup returns a genuine null for it while a
* surviving row is still readable.
*
* QUERY-CACHING CONSTRAINT: identical data-extension queries are cached
* within one request, so every verification query below is issued only once.
*
* SCOPE: CloudPage only (MCDEV_Training_QA business unit). The claim that
* the official docs restrict DeleteDE to email is tested here only in the
* CloudPage context — the email context was NOT exercised.
*
* 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 addField(de, name, len, isKey) {
var f = Platform.Function.CreateObject("DataExtensionField");
Platform.Function.SetObjectProperty(f, "Name", name);
Platform.Function.SetObjectProperty(f, "FieldType", "Text");
Platform.Function.SetObjectProperty(f, "MaxLength", len);
Platform.Function.SetObjectProperty(f, "IsPrimaryKey", isKey);
Platform.Function.SetObjectProperty(f, "IsRequired", isKey);
Platform.Function.AddObjectArrayItem(de, "Fields", f);
}
function createDE(name, key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
Platform.Function.SetObjectProperty(de, "Name", name);
addField(de, "Email", "100", "true");
addField(de, "Status", "50", "false");
var st = [0, 0];
return String(Platform.Function.InvokeCreate(de, st, null));
}
function dropDE(key) {
var de = Platform.Function.CreateObject("DataExtension");
Platform.Function.SetObjectProperty(de, "CustomerKey", key);
var st = [0, 0];
return String(Platform.Function.InvokeDelete(de, st, null));
}
function rowCount(deName, field, value) {
var rows = Platform.Function.LookupRows(deName, field, value);
return rows === null ? 0 : rows.length;
}
var deName = "ssjsguide_deletedata_notes_name";
var deKey = "ssjsguide_deletedata_notes_key";
assert("setup: the throwaway data extension is created", createDE(deName, deKey), "OK");
assert("setup: row n1 inserted", Platform.Function.InsertData(deName, ["Email", "Status"], ["n1@example.com", "keep"]), 1);
assert("setup: row n2 inserted", Platform.Function.InsertData(deName, ["Email", "Status"], ["n2@example.com", "keep"]), 1);
assert("setup: row n3 inserted", Platform.Function.InsertData(deName, ["Email", "Status"], ["n3@example.com", "keep"]), 1);
/* 1. No match returns the number 0 and does not throw. */
var none = Platform.Function.DeleteData(deName, ["Email"], ["nobody@example.com"]);
assert("no match returns a number", String(typeof none), "number");
assert("no match returns exactly 0", none, 0);
assert("no match is not null", none === null ? "null" : "not null", "not null");
assert("no match is not undefined", none === undefined ? "undefined" : "defined", "defined");
assert("no match deleted nothing: all 3 rows are still there", rowCount(deName, "Status", "keep"), 3);
/* 2. Resolved by Name, not by external key / CustomerKey. */
assertThrows("the external key / CustomerKey is rejected", function () {
return Platform.Function.DeleteData(deKey, ["Email"], ["n1@example.com"]);
});
assert("the Name form deletes the row the CustomerKey attempt could not touch", Platform.Function.DeleteData(deName, ["Email"], ["n1@example.com"]), 1);
/* 3. DEV — DeleteDE returns a genuine null and still commits on a CloudPage. */
var deResult = Platform.Function.DeleteDE(deName, ["Email"], ["n2@example.com"]);
assert("DEV DeleteDE returns a genuine JavaScript null, not a row count (docs: email-only function)", deResult === null ? "null" : "not null", "null");
assert("DEV DeleteDE's return value is NOT undefined", deResult === undefined ? "undefined" : "defined", "defined");
assert("DEV DeleteDE commits on a CloudPage (docs: email context only)", rowCount(deName, "Email", "n2@example.com"), 0);
/* 4. DeleteData is preferred outside email because it returns the count. */
assert("DeleteData returns a number even when nothing matches", String(typeof Platform.Function.DeleteData(deName, ["Email"], ["nobody2@example.com"])), "number");
/* 5. Irreversible — the deleted row does not come back. */
assert("Lookup returns a genuine null for the deleted row n1", Platform.Function.Lookup(deName, "Status", "Email", "n1@example.com") === null ? "null" : "not null", "null");
assert("the surviving row n3 is still readable", String(Platform.Function.Lookup(deName, "Status", "Email", "n3@example.com")), "keep");
assert("cleanup: the throwaway data extension is deleted", dropDE(deKey), "OK");
</script>