Open-source AI-first workflow automation platform. Self-hosted or cloud. 400+ pieces. MCP support.
-
Multi-tenant: Platform → Projects → Users. ALL queries MUST filter by
projectIdorplatformId. -
Editions: CE (
ce), EE (ee), Cloud (cloud) viaAP_EDITION. EE extends CE viahooksFactory— never importsrc/app/ee/in CE code. -
Feature gating:
platformMustHaveFeatureEnabled((p) => p.plan.myFlag)on EE modules. -
Entity registration: New entities MUST be added to
getEntities()indatabase-connection.ts— TypeORM does NOT auto-discover. -
HTTP:
POSTfor all create/update mutations.DELETEfor deletes. Never PUT/PATCH. -
Security: Every endpoint needs
securityAccessconfig. -
Side effects: Separated into
*-side-effects.tsfiles, called explicitly after mutations. -
Multi-server: Use
distributedLock, BullMQ deduplication, orFOR UPDATE SKIP LOCKEDfor concurrent operations. -
Managed PostgreSQL: No custom extensions. Use
sanitizeObjectForPostgresql()for external data. -
Before modifying a module: Read its
.agents/features/<name>.mdfile for entities, services, and integration details. |.agents/features/*.md| ~60 lines each | When Claude explores the feature | Entity schemas, services, data flows | |.claude/rules/| 3-5 lines each | Every session | Critical safety checks (entity registration, data isolation, edition safety) | |.agents/skills/| 30-65 lines each | When invoked | Step-by-step workflows (/add-feature,/add-entity,/add-endpoint) | -
Exported types and constants must be placed at the end of the file, after all logic (functions, hooks, components, classes, etc.). This keeps the logic front and centre when reading a file, and groups the public contract at a predictable location.
// ✅ Correct function doSomething() { ... } export const MY_CONST = 'value'; export type MyType = { ... }; // ✅ Correct const businessService = () => { ... } export const MY_CONST = 'value'; export type MyType = { ... }; // ❌ Wrong — types/consts mixed in before logic export const MY_CONST = 'value'; export type MyType = { ... }; function doSomething() { ... }
- No
anytype — Use proper type definitions orunknownwith type guards - No type casting — Do not use
as SomeTypeto force types. If you encounter an unnecessary cast, remove it. - No deprecated APIs — Before using any library method or export, check its JSDoc. If it carries a
@deprecatedtag, use the recommended replacement instead. Examples: preferz.enumoverz.nativeEnum. - Go-style error handling — Use
tryCatch/tryCatchSyncfrom@activepieces/shared - Zod error messages must be i18n keys — Every
.min(),.refine(),.superRefine(), etc. that surfaces a user-facing message must pass a string that exists as a key inpackages/web/public/locales/en/translation.json. For common messages (e.g. required fields) use theformErrorsconstant from@activepieces/shared. Add a new translation key if none fits; never use raw English sentences that are not in the translation file. @activepieces/sharedversion bump — Any change topackages/sharedmust be accompanied by a version bump inpackages/shared/package.json: bump the patch version for non-breaking additions or fixes, bump the minor version for new exports or behaviour changes after you check if it has already been bumped in the current branch or not- Helper functions — Define non-exported helpers outside of const declarations
- Named parameters — Always use a single destructured object parameter instead of positional arguments. This applies to every function with more than one parameter, regardless of type. It prevents mix-ups at the call site and makes future additions non-breaking.
- Prefer immutable data flow — Functions should produce data by returning it, not by mutating an array/object the caller passes in. If a helper accumulates results (logs, derived rows, computed bindings), it should build the collection locally and return it — not take a pre-allocated bag the caller will read after. Local mutation inside a function's own body is fine; mutation that crosses the function boundary is not. Build new collections with
.map/.filter/.reduce/ spread rather than in-placepush/splice/ property assignment when feasible. - File order: Imports → Exported functions/constants → Helper functions → Types
- Comments — Only comment to explain why something is done, never what the code is doing. Code should be self-explanatory; comments that restate the code add noise and rot.
- Util file exports — When a util file exposes multiple plain functions or constants (non-React), do not export them individually. Instead, group them into a single named
constand export that one object (e.g.export const myUtils = { fn1, fn2 }). Callers usemyUtils.fn1()at the call site. React components in the same file should be named exports (e.g.export function MyAlert()orexport const MyAlert = …) and imported by name — do not bundle them into a wrapper object for the sake of this rule. - Safe outbound HTTP (SSRF) — For any outbound HTTP in
packages/server/{api,worker,utils}, usesafeHttp.axios/safeHttp.createAxios({ ... })from@activepieces/server-utils. Never use rawfetchoraxios.createfor URLs that come from user input, admin config, OAuth endpoints, or third-party integrations — they bypass the SSRF filter (private/loopback/metadata IPs). See.claude/rules/safe-http.md.
- Global error dialog via
meta—app.tsxhas aQueryCache.onErrorhandler that shows an error dialog whenquery.meta?.showErrorDialogis truthy. When adding a newuseQuerythat fetches primary page data (e.g. table rows, list data), addmeta: { showErrorDialog: true }to the query options. - Do NOT add
showErrorDialogto minor/auxiliary queries (feature flags, piece metadata, single-item fetches, filter options, user details). These should fail silently. - Rule of thumb: if the query failure would leave the user staring at an empty table or blank page with no explanation, it should have
meta: { showErrorDialog: true }.
apId(), tryCatch(), tryCatchSync(), isNil(), spreadIfDefined(), spreadIfNotUndefined(), ActivepiecesError({ code, params }), SeekPage<T>, formErrors, BaseModelSchema, chunk(), partition(), unique(), omit(), sanitizeObjectForPostgresql()
npm run test-unit # Vitest: engine + shared
npm run test-api # API integration (CE, EE, Cloud)API tests: setupTestEnvironment() + createTestContext(app) → ctx.post(), ctx.get(). DB auto-cleaned between tests.
This monorepo uses turbo (see turbo.json). There is no Nx — never invoke nx or npx nx.
npm start # Setup dev + start all
npm run dev # Frontend + backend
npm run lint-dev # Lint with auto-fix (ALWAYS before done)
npx turbo run lint --filter=<package> # Lint a single package, e.g. --filter=web
npx turbo run serve --filter=web -- --mode=cloud # Run local frontend against the cloud backendWhen running in --mode=cloud, do not use OAuth2 connections — the OAuth provider will redirect back to cloud.activepieces.com after sign-in instead of your local frontend, breaking the flow. Use API-key / basic-auth connections, or test OAuth2 against a fully local backend.
- Always prefix
git pushwithCLAUDE_PUSH=yesto auto-approve the pre-push lint/test gate, e.g.CLAUDE_PUSH=yes git push -u origin HEAD.
- When creating a PR with
gh pr create, always apply exactly one of these labels based on the nature of the change:feature— new functionalitybug— bug fixskip-changelog— changes that should not appear in the changelog (docs, CI tweaks, internal refactors, etc.)
- If the PR includes any contributions to pieces (integrations under
packages/pieces), also add the appropriate pieces label (in addition to the primary label above):area/third-party-pieces— for third-party integrations (most pieces underpackages/pieces/community/)area/core-pieces— for core pieces (underpackages/pieces/core/)
- Before creating or modifying a database migration, always read the Database Migrations Playbook first. Follow its instructions for generating and structuring migrations.
- Always run
npm run lint-devas part of any verification step before considering a task complete.
- All customer-facing UI must be white-labeled. Sign-in/signup pages, email templates, logos, and any user-visible branding must use the platform's configured appearance (name, colors, logos) — never hardcode "Activepieces" in user-facing surfaces.
- Test across all edition paths. Every customer-facing feature must be verified on:
- Community Edition (self-hosted,
AP_EDITION=ce) — no custom branding, open-source plan - Enterprise Edition (self-hosted,
AP_EDITION=ee) — custom branding behindcustomAppearanceEnabledflag - Cloud Freemium (
AP_EDITION=cloud, standard plan) — always applies platform branding - Cloud Self-Serve Paid (
AP_EDITION=cloud, upgraded plan) — same as freemium with higher limits - Cloud Enterprise (
AP_EDITION=cloud, enterprise plan) — full feature set
- Community Edition (self-hosted,
- Appearance is edition-gated. Community always uses the default theme. Cloud always applies custom branding. Enterprise requires
platform.plan.customAppearanceEnabled. Seepackages/server/api/src/app/ee/helper/appearance-helper.ts. - Feature gating pattern: Backend uses
platformMustHaveFeatureEnabled()middleware (returns 402). Frontend usesLockedFeatureGuardcomponent andenabled: platform.plan.<flag>on queries.