Ever wondered how to fetch all your email assets in Salesforce Marketing Cloud and log their juicy metadata (like custom attributes) into a Data Extension?
Yeah, we did too. And let’s be real—manually opening every email asset like it’s 2010 isn’t it. So we built a smart, server-side script that does all the heavy lifting for us. Buckle up—this one’s a time-saver.
What’s This Script All About?
This script is a full-blown automation for metadata logging. It:
-
Authenticates using OAuth2 Client Credentials
-
Pulls paginated email asset data from the SFMC Asset API
-
Extracts custom metadata (like __AdditionalEmailAttribute1 through 5)
-
Inserts records into a logging Data Extension: EmailAssetLogging_DE
Prerequisites
Before diving in, make sure you've got these essentials ready:
✅ A valid Client ID, Client Secret, and Account ID
✅ An Installed Package with Asset REST API access
✅ A Data Extension named EmailAssetLogging_DE with fields:
EmailID, EmailName, AdditionalEmailAttribute1 ... up to 5
Step 1: Authenticate Like a Boss
var payload = {
"grant_type": "client_credentials",
"client_id": clientId,
"client_secret": clientSecret,
"account_id": accountId
};
var payload = {
"grant_type": "client_credentials",
"client_id": clientId,
"client_secret": clientSecret,
"account_id": accountId
};
We’re hitting the /v2/token endpoint to get our access_token. Without it, we’re not going anywhere.
Step 2: Get Paginated Email Asset Data
This API call grabs all the email assets in pages of 100. You can tweak that size if you’re feeling adventurous (or throttled).
var pagedUrl = assetUrlBase
+ "?$page=" + currentPage
+ "&$pageSize=" + pageSize
+ "&$orderBy=name asc"
+ "&$filter=assetType.name like 'email'"
+ "&$fields=id,customerKey,name,data";
var pagedUrl = assetUrlBase
+ "?$page=" + currentPage
+ "&$pageSize=" + pageSize
+ "&$orderBy=name asc"
+ "&$filter=assetType.name like 'email'"
+ "&$fields=id,customerKey,name,data";
Notice the $filter for "assetType.name like 'email'"—we don’t want images, folders, or old relics.
Step 3: Extract Custom Attributes (If They Exist)
Custom attributes are tucked inside item.data.email.attributes. We gracefully parse and loop through them.
if (
item.hasOwnProperty("data") &&
item.data.hasOwnProperty("email") &&
item.data.email.hasOwnProperty("attributes")
) {
attributes = item.data.email.attributes;
}
if (
item.hasOwnProperty("data") &&
item.data.hasOwnProperty("email") &&
item.data.email.hasOwnProperty("attributes")
) {
attributes = item.data.email.attributes;
}
Then we match those attribute names like __AdditionalEmailAttribute1, etc. and assign values accordingly.
Step 4: Insert Into Data Extension
We throw it all into EmailAssetLogging_DE using:
Platform.Function.InsertData("EmailAssetLogging_DE",
["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", ...],
[emailId, emailName, attr1, attr2, ...]);
Platform.Function.InsertData("EmailAssetLogging_DE",
["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", ...],
[emailId, emailName, attr1, attr2, ...]);
very successful insert increments our count. Failures are logged for debugging.
Step 5: Validate the Results
Once all pages are processed, we do a final validation:
var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");
var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");
We compare:
-
✅ Total assets reported by API
-
✅ Rows inserted
-
✅ Rows currently in the DE
If they all match up, you’re golden. If not? Check for paging issues or failed inserts.
Cross-Check with Content Builder UI
We’re not just trusting the API—we’re checking receipts.
Here’s how:
-
Navigate to Content Builder
-
Apply a filter for Asset Type = Email
-
Scroll to the bottom and note the total asset count
-
Compare that number with:
-
API count (totalCount)
-
DE count (finalRowCount)
If all three line up? 💯 Your system’s bulletproof.

Full Script
<script runat="server">
Platform.Load("Core", "1.1.1");
Write("<br>Script started...");
// CONFIG
var clientId = "ihdfl24a1e3vth3gq2rfc21u";
var clientSecret = "ENMBiBZ9whE5lbHMwRupGiHQ";
var accountId = "546002178";
var authUrl = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.auth.marketingcloudapis.com/v2/token";
var assetUrlBase = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.rest.marketingcloudapis.com/asset/v1/content/assets";
Write("<br>Config loaded.");
// AUTHENTICATE
Write("<br>Authenticating...");
var payload = {
"grant_type": "client_credentials",
"client_id": clientId,
"client_secret": clientSecret,
"account_id": accountId
};
var authResp = HTTP.Post(authUrl, "application/json", Stringify(payload));
var token = "";
if (authResp.StatusCode === 200) {
var authData = Platform.Function.ParseJSON(authResp.Response[0]);
token = authData.access_token;
Write("<br>Access token acquired.");
} else {
Write("<br>Authentication failed. Response: " + authResp.Response[0]);
return;
}
// HEADERS (no Content-Type on GET)
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + token];
// INIT PAGINATION
var pageSize = 100;
var currentPage = 1;
var totalCount = 0;
var insertedCount = 0;
Write("<br>Pagination initialized.");
// FETCH + PROCESS LOOP
do {
var pagedUrl = assetUrlBase
+ "?%24page=" + currentPage
+ "&%24pageSize=" + pageSize
+ "&%24orderBy=name%20asc"
+ "&%24filter=assetType.name%20like%20%27email%27"
+ "&%24fields=id%2CcustomerKey%2Cname%2Cdata";
Write("<br><br>Fetching Page: " + currentPage);
var resp = HTTP.Get(pagedUrl, headerNames, headerValues);
// Skip pages with no content
if (!resp.Content || resp.Content.length < 10) {
Write("<br>No content returned on this page. Breaking.");
break;
}
var parsed;
try {
parsed = Platform.Function.ParseJSON(resp.Content);
} catch (e) {
Write("<br>Failed to parse response JSON: " + Stringify(resp.Content));
break;
}
if (currentPage === 1) {
totalCount = parsed.count || 0;
Write("<br>Total asset count reported by API: " + totalCount);
}
var items = parsed.items || [];
Write("<br>Items in this page: " + items.length);
for (var i = 0; i < items.length; i++) {
var item = items[i];
var emailId = item.id;
var emailName = item.name || "";
var attr1 = "", attr2 = "", attr3 = "", attr4 = "", attr5 = "";
try {
var attributes = [];
if (
item.hasOwnProperty("data") &&
item.data.hasOwnProperty("email") &&
item.data.email.hasOwnProperty("attributes")
) {
attributes = item.data.email.attributes;
}
for (var j = 0; j < attributes.length; j++) {
var attr = attributes[j];
var name = attr.name;
var value = attr.value || "";
if (name === "__AdditionalEmailAttribute1") attr1 = value;
if (name === "__AdditionalEmailAttribute2") attr2 = value;
if (name === "__AdditionalEmailAttribute3") attr3 = value;
if (name === "__AdditionalEmailAttribute4") attr4 = value;
if (name === "__AdditionalEmailAttribute5") attr5 = value;
}
} catch (e) {
Write("<br>Error reading attributes for Email ID: " + emailId + ". Skipping.");
continue;
}
// Insert into DE
var inserted = Platform.Function.InsertData("EmailAssetLogging_DE",
["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", "AdditionalEmailAttribute3", "AdditionalEmailAttribute4", "AdditionalEmailAttribute5"],
[emailId, emailName, attr1, attr2, attr3, attr4, attr5]);
if (inserted > 0) {
insertedCount++;
Write("<br>Inserted Email ID: " + emailId);
} else {
Write("<br>Insert failed for Email ID: " + emailId);
}
}
currentPage++;
} while ((currentPage - 1) * pageSize < totalCount);
// FINAL VALIDATION (using LookupRows)
Write("<br><br>Final validation...");
var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");
var finalRowCount = finalRows ? finalRows.length : 0;
Write("<br>Total reported from API: " + totalCount);
Write("<br>Total rows inserted in this run: " + insertedCount);
Write("<br>Total rows currently in DE: " + finalRowCount);
if (finalRowCount == totalCount) {
Write("<br>Validation passed. All records are in sync.");
} else {
Write("<br>Validation mismatch. DE has: " + finalRowCount + ", Expected: " + totalCount);
}
</script>
<script runat="server">
Platform.Load("Core", "1.1.1");
Write("<br>Script started...");
// CONFIG
var clientId = "ihdfl24a1e3vth3gq2rfc21u";
var clientSecret = "ENMBiBZ9whE5lbHMwRupGiHQ";
var accountId = "546002178";
var authUrl = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.auth.marketingcloudapis.com/v2/token";
var assetUrlBase = "https://mcpbq4z2b121rg0v4mmy7tjls8h4.rest.marketingcloudapis.com/asset/v1/content/assets";
Write("<br>Config loaded.");
// AUTHENTICATE
Write("<br>Authenticating...");
var payload = {
"grant_type": "client_credentials",
"client_id": clientId,
"client_secret": clientSecret,
"account_id": accountId
};
var authResp = HTTP.Post(authUrl, "application/json", Stringify(payload));
var token = "";
if (authResp.StatusCode === 200) {
var authData = Platform.Function.ParseJSON(authResp.Response[0]);
token = authData.access_token;
Write("<br>Access token acquired.");
} else {
Write("<br>Authentication failed. Response: " + authResp.Response[0]);
return;
}
// HEADERS (no Content-Type on GET)
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + token];
// INIT PAGINATION
var pageSize = 100;
var currentPage = 1;
var totalCount = 0;
var insertedCount = 0;
Write("<br>Pagination initialized.");
// FETCH + PROCESS LOOP
do {
var pagedUrl = assetUrlBase
+ "?%24page=" + currentPage
+ "&%24pageSize=" + pageSize
+ "&%24orderBy=name%20asc"
+ "&%24filter=assetType.name%20like%20%27email%27"
+ "&%24fields=id%2CcustomerKey%2Cname%2Cdata";
Write("<br><br>Fetching Page: " + currentPage);
var resp = HTTP.Get(pagedUrl, headerNames, headerValues);
// Skip pages with no content
if (!resp.Content || resp.Content.length < 10) {
Write("<br>No content returned on this page. Breaking.");
break;
}
var parsed;
try {
parsed = Platform.Function.ParseJSON(resp.Content);
} catch (e) {
Write("<br>Failed to parse response JSON: " + Stringify(resp.Content));
break;
}
if (currentPage === 1) {
totalCount = parsed.count || 0;
Write("<br>Total asset count reported by API: " + totalCount);
}
var items = parsed.items || [];
Write("<br>Items in this page: " + items.length);
for (var i = 0; i < items.length; i++) {
var item = items[i];
var emailId = item.id;
var emailName = item.name || "";
var attr1 = "", attr2 = "", attr3 = "", attr4 = "", attr5 = "";
try {
var attributes = [];
if (
item.hasOwnProperty("data") &&
item.data.hasOwnProperty("email") &&
item.data.email.hasOwnProperty("attributes")
) {
attributes = item.data.email.attributes;
}
for (var j = 0; j < attributes.length; j++) {
var attr = attributes[j];
var name = attr.name;
var value = attr.value || "";
if (name === "__AdditionalEmailAttribute1") attr1 = value;
if (name === "__AdditionalEmailAttribute2") attr2 = value;
if (name === "__AdditionalEmailAttribute3") attr3 = value;
if (name === "__AdditionalEmailAttribute4") attr4 = value;
if (name === "__AdditionalEmailAttribute5") attr5 = value;
}
} catch (e) {
Write("<br>Error reading attributes for Email ID: " + emailId + ". Skipping.");
continue;
}
// Insert into DE
var inserted = Platform.Function.InsertData("EmailAssetLogging_DE",
["EmailID", "EmailName", "AdditionalEmailAttribute1", "AdditionalEmailAttribute2", "AdditionalEmailAttribute3", "AdditionalEmailAttribute4", "AdditionalEmailAttribute5"],
[emailId, emailName, attr1, attr2, attr3, attr4, attr5]);
if (inserted > 0) {
insertedCount++;
Write("<br>Inserted Email ID: " + emailId);
} else {
Write("<br>Insert failed for Email ID: " + emailId);
}
}
currentPage++;
} while ((currentPage - 1) * pageSize < totalCount);
// FINAL VALIDATION (using LookupRows)
Write("<br><br>Final validation...");
var finalRows = Platform.Function.LookupRows("EmailAssetLogging_DE", "EmailID", "*");
var finalRowCount = finalRows ? finalRows.length : 0;
Write("<br>Total reported from API: " + totalCount);
Write("<br>Total rows inserted in this run: " + insertedCount);
Write("<br>Total rows currently in DE: " + finalRowCount);
if (finalRowCount == totalCount) {
Write("<br>Validation passed. All records are in sync.");
} else {
Write("<br>Validation mismatch. DE has: " + finalRowCount + ", Expected: " + totalCount);
}
</script>