Custom vs legacy components

Custom components replace and enhance our older, legacy components.


If you started using Parcel on or after April 23, 2024, you're already set up to create our latest, custom components!

Custom components are not new to Parcel. You may be familiar with our legacy implementation where you could create components then reference them across any email to render the same code. Later, if you updated the original component, those changes would cascade down across all of your emails. The same applies with our new version of custom components, but below we'll get into the differences and how our next generation is even more powerful.

Overall, the concept and way you reference components in emails has stayed the same. It’s the way you build them that's changed.

How is this better than before?

Our latest and greatest custom components enable you to:

  • run more advanced and complex JavaScript logic - no more complex objects mixed inside your markup. You can pass properties/variables defined in the <script> tag to both the <style> and <template> tags.
  • set the HTML inside of MSO comments completely dynamically making components a lot more flexible
  • differentiate ESP syntax from the component using the #is:raw directive

Important differences

Here's a high-level summary of the differences between our legacy and latest implementation of custom components.

With our latest, custom components:

  1. There are now six top-level elements you can utilize. Previously, there were four.
  2. You can use dashes within your naming structure like first-name.
  3. Setting a property is now required by default. You must specify if it's optional, which is opposite from our legacy implementation.
  4. You must use #isolated instead of discrete with the <style> tag.
  5. Directives:
    • now require hashtags, like #if.
    • foreach has changed to #each.
    • elseif is now #else-if. (note the dash!)
    • You can now pass an object to the #set directive to set multiple attributes at once. You can also use it to set HTML content.
    • We've added #is. It's a new directive that can change the element used.
  6. You can now render variables in the <style> tag and conditional comments.

Read more about each below. Then proceed to upgrade when you're ready!

Top-level elements

Our legacy components had four, top-level elements:

  • <meta> - to label the component
  • <fieldset> - to create variables and set up slots
  • <style> - to add CSS
  • <component> - to add the HTML

In our updated components, we now have six, top-level elements:

  • <script> - this is used for a wide range of things including labelling the component and setting presets using config, creating variables including validation and UI settings for the visual editor using props, using slots, as well as doing anything you’d normally do in a <script> tag. For example, you can now create complex logic based on the properties that are passed into the component.
  • <style> - this is very similar to what we had in the legacy components; however, now you can render variables inside the style tag.
  • <title> - this can be used to insert a title element into the <head> of the email.
  • <link> - this can be used to insert a link element into the <head> of the email.
  • <meta> - this can be used to insert a meta element into the <head> of the email.
  • <template> - This replaces the <component> tag from before.

<script> and <template> are required in all components.

Script tag

Where our legacy components allowed for some basic Javascript expressions in the HTML such as toUpperCase() or JSON.stringify(), our new components have a <script> tag that lets you run more advanced and complex JavaScript logic. You can also create new variables and use them in the <template>. By doing this, it keeps the HTML in the <template> much cleaner and easier to read.

This means <fetch> and <scrape>, which previously worked in the HTML, are now deprecated. Use <context> in the <script> tag, which allows for a lot more flexibility.

Setting things in a <script> tag also opens up more possibility in terms of data validation because it supports the full JavaScript run-time allowing us to do all kinds of advanced and complex JavaScript logic.

Properties (variables)

Previously, you created variables using <input> elements inside <fieldset>. This had a nice familiar feel to it for anyone comfortable with HTML. However, it was also quite limited as we only had the input types and validation that was included in the HTML. This has now changed to creating props in a <script> tag.

export const props = defineProps({
'first-name': PropTypes.string(),
age: PropTypes.number(),
'profile-picture': PropTypes.string().url().optional(),

This format may be familiar to JavaScript developers, but potentially less so to email developers. We’ll break it down for you here:

export const props = defineProps({})

  • where you create and define props; we export these so you can edit them in the properties menu of the visual editor (coming soon!).

'first-name': PropTypes.string(),

  • an example property; the prop is called first-name (Note how we have a dash in the name. That’s something that caused issues before but no longer!); the PropTypes is set to string, similar to how we used <input type="text"> before.

'age': PropTypes.number(),

  • this replaces <input type="number"> from before.

'profile-picture': PropTypes.string().url().optional(),

  • this replaces <input type="url"> from before. Note that this is set to .optional(). In our legacy components, the default state was optional and required needed to be set. In our latest components, this is reversed.


Previously, you created slots with <input type="slot" name="__content" accept />, but these now export from the <script> tag.

export const slots = Component.defineSlots({
default: Component.slots.any(),

Here we're exporting the default slot and saying it can contain any other components. default is the default name for a slot; however, if you use named slots, then you can replace this with the slot name.

To limit a slot to only accept certain elements, you can set a list like so:

schema: Component.slots.children(['my-nested-component1','my-nested-component2']),

To set a text slot, you can use:

schema: Component.slots.text()

Style tag

Styling is similar to legacy components, but now you can render variables inside the <style> tag. This lets you create more complex logic in the <script> tag, then easily pass the result into the style.

In addition, we've replaced the discrete attribute with #isolated. Like before, apply it to a <style> tag to isolate it on its own. #isolated can also take a name like #isolated="outlookweb". We group any other isolated styles with the same name together.

Template tag

The <template> tag replaces the <component> tag of legacy components.

You'll reference variables in the same way as before, except they're referred to as props now. For instance, ${name} is now ${}. We now have a more flexible naming structure, so you can set dashed attributes like first-name too: ${props["first-name"]}.


Directives are the special attributes that control (aka direct) an element's behavior. With our new, custom components, directives now start with #. This makes them easier to pick out compared to regular attributes:

  • foreach has changed to #each
  • if is now #if
  • else is now #else
  • elseif is now #else-if - also note the dash here, which helps make it more readable

These all render the same as before; only the names have changed.


set has also changed to #set and renders the same as before but has some new features.

You can pass an object to the #set directive to set multiple attributes at once.

<!-- source -->
<div #set="{ id: 'my-id', class: 'my-class' }"></div>
<!-- result -->
<div id="my-id" class="my-class"></div>

You can now use #set to set HTML content, too. When dynamically inserting values into text, the output is escaped by default to prevent accidentally injecting HTML. To skip this escaping step, use the #set:html directive.

<div #set:html="rawHTMLVariable"></div>

Use it with <fragment> to avoid adding an extra element to your output.

<fragment #set:html="rawHTMLVariable" />


#is is a new directive that can change the element used.

<fragment #set:is="styles ? 'span' : 'fragment' " #set:style="styles">
If there are styles. this will output a span. If not, then this will be plain

#is:raw will skip any processing of the content inside. This is helpful when you're inserting text or attributes with conflicting syntax and you don’t want Parcel to process it.

<div #is:raw>
<a #if="I am not evaluated">${I am also not evaluated}</a>

Conditional comments

Previously, we were unable to render variables inside conditional comments. This caused some frustration and required some long-winded workarounds. With our new components, this is no longer an issue. It just works!