Skip to main content

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:

Create simple checkbox
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:

Checkbox checked by default
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:

Create empty text input field
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:

Capture multiple strings
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:

Add default content
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 Should Know

You can provide default values also for string arrays:

Default values for array string
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.

Limit possible values with select
@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:

Provide default select value
@options(['brandA', 'brandB', 'brandC'])
brand: string = 'brandA';
// Input: Editable prefilled text input showing dropdown with configured options on click
// Output: "Selected text"
You Should Know

You can also create a select field without the decorator using literals:

Literal select field
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.

Multi-picklist
@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:

Provide default multi-select values
@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 Should Know

You can also create a multi-select field without the decorator using literals:

Literal multi-select field
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:

Capture styling with a single 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.

You Should Know

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:

Create empty numeric input
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 Should Know

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 decorator on number
@unit('ms')
delayBeforeDisplay: number;

› Array Number

Just as with strings, you can capture multiple numeric values by adding []:

Capture multiple numbers
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:

Default numeric value
recommendationsCount: number = 6;
// Input: Editable prefilled text input
// Output: 6

The same can be done for Array Numbers:

Default numeric array value
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:

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:

Numeric picklist with 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:

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:

  1. You cannot have 0 as one of the options. The picklist will crash.
  2. You cannot use @options to create the picklist. It will be ignored.
  3. 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:

Create color picker
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.

You Should Know

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.

Create datetime picker
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:

  1. The DateTime property always outputs an object with a single dateTime property assigned to an array of strings.
  2. 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.

Create range datetime picker
@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:

Static readonly 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:

Dynamic readonly property
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 Should Know

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:

Define complex property
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:

Use complex property
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?

  1. You can define your complex property class once and then use it multiple times in your form.
  2. 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:

Payload using basic properties
@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
// }
//}
Payload using complex property
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).

  1. 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.

  1. 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 []:

Array of complex properties
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).

You Should Know

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:

  1. You can provide default values within the defining class directly on basic properties.
Define complex property with default values
export class RecommendationsConfig {
recommendationsHeader: string = 'Chosen for You';
recommendationsDisplayed: number = 6;
}

This approach will work for complex property arrays but not single complex properties.

  1. You can provide a default value in the main class implementing the CampaignTemplateComponent.
Use complex property with default value
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.

Use an array of complex properties with default values
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:

Complex property with a hidden label property
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:

Change complex property to a single picklist with @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 Should Know

You can leverage complex picklist and default values at the same time. Just pass one of the @options decorator objects.

Complex property picklist with default value
@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.

Example complex property with two numeric inputs
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 decorator on a single complex property
@tabular()
priceRange: PriceRange;
// Input: Two inputs are provided side by side instead of one below the other
// Output: {
// "lowerPriceBoundary": 10,
// "upperPriceBoundary": 20
//}
You Should Know

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:

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):

Examples of default conversion from property name to label
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.

Examples of default conversion from property name to label
@title('Recommendations Box Header')
header: string; // Label: Recommendations Box Header
You Should Know

You can also pass a space into the @title decorator to remove the input label altogether:

Using space to hide the label
@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.

Example use of @subheader decorator for added context
@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.

Notice the backticks used to open and close markdown content in this decorator
@markdown(`
---
#### Conditional Configuration

**Use only on campaigns targeted to small audiences**
`)
@title('Use Conditional Configuration?')
isConditionalConfigurationUsed: boolean = false;
You Should Know

For @markdown to work, you need to add the content without any indentation:

@markdown decorator works only with unindented markdown
    @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.

Example use of @subheader decorator for added context
@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.

Example use of simple boolean @hidden decorator
@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:

Example use of function-based @shownIf decorator
bannerType: 'Manual' | 'Promotion' | 'Einstein';

@shownIf(this, (self) => self.bannerType === 'Manual')
imageURL: string;
You Should Know

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:

Example use of @buttonGroup decorator
@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.