activepieces
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseActivepieces Workflow Automation Skill
Activepieces 工作流自动化技能
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.
掌握Activepieces,实现支持类型安全自定义组件、可视化流程构建和企业级工作流编排的自托管无代码自动化。本技能涵盖流程设计、组件开发、触发器、连接管理以及生产部署模式。
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
- 使用类型安全自定义组件构建业务自动化
- 出于数据主权和合规要求需要自托管
- 需要模块化、可复用的自动化组件
- 创建包含人工审核环节的审批工作流
- 通过可视化流程构建器连接API
- 团队同时需要无代码和代码优先的解决方案
- 需要MIT许可的开源自动化工具
- 构建内部工具自动化
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)
- 复杂的基于DAG的数据管道编排(请使用Airflow)
- 与Git紧密耦合的CI/CD流水线(请使用GitHub Actions)
- 需要立即使用400+预构建集成(请使用n8n)
- 亚秒级延迟要求(请使用直接API调用)
- 简单的单触发器定时任务(请使用systemd timers)
Prerequisites
前提条件
Installation Options
安装选项
Option 1: Docker Compose (Recommended)
yaml
undefined选项1:Docker Compose(推荐)
yaml
undefineddocker-compose.yml
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:
```bashversion: '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:
```bashGenerate 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)
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
docker compose up -d
Access UI at http://localhost:8080
**Option 2: Kubernetes with Helm**
```bash
**选项2:使用Helm在Kubernetes部署**
```bashAdd Activepieces Helm repo
添加Activepieces Helm仓库
helm repo add activepieces https://activepieces.github.io/activepieces
helm repo update
helm repo add activepieces https://activepieces.github.io/activepieces
helm repo update
Create namespace
创建命名空间
kubectl create namespace activepieces
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)
--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)
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)
--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
安装Activepieces
helm install activepieces activepieces/activepieces
--namespace activepieces
--set ingress.enabled=true
--set ingress.hosts[0].host=activepieces.example.com
--namespace activepieces
--set ingress.enabled=true
--set ingress.hosts[0].host=activepieces.example.com
helm install activepieces activepieces/activepieces
--namespace activepieces
--set ingress.enabled=true
--set ingress.hosts[0].host=activepieces.example.com
--namespace activepieces
--set ingress.enabled=true
--set ingress.hosts[0].host=activepieces.example.com
Get initial setup URL
获取初始设置URL
kubectl get ingress -n activepieces
**Option 3: Local Development**
```bashkubectl get ingress -n activepieces
**选项3:本地开发**
```bashClone repository
克隆仓库
git clone https://github.com/activepieces/activepieces.git
cd activepieces
git clone https://github.com/activepieces/activepieces.git
cd activepieces
Install dependencies
安装依赖
npm install
npm install
Setup environment
设置环境变量
cp .env.example .env
cp .env.example .env
Start development server
启动开发服务器
npm run dev
npm run dev
Access at http://localhost:4200
undefinedundefinedDevelopment Setup for Custom Pieces
自定义组件开发环境设置
bash
undefinedbash
undefinedInstall Activepieces CLI
安装Activepieces CLI
npm install -g @activepieces/cli
npm install -g @activepieces/cli
Create new piece project
创建新组件项目
ap create-piece my-custom-piece
ap create-piece my-custom-piece
Navigate to piece directory
进入组件目录
cd pieces/my-custom-piece
cd pieces/my-custom-piece
Install dependencies
安装依赖
npm install
npm install
Build piece
构建组件
npm run build
npm run build
Test piece locally
本地测试组件
npm run test
undefinednpm run test
undefinedCore Capabilities
核心能力
1. Basic Flow Structure
1. 基础流程结构
typescript
// 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"
}
]
};typescript
// 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"
}
]
};2. Webhook Triggers
2. Webhook触发器
typescript
// 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"
}
]
};typescript
// 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"
}
]
};3. Scheduled Flows
3. 定时流程
typescript
// 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"
}
]
};typescript
// 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"
}
]
};4. Branching and Conditional Logic
4. 分支与条件逻辑
typescript
// 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"
}
]
};typescript
// 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"
}
]
};5. Loop and Iteration
5. 循环与迭代
typescript
// 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"
}
]
};typescript
// 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"
}
]
};6. Custom Piece Development
6. 自定义组件开发
typescript
// 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:
1. Go to Settings > API Keys in your dashboard
2. Click "Create New Key"
3. 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]
});typescript
// 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
};
}
});typescript
// 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'
}
});typescript
// 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:
1. Go to Settings > API Keys in your dashboard
2. Click "Create New Key"
3. 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]
});typescript
// 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
};
}
});typescript
// 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'
}
});7. Approval Flows
7. 审批流程
typescript
// 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"
}
]
};typescript
// 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"
}
]
};8. Error Handling and Retry Logic
8. 错误处理与重试逻辑
typescript
// 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"
}
]
};typescript
// 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
Notion与Slack集成
typescript
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"
}
]
};typescript
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
最佳实践
1. Flow Organization
1. 流程组织
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.jsonflows/
├── 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.json2. Error Handling Best Practices
2. 错误处理最佳实践
typescript
// 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()
};
}typescript
// 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()
};
}3. Security Best Practices
3. 安全最佳实践
yaml
undefinedyaml
undefinedEnvironment variables
Environment variables
AP_ENCRYPTION_KEY: "32-character-secure-key"
AP_JWT_SECRET: "secure-jwt-secret"
AP_WEBHOOK_TIMEOUT_SECONDS: 30
AP_ENCRYPTION_KEY: "32-character-secure-key"
AP_JWT_SECRET: "secure-jwt-secret"
AP_WEBHOOK_TIMEOUT_SECONDS: 30
Use connections for all credentials
Use connections for all credentials
Never hardcode API keys in flow definitions
Never hardcode API keys in flow definitions
undefinedundefined4. Performance Tips
4. 性能优化技巧
typescript
// 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);typescript
// 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
bash
undefined问题:流程未触发
bash
undefinedCheck webhook URL is accessible
检查webhook URL是否可访问
curl -X POST https://activepieces.example.com/api/v1/webhooks/your-flow-id
-H "Content-Type: application/json"
-d '{"test": true}'
-H "Content-Type: application/json"
-d '{"test": true}'
curl -X POST https://activepieces.example.com/api/v1/webhooks/your-flow-id
-H "Content-Type: application/json"
-d '{"test": true}'
-H "Content-Type: application/json"
-d '{"test": true}'
Verify flow is enabled
验证流程是否已启用
Check logs in Activepieces UI
在Activepieces UI中查看日志
**Issue: Connection authentication failing**
```bash
**问题:连接认证失败**
```bash
#手动测试凭证
curl -X GET "https://api.service.com/test" \
-H "Authorization: Bearer YOUR_TOKEN"Test credentials manually
在UI中重新认证连接
curl -X GET "https://api.service.com/test"
-H "Authorization: Bearer YOUR_TOKEN"
-H "Authorization: Bearer YOUR_TOKEN"
**问题:代码步骤超时**
```yamlRe-authenticate connection in UI
增加沙箱超时时间
**Issue: Code step timeout**
```yamlAP_SANDBOX_RUN_TIME_SECONDS: 600
undefinedIncrease sandbox timeout
调试技巧
AP_SANDBOX_RUN_TIME_SECONDS: 600
undefinedtypescript
// 在代码步骤中添加日志
console.log('Input:', JSON.stringify(inputs, null, 2));
console.log('Processing step...');
// 返回调试信息
return {
result: processedData,
debug: {
input_count: inputs.items.length,
processing_time_ms: Date.now() - startTime
}
};Debugging Tips
版本历史
typescript
// 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
}
};| 版本 | 日期 | 变更 |
|---|---|---|
| 1.0.0 | 2026-01-17 | 初始版本,包含全面的流程模式 |
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.
—