Tips > Building Workflows

Use Execute Sub-Workflow for Reusable Utility Workflows

Utility sub-workflows are shared building blocks that multiple parent workflows can call.

Utility sub-workflows are shared building blocks that multiple parent workflows can call. Instead of duplicating the same Slack notification logic in 10 workflows, create one "Send Formatted Slack Alert" sub-workflow and call it from all of them. When you need to change the alert format, you update one workflow instead of ten.

Real-world example: A "Send Formatted Slack Alert" utility sub-workflow used by error handlers, monitoring workflows, and business process notifications.

Sub-workflow: "Slack - Send Formatted Alert - v2"

[Execute Sub-Workflow Trigger] → [Code: Build Block Kit] → [Slack: Send Message]
```text
The trigger receives a standardized input schema:

```json
{
  "severity": "info | warning | error | critical",
  "title": "Brief alert title",
  "message": "Detailed description",
  "source_workflow": "Name of the calling workflow",
  "fields": [
    { "label": "Customer", "value": "jane@acme.com" },
    { "label": "Order ID", "value": "ORD-12345" }
  ],
  "channel_override": null
}
```text
Code node that builds the Slack Block Kit message:

```javascript
const input = $json;

const severityConfig = {
  info:     { color: '#36a64f', icon: '[INFO]' },
  warning:  { color: '#ff9900', icon: '[WARNING]' },
  error:    { color: '#ff0000', icon: '[ERROR]' },
  critical: { color: '#8b0000', icon: '[CRITICAL]' }
};

const config = severityConfig[input.severity] || severityConfig.info;

const fields = (input.fields || []).map(f => ({
  type: 'mrkdwn',
  text: `*${f.label}:*\n${f.value}`
}));

const blocks = [
  {
    type: 'header',
    text: { type: 'plain_text', text: `${config.icon} ${input.title}` }
  },
  {
    type: 'section',
    text: { type: 'mrkdwn', text: input.message }
  }
];

if (fields.length > 0) {
  blocks.push({ type: 'section', fields: fields });
}

blocks.push({
  type: 'context',
  elements: [{
    type: 'mrkdwn',
    text: `Source: ${input.source_workflow} | ${new Date().toISOString()}`
  }]
});

const channel = input.channel_override || process.env.SLACK_CHANNEL || '#alerts';

return [{ json: { channel, blocks, color: config.color } }];
```text
Calling the sub-workflow from any parent:

```json
// Execute Sub-Workflow node input mapping
{
  "severity": "error",
  "title": "Payment Processing Failed",
  "message": "Stripe returned a declined card error for order ORD-12345.",
  "source_workflow": "{{ $workflow.name }}",
  "fields": [
    { "label": "Customer", "value": "{{ $json.customer_email }}" },
    { "label": "Error", "value": "{{ $json.stripe_error }}" },
    { "label": "Amount", "value": "${{ $json.amount }}" }
  ]
}
```text
> **Tip: Build a Utility Library**
>
> Common utility sub-workflows to create: Slack alert, email with template, error logger, data validation, API authentication refresh, rate limiter. Tag them all with a "utility" tag for easy discovery.

Reusable sub-workflows enforce consistency and reduce the maintenance surface area of your workflow system.

**Related:** [Always Set an Error Workflow on Every Production Workflow](../error-handling-and-reliability/01-always-set-an-error-workflow-on-every-production-workflow.md) | [Use "Pin Data" to Freeze Node Output](../testing-and-debugging/01-use-pin-data-to-freeze-node-output.md)

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

191 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book a 20-minute call