Code steps are only supported in the US region. We are working on supporting this in EU region.
Supported channels
| Channel | Required Output |
|---|---|
subject, body (HTML string) | |
| SMS | body |
| Push | subject, body |
| Chat | body |
| In-App | subject, body (plus optional avatar, primaryAction, secondaryAction, data, redirect) |
Quick Start
Create workflow and step in the UI
Create a workflow and add any channel step in the workflow from UI. Once the step is added. Go to the step editor and Toggle the switch to Custom Code from Editor option to enable custom code for this step.
Get the CLI command from the dashboard
The Novu dashboard shows a prefilled publish command in the step editor screen, click on the Copy button to copy the command. It includes your secret key, workflow ID, step ID, and API URL.
Run the CLI command in your project
| Flag | Type | Default | Description |
|---|---|---|---|
-s, --secret-key <key> | string | $NOVU_SECRET_KEY | Novu API secret key |
-a, --api-url <url> | string | config or https://api.novu.co | Novu API URL |
--config <path> | string | novu.config.ts | Path to config file |
--out <path> | string | from config or ./novu | Directory containing handlers |
--workflow <id> | string (repeatable) | all | Deploy only steps for specific workflow(s) |
--step <id> | string (repeatable) | all | Deploy only a specific step within a workflow |
--template <path> | string | — | Path to a React Email template; scaffolds a React Email email handler if the step file doesn’t exist. Requires --workflow and --step (single values each). |
--bundle-out-dir [path] | string or boolean | — | Write bundle artifacts to disk (debug mode, skips minification) |
--dry-run | boolean | false | Bundle without deploying |
- Look up the step type from the Novu API.
- Auto-scaffold a placeholder handler at
novu/<workflowId>/<stepId>.step.tsx. - Bundle all handlers in the
novu/directory. - Deploy to Novu.
.env file if present.Also similarly for apiUrl, by default it uses US, but you can specify EU or other regions either in novu.config.ts or directly in CLI command.Edit the handler
The generated file will be located at
novu/<workflowId>/<stepId>.step.tsx. The folder name is the workflowId. The CLI reads it from the path at publish time. You should commit this file to your repository.Open the generated file and replace the placeholder content with your real logic. Each handler receives the full context:novu/workflow-id/step-id.step.tsx
Defining Controls (Optional)
Controls allow dashboard users to override specific values without changing code. You can define acontrolSchema using Zod or plain JSON Schema:
Controls vs Payload
| Property | Controls | Payload |
|---|---|---|
| Set by | Dashboard users (with code-defined defaults) | Your application at trigger time |
| Purpose | Content overrides (subject lines, toggles, copy) | Dynamic data (username, order ID, etc.) |
| Defined via | controlSchema in the step handler | payloadSchema (types only) |
Skipping a Step Conditionally
Useskip to prevent a step from executing at runtime based on your logic. The function receives (controls, ctx) where ctx contains payload, subscriber, context, and steps.
skip is not called during preview. You will always see the step output in the dashboard preview regardless of the skip condition.Provider Overrides
Provider overrides let you customize the raw request sent to the underlying notification provider for a step. This is useful for setting provider-specific options not exposed by Novu standard output schema._passthrough field merges directly into the API body sent to the provider. You can also set headers and query for header and query string overrides.
Disabling Output Sanitization
Novu sanitizes HTML inemail and in_app outputs by default to prevent XSS vulnerabilities. To opt out, you can set disableOutputSanitization:
Configuration File (Optional)
You can create anovu.config.ts file for advanced use cases:
TypeScript Support
You can install@novu/framework as a dev dependency to get full TypeScript types for controls, payload, subscriber, context, and steps:
The CLI publishes to non production environments only. Publishing directly to Production is blocked. To promote changes to Production, use the Publish changes button in the Novu dashboard.
Frequently Asked Questions
Frequently asked questions related to code steps:What is the attachment limit for email type code step
If email step is created using custom code, then maximum attachment size limit is 7MB. Checkout the ending email attachments documentation to learn on how to send the email attachments while triggering the workflow.Troubleshooting
| Problem | Solution |
|---|---|
| ”Publishing to Production is not allowed” | Use your Development environment’s secret key. Promote to Production via the dashboard. |
| ”Template file not found” | Check the --template path is correct relative to your current directory. |
| ”Workflow not found” / “Step not found” | Create the workflow and step in the Novu dashboard before publishing. |
| Authentication failed (401) | Verify your secret key is correct. |
| Bundle too large (>10 MB) | Reduce dependencies or publish specific workflows with --workflow. |
| No step files found | Run with --workflow=<id> --step=<id> to scaffold, or create the file manually. |
| Preview not updating in dashboard | Republish your handler. The dashboard polls every 3 seconds. |
| Controls not appearing in dashboard | Export a controlSchema in your step handler and republish. |
--step requires --workflow | The --step flag must always be paired with --workflow. |
Provider output rejected (INVALID_PROVIDER_OUTPUT) | Check that your providers function returns values matching the provider schema. |
skip not working in preview | By design, skip is only evaluated at send time, not during dashboard preview. |