MCP Serverside Code Properties
Build your marketers' dream campaign configuration UI in Marketing Cloud Personalization (Interaction Studio). All the input magic in one place.
Once you learn the very basics of Serverside Code in Marketing Cloud Personalization (Interaction Studio), it is time to start writing code. The easiest way is, to begin with creating the campaign creation UI for the marketer - defining all the inputs they will need to fill in to drive the experience. It will be our focus in this article.
MCP Serverside Code offers five basic property data types: Boolean, String, Number, Color and DateTime. Those, their combinations into complex properties and modifications through decorators enable you to build nearly anything you need for your campaign configuration screen.
Let's dive into how you can make the most of those features.
Basic properties
Basic properties are the base building blocks of your campaign configuration. They are all you need to make a perfect campaign template, and mastering them will be essential to doing more complex UIs with complex properties and decorators.
Boolean
The boolean property lets you capture a true
or false
flag by creating a checkbox input:
isTextLeftToRight: boolean;
// Input: Editable unchecked checkbox
// Output: true or false
The boolean property is unchecked by default (returns false
), but you can change it by passing a true
value in the code:
isTextLeftToRight: boolean = true;
// Input: Editable checked checkbox
// Output: true or false
Boolean fields are crucial for template development as they are perfect for building logic, for example, with the @shownIf
decorator.
String
String property lets you capture a text input:
content: string;
// Input: Empty text input
// Output: "Provided text"
However, there is much more to string property than just that.
› Array String
You may want to capture more than one string. Easy, you can do it with two characters:
hashtags: string[];
// Input: Empty text input with an option for converting entered text into one of the values
// Output: ["Array", "of", "Strings"]
By adding []
after the type definition, we convert the field to an array of strings. During configuration, you can provide multiple values that, in the payload, will be returned as an array. It doesn't accept duplicate values.
› Default String
To simplify the work for your marketers or to provide recommended examples of expected data, you can easily add a default value to your string by delivering it as a string after the equal sign:
content: string = 'This is default content';
// Input: Editable prefilled text input
// Output: "Provided text"
It can be freely edited during configuration but will be passed as-is to the payload if no changes are made.
You can provide default values also for string arrays:
hashtags: string[] = ['interaction-studio', 'marketing-cloud-personalization'];
// Input: Editable prefilled text input with two deletable values and space to write additional ones
// Output: ["Array", "of", "Strings"]
› Select String
You can go one step further and lock the string property to only a set of preconfigured values using the @options
decorator. It is excellent when you need a particular value, for example, for a programmatic logic in other parts of your template.
@options(['brandA', 'brandB', 'brandC'])
brand: string;
// Input: Empty text input showing dropdown with configured options on click
// Output: "Selected text"
With such code, the campaign configuration will display a picklist with the hardcoded values. It will output a string to the campaign payload.
You can also provide default value for your select field:
@options(['brandA', 'brandB', 'brandC'])
brand: string = 'brandA';
// Input: Editable prefilled text input showing dropdown with configured options on click
// Output: "Selected text"
You can also create a select field without the decorator using literals:
brand: 'brandA' | 'brandB' | 'brandC';
// Input: Empty text input showing dropdown with configured options on click
// Output: "Selected text"
Just as you can capture an array of text inputs, you can do the same for select strings and create a multi-picklist. However, while the preconfigured options will be visible and accessible to pick, new options - outside of what you hardcoded - can be freely added.
@options(['brandA', 'brandB', 'brandC'])
brands: string[];
// Input: Empty text field with picklist and option to create new values. Allows for multiple additions.
// Output: ["Array", "of", "Strings"]
You can also provide default value for your multi-select field:
@options(['brandA', 'brandB', 'brandC'])
brands: string[] = ['brandA', 'brandB'];
// Input: Editable prefilled text field with picklist and option to create new values. Allows for multiple additions.
// Output: ["Array", "of", "Strings"]
It will output an array of strings to the campaign payload.
You can also create a multi-select field without the decorator using literals:
brand: ('brandA' | 'brandB' | 'brandC')[];
// Input: Empty text field with picklist and option to create new values. Allows for multiple additions.
// Output: ["Array", "of", "Strings"]
› Rich Text String
You can easily convert this simple text input into a rich text field by using a @richText
decorator:
@richText(true)
content: string;
// Input: Text input that adapts to the content size and provides bold, italic, underscore and link WYSIWYG options.
// Output: "String with <span style='font-weight:bold;'>optional</span>HTML<br/>Includes multiline"
This decorator will change the input field to a multiline box with bold, italic, underscore and link features. The payload will return those styles in the form of HTML that can then be used directly, for example, in the Handlebars tab of Web Campaign.
While the rich text options are minimal, you can write some other HTML (for example, <span>
with style attribute) directly in the box, which will be passed to the output correctly. It will even display as a formatted text after you revisit the configuration.
Number
Number property lets you capture integer (3
) and decimal (3.14
) values:
recommendationsCount: number;
// Input: Empty text (sic!) input
// Output: 3.14
Keep in mind that non-numeric values might break the campaign without any error visible in the configuration screen. You can either display a warning using @shownIf
decorator and a string field or create sanitization logic in the run block.
You can use @unit
decorator to provide a unit description next to the input. It has no impact on the outputted value but gives marketer information about the expected value:
@unit('ms')
delayBeforeDisplay: number;
› Array Number
Just as with strings, you can capture multiple numeric values by adding []
:
productIds: number[];
// Input: A plus icon that adds new text inputs with delete buttons
// Output: [3.14, 3, 5]
It will output an array of numbers to the campaign payload.
› Default Number
Again, just as with strings, you can provide an editable default value:
recommendationsCount: number = 6;
// Input: Editable prefilled text input
// Output: 6
The same can be done for Array Numbers:
productIds: number[] = [123, 456, 789];
// Input: Editable and deletable prefilled text inputs and a plus icon that can add more
// Output: [123, 456, 32]
› Select Number
As with strings, you can create a numeric picklist:
recommendationsCount: 1 | 3 | 6 | 9;
// Input: Empty text input showing dropdown with configured options on click
// Output: 1
You can also pass a default value:
recommendationsCount: 1 | 3 | 6 | 9 = 6;
// Input: Editable prefilled text input showing dropdown with configured options on click
// Output: 6
You can also create a numeric multi-picklist:
orderOfSections: (1 | 2 | 3 | 4 | 5)[];
// Input: Empty text field with picklist. Allows for multiple additions.
// Output: [1, 3, 4, 2, 5]
Different from strings multi-select, with a numeric multi-picklist, the marketer won't be able to add new options outside of what you preconfigured. Yay!
However, there are three caveats to keep in mind:
- You cannot have
0
as one of the options. The picklist will crash. - You cannot use
@options
to create the picklist. It will be ignored. - The single-select picklist will always cut the visibility of the last digit in the longest option after selecting it.
Color
Color property lets you create a color picker with a single line of code:
backgroundColor: Color;
// Input: An input pseudo-prefilled with #FFFFFF and a color box that, on click, opens the color selection interface
// Output: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
//}
You will be able to select the color by dragging the selector over a colour palette or by providing hex/rgba/hsl values. The property will output to payload a color
object with both hex and rgba values.
While the property will look like it is prefilled with white color, it will output 'null' until you pick a color in the interface. If you want the white to be a working default option, you must configure it explicitly.
› Default Color
You can provide a default color, but it will be more complex. You cannot just give a single hex value; you need to replicate the whole output object:
backgroundColor: Color = {
"hex": "#da4e55",
"r": 218,
"g": 78,
"b": 85,
"a": 1
};
// Input: An input truly prefilled with #FFFFFF and a color box that, on click, opens the color selection interface
// Output: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
//}
Datetime
Datetime property provides a clean-looking calendar widget that lets the marketer easily pick up a date.
promotionDate: DateTime;
// Input: Empty input with calendar icon that, on click, opens full calendar for date selection
// Output: {
// "dateTime": [
// "2023-10-01T16:00:00.000Z"
// ]
//}
There are two payload-related things to consider:
- The DateTime property always outputs an object with a single
dateTime
property assigned to an array of strings. - The date-times are returned as ISO 8601 strings (
2023-10-01T16:00:00.000Z
).
› Datetime Range
While you cannot create a multi-select datetime field, you can use the @range
decorator to select two dates within a nice UI.
@range(true)
promotionDate: DateTime;
// Input: Empty input with calendar icon that on click opens full calendar for two dates selection with range indicator
// Output: {
// "dateTime": [
// "2023-10-01T16:00:00.000Z",
// "2023-10-03T16:00:00.000Z"
// ]
//}
The output will return both selected dates in the dateTime
array of strings.
Readonly property
Apart from basic string property there is also a very similar readonly string property:
readonly templateVersion = "Version 1.1"
// Input: No input, just a readonly text written in the form
// Output: 'Version 1.1'
As you can see, the key difference is the readonly
prefix before defining the property. It also uses the default string approach to assign the value.
However, readonly property has one superpower. You can assign an arrow function to it (as long as it will return a string) and use values from other properties to transform them:
header: string = 'DEFault heaDER';
readonly upperCaseHeader = () => this?.header.toUpperCase() || '';
// Input: No input, just a readonly text updating real-time based on header input
// Output: 'DEFAULT HEADER'
readonly titleCaseHeader = () => {
return this?.header
.toLowerCase()
.split(' ')
.filter(word => word !== '')
.map(word => word.replace(word[0], word[0].toUpperCase()))
.join(' ')
|| '';
}
// Input: No input, just a readonly text updating real-time based on header input
// Output: 'Default Header'
With the dynamic readonly property, you can transform string inputs, concatenate multiple inputs into one field or even create a logic based on non-string inputs (as long as the output is a string).
You can do the same things later in the run() block. It will be even more powerful there, as you can use the context object. However, you must manually add those calculated values to the return statement to see it in the payload.
For simple use cases, the readonly property will be more straightforward and provide a nifty real-time preview of the calculated value for the marketer.
Complex property
The input configuration fun starts when you combine basic properties into complex ones. The method is straightforward. Outside the main class that implements CampaignTemplateComponent, create a new class export that contains all the basic inputs you need:
export class TimeframedColorPicker {
@range(true)
timeframe: DateTime;
color: Color;
}
With complex property defined, you can leverage it in the main class the same way as you do with basic properties:
timeframedColor: TimeframedColorPicker;
// Input: Set of basic properties - datetime range and color pickers, in this case
// Output: {
// timeframedColor: {
// timeframe: "dateTime": [
// "2023-10-01T16:00:00.000Z",
// "2023-10-03T16:00:00.000Z"
// ],
// color: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
// }
// }
//}
What are the benefits of this approach?
- You can define your complex property class once and then use it multiple times in your form.
- You can control the structure of the Serverside payload.
A complex property creates a new nested object assigned to an input property. Let's look at the difference:
@range(true)
timeframe: DateTime;
color: Color;
// Input: Two basic properties - datetime range and color pickers, in this case
// Output: {
// timeframe: "dateTime": [
// "2023-10-01T16:00:00.000Z",
// "2023-10-03T16:00:00.000Z"
// ],
// color: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
// }
//}
timeframedColor: TimeframedColorPicker;
// Input: Set of basic properties - datetime range and color pickers in this case
// Output: {
// timeframedColor: {
// timeframe: "dateTime": [
// "2023-10-01T16:00:00.000Z",
// "2023-10-03T16:00:00.000Z"
// ],
// color: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
// }
// }
//}
As you can see, data from basic inputs is assigned as properties to the complex property. This can help with payload readability and might be crucial when adapting your payload to specific requirements (for example, schema expected by a React website or 3rd party system).
- You can nest complex properties
Just as you can add a complex property to a field, you can also create a complex property using complex properties. While it can again give you all the benefits mentioned here, be careful not to go overboard. Deep nesting is more challenging to understand and use. There is no perfect rule, but check out Simple/Complex recommendations in Zen of SFMC.
- You can leverage it to build an Array of complex properties!
This is the real game changer - out of the basic properties, only Strings and Numbers can be used as arrays. But using a complex one, you can also leverage Boolean, Color and Datetime. The approach is the same as previously - just add []
:
timeframedColor: TimeframedColorPicker[];
// Input: A plus icon that adds new sets of basic properties with delete buttons
// Output: {
// timeframedColor: [{
// timeframe: "dateTime": [
// "2023-10-01T16:00:00.000Z",
// "2023-10-03T16:00:00.000Z"
// ],
// color: {
// "hex": "#da4e55",
// "r": 218,
// "g": 78,
// "b": 85,
// "a": 1
// }
// }]
//}
It allows you to capture multiple complex configurations (for example, multiple recommendations or - as in the example above - various colors that can change in the campaign based on the current date).
There is a bug with the removal UI for arrays of complex properties.
Let's say you have multiple properties configured in an array and want to remove one. If you click the removal button, regardless of which element you did it, the UI will remove the bottom one. However, the correct one was removed in the backend, which you can check in the Payload Preview. Save, reenter the configuration screen or refresh, and you will see the correct configuration.
Complex default values
With complex properties, you can provide default values in two ways:
- You can provide default values within the defining class directly on basic properties.
export class RecommendationsConfig {
recommendationsHeader: string = 'Chosen for You';
recommendationsDisplayed: number = 6;
}
This approach will work for complex property arrays but not single complex properties.
- You can provide a default value in the main class implementing the CampaignTemplateComponent.
recsConfig: RecommendationsConfig = {
"recommendationsHeader": "Chosen for You",
"recommendationsDisplayed": 6
};
This approach will work for single complex property and - if you provide the default value(s) in the array - also for arrays for complex properties.
recsConfig: RecommendationsConfig = [{
"recommendationsHeader": "Chosen for You",
"recommendationsDisplayed": 6
}, {
"recommendationsHeader": "Bestsellers",
"recommendationsDisplayed": 3
}];
As you can see, both approaches work for arrays of complex properties but result in a different outcome. The first approach provides default values to all elements of a complex property array you will create. The second approach prefills the array with the provided default elements. You can use both simultaneously to have a few array elements prefilled and provide default values for all new elements created on top of it.
Complex picklist
Similar to how you can create a selection for strings and numbers, you can also build picklists for complex properties. And I don't mean using the selects within the complex property (which is also possible), but simplifying your complex property to a single straightforward picklist.
Using the previous complex property example:
export class RecommendationsConfig {
recommendationsHeader: string;
recommendationsDisplayed: number;
@hidden(true)
label: string;
}
Instead of requiring the user to fill in those values and giving them a free hand at that, you can instead provide preconfigured options with user-friendly labels using the @options
decorator:
@options([
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 12, label: "Bestsellers Home Page" },
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 8, label: "Bestsellers Category Page" },
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 4, label: "Bestsellers Search Page" },
{ recommendationsHeader: "Chosen For You", recommendationsDisplayed: 6, label: "Chosen for You Global" },
])
recsConfig: RecommendationsConfig;
// Input: Single picklist showing only the label values
// Output: {
// "recommendationsHeader": "Bestsellers",
// "recommendationsDisplayed": 12
// "label": "Bestsellers Home Page"
//}
The campaign UI will display only a single picklist using the label values. At the same time, you can provide dozens of appropriately configured properties to the campaign payload in the backend.
You can leverage complex picklist and default values at the same time. Just pass one of the @options
decorator objects.
@options([
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 12, label: "Bestsellers Home Page" },
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 8, label: "Bestsellers Category Page" },
{ recommendationsHeader: "Bestsellers", recommendationsDisplayed: 4, label: "Bestsellers Search Page" },
{ recommendationsHeader: "Chosen For You", recommendationsDisplayed: 6, label: "Chosen for You Global" },
])
recsConfig: RecommendationsConfig = {
recommendationsHeader: "Chosen For You",
recommendationsDisplayed: 6,
label: "Chosen for You Global"
};
Complex tabular view
Complex properties can take up a lot of space in your campaign configuration screen. Sometimes, you can save some space using the @tabular
decorator.
export class PriceRange {
@title('Price starts at')
lowerPriceBoundary: number;
@title('Price ends at')
upperPriceBoundary: number;
}
The complex property above will take 5 lines in the campaign configuration pane - complex property title and two input label + title sets. We can change it to 3 lines with the @tabular
decorator:
@tabular()
priceRange: PriceRange;
// Input: Two inputs are provided side by side instead of one below the other
// Output: {
// "lowerPriceBoundary": 10,
// "upperPriceBoundary": 20
//}
You can also use that decorator for arrays of complex properties, but you will see that the input labels are now visible only on the first element of the array for an even more compact view. If it doesn't suit your needs, you can pass an argument to the decorator to change that behaviour: @tabular({headersPerRow: true})
.
Decorators
Decorators are a TypeScript feature that can change or extend the behaviour of properties in the MC Personalization's Serverside Code. You have already seen some of them:
@options
used to provide picklist values for strings, numbers and complex properties.@richText
that changes string input.@unit
that gives context to a numeric input.@range
that modifies date selection into range selection.@tabular
that changes how a complex object is displayed.
But there is so much more available:
@title & @subtitle
When you add a property, its name will be used as a label for the input - in a smart way, with space being added before each uppercase (but not a digit):
header: string; // Label: Header
productDescription: string; // Label: Product Description
listElement3: string; // Label: List Element3
That's neat, but sometimes you might want to be more descriptive - without changing the actual property passed in the payload. This is where the @title
decorator comes in. Adding it above a property and passing a string can change the label to anything you want.
@title('Recommendations Box Header')
header: string; // Label: Recommendations Box Header
You can also pass a space into the @title
decorator to remove the input label altogether:
@title(' ')
header: string; // No label
It can be helpful in some cases, like complex objects with their own label and labels of all properties used to create them, or cases where you want to use @markdown
decorator instead.
On the other hand, adding a @subtitle
decorator above a property will show the provided text in a smaller font right below the input. It's a great tool to give more context or example data to aid the person configuring the campaign.
@title('Recommendations Box Header')
@subtitle('Use Title Case and stay below 40 characters')
header: string;
@markdown
When @title
and @subtitle
are not enough for the context you want to provide, you can use the @markdown
decorator to go wild with text, styling and even links.
@markdown(`
---
#### Conditional Configuration
**Use only on campaigns targeted to small audiences**
`)
@title('Use Conditional Configuration?')
isConditionalConfigurationUsed: boolean = false;
For @markdown
to work, you need to add the content without any indentation:
@markdown(`
---
#### Conditional Configuration
**Use only on campaigns targeted to small audiences**
`)
@title('Use Conditional Configuration?')
isConditionalConfigurationUsed: boolean = false;
@header & @headerSubtitle
There is also a pair of @header
and @subheader
decorators that are very similar to @title
and @subtitle
with one key difference - they are not attaching to a property. That's right, you can use them anywhere to add context to whole sections of your campaign configuration form.
@header('Recommendations Box Header')
@headerSubtitle('Use Title Case and stay below 40 characters')
The @header
will be in the same font size as the input labels, and @headerSubtitle
will match @subtitle
style. Oh, and passing a space - @header(' ')
- will add a bit of whitespace. Perfect for those of us with OCD who can't stand that uneven spacing between inputs.
@hidden & @shownIf
@hidden
& @shownIf
are some of the most important decorators, as they allow you to control the visibility of the inputs. Big if you want to provide a nice and clean campaign configuration UI that won't overwhelm the marketer.
There are two ways to use them.
First, with a basic true
boolean argument, it makes sense only for @hidden
. It is perfect for data you will calculate in the run() block or don't want to show to the marketer.
@hidden(true)
templateVersion: string = "Version 1.1";
It gets much more interesting with the second way to use those decorators - with a function that returns a boolean as an argument. With this, you can build conditional logic based on other inputs:
bannerType: 'Manual' | 'Promotion' | 'Einstein';
@shownIf(this, (self) => self.bannerType === 'Manual')
imageURL: string;
Technically, in both scenarios, you can use @hidden
and @shownIf
interchangeably after appropriately flipping the boolean. However, I find using @hidden
only with a true
boolean argument and @shownIf
with a function argument easier to grasp when reading the code.
@buttonGroup
@buttonGroup
is a simple decorator that can change a single-select picklist
into a set of buttons with one line:
@buttonGroup(true)
bannerType: 'Manual' | 'Promotion' | 'Einstein';
It's nice if you have a small pool of options with short names.
@optional
The @optional
decorator was an excellent tool for setting required and non-required inputs. Was. It no longer works. Whether you add it to the code or not, nothing will change in the UI or on saving. And I doubt it will start working, as bringing the functionality back would break all templates created with it not working in mind. To sum up, there is no way to enforce filling an input.