Creating Folders in Salesforce Marketing Cloud with SSJS
If you’re automating anything in Salesforce Marketing Cloud, such as cloning Data Extensions, generating Query Activities, or building emails programmatically, you’ll eventually hit the same small problem: before you can create the item, you need somewhere to put it. That means a folder needs to exist first. If it doesn’t, your script needs to create it.
The good news is that SFMC handles folders the same way across nearly every tool it has, so once you’ve written this logic once, you can reuse it everywhere.
Folders in SFMC
In Salesforce Marketing Cloud (SFMC), the different folder structures you see throughout the interface—such as Content Builder, Automation Studio (including Automations, Queries, and Data Extensions)—all use the same underlying Folder object. In the SOAP API, this object is commonly exposed as DataFolder.
The property that determines which tree a folder appears in is ContentType.
For example:
| Folder Tree | Typical ContentType |
| Content Builder | asset |
| Data Extensions | dataextension |
| Queries | queryactivity |
| Automations | automation |
| Triggered Sends | triggered_send |
| Import Activities | import |
| Filter Activities | filteractivity |
When SFMC renders the folder hierarchy, it filters folders by their ContentType. This means:
- Every folder has a parent (ParentFolder/ParentFolder.ID).
- Every folder belongs to exactly one logical tree because of its ContentType.
- Two folders can have identical names but appear in different trees because they have different ContentType values.
- Creating a folder with the wrong ContentType causes it to appear in the wrong section of the application (or not where you expect).
In other words, the “Queries” tree and the “Data Extensions” tree are not different object types. They’re simply different filtered views over the same underlying folder table, distinguished primarily by the ContentType value.
The pattern
Creating a folder in a way that’s safe to run repeatedly (an idempotent process) involves four simple steps:
- Find the parent folder you want to create the new folder under.
- Make sure it allows children. Folders in SFMC have an AllowChildren flag; if it’s off, you need to switch it on before you can add anything underneath.
- Check whether your target folder already exists under that parent. If it does, just use it — don’t try to create a duplicate.
- Create it if it doesn’t exist, with the right Name, ParentFolderID, and ContentType.
Everything else about creating a folder—checking the parent, making sure it can contain child folders, and confirming that the target folder doesn’t already exist—works the same regardless of what you’re creating. The only part that’s easy to get wrong is ContentType.
If ContentType is incorrect, SFMC won’t throw an error. Folder.Add still returns “OK”, and the folder is created successfully—it just ends up in the wrong tool. Most of the time, you won’t realise anything is wrong until you go looking for the folder and can’t find it.
A worked example: resolving the parent by name
Folder IDs aren’t something you’ll typically have memorised, so it’s usually more practical to look up a folder by name. The catch is that folder names aren’t unique across Salesforce Marketing Cloud. It’s common for a Content Builder folder and a Data Extension folder to have the exact same name because they’re often organised around the same campaign or business unit structure. If you search by name alone, there’s no way to know which folder tree you’ve actually found.
The safe way to resolve a folder by name is to fetch every folder with that name, then filter for the right ContentType in code, rather than trying to filter for both conditions in a single API call:
/**
* Finds a folder by name AND content type. Filtering ContentType
* client-side, after retrieving all name matches.
*/
function findFolderByNameAndType(folderName, contentType) {
var allMatches = Folder.Retrieve({
Property: "Name",
SimpleOperator: "equals",
Value: folderName
});
if (!allMatches || allMatches.length === 0) {
return null;
}
for (var i = 0; i < allMatches.length; i++) {
if (allMatches[i].ContentType == contentType) {
return allMatches[i];
}
}
return null;
}
/**
* Ensures a folder exists under a given parent (identified by name),
* creating it if necessary. Works for any SFMC folder type — pass the
* appropriate ContentType.
*
* @param {string} folderName Name of the folder to find or create
* @param {string} parentFolderName Name of the parent folder
* @param {string} contentType e.g. "asset", "queryactivity", "dataextension",
* "filterdefinition", "userinitiatedsend", "importdefinition"
* @returns {object} { success, folderID, created, message }
*/
function ensureFolder(folderName, parentFolderName, contentType) {
if (!parentFolderName) {
return { success: false, message: "Invalid parent folder name." };
}
var parentFolder = findFolderByNameAndType(parentFolderName, contentType);
if (!parentFolder) {
return { success: false, message: "Parent folder '" + parentFolderName + "' (ContentType: " + contentType + ") was not found." };
}
var parentFolderId = parentFolder.ID;
// Make sure the parent folder can actually hold children
if (parentFolder.AllowChildren == false) {
var folder = Folder.Init(parentFolderId);
folder.Update({
"ID": parentFolderId,
"AllowChildren": true,
"AllowChildrenSpecified": true
});
}
// Check whether a folder with this name already exists under this parent
var existingFolder = Folder.Retrieve({
LeftOperand: {
Property: "Name",
SimpleOperator: "equals",
Value: folderName
},
LogicalOperator: "AND",
RightOperand: {
Property: "ParentFolder.ID",
SimpleOperator: "equals",
Value: parentFolderId
}
});
if (existingFolder && existingFolder.length > 0) {
return {
success: true,
folderID: existingFolder[0].ID,
created: false,
message: "Folder '" + folderName + "' already exists."
};
}
var pGUID = Platform.Function.GUID();
var newFolder = {
"Name": folderName,
"CustomerKey": pGUID,
"Description": "",
"ContentType": contentType,
"IsActive": true,
"IsEditable": true,
"AllowChildren": true,
"ParentFolderID": parentFolderId
};
var status = Folder.Add(newFolder);
if (status != "OK") {
return { success: false, message: "Failed to create folder '" + folderName + "'. Status: " + status };
}
var created = Folder.Retrieve({
Property: "CustomerKey",
SimpleOperator: "equals",
Value: pGUID
});
return {
success: true,
folderID: created[0].ID,
created: true,
message: "Folder '" + folderName + "' was created."
};
}Code language: PHP (php)
You’ll notice that the “already exists” check further down—matching Name and ParentFolder.ID together—uses the same combined LeftOperand/RightOperand filter that we intentionally avoided earlier for Name + ContentType. That’s safe in this case because a folder name is only ambiguous across the broader folder hierarchy. Within a single parent folder, a matching name uniquely identifies the folder, so the combined filter is reliable.
The problem is specifically with Name + ContentType searches across the entire folder tree. SFMC’s filtering behaviour isn’t reliable for that scenario, which is why the earlier lookup retrieves a broader set of results and filters them client-side instead.
How to use it
<script runat="server">
Platform.Load("core", "1.1.5");
try {
var result = ensureFolder("Campaign A", "Business", "asset");
Write(Stringify(result));
result = ensureFolder("Campaign B", "Business", "userinitiatedsends");
Write(Stringify(result));
} catch (e) {
Write(Stringify(e));
}
// findFolderByNameAndType and ensureFolder defined above
</script>Code language: HTML, XML (xml)
The returned result.folderID is the value you’ll pass into whatever you create next. For example, use it as the CategoryID when creating a new Data Extension or QueryDefinition, or when building the payload for a new Content Builder asset. The complete code can be downloaded here on GitHub.
A few things worth knowing
- Permissions are scoped per tool, not globally. An API user that can create Content Builder folders won’t automatically be able to create Automation Studio or Data Extension folders — each area requires its own permission grant. If a script loops through several ContentTypes in one run, it’s possible for some to succeed and others to fail purely on a permissions boundary.
- The “already exists” check is intentionally scoped to one parent. Folder names are only unique within a parent, not across the whole account, so don’t loosen that filter to just Name — you’d get false matches against unrelated folders elsewhere in the tree.
Wrapping up
By encapsulating this logic in a reusable function, folder management becomes a single function call instead of another block of check-then-create code that has to be copied, maintained, and kept in sync across multiple automations.