Activepieces Workflow Automation Skill
Master Activepieces for self-hosted no-code automation with type-safe custom pieces, visual flow building, and enterprise-ready workflow orchestration. This skill covers flow design, piece development, triggers, connections management, and production deployment patterns.
When to Use This Skill
USE when:
-
Building business automations with type-safe custom components
-
Self-hosting is required for data sovereignty and compliance
-
Need modular, reusable automation pieces
-
Creating approval workflows with human-in-the-loop
-
Connecting APIs with visual flow builder
-
Teams need both no-code and code-first options
-
Require MIT-licensed open-source automation
-
Building internal tool automations
DON'T USE when:
-
Complex DAG-based data pipeline orchestration (use Airflow)
-
CI/CD pipelines tightly coupled with git (use GitHub Actions)
-
Need 400+ pre-built integrations immediately (use n8n)
-
Sub-second latency requirements (use direct API calls)
-
Simple single-trigger cron jobs (use systemd timers)
Prerequisites
Installation Options
Option 1: Docker Compose (Recommended)
docker-compose.yml
version: '3.8'
services: activepieces: image: activepieces/activepieces:latest restart: always ports: - "8080:80" environment: - AP_ENGINE_EXECUTABLE_PATH=dist/packages/engine/main.js - AP_ENVIRONMENT=prod - AP_FRONTEND_URL=http://localhost:8080 - AP_WEBHOOK_TIMEOUT_SECONDS=30 - AP_TRIGGER_DEFAULT_POLL_INTERVAL=5 - AP_POSTGRES_DATABASE=activepieces - AP_POSTGRES_HOST=postgres - AP_POSTGRES_PORT=5432 - AP_POSTGRES_USERNAME=activepieces - AP_POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - AP_ENCRYPTION_KEY=${AP_ENCRYPTION_KEY} - AP_JWT_SECRET=${AP_JWT_SECRET} - AP_QUEUE_MODE=MEMORY - AP_REDIS_HOST=redis - AP_REDIS_PORT=6379 - AP_SANDBOX_RUN_TIME_SECONDS=600 - AP_TELEMETRY_ENABLED=false depends_on: - postgres - redis volumes: - ./data:/root/.activepieces
postgres: image: postgres:15 restart: always environment: - POSTGRES_USER=activepieces - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=activepieces volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U activepieces"] interval: 10s timeout: 5s retries: 5
redis: image: redis:7 restart: always healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5
volumes: postgres_data:
Generate required secrets
export POSTGRES_PASSWORD=$(openssl rand -hex 32) export AP_ENCRYPTION_KEY=$(openssl rand -hex 16) export AP_JWT_SECRET=$(openssl rand -hex 32)
Start services
docker compose up -d
Access UI at http://localhost:8080
Option 2: Kubernetes with Helm
Add Activepieces Helm repo
helm repo add activepieces https://activepieces.github.io/activepieces helm repo update
Create namespace
kubectl create namespace activepieces
Create secrets
kubectl create secret generic activepieces-secrets
--namespace activepieces
--from-literal=encryption-key=$(openssl rand -hex 16)
--from-literal=jwt-secret=$(openssl rand -hex 32)
--from-literal=postgres-password=$(openssl rand -hex 32)
Install Activepieces
helm install activepieces activepieces/activepieces
--namespace activepieces
--set ingress.enabled=true
--set ingress.hosts[0].host=activepieces.example.com
Get initial setup URL
kubectl get ingress -n activepieces
Option 3: Local Development
Clone repository
git clone https://github.com/activepieces/activepieces.git cd activepieces
Install dependencies
npm install
Setup environment
cp .env.example .env
Start development server
npm run dev
Access at http://localhost:4200
Development Setup for Custom Pieces
Install Activepieces CLI
npm install -g @activepieces/cli
Create new piece project
ap create-piece my-custom-piece
Navigate to piece directory
cd pieces/my-custom-piece
Install dependencies
npm install
Build piece
npm run build
Test piece locally
npm run test
Core Capabilities
- Basic Flow Structure
// Flow definition structure interface Flow { id: string; projectId: string; folderId?: string; status: 'ENABLED' | 'DISABLED'; schedule?: { cronExpression: string; timezone: string; }; trigger: Trigger; steps: Step[]; }
// Example flow JSON const basicFlow = { "displayName": "New Customer Onboarding", "trigger": { "name": "webhook", "type": "WEBHOOK", "settings": { "inputUiInfo": {} }, "valid": true, "displayName": "Webhook Trigger" }, "steps": [ { "name": "validate_data", "type": "CODE", "settings": { "input": { "customer": "{{trigger.body.customer}}" }, "sourceCode": { "code": "export const code = async (inputs) => {\n const { customer } = inputs;\n \n if (!customer.email || !customer.name) {\n throw new Error('Missing required fields');\n }\n \n return {\n valid: true,\n customer: {\n ...customer,\n email: customer.email.toLowerCase().trim(),\n created_at: new Date().toISOString()\n }\n };\n};" } }, "displayName": "Validate Customer Data" }, { "name": "create_crm_contact", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-hubspot", "pieceVersion": "~0.5.0", "actionName": "create_contact", "input": { "email": "{{validate_data.customer.email}}", "firstName": "{{validate_data.customer.name.split(' ')[0]}}", "lastName": "{{validate_data.customer.name.split(' ').slice(1).join(' ')}}", "properties": { "source": "activepieces_onboarding", "created_via": "automation" } } }, "displayName": "Create HubSpot Contact" }, { "name": "send_welcome_email", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-sendgrid", "pieceVersion": "~0.3.0", "actionName": "send_email", "input": { "to": "{{validate_data.customer.email}}", "subject": "Welcome to Our Platform!", "html": "<h1>Welcome, {{validate_data.customer.name}}!</h1><p>We're excited to have you on board.</p>" } }, "displayName": "Send Welcome Email" }, { "name": "notify_team", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-slack", "pieceVersion": "~0.6.0", "actionName": "send_message", "input": { "channel": "#new-customers", "text": "New customer onboarded: {{validate_data.customer.name}} ({{validate_data.customer.email}})" } }, "displayName": "Notify Sales Team" } ] };
- Webhook Triggers
// Webhook trigger configuration const webhookFlow = { "displayName": "Payment Webhook Handler", "trigger": { "name": "webhook", "type": "WEBHOOK", "settings": { "inputUiInfo": { "customizedInputs": {} } }, "displayName": "Payment Webhook" }, "steps": [ { "name": "verify_signature", "type": "CODE", "settings": { "input": { "payload": "{{trigger.body}}", "signature": "{{trigger.headers['x-signature']}}", "secret": "{{connections.payment_webhook_secret}}" }, "sourceCode": { "code": ` import crypto from 'crypto';
export const code = async (inputs) => { const { payload, signature, secret } = inputs;
const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex');
if (signature !== expectedSignature) { throw new Error('Invalid webhook signature'); }
return { verified: true, event: payload.event_type, data: payload.data }; };` } }, "displayName": "Verify Webhook Signature" }, { "name": "route_by_event", "type": "BRANCH", "settings": { "conditions": [ { "name": "payment_completed", "expression": { "type": "EXPRESSION", "value": "{{verify_signature.event}} === 'payment.completed'" } }, { "name": "payment_failed", "expression": { "type": "EXPRESSION", "value": "{{verify_signature.event}} === 'payment.failed'" } }, { "name": "refund_initiated", "expression": { "type": "EXPRESSION", "value": "{{verify_signature.event}} === 'refund.initiated'" } } ] }, "displayName": "Route by Event Type" } ] };
- Scheduled Flows
// Scheduled flow with cron expression const scheduledFlow = { "displayName": "Daily Sales Report", "schedule": { "cronExpression": "0 9 * * 1-5", // 9 AM Mon-Fri "timezone": "America/New_York" }, "trigger": { "name": "schedule", "type": "SCHEDULE", "settings": {}, "displayName": "Daily Schedule" }, "steps": [ { "name": "get_date_range", "type": "CODE", "settings": { "sourceCode": { "code": ` export const code = async () => { const now = new Date(); const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1);
return {
start_date: yesterday.toISOString().split('T')[0],
end_date: now.toISOString().split('T')[0],
report_date: now.toISOString()
};
}; } }, "displayName": "Calculate Date Range" }, { "name": "fetch_sales_data", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-http", "pieceVersion": "~0.4.0", "actionName": "send_request", "input": { "method": "GET", "url": "{{connections.sales_api.base_url}}/api/v1/sales", "headers": { "Authorization": "Bearer {{connections.sales_api.api_key}}" }, "queryParams": { "start_date": "{{get_date_range.start_date}}", "end_date": "{{get_date_range.end_date}}" } } }, "displayName": "Fetch Sales Data" }, { "name": "generate_report", "type": "CODE", "settings": { "input": { "sales": "{{fetch_sales_data.body.data}}", "report_date": "{{get_date_range.report_date}}" }, "sourceCode": { "code":
export const code = async (inputs) => {
const { sales, report_date } = inputs;
const totalRevenue = sales.reduce((sum, sale) => sum + sale.amount, 0); const totalOrders = sales.length; const avgOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
const byCategory = sales.reduce((acc, sale) => { const cat = sale.category || 'Other'; acc[cat] = (acc[cat] || 0) + sale.amount; return acc; }, {});
return { report_date, summary: { total_revenue: totalRevenue.toFixed(2), total_orders: totalOrders, avg_order_value: avgOrderValue.toFixed(2) }, by_category: Object.entries(byCategory) .map(([category, amount]) => ({ category, amount })) .sort((a, b) => b.amount - a.amount), top_products: sales .sort((a, b) => b.amount - a.amount) .slice(0, 5) }; };` } }, "displayName": "Generate Report Summary" }, { "name": "send_report", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-gmail", "pieceVersion": "~0.5.0", "actionName": "send_email", "input": { "to": ["sales-team@example.com", "management@example.com"], "subject": "Daily Sales Report - {{get_date_range.start_date}}", "body": "<h1>Daily Sales Report</h1><h2>Summary</h2><ul><li>Total Revenue: ${{generate_report.summary.total_revenue}}</li><li>Total Orders: {{generate_report.summary.total_orders}}</li><li>Average Order Value: ${{generate_report.summary.avg_order_value}}</li></ul>" } }, "displayName": "Send Report Email" } ] };
- Branching and Conditional Logic
// Branch step with multiple conditions const branchingFlow = { "displayName": "Lead Qualification Flow", "trigger": { "name": "webhook", "type": "WEBHOOK", "settings": {}, "displayName": "New Lead" }, "steps": [ { "name": "enrich_lead", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-clearbit", "pieceVersion": "~0.2.0", "actionName": "enrich_company", "input": { "domain": "{{trigger.body.company_domain}}" } }, "displayName": "Enrich Lead Data" }, { "name": "calculate_score", "type": "CODE", "settings": { "input": { "lead": "{{trigger.body}}", "enriched": "{{enrich_lead}}" }, "sourceCode": { "code": ` export const code = async (inputs) => { const { lead, enriched } = inputs; let score = 0;
// Company size scoring if (enriched.metrics?.employees > 1000) score += 30; else if (enriched.metrics?.employees > 100) score += 20; else if (enriched.metrics?.employees > 10) score += 10;
// Industry scoring const highValueIndustries = ['technology', 'finance', 'healthcare']; if (highValueIndustries.includes(enriched.category?.industry?.toLowerCase())) { score += 25; }
// Title scoring const seniorTitles = ['ceo', 'cto', 'vp', 'director', 'head']; if (seniorTitles.some(t => lead.title?.toLowerCase().includes(t))) { score += 20; }
// Budget scoring if (lead.budget > 100000) score += 25; else if (lead.budget > 50000) score += 15; else if (lead.budget > 10000) score += 10;
return { score, tier: score >= 70 ? 'hot' : score >= 40 ? 'warm' : 'cold', lead: { ...lead, enriched } }; };` } }, "displayName": "Calculate Lead Score" }, { "name": "route_by_tier", "type": "BRANCH", "settings": { "conditions": [ { "name": "hot_lead", "expression": { "type": "EXPRESSION", "value": "{{calculate_score.tier}} === 'hot'" }, "steps": [ { "name": "notify_sales_urgent", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-slack", "actionName": "send_message", "input": { "channel": "#hot-leads", "text": "HOT LEAD: {{calculate_score.lead.name}} from {{calculate_score.lead.company}} (Score: {{calculate_score.score}})" } } }, { "name": "create_salesforce_lead", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-salesforce", "actionName": "create_record", "input": { "objectName": "Lead", "fields": { "FirstName": "{{calculate_score.lead.first_name}}", "LastName": "{{calculate_score.lead.last_name}}", "Company": "{{calculate_score.lead.company}}", "Email": "{{calculate_score.lead.email}}", "LeadSource": "Website", "Rating": "Hot" } } } }, { "name": "schedule_demo", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-calendly", "actionName": "create_scheduling_link", "input": { "event_type": "sales-demo", "invitee_email": "{{calculate_score.lead.email}}" } } } ] }, { "name": "warm_lead", "expression": { "type": "EXPRESSION", "value": "{{calculate_score.tier}} === 'warm'" }, "steps": [ { "name": "add_to_nurture", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-mailchimp", "actionName": "add_subscriber", "input": { "list_id": "{{connections.mailchimp.nurture_list_id}}", "email": "{{calculate_score.lead.email}}", "tags": ["warm-lead", "nurture-sequence"] } } } ] }, { "name": "cold_lead", "expression": { "type": "EXPRESSION", "value": "{{calculate_score.tier}} === 'cold'" }, "steps": [ { "name": "add_to_drip", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-mailchimp", "actionName": "add_subscriber", "input": { "list_id": "{{connections.mailchimp.general_list_id}}", "email": "{{calculate_score.lead.email}}", "tags": ["cold-lead"] } } } ] } ] }, "displayName": "Route by Lead Tier" } ] };
- Loop and Iteration
// Loop over items const loopFlow = { "displayName": "Batch Order Processing", "trigger": { "name": "webhook", "type": "WEBHOOK", "settings": {}, "displayName": "Batch Orders" }, "steps": [ { "name": "process_orders", "type": "LOOP_ON_ITEMS", "settings": { "items": "{{trigger.body.orders}}", "steps": [ { "name": "validate_order", "type": "CODE", "settings": { "input": { "order": "{{loop.item}}" }, "sourceCode": { "code": ` export const code = async (inputs) => { const { order } = inputs;
const errors = [];
if (!order.customer_id) errors.push('Missing customer_id'); if (!order.items || order.items.length === 0) errors.push('No items in order'); if (!order.total || order.total <= 0) errors.push('Invalid total');
return {
valid: errors.length === 0,
errors,
order: errors.length === 0 ? order : null
};
}; } }, "displayName": "Validate Order" }, { "name": "check_inventory", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-http", "actionName": "send_request", "input": { "method": "POST", "url": "{{connections.inventory_api.base_url}}/api/check-availability", "headers": { "Authorization": "Bearer {{connections.inventory_api.api_key}}" }, "body": { "items": "{{validate_order.order.items}}" } } }, "displayName": "Check Inventory" }, { "name": "process_payment", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-stripe", "actionName": "create_charge", "input": { "amount": "{{validate_order.order.total}}", "currency": "usd", "customer": "{{validate_order.order.stripe_customer_id}}", "description": "Order {{validate_order.order.id}}" } }, "displayName": "Process Payment" }, { "name": "create_shipment", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-shippo", "actionName": "create_shipment", "input": { "address_from": "{{connections.shippo.warehouse_address}}", "address_to": "{{validate_order.order.shipping_address}}", "parcels": "{{validate_order.order.items}}" } }, "displayName": "Create Shipment" } ] }, "displayName": "Process Each Order" }, { "name": "summarize_results", "type": "CODE", "settings": { "input": { "results": "{{process_orders}}" }, "sourceCode": { "code":
export const code = async (inputs) => {
const { results } = inputs;
const successful = results.filter(r => r.success); const failed = results.filter(r => !r.success);
return { total_processed: results.length, successful: successful.length, failed: failed.length, failed_orders: failed.map(f => ({ order_id: f.order_id, error: f.error })), total_revenue: successful.reduce((sum, r) => sum + r.amount, 0) }; };` } }, "displayName": "Summarize Results" } ] };
- Custom Piece Development
// pieces/my-custom-api/src/index.ts import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; import { createCustomResource } from './lib/actions/create-resource'; import { getCustomResource } from './lib/actions/get-resource'; import { listCustomResources } from './lib/actions/list-resources'; import { resourceCreatedTrigger } from './lib/triggers/resource-created';
const authDescription = ` To get your API key:
- Go to Settings > API Keys in your dashboard
- Click "Create New Key"
- Copy the generated key `;
export const customApiAuth = PieceAuth.SecretText({
displayName: 'API Key',
required: true,
description: authDescription,
validate: async ({ auth }) => {
// Validate the API key
const response = await fetch('https://api.example.com/validate', {
headers: { Authorization: Bearer ${auth} }
});
if (!response.ok) {
return {
valid: false,
error: 'Invalid API key'
};
}
return { valid: true };
} });
export const myCustomApi = createPiece({ displayName: 'My Custom API', auth: customApiAuth, minimumSupportedRelease: '0.20.0', logoUrl: 'https://example.com/logo.png', authors: ['your-name'], description: 'Connect to My Custom API for resource management', actions: [createCustomResource, getCustomResource, listCustomResources], triggers: [resourceCreatedTrigger] });
// pieces/my-custom-api/src/lib/actions/create-resource.ts import { createAction, Property } from '@activepieces/pieces-framework'; import { customApiAuth } from '../../index';
export const createCustomResource = createAction({ name: 'create_resource', displayName: 'Create Resource', description: 'Create a new resource in My Custom API', auth: customApiAuth, props: { name: Property.ShortText({ displayName: 'Resource Name', required: true, description: 'The name of the resource' }), type: Property.Dropdown({ displayName: 'Resource Type', required: true, refreshers: [], options: async () => ({ options: [ { label: 'Document', value: 'document' }, { label: 'Image', value: 'image' }, { label: 'Video', value: 'video' } ] }) }), tags: Property.Array({ displayName: 'Tags', required: false, description: 'Tags to categorize the resource' }), metadata: Property.Object({ displayName: 'Metadata', required: false, description: 'Additional metadata for the resource' }), content: Property.LongText({ displayName: 'Content', required: true, description: 'The content of the resource' }) }, async run(context) { const { auth, propsValue } = context;
const response = await fetch('https://api.example.com/resources', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${auth}`
},
body: JSON.stringify({
name: propsValue.name,
type: propsValue.type,
tags: propsValue.tags || [],
metadata: propsValue.metadata || {},
content: propsValue.content
})
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to create resource: ${error}`);
}
const result = await response.json();
return {
id: result.id,
name: result.name,
type: result.type,
created_at: result.created_at,
url: result.url
};
} });
// pieces/my-custom-api/src/lib/triggers/resource-created.ts import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework'; import { customApiAuth } from '../../index';
export const resourceCreatedTrigger = createTrigger({ name: 'resource_created', displayName: 'Resource Created', description: 'Triggers when a new resource is created', auth: customApiAuth, props: { resourceType: Property.Dropdown({ displayName: 'Resource Type', required: false, refreshers: [], options: async () => ({ options: [ { label: 'All Types', value: 'all' }, { label: 'Document', value: 'document' }, { label: 'Image', value: 'image' }, { label: 'Video', value: 'video' } ] }) }) }, type: TriggerStrategy.WEBHOOK, async onEnable(context) { const { auth, propsValue, webhookUrl, store } = context;
// Register webhook with the external API
const response = await fetch('https://api.example.com/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${auth}`
},
body: JSON.stringify({
url: webhookUrl,
events: ['resource.created'],
filter: propsValue.resourceType !== 'all'
? { type: propsValue.resourceType }
: undefined
})
});
const webhook = await response.json();
// Store webhook ID for cleanup
await store.put('webhookId', webhook.id);
}, async onDisable(context) { const { auth, store } = context;
const webhookId = await store.get('webhookId');
if (webhookId) {
await fetch(`https://api.example.com/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${auth}`
}
});
}
}, async run(context) { const { payload } = context;
return [
{
id: payload.body.data.id,
name: payload.body.data.name,
type: payload.body.data.type,
created_at: payload.body.data.created_at,
created_by: payload.body.data.created_by
}
];
}, sampleData: { id: 'res_123456', name: 'Sample Resource', type: 'document', created_at: '2026-01-17T10:00:00Z', created_by: 'user_789' } });
- Approval Flows
// Human approval workflow const approvalFlow = { "displayName": "Expense Approval Workflow", "trigger": { "name": "webhook", "type": "WEBHOOK", "settings": {}, "displayName": "New Expense Request" }, "steps": [ { "name": "validate_expense", "type": "CODE", "settings": { "input": { "expense": "{{trigger.body}}" }, "sourceCode": { "code": ` export const code = async (inputs) => { const { expense } = inputs;
return { ...expense, requires_approval: expense.amount > 500, approval_level: expense.amount > 5000 ? 'executive' : expense.amount > 500 ? 'manager' : 'auto', formatted_amount: new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(expense.amount) }; };` } }, "displayName": "Validate Expense" }, { "name": "check_approval_required", "type": "BRANCH", "settings": { "conditions": [ { "name": "needs_approval", "expression": { "type": "EXPRESSION", "value": "{{validate_expense.requires_approval}} === true" }, "steps": [ { "name": "send_approval_request", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-slack", "actionName": "send_message", "input": { "channel": "#expense-approvals", "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "Expense Approval Required" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "Submitted by:\n{{validate_expense.employee_name}}" }, { "type": "mrkdwn", "text": "Amount:\n{{validate_expense.formatted_amount}}" }, { "type": "mrkdwn", "text": "Category:\n{{validate_expense.category}}" }, { "type": "mrkdwn", "text": "Description:\n{{validate_expense.description}}" } ] }, { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "Approve" }, "style": "primary", "action_id": "approve_expense", "value": "{{validate_expense.id}}" }, { "type": "button", "text": { "type": "plain_text", "text": "Reject" }, "style": "danger", "action_id": "reject_expense", "value": "{{validate_expense.id}}" } ] } ] } } }, { "name": "wait_for_approval", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-approval", "actionName": "wait_for_approval", "input": { "timeout_hours": 48, "approval_link_text": "Click to review expense" } } }, { "name": "process_decision", "type": "BRANCH", "settings": { "conditions": [ { "name": "approved", "expression": { "type": "EXPRESSION", "value": "{{wait_for_approval.status}} === 'APPROVED'" }, "steps": [ { "name": "process_reimbursement", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-http", "actionName": "send_request", "input": { "method": "POST", "url": "{{connections.finance_api.base_url}}/reimbursements", "body": { "expense_id": "{{validate_expense.id}}", "amount": "{{validate_expense.amount}}", "employee_id": "{{validate_expense.employee_id}}", "approved_by": "{{wait_for_approval.approved_by}}" } } } } ] }, { "name": "rejected", "expression": { "type": "EXPRESSION", "value": "{{wait_for_approval.status}} === 'REJECTED'" }, "steps": [ { "name": "notify_rejection", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-gmail", "actionName": "send_email", "input": { "to": "{{validate_expense.employee_email}}", "subject": "Expense Request Rejected", "body": "Your expense request for {{validate_expense.formatted_amount}} has been rejected." } } } ] } ] } } ] }, { "name": "auto_approve", "expression": { "type": "EXPRESSION", "value": "{{validate_expense.requires_approval}} === false" }, "steps": [ { "name": "auto_process", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-http", "actionName": "send_request", "input": { "method": "POST", "url": "{{connections.finance_api.base_url}}/reimbursements", "body": { "expense_id": "{{validate_expense.id}}", "amount": "{{validate_expense.amount}}", "auto_approved": true } } } } ] } ] }, "displayName": "Check Approval Required" } ] };
- Error Handling and Retry Logic
// Error handling patterns const errorHandlingFlow = { "displayName": "Resilient API Integration", "trigger": { "name": "schedule", "type": "SCHEDULE", "settings": { "cronExpression": "*/15 * * * *" }, "displayName": "Every 15 Minutes" }, "steps": [ { "name": "fetch_with_retry", "type": "CODE", "settings": { "input": { "api_url": "{{connections.external_api.base_url}}/data", "api_key": "{{connections.external_api.api_key}}" }, "sourceCode": { "code": ` export const code = async (inputs) => { const { api_url, api_key } = inputs; const maxRetries = 3; const baseDelay = 1000;
for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch(api_url, { headers: { Authorization: `Bearer ${api_key}` }, signal: AbortSignal.timeout(30000) // 30 second timeout });
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
}
if (!response.ok) {
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
}
const data = await response.json();
return {
success: true,
data,
attempts: attempt
};
} catch (error) {
if (attempt === maxRetries) {
return {
success: false,
error: error.message,
attempts: attempt
};
}
// Exponential backoff
const delay = baseDelay * Math.pow(2, attempt - 1);
await new Promise(r => setTimeout(r, delay));
}
}
}; } }, "displayName": "Fetch with Retry" }, { "name": "handle_result", "type": "BRANCH", "settings": { "conditions": [ { "name": "success", "expression": { "type": "EXPRESSION", "value": "{{fetch_with_retry.success}} === true" }, "steps": [ { "name": "process_data", "type": "CODE", "settings": { "input": { "data": "{{fetch_with_retry.data}}" }, "sourceCode": { "code":
export const code = async (inputs) => {
const { data } = inputs;
// Process successful data
return {
processed: true,
count: data.length,
timestamp: new Date().toISOString()
};
};`
}
}
}
]
},
{
"name": "failure",
"expression": {
"type": "EXPRESSION",
"value": "{{fetch_with_retry.success}} === false"
},
"steps": [
{
"name": "alert_failure",
"type": "PIECE",
"settings": {
"pieceName": "@activepieces/piece-slack",
"actionName": "send_message",
"input": {
"channel": "#alerts",
"text": "API Integration Failed after {{fetch_with_retry.attempts}} attempts. Error: {{fetch_with_retry.error}}"
}
}
},
{
"name": "log_failure",
"type": "PIECE",
"settings": {
"pieceName": "@activepieces/piece-http",
"actionName": "send_request",
"input": {
"method": "POST",
"url": "{{connections.logging_api.base_url}}/errors",
"body": {
"flow": "Resilient API Integration",
"error": "{{fetch_with_retry.error}}",
"attempts": "{{fetch_with_retry.attempts}}",
"timestamp": "{{now}}"
}
}
}
}
]
}
]
},
"displayName": "Handle Result"
}
]
};
Integration Examples
Integration with Notion and Slack
const notionSlackSync = { "displayName": "Notion to Slack Sync", "trigger": { "name": "notion_database_updated", "type": "PIECE_TRIGGER", "settings": { "pieceName": "@activepieces/piece-notion", "triggerName": "database_item_updated", "input": { "database_id": "{{connections.notion.task_database_id}}" } }, "displayName": "Task Updated in Notion" }, "steps": [ { "name": "check_status_change", "type": "CODE", "settings": { "input": { "item": "{{trigger}}" }, "sourceCode": { "code": ` export const code = async (inputs) => { const { item } = inputs;
const statusProperty = item.properties?.Status; const currentStatus = statusProperty?.select?.name;
return { task_name: item.properties?.Name?.title?.[0]?.plain_text || 'Unnamed Task', status: currentStatus, assignee: item.properties?.Assignee?.people?.[0]?.name || 'Unassigned', due_date: item.properties?.['Due Date']?.date?.start, url: item.url, is_completed: currentStatus === 'Done', is_blocked: currentStatus === 'Blocked' }; };` } }, "displayName": "Extract Task Details" }, { "name": "route_notification", "type": "BRANCH", "settings": { "conditions": [ { "name": "task_completed", "expression": { "type": "EXPRESSION", "value": "{{check_status_change.is_completed}} === true" }, "steps": [ { "name": "celebrate_completion", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-slack", "actionName": "send_message", "input": { "channel": "#team-wins", "text": "Task completed! {{check_status_change.task_name}} by {{check_status_change.assignee}}" } } } ] }, { "name": "task_blocked", "expression": { "type": "EXPRESSION", "value": "{{check_status_change.is_blocked}} === true" }, "steps": [ { "name": "alert_blockers", "type": "PIECE", "settings": { "pieceName": "@activepieces/piece-slack", "actionName": "send_message", "input": { "channel": "#blockers", "text": "Task blocked: {{check_status_change.task_name}} - Assigned to {{check_status_change.assignee}}" } } } ] } ] }, "displayName": "Route Notification" } ] };
Best Practices
- Flow Organization
flows/ ├── onboarding/ │ ├── customer-onboarding.json │ └── employee-onboarding.json ├── integrations/ │ ├── crm-sync.json │ ├── inventory-sync.json │ └── payment-webhooks.json ├── notifications/ │ ├── alert-routing.json │ └── daily-reports.json └── utilities/ ├── data-validation.json └── error-handler.json
- Error Handling Best Practices
// Always wrap external calls in try-catch try { const result = await externalApiCall(); return { success: true, data: result }; } catch (error) { // Return structured error for downstream handling return { success: false, error: error.message, retryable: !error.message.includes('PERMANENT'), timestamp: new Date().toISOString() }; }
- Security Best Practices
Environment variables
AP_ENCRYPTION_KEY: "32-character-secure-key" AP_JWT_SECRET: "secure-jwt-secret" AP_WEBHOOK_TIMEOUT_SECONDS: 30
Use connections for all credentials
Never hardcode API keys in flow definitions
- Performance Tips
// Batch operations when possible const BATCH_SIZE = 50; const items = inputs.items;
for (let i = 0; i < items.length; i += BATCH_SIZE) { const batch = items.slice(i, i + BATCH_SIZE); await processBatch(batch); }
// Use appropriate timeouts const controller = new AbortController(); setTimeout(() => controller.abort(), 30000);
Troubleshooting
Common Issues
Issue: Flow not triggering
Check webhook URL is accessible
curl -X POST https://activepieces.example.com/api/v1/webhooks/your-flow-id
-H "Content-Type: application/json"
-d '{"test": true}'
Verify flow is enabled
Check logs in Activepieces UI
Issue: Connection authentication failing
Test credentials manually
curl -X GET "https://api.service.com/test"
-H "Authorization: Bearer YOUR_TOKEN"
Re-authenticate connection in UI
Issue: Code step timeout
Increase sandbox timeout
AP_SANDBOX_RUN_TIME_SECONDS: 600
Debugging Tips
// Add logging in code steps console.log('Input:', JSON.stringify(inputs, null, 2)); console.log('Processing step...');
// Return debug info return { result: processedData, debug: { input_count: inputs.items.length, processing_time_ms: Date.now() - startTime } };
Version History
Version Date Changes
1.0.0 2026-01-17 Initial release with comprehensive flow patterns
Resources
-
Activepieces Documentation
-
Pieces Directory
-
GitHub Repository
-
Community Discord
-
Custom Piece Development
This skill provides production-ready patterns for Activepieces workflow automation, tested across enterprise integration scenarios.