Activepieces is an open-source, MIT-licensed workflow automation platform that has gained rapid adoption among Arabic-speaking teams and MENA enterprises. Unlike many automation tools that charge per task or per workflow execution, Activepieces can be fully self-hosted at zero marginal cost. And unlike n8n — which dominates Arabic tutorial content — Activepieces is TypeScript-first from the ground up, meaning every integration (called a piece) is built and type-checked with the same TypeScript you already write.
In this tutorial, you will:
- Self-host Activepieces with Docker Compose in under 10 minutes
- Build your first flow connecting Google Sheets to WhatsApp
- Create a custom TypeScript piece from scratch using the
@activepieces/pieces-frameworkSDK - Deploy a real-world MENA piece: a ZATCA filing deadline calculator that sends automatic WhatsApp reminders
Prerequisites
Before starting, ensure you have:
- Docker and Docker Compose installed
- Node.js 20+ and npm
- A server or local machine with at least 2 GB RAM
- A WhatsApp Business account or access to the Meta WhatsApp Cloud API
- Basic TypeScript knowledge (async/await, interfaces, generics)
What is Activepieces?
Activepieces is a visual workflow automation platform. Each automation is called a flow. A flow has:
- One trigger — what starts the flow (a webhook, a cron schedule, or an event from an app)
- One or more actions — what happens next (send a message, update a database, call an API)
Each trigger or action is a piece — Activepieces' term for an integration module. There are over 400 built-in pieces covering Google Workspace, Slack, WhatsApp, Notion, PostgreSQL, and more.
Why Activepieces for MENA developers?
| Feature | Activepieces | n8n | Zapier |
|---|---|---|---|
| License | MIT (free forever) | Fair-code (restricted) | SaaS only |
| Self-hostable | Yes | Yes | No |
| Piece SDK language | TypeScript | JavaScript/TS | No SDK |
| Per-task pricing | None | None | Yes |
| MENA Arabic UI | Yes | No | No |
Activepieces is a practical choice when you need full data sovereignty (no client data leaving your servers), a TypeScript-native extension model, and freedom from per-task billing.
Step 1: Self-Hosting Activepieces with Docker
Create a deployment directory:
mkdir activepieces-deploy
cd activepieces-deployCreate a docker-compose.yml file:
version: "3"
services:
activepieces:
image: activepieces/activepieces:latest
ports:
- "8080:80"
depends_on:
- postgres
- redis
environment:
- AP_DB_TYPE=POSTGRES
- AP_POSTGRES_DATABASE=activepieces
- AP_POSTGRES_HOST=postgres
- AP_POSTGRES_PORT=5432
- AP_POSTGRES_USERNAME=activepieces
- AP_POSTGRES_PASSWORD=strong_password_here
- AP_REDIS_URL=redis://redis:6379
- AP_ENCRYPTION_KEY=your_32_char_hex_key_here
- AP_JWT_SECRET=your_jwt_secret_here
- AP_FRONTEND_URL=http://localhost:8080
- AP_SIGN_UP_ENABLED=true
- AP_TELEMETRY_ENABLED=false
volumes:
- activepieces_data:/root/.activepieces
postgres:
image: postgres:15
environment:
- POSTGRES_DB=activepieces
- POSTGRES_USER=activepieces
- POSTGRES_PASSWORD=strong_password_here
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
- redis_data:/data
volumes:
activepieces_data:
postgres_data:
redis_data:Generate a secure 32-character encryption key:
openssl rand -hex 16Replace your_32_char_hex_key_here with the output and set a strong AP_JWT_SECRET. Then start the stack:
docker compose up -dAfter about 60 seconds, open http://localhost:8080. Create your admin account — this is your private Activepieces instance.
Step 2: Your First Flow — WhatsApp Notifications from Google Sheets
This flow fires when a new row is added to a Google Sheet (for example, a sales enquiry form response) and sends a WhatsApp message to your team.
In the Activepieces dashboard:
- Click New Flow, then name it "New Enquiry Notification"
- Click Trigger → search for Google Sheets → select New Row Added
- Connect your Google account, then select the target spreadsheet and sheet
- Click the + button to add an action
- Search for WhatsApp Business Cloud → select Send Text Message
- In the message body field, click the data icon and map the row columns — for example: "New enquiry from [Name] — Phone: [Phone]"
- Click Test Step on each step to confirm the connections work
- Toggle the flow to Active
Your flow is now live. No code written, no server management beyond the Docker stack you already deployed.
Step 3: Building a Custom TypeScript Piece
Built-in pieces cover most integrations, but custom pieces let you wrap any regional or business-specific API. This is where Activepieces' TypeScript-first SDK shines.
Setting Up the Development Environment
Clone the Activepieces monorepo:
git clone https://github.com/activepieces/activepieces.git
cd activepieces
npm installScaffold a new piece with the CLI:
npm run create-pieceEnter zatca-reminder as the piece name. The CLI generates this structure:
packages/pieces/custom/zatca-reminder/
├── src/
│ ├── index.ts
│ └── lib/
│ ├── actions/
│ │ └── get-next-deadline.ts
│ └── triggers/
├── package.json
└── tsconfig.json
The Piece Entry Point
Open src/index.ts. The scaffolded file looks like this:
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { getNextDeadlineAction } from './lib/actions/get-next-deadline';
export const zatcaReminder = createPiece({
displayName: 'ZATCA Reminder',
auth: PieceAuth.None(),
minimumSupportedRelease: '0.20.0',
logoUrl: 'https://your-cdn.com/zatca-logo.png',
authors: ['your-name'],
actions: [getNextDeadlineAction],
triggers: [],
});No authentication is needed here because this piece performs local date calculations — it does not call an external API. For pieces that do call external APIs, you would use PieceAuth.SecretText() or PieceAuth.CustomAuth().
Building the Action: ZATCA Filing Deadline Calculator
Replace src/lib/actions/get-next-deadline.ts with:
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
export const getNextDeadlineAction = createAction({
name: 'get_next_deadline',
displayName: 'Get Next ZATCA Filing Deadline',
description:
'Calculate the next VAT or e-invoice submission deadline for Saudi businesses registered with ZATCA.',
props: {
filingPeriod: Property.StaticDropdown({
displayName: 'Filing Period',
description: 'Is this company a monthly or quarterly VAT filer?',
required: true,
options: {
options: [
{ label: 'Monthly (شهري)', value: 'monthly' },
{ label: 'Quarterly (ربع سنوي)', value: 'quarterly' },
],
},
}),
referenceDate: Property.ShortText({
displayName: 'Reference Date (YYYY-MM-DD)',
description: 'Calculation base date. Leave blank to use today.',
required: false,
}),
},
async run(context) {
const { filingPeriod, referenceDate } = context.propsValue;
const base = referenceDate ? new Date(referenceDate) : new Date();
const year = base.getFullYear();
const month = base.getMonth(); // 0-indexed
let deadlineDate: Date;
if (filingPeriod === 'monthly') {
// Monthly filers: submit by the last day of the following month
deadlineDate = new Date(year, month + 2, 0);
} else {
// Quarterly filers: Q1 (Jan-Mar) due Apr 30, Q2 (Apr-Jun) due Jul 31, etc.
const quarter = Math.floor(month / 3);
const deadlineMonth = (quarter + 1) * 3;
deadlineDate = new Date(year, deadlineMonth + 1, 0);
}
const formatted = deadlineDate.toISOString().split('T')[0];
const msPerDay = 1000 * 60 * 60 * 24;
const daysRemaining = Math.ceil(
(deadlineDate.getTime() - base.getTime()) / msPerDay
);
const arabicMessage =
filingPeriod === 'monthly'
? `تذكير ZATCA: موعد الإقرار الشهري ${formatted} — بعد ${daysRemaining} يوم`
: `تذكير ZATCA: موعد الإقرار الفصلي ${formatted} — بعد ${daysRemaining} يوم`;
return {
deadlineDate: formatted,
daysRemaining,
arabicMessage,
isUrgent: daysRemaining <= 7,
};
},
});What this action returns:
deadlineDate— the ISO date of the next filing deadlinedaysRemaining— integer count of days from the reference datearabicMessage— a ready-to-send Arabic WhatsApp message stringisUrgent— boolean flag when the deadline is within 7 days
Step 4: Testing Your Custom Piece
Build the piece:
npm run build -- --filter=@activepieces/piece-zatca-reminderStart the development server with your custom piece loaded:
AP_DEV_PIECES=zatca-reminder npm run startOpen http://localhost:4200. Your piece appears in the flow builder's piece search — search for "ZATCA" and drag the action into a new flow.
Building the complete ZATCA reminder flow:
- Trigger: Schedule → set to run on the 20th of each month
- Action 1: ZATCA Reminder → Get Next ZATCA Filing Deadline → Filing Period: Monthly
- Action 2: Filter step — only continue if
isUrgentequalstrue - Action 3: WhatsApp Business Cloud → Send Text Message → body: the
arabicMessageoutput from Action 1
Your finance team now receives an automatic WhatsApp reminder whenever a ZATCA deadline is within one week — without anyone manually tracking dates.
Step 5: Adding Your Piece to Production
Once you have tested the piece in development, package it for your production Activepieces instance.
Create a custom Dockerfile that extends the official image:
FROM activepieces/activepieces:latest
COPY packages/pieces/custom/zatca-reminder/dist /root/custom-pieces/zatca-reminderAdd the custom piece path to your Docker Compose environment:
environment:
- AP_CUSTOM_PIECES_PATH=/root/custom-piecesRebuild and restart:
docker compose build
docker compose up -dThe ZATCA Reminder piece is now available in your production flow builder alongside all built-in pieces.
Troubleshooting
Container fails to start: Run docker compose logs activepieces to read the startup log. The most common cause is a malformed AP_ENCRYPTION_KEY — it must be exactly 32 hexadecimal characters (64 characters if using openssl rand -hex 32; use openssl rand -hex 16 for 32 chars).
Custom piece not appearing in search: After building, restart the container and clear your browser cache. Confirm that AP_DEV_PIECES matches the package name in package.json — not the displayName.
WhatsApp messages not delivered: Meta's temporary tokens expire every 24 hours. For production, generate a permanent system user token in the Meta Business Manager under Business Settings → System Users.
TypeScript compile errors in your piece: Run npx tsc --noEmit inside the piece directory. The framework's types are strict — all Property values return unknown by default; cast them explicitly or use the PiecePropValueSchema helper type.
Next Steps
The same custom piece pattern works for any MENA-specific API:
- Qiwa compliance monitor — poll the Qiwa API for Nitaqat band changes and alert HR managers via WhatsApp automatically
- Moyasar payment trigger — receive a Moyasar webhook and route it through an Activepieces flow to update your CRM
- NPHIES claim status poller — check healthcare claim status daily and notify the billing team on any status change
For background on the business case, see the AI Workflow Automation for SMEs guide and the Workflow Automation Cost Breakdown for Saudi projects.
If your team uses WhatsApp as the primary customer channel, the WhatsApp Cloud API agent tutorial shows how to handle inbound messages intelligently alongside outbound notifications.
Ready to automate your Saudi or MENA business workflows with a custom Activepieces setup? Our team specialises in API integration and TypeScript automation for Arabic-speaking markets. Contact us to scope your project.