Skip to main content
Convert one workflow at a time. Start from a captured legacy definition and finish with test evidence for every execution path.

Build the payload contract first

Legacy workflows accepted payload variables without a declared workflow schema. The new Dashboard uses the payload schema to make fields available to templates, conditions, grouping keys, and dynamic action-step settings.
A payload schema and schema enforcement are separate controls. You must declare a payload field before using it as a Dashboard variable. You can leave enforcement disabled while callers are being updated, but an undeclared field is not a substitute for a migration plan.
For each legacy workflow:
  1. Capture successful production payloads from every trigger producer.
  2. List each field referenced by content, conditions, variants, delay or digest configuration, and provider overrides.
  3. Resolve inconsistent types. For example, do not define orderId as an integer if one producer sends it as a string.
  4. Add nested objects and array item types.
  5. Mark a field required only if every valid trigger must provide it.
  6. Add defaults only when a missing value has an unambiguous meaning.
  7. Import a representative JSON object or create the properties in Manage workflow schema.
  8. Preview the workflow with minimum and maximum valid values.
  9. Enable schema enforcement after all trigger producers conform.
For example, a legacy trigger payload:
should define orderId as a string, customerTier as an enum or string, and items as an array of objects with typed name and quantity properties. See Configure workflow for supported schema types and constraints.

Convert Handlebars to LiquidJS

Do not perform a global brace replacement. Handlebars helpers and Liquid tags have different parsing, scoping, truthiness, and formatting behavior.

Variable paths

Conditions and loops

When comparing string values, use single quotes in Liquid conditions:
Do not assume every Novu-specific Handlebars helper has a direct Liquid equivalent. Use the documented Liquid filters for case conversion, dates, numbers, and pluralization. Precompute a display-ready value in application code when exact output parity is not available. In particular, redesign templates that depend on legacy groupBy, implicit with context, or different date-format tokens. Use the variable picker where possible. It inserts the current namespace and reduces errors caused by renamed fields. See Personalize content for the supported namespaces, conditions, loops, and filters.

Choose the email editing model

The code editor accepts Liquid variables, tags, and filters directly in HTML. The block editor stores structured nodes and inserts variables through its variable controls. Some block attributes use selected variable paths rather than handwritten {{ ... }} expressions. For an exact legacy HTML migration:
  1. Start with the code editor.
  2. Convert Handlebars to LiquidJS in the source.
  3. Compare the rendered HTML and text output with the legacy message.
Use the block editor when you intend to rebuild the email as structured content. Configure Repeat, Digest, and conditional blocks through their controls. Do not paste code-editor Liquid into every block attribute and assume it has the same representation. Switching editor modes can reset content. Back up the migrated source before changing modes.

Replace variants with conditional flow

Legacy variants selected alternate content or configuration inside one step. The new editor models each alternative as an independent step with its own condition. Assume a legacy Email step has:
  • A Gold variant when customerTier equals gold
  • A Silver variant when customerTier equals silver
  • A root variant for every other value
Create three Email steps: The expressions below describe rules in the Dashboard condition builder. They are not Liquid template syntax. All three steps are evaluated in sequence. Their conditions must be mutually exclusive. Otherwise, one trigger can execute more than one replacement step.
Include missing or null values in the fallback design. A not in rule might not match a missing property. If the field is required, enforce it in the payload schema. If it is optional, test a trigger that omits it and add an explicit fallback condition supported by the field type.
Apply the same pattern to variants of action steps:
  • A legacy Delay variant becomes a separate conditional Delay step.
  • A legacy Digest variant becomes a separate conditional Digest path.
  • All downstream steps must have conditions that prevent execution after the wrong action path.
For complex variant trees, separate workflows are often easier to verify than a long linear sequence of duplicated conditional steps. Choose separate workflows when the paths have different step order, different action semantics, or independent ownership.

Convert step conditions

Recreate conditions from their intent, not only their labels.

Payload and subscriber conditions

Map legacy fields to current namespaced fields:
  • Payload: payload.<declared-property>
  • Standard subscriber fields: subscriber.firstName, subscriber.email, subscriber.locale, or another supported field
  • Custom subscriber fields: subscriber.data.<property>
The current condition builder supports nested AND and OR groups. Match the legacy grouping exactly and test boundary values for numeric, range, empty, null, in, and string operators.

Webhook response conditions

Replace a legacy webhook condition with:
  1. An HTTP step before the dependent steps
  2. A response schema that declares every consumed field
  3. A condition that reads steps.<http-step-id>.<response-field>
  4. An explicit failure policy
If the old webhook returned { "eligible": true }, a later Email step can use a condition on steps.check-eligibility.eligible. Test timeout, non-2xx, malformed response, and false-result paths. A successful preview request does not create Activity feed evidence, so also run the complete workflow.

Previous message state

For fallback delivery:
  1. Send the in-app step.
  2. Add enough Delay time for the subscriber to interact.
  3. Add a condition to the later step using the exact ID of the in-app step.
  4. Test read, unread, seen, and unseen outcomes independently.
Do not rename referenced step IDs without updating every template and condition that consumes their results.

Migrate email layouts

Legacy layouts and current layouts have different content contracts. Use this conversion order:
  1. Copy the legacy HTML to a backup file.
  2. Replace {{{body}}} with {{content}}.
  3. Find every payload or tenant-dependent expression.
  4. Move workflow payload-dependent markup into the Email step.
  5. Replace stable global values with static layout content.
  6. Replace environment-specific values with env.*.
  7. Replace reusable tenant or brand values with context.*.
  8. Convert remaining Handlebars expressions to LiquidJS.
  9. Enable and migrate layout translations if the layout contains localized text.
  10. Select the layout explicitly on every migrated Email step.
  11. Preview long content, empty optional fields, mobile width, links, images, and dark-mode-sensitive styles.
If legacy triggers choose layoutIdentifier dynamically, decide whether to use separate conditional Email steps or separate workflows. Do not leave the legacy override in application code without proving that the current delivery path supports the intended result. See Email layouts.

Migrate translations

Legacy translation groups were independent resources referenced with the i18n Handlebars helper. Current translations belong to a workflow or layout and use the t namespace. Example:
For each group:
  1. Identify which workflows and layouts use the group.
  2. Copy the required keys into each owning workflow or layout.
  3. Remove the legacy group prefix from expressions when it is no longer part of the current JSON key path.
  4. Rewrite helper parameters and nested variables using LiquidJS.
  5. Rename files to language_REGION.json.
  6. Set the default and target locales.
  7. Import the default locale, then each target locale.
  8. Preview with subscribers that have each locale.
  9. Test fallback behavior for missing keys and unsupported locales.
  10. Publish translation resources with their workflows and layouts.
An edit to the default locale can mark target locales outdated. Resolve outdated translations before Production publication. See Translations.

Replace tenants with contexts

Map each legacy tenant use separately: Keep context data reusable and stable. Keep event-specific data in the payload. The current platform limits the number of contexts per trigger, so check Limits before mapping one legacy tenant into several context objects. Provider routing and workflow step conditions are separate controls. Recreate a tenant-based integration condition in Integrations, then recreate content or orchestration conditions on the workflow steps. Test the matching integration, the fallback integration, and a trigger with no tenant context. See Contexts.

Review action steps

Delay

Both dashboards support a fixed delay and a payload-driven delay. Map the legacy type before changing anything else. The Dynamic delay also accepts a { "amount": 30, "unit": "minutes" } duration object. Treat that as a follow-up change, because the legacy step only accepted an ISO date. The new Scheduled delay is calendar-based and uses a recurring minute, hour, day, week, or month. It is not the replacement for the legacy Scheduled delay. Use it only when you intend to change behavior. Enabling extension to the subscriber’s schedule changes delivery time. Leave it disabled if exact legacy timing is required during migration. A dynamic delay fails the workflow when the variable is missing, is not a valid ISO date or duration object, or resolves to a past time. Test each of those cases before cutover.

Digest

Both dashboards support a regular window, a repeat-event start, and a scheduled window. Also confirm subscriber grouping, the optional payload aggregation key, extension to the subscriber schedule, and every downstream reference to digest events and counts. The aggregation key must exist in the payload schema. Test multiple events for one subscriber, the same subscriber with different aggregation keys, and different subscribers with the same key.

Throttle

Throttle is new relative to the primary legacy action-step model. Do not add it merely because it is available. If you adopt it, define:
  • Fixed or dynamic window
  • Execution threshold
  • Subscriber and optional payload grouping
  • Behavior for critical workflows

HTTP

Use HTTP to replace legacy webhook-condition enrichment or to fetch application state. Define response schemas, signatures, timeout behavior, and whether workflow execution continues after failure. See Add and configure steps for current step behavior and plan-dependent duration limits.

Validate channel parity

Use the current channel template editor reference to validate each copied channel. Keep this page focused on conversion rules rather than duplicating channel configuration. For every channel in a legacy workflow, record the rendered content, provider, credentials, overrides, condition result, and delivery result. For In-app steps, also confirm Data, actions, tags, avatar, and sanitization. Complete the client-side field and feed migration in the Inbox migration guide.

Workflow validation record

Create one record per workflow with: After all workflow records pass, continue with application migration and production cutover.