SSJS: Generate a dynamic form based on a Data Extension
In this tutorial, we explore how to dynamically generate forms in Salesforce Marketing Cloud using Server-Side JavaScript (SSJS). Learn how to retrieve Data Extension fields, build forms on the fly, and handle form submissions—all while leveraging the flexibility of SSJS for custom form handling.
Introduction
In this SSJS example, we will create a dynamic form based on a Data Extension. This post is not meant as a replacement for Smart Capture, but rather an alternative solution for more customized use cases. Through this post, you will gain a better understanding of:
- How to differentiate between POST and GET requests in CloudPages
- How to handle form data in SSJS
- How to dynamically create a form based on an existing Data Extension
- How to use SSJS and AMPScript together in a CloudPage
Where This Could Be Useful
Salesforce Marketing Cloud’s Contact Builder allows you to manually add data to a Data Extension, and Smart Capture provides an easy way to generate dynamic forms that save data directly to a Data Extension. However, this example offers an alternative approach that could be useful in scenarios where:
- You need more control over how form fields are generated and displayed.
- You want to implement conditional logic or custom validation that Smart Capture might not fully support.
- You aim to integrate with additional backend services or perform other tasks before saving data to a Data Extension.
By following this tutorial, you’ll learn how to leverage SSJS and AMPScript together for more flexible form handling in CloudPages, providing you with a deeper level of customization and control. Let's get started.
How The Page works
This solution is built within a single CloudPage that:
- Accepts a Data Extension External Key via the URL: The page captures the external key of the Data Extension from the URL parameter (de). This key is used to retrieve the fields of the specific Data Extension.
- Dynamically Renders a Form: Based on the retrieved Data Extension fields, the form is dynamically generated. Each form input corresponds to a field in the Data Extension and is rendered according to its field type (e.g., text, number, checkbox, etc.).
- Handles Form Submission and Saves Data: Upon submission, the form data is processed and saved into the corresponding Data Extension. The server-side script handles the form’s POST request, ensuring data is stored accurately.
- Provides Basic Error Handling: The script includes error handling for situations like missing Data Extension keys or failed data submissions, displaying relevant messages to the user when issues arise.
The code
You can access the full code example at the end of this post or on this github repository.
Differentiating between GET and POST requests
In this solution, one of the first tasks is to determine whether the request is a GET or a POST. This allows us to either display the form or handle the form submission.
GET Request:
When the page is loaded via a GET request (usually when the user first lands on the page), the primary goal is to:
- Retrieve the Data Extension Key from the URL.
- Generate the Form based on the Data Extension fields.
Here’s the basic structure for handling the GET request:
if (Platform.Request.Method == "GET") {
// Check if the 'de' parameter is provided in the URL
var de_key = Platform.Request.GetQueryStringParameter("de");
if (de_key == null) {
// If the Data Extension key is missing, display an error message
Variable.SetValue("@ShowMessage", true);
Variable.SetValue("@Message", "Please provide a Data Extension External Key in the URL.");
} else {
// If the key is present, retrieve the Data Extension fields and generate the form
var de = DataExtension.Init(de_key);
var fields = de.Fields.Retrieve();
Variable.SetValue("@Form", generateForm(fields));
}
}
POST Request:
When the user submits the form, a POST request is triggered. In this step, we:
- Capture the Form Data submitted by the user.
- Save the Data into the Data Extension.
- Handle Success or Error by displaying a message to the user.
Here’s how the POST request is handled:
if (Platform.Request.Method == "POST") {
// Get form data from the POST request
var form_data = Platform.Request.GetPostData();
var de_key = Platform.Request.GetQueryStringParameter("de");
if (de_key) {
var de = DataExtension.Init(de_key);
var data = GetFormData(form_data);
// Insert data into the Data Extension
var result = de.Rows.Add(data);
// Check if the data insertion was successful
if (result == "1") {
Variable.SetValue("@Message", "Data added successfully");
} else {
Variable.SetValue("@Message", "Error adding data");
}
}
}
The GenerateForm() function
The generateForm()
function is the core of this solution. It dynamically builds the form by looping through the fields of the Data Extension, generating the appropriate HTML input elements based on the field type. This eliminates the need to manually code forms for every Data Extension, making the form creation process highly flexible and efficient.
How it works
- Retrieve the Fields:
The function takes an array of fields from the Data Extension and loops through them. Each field has a type (e.g., text, number, date), and this type determines what kind of input element will be generated.
- Create the Appropriate Input Element:
Based on the field type, generateForm()
creates the corresponding form element using simple HTML tags (<input>
, <checkbox>
, etc.). For example, text fields will generate a text input, while boolean fields generate a checkbox.
3. Apply HTML Structure and Styling:
In this example, basic HTML structure is used to wrap labels and input elements together. Styling can be handled separately (using Tailwind CSS, for instance), but the function focuses on ensuring that the form fields are mapped correctly to the Data Extension’s field types.
Here’s a breakdown of the code:
// function to get a form element given the fields
function generateForm(fields){
form = ""
for (var i = 0; i < fields.length; i++) {
if (fields[i].FieldType == "Text" && fields[i].DefaultValue.indexOf(";") > -1) {
// Adding Tailwind classes to the label and text input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<select name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'>";
var options = fields[i].DefaultValue.split(";");
for (var j = 0; j < options.length; j++) {
form += "<option value='" + options[j] + "'>" + options[j] + "</option>";
}
form += "</select><br>";
}else if(fields[i].FieldType == "Text"){
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='text' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br/>";
}else if (fields[i].FieldType == "Number") {
// Adding Tailwind classes to the label and number input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='number' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Date" && fields[i].DefaultValue == "") {
// Adding Tailwind classes to the label and date input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='date' name='" + fields[i].Name + "' value='' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Boolean") {
// Adding Tailwind classes to the label and checkbox
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='checkbox' name='" + fields[i].Name + "' id='" + fields[i].Name + "' value='True' class='h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "EmailAddress") {
// Adding Tailwind classes to the label and email input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='email' name='" + fields[i].Name + "' value='" + fields[i].DefaultValue + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Decimal") {
// Adding Tailwind classes to the label and decimal input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='number' name='" + fields[i].Name + "' step='0.01' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Phone") {
// Adding Tailwind classes to the label and phone input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='tel' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
}
}
return form;
}
Key Parts of the Function:
- Field Type Handling: Each field type (text, number, date, etc.) is handled separately to ensure the correct HTML input type is generated. This ensures that the form is appropriately structured for the data types in your Data Extension.
- Label and Input Pairing: For every field, a corresponding label is created, followed by the input element. This ensures a user-friendly form where each input field is properly labeled.
- Dynamic Form Generation: The function returns the entire form as a string, allowing it to be injected directly into the page. This keeps the process highly dynamic, adjusting to the structure of whatever Data Extension you are working with.
Putting it All Together
Now that we’ve walked through the core components—differentiating between GET and POST requests and dynamically generating form fields with the generateForm() function—let’s see how it all works as a complete solution.
How It Flows
- Accept the Data Extension Key:
The page first looks for the Data Extension key in the URL parameter (de). If it’s missing, an error message is shown. Otherwise, we use this key to fetch the Data Extension fields.
- Render the Form Dynamically:
Using the generateForm() function, the form is dynamically built based on the fields retrieved from the Data Extension. Each field type (e.g., text, number, date, checkbox) is mapped to the appropriate input element.
- Handle Form Submission:
When the form is submitted, the data is captured via a POST request. The values entered by the user are processed and added to the Data Extension, while providing feedback on the success or failure of the data submission.
- Display Messages and Handle Errors:
Throughout the process, error handling ensures that the user is informed if something goes wrong (e.g., missing Data Extension key, invalid fields). Success or error messages are dynamically displayed based on the result of the form submission.
Complete Code Example
Here's the complete CloudPages code:
<script runat="server">
Platform.Load("Core", "1.1.1");
try {
// Handle GET request
if (Platform.Request.Method == "GET") {
// Try getting the data extension key from the URL using the parameter "de"
// If it's not provided, show an error message
de_key = Platform.Request.GetQueryStringParameter("de");
Write(de_key);
if (Platform.Request.GetQueryStringParameter("de") == null) {
Variable.SetValue("@ShowMessage", true);
Variable.SetValue("@Message", "Please provide a Data Extension External Key in the URL using the parameter 'de'. Such as '?de=MyDataExtension'");
} else {
// If the data extension key is provided, get the data extension fields
var de_key = Platform.Request.GetQueryStringParameter("de");
var de = DataExtension.Init(de_key);
var fields = de.Fields.Retrieve();
// Check if there are no fields or invalid data extension key
if (fields.length == 0) {
Variable.SetValue("@ShowMessage", true);
Variable.SetValue("@Message", "No fields found in the Data Extension. Or Data Extension key is invalid.");
} else {
// If fields are found, render the form
Variable.SetValue("@RenderForm", true);
Variable.SetValue("@Message", "Data Extension found");
var form = generateForm(fields);
// Loop through the fields and generate form inputs based on the field type
// Set the form content
Variable.SetValue("@Form", form);
}
}
}
// Handle POST request
if (Platform.Request.Method == "POST") {
// Get the form data from the POST request
var form_data = Platform.Request.GetPostData();
// Write(Stringify(GetFormData(form_data)));
var de_key = Platform.Request.GetQueryStringParameter("de");
var de = DataExtension.Init(de_key);
var fields = de.Fields.Retrieve();
var data = GetFormData(form_data);
var form = generateForm(fields);
Variable.SetValue("@Form", form);
// Add a new row to the Data Extension
var result = de.Rows.Add(data);
// Write(Stringify(result));
// Display success or error message based on the result
if (result == "1") {
Variable.SetValue("@Message", "Data added successfully");
} else {
Variable.SetValue("@Message", "Error adding data");
}
//display the form again
Variable.SetValue("@RenderForm", true);
}
// Function to parse form data from the URL-encoded string format
function GetFormData(form_data_string) {
var form_data = {};
var form_data_array = form_data_string.split("&");
for (var i = 0; i < form_data_array.length; i++) {
var key_value = form_data_array[i].split("=");
form_data[decodeURIComponent(key_value[0])] = decodeURIComponent(key_value[1]);
}
return form_data;
}
// function to get a form element given the fields
function generateForm(fields){
form = ""
for (var i = 0; i < fields.length; i++) {
if (fields[i].FieldType == "Text" && fields[i].DefaultValue.indexOf(";") > -1) {
// Adding Tailwind classes to the label and text input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<select name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'>";
var options = fields[i].DefaultValue.split(";");
for (var j = 0; j < options.length; j++) {
form += "<option value='" + options[j] + "'>" + options[j] + "</option>";
}
form += "</select><br>";
}else if(fields[i].FieldType == "Text"){
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='text' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br/>";
}else if (fields[i].FieldType == "Number") {
// Adding Tailwind classes to the label and number input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='number' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Date" && fields[i].DefaultValue == "") {
// Adding Tailwind classes to the label and date input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='date' name='" + fields[i].Name + "' value='' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Boolean") {
// Adding Tailwind classes to the label and checkbox
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='checkbox' name='" + fields[i].Name + "' id='" + fields[i].Name + "' value='True' class='h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "EmailAddress") {
// Adding Tailwind classes to the label and email input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='email' name='" + fields[i].Name + "' value='" + fields[i].DefaultValue + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Decimal") {
// Adding Tailwind classes to the label and decimal input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='number' name='" + fields[i].Name + "' step='0.01' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
} else if (fields[i].FieldType == "Phone") {
// Adding Tailwind classes to the label and phone input
form += "<label for='" + fields[i].Name + "' class='block text-sm font-medium text-gray-700 mb-1'>" + fields[i].Name + "</label>";
form += "<input type='tel' name='" + fields[i].Name + "' id='" + fields[i].Name + "' class='w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4'><br>";
}
}
return form;
}
} catch (e) {
// Catch and display any errors that occur during execution
Write(Stringify("Error: " + e));
}
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Extension - Add Fields</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 text-gray-900">
<div class="max-w-4xl mx-auto py-8">
<div class="bg-white p-8 shadow-md rounded-lg">
<!-- Server-side script -->
<script runat="server">
try {
</script>
<!-- Show fields or messages dynamically -->
%%=v(@Fields)=%%
%%[ IF @Message != "" THEN ]%%
<div class="mb-4">
<p class="text-green-500 font-semibold">%%=v(@Message)=%%</p>
</div>
%%[ENDIF]%%
%%[
IF @RenderForm THEN
]%%
<form method="POST" class="space-y-6">
<!-- Dynamically inserted form fields -->
%%=v(@Form)=%%
<div>
<input type="submit" value="Submit" class="bg-blue-500 text-white px-4 py-2 rounded-lg shadow hover:bg-blue-600">
</div>
</form>
%%[ENDIF]%%
<script runat="server">
} catch (e) {
Write(Stringify(e));
}
</script>
</div>
</div>
</body>
</html>
Paste the code into a CloudPage and publish it. To use the form, pass the de parameter in the URL, setting it to the External Key of the Data Extension you want to generate the form for. Example: https://your-cloud-page-url.com?de=MyDataExtension
Conclusion
In this advanced example, we explored how to dynamically generate forms in Salesforce Marketing Cloud using SSJS, based on Data Extension fields. This approach offers flexibility and control, making it a great alternative to Smart Capture for custom form handling. By combining GET and POST request handling with dynamic form generation, you can easily adapt this solution to fit various use cases in SFMC, streamlining data collection and management.
Happy coding!