A custom steps allows to execute any custom logic and persist in the durable execution context. The result of this step can be used in subsequent steps.

Common usecases

  • Making an API call to 3rd party service
  • Fetch data from a database to be used in subsequent steps
  • Execute a custom logic to transform data
  • Custom provider implementation

Custom Step Interface

const stepResult = await step.custom('custom-step', async () => {
  return {
    item_name: 'A product name',
    item_price: 100,
  };
}, {
    outputSchema: {
        type: 'object',
        properties: {
            item_name: { type: 'string' },
            item_price: { type: 'number' },
        },
        required: ['item_name', 'item_price'],
    }
});

Output Schema Definition

This JSON Schema definition is used to validate the output of the custom step. If the output does not match the schema, the workflow will fail. Novu Framework will infer the Typescript interface from the JSON Schema definition.

Return Value

The Custom Step function should return a valid serializable object. The return value will be persisted in the durable execution context.

Using the Custom Step Result

The result can only be used in the resolver of the step/providers/skip functions of subsequent steps.

workflow('hello-world-workflow', async ({ payload }) => {
    const task = await step.custom('fetch-db-data', async () => {
        const taskData = db.fetchTask(payload.task_id);
        return {
            task_id: taskData.id,
            task_title: taskData.title,
            complete: taskData.complete,
        };
    }, {
        outputSchema: {
            type: 'object',
            properties: {
                task_title: { type: 'string' },
                task_id: { type: 'string' },
                complete: { type: 'boolean' },
            },
            required: ['task_id', 'complete'],
        }
    });

    await step.email('send-email', () => {
        return {
            subject: `Task reminder for ${task.task_title}`,
            body: 'Task is not yet complete. Please complete the task.'
        }
    }, {
        // Only send the reminder E-mail if the task is not complete
        skip: () => !task.complete
    })
});