freepieces
v0.2.2
Published
> Use all 700+ MIT-licensed community pieces from [Activepieces](https://github.com/activepieces/activepieces/tree/main/packages/pieces/community) on Cloudflare Workers — with a clean MIT framework, OAuth2, and a CLI that installs and deploys them in minu
Readme
freepieces
Use all 700+ MIT-licensed community pieces from Activepieces on Cloudflare Workers — with a clean MIT framework, OAuth2, and a CLI that installs and deploys them in minutes.
About
The Activepieces community ships 700+ integration pieces (Gmail, Slack, GitHub, Notion, Stripe, and hundreds more) as individual MIT-licensed npm packages. freepieces gives you a lightweight MIT framework and compatibility shim to run them on Cloudflare Workers — along with a CLI to search, install, and deploy them in minutes.
Use it when you want:
- All 700+ Activepieces community pieces without licensing blockers
- First-class Cloudflare Workers support (KV, Secrets, Web Crypto)
- A CLI workflow to search, install, and deploy
@activepieces/piece-*packages - An admin UI to manage piece credentials and OAuth tokens
Features
fpCLI — scaffold a new Worker, search npm for pieces, install and generate wrappers, deploy- Piece framework —
createPiece()andcreateAction()builders with full TypeScript types - OAuth2 + API-key auth — CSRF-protected OAuth flow, AES-256-GCM encrypted token storage in Cloudflare KV
- Admin UI — React SPA for managing pieces, secrets, connected OAuth users, OAuth sessions, and embedded MDX docs
- Activepieces compat shims — drop-in
createAction,PieceAuth, andPropertywrappers for porting community pieces
Installation
npm install -g freepieces
# or
npx freepieces initRequirements
- Node.js ≥ 20
- A Cloudflare account (free tier works)
- Wrangler CLI — installed automatically if missing
Quick start
# Scaffold a new Worker project
fp init
# Search for available pieces
fp search gmail
# Install a piece and generate a wrapper
fp install @activepieces/piece-gmail
# Start the local dev worker
npm run worker:dev
# Deploy to Cloudflare
fp deployCLI reference
| Command | Description |
| --- | --- |
| fp / fp tui | Interactive piece selector (TUI) |
| fp init | Scaffold a new Worker deployment |
| fp search [query] | Search npm for @activepieces/piece-* packages |
| fp install <pkg> | Install a piece and generate a wrapper stub |
| fp uninstall [pkg] | Remove a piece and its wrapper (alias: fp remove) |
| fp config | Configure Worker secrets interactively |
| fp deploy | Build admin SPA and deploy to Cloudflare |
Run fp --help or fp <command> --help for full options.
Documentation
docs/quick-start.mdx— bootstrap the repo locally, run a smoke test, and deploy to Cloudflaredocs/install.mdx— detailed reference for the local bootstrap flowdocs/auth.mdx— detailed auth guide covering runtime headers, OAuth storage, admin sessions, and webhook verificationscripts/install.sh— local bootstrap helper for this repositorydocs/pieces.mdx— piece architecture, registration, and native vs AP piecesdocs/actions.mdx— action runtime contract and examplesdocs/triggers.mdx— webhook subscriptions, callback delivery, and queue deliverydocs/pooling.mdx— polling triggers, with Gmail as the main example
Deploy to Cloudflare Workers
# 1. Set required secrets
wrangler secret put RUN_API_KEY # prefix with fp_sk_, e.g. fp_sk_<hex32>
wrangler secret put TOKEN_ENCRYPTION_KEY # openssl rand -hex 32
# 2. Set piece-specific OAuth secrets for every OAuth piece you enable
wrangler secret put GMAIL_CLIENT_ID
wrangler secret put GMAIL_CLIENT_SECRET
wrangler secret put EXAMPLE_OAUTH_CLIENT_ID
wrangler secret put EXAMPLE_OAUTH_CLIENT_SECRET
# 3. Create the KV namespace
wrangler kv namespace create TOKEN_STORE
# 4. Add the returned namespace ID to .env as TOKEN_STORE_ID
# and set FREEPIECES_PUBLIC_URL / FREEPIECES_URL there too
# 5. Deploy
npm run deploynpm run deploy runs ./scripts/deploy.sh, which renders wrangler.toml from wrangler.toml.tmpl and your local .env before calling Wrangler.
Native and compat OAuth pieces must declare their own clientIdEnvKey and clientSecretEnvKey values. Direct registerApPiece() integrations derive secret names from the piece name, for example my-piece → MY_PIECE_CLIENT_ID and MY_PIECE_CLIENT_SECRET.
API routes
| Method | Path | Description |
| --- | --- | --- |
| GET | /health | Health check |
| GET | /pieces | List registered pieces and actions |
| GET | /auth/login/:piece?userId=<id> | Start OAuth2 flow |
| GET | /auth/callback/:piece | OAuth2 callback, stores token |
| POST | /run/:piece/:action | Execute an action (JSON body = props) |
| POST | /trigger/:piece/:trigger | Execute a trigger filter for an inbound payload |
| POST | /subscriptions/:piece/:trigger | Register a webhook subscription |
| GET | /subscriptions/:piece | List subscriptions for the current runtime identity |
| DELETE | /subscriptions/:piece/:trigger/:id | Delete a subscription for the current runtime identity |
Runtime auth contract
If RUN_API_KEY is configured on the worker, runtime endpoints use a split contract:
Authorization: Bearer <RUN_API_KEY>— authenticates the callerX-User-Id: <userId>— identifies which stored OAuth2 token to read from KVX-Piece-Token: <token>— passes a direct runtime credential for API-key or single-propCUSTOM_AUTHpiecesX-Piece-Auth: {"prop":"val",…}— passes multiple named credentials for multi-propCUSTOM_AUTHpieces; value must be a JSON object where every value is a string
Use X-User-Id for OAuth2 pieces such as Gmail. Use X-Piece-Token for a single direct credential such as a Slack bot token or API key. Use X-Piece-Auth when a CUSTOM_AUTH piece requires more than one named credential — for example {"botToken":"xoxb-…","signingSecret":"…"}. You may send multiple headers; the worker merges them in order.
In local dev, if RUN_API_KEY is not set, the bearer token remains the fallback for both modes. The SDK and examples also send X-User-Id / X-Piece-Token when available so local and deployed behavior stay aligned.
Queue delivery for subscriptions
Webhook subscriptions can deliver matched events to a Cloudflare Queue instead of an HTTPS callback URL. This keeps event processing inside the Cloudflare network with no public endpoint required on the consumer side.
1. Create the queue and add a producer binding to wrangler.toml:
[[queues.producers]]
queue = "slack-new-message"
binding = "QUEUE_SLACK_NEW_MESSAGE"Binding naming convention: QUEUE_ + queue name in UPPER_SNAKE_CASE (hyphens become underscores).
2. Create a subscription with queueName instead of callbackUrl:
curl "https://freepieces.example.workers.dev/subscriptions/npm-slack/new_message" \
-X POST \
-H "Authorization: Bearer $FREEPIECES_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"queueName": "slack-new-message",
"propsValue": { "channel": "C0123456789" }
}'Matched events are sent to the queue as JSON with the same shape as the HTTP delivery payload:
{ "piece": "npm-slack", "trigger": "new_message", "events": [...] }3. Consume the queue in a separate Worker (or the same Worker with a queue() handler) bound as a consumer.
callbackUrl and queueName are mutually exclusive — provide exactly one per subscription.
SDK usage
import { createClient } from 'freepieces/sdk';
const client = createClient({
baseUrl: 'https://freepieces.example.workers.dev',
token: process.env.RUN_API_KEY, // fp_sk_<hex32>
userId: '[email protected]', // KV lookup key for OAuth2 pieces
pieceToken: 'xoxb-...', // optional direct credential for API-key/CUSTOM_AUTH pieces
});Admin UI
The admin console is a React SPA served from /admin/.
Authentication uses OpenAuth with invite-only registration.
# Set admin emails (comma-separated)
wrangler secret put ADMIN_EMAILS # e.g. "[email protected],[email protected]"
# Optional: allow additional non-admin users
wrangler secret put ALLOWED_EMAILS # e.g. "[email protected]"
# Optional: enable social login providers
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
# Build and deploy
npm run build:admin && ./scripts/deploy.shThen open https://<your-worker>.workers.dev/admin/ and sign in with email code, Google, or GitHub.
OAuth-backed pieces also show a foldable Users section in the admin UI so you can inspect which stored userId values currently have tokens.
Local dev: add ADMIN_EMAILS to .env, run npm run worker:dev, and open http://localhost:9321/admin/. Verification codes are logged to the console when the EMAIL binding is absent.
The admin UI also includes a Docs tab that renders the repository guides directly from docs/*.mdx.
Run
npm run build:adminat least once beforewrangler dev— the SPA is served fromdist/public/via the ASSETS binding.
Security
| Data | Storage | Protection |
| --- | --- | --- |
| Per-piece OAuth client ID / secret | Cloudflare Secret | Deployment-time value, never in source |
| AES-GCM encryption key | Cloudflare Secret | openssl rand -hex 32 |
| Per-user OAuth tokens | Cloudflare KV | AES-256-GCM encrypted, fresh random IV per write |
| Runtime API key (RUN_API_KEY) | Cloudflare Secret | Prefix with fp_sk_; authenticates runtime callers |
| Direct piece credentials | Request headers or Cloudflare Secrets | X-Piece-Token (single), X-Piece-Auth (multi-prop JSON), or per-piece env secret |
OAuth state is a signed blob (<payload>.<hmac-sha256>). The callback handler rejects any state that fails HMAC verification.
Writing a piece
Native freepieces API
import { createPiece, createAction } from 'freepieces/framework';
export const myPiece = createPiece({
name: 'my-piece',
displayName: 'My Piece',
version: '0.1.0',
auth: {
type: 'oauth2',
authorizationUrl: 'https://provider.example/oauth/authorize',
tokenUrl: 'https://provider.example/oauth/token',
scopes: ['read', 'write'],
clientIdEnvKey: 'MY_PIECE_CLIENT_ID',
clientSecretEnvKey: 'MY_PIECE_CLIENT_SECRET',
},
actions: [
createAction({
name: 'do-something',
displayName: 'Do Something',
props: {},
async run() {
return { ok: true };
}
})
]
});Native freepieces OAuth pieces do not share one global OAuth client credential pair. Each piece names its own secrets explicitly.
Porting an Activepieces community piece
import {
createPiece,
createAction,
PieceAuth,
Property
} from 'freepieces/compat/activepieces';
export const myPiece = createPiece({
name: 'my-piece',
displayName: 'My Piece',
version: '0.1.0',
auth: PieceAuth.OAuth2({
authorizationUrl: 'https://provider.example/oauth/authorize',
tokenUrl: 'https://provider.example/oauth/token',
scope: ['read', 'write'],
clientIdEnvKey: 'MY_PIECE_CLIENT_ID',
clientSecretEnvKey: 'MY_PIECE_CLIENT_SECRET',
}),
actions: [
createAction({
name: 'do-something',
displayName: 'Do Something',
props: {
message: Property.ShortText({ displayName: 'Message', required: true })
},
async run({ auth, propsValue }) {
return { auth, message: propsValue['message'] };
}
})
]
});Compat OAuth pieces should name their secrets explicitly too. Only direct registerApPiece() integrations derive MY_PIECE_CLIENT_ID / MY_PIECE_CLIENT_SECRET automatically from the piece name.
Use as a library
freepieces can also be consumed as an npm package from another project. This lets you deploy your own Cloudflare Worker with custom pieces without forking this repo.
npm install freepieces honoSubpath exports
| Import | What you get |
| --- | --- |
| freepieces/worker | createFreepiecesWorker() — returns { fetch, queue } |
| freepieces/framework | registerPiece, registerApPiece, createPiece, types (Env, PieceDefinition, ApPiece, …) |
| freepieces/sdk | TypeScript SDK client (unchanged) |
| freepieces/admin-assets | Path to the compiled admin SPA (dist/public/) |
Minimal consumer worker
// src/worker.ts
import { createFreepiecesWorker } from 'freepieces/worker';
import './pieces/index.js'; // registers your pieces as side effects
export default createFreepiecesWorker();// src/pieces/index.ts
import { registerApPiece } from 'freepieces/framework';
import type { ApPiece } from 'freepieces/framework';
import airtablePkg from '@activepieces/piece-airtable';
registerApPiece('airtable', (airtablePkg as unknown as { airtable: ApPiece }).airtable);# wrangler.toml — point [assets] at the package's compiled admin SPA
[assets]
directory = "./node_modules/freepieces/dist/public"
binding = "ASSETS"A working minimal example lives in examples/consumer-worker/.
Important: ensure only one copy of
freepiecesis resolved in your project. The piece registry is module-global; if two copies exist the pieces registered beforecreateFreepiecesWorker()will be invisible at runtime.
Development
# Install dependencies
pnpm install
# Type-check all targets
npm run check
# Start local dev worker
npm run worker:dev
# Run tests
npm test
# Build everything
npm run buildProject layout
src/
├── worker.ts ← Cloudflare Worker entrypoint
├── framework/ ← createPiece, createAction, registry, auth helpers
├── lib/ ← AES-GCM crypto, KV token store, OAuth2 flow
├── compat/ ← Activepieces shims (createAction, PieceAuth, Property)
├── pieces/ ← Example and bundled pieces
├── client/ ← Script client (Node.js / Deno)
├── admin/ ← React admin SPA
└── cli/ ← fp CLI (commander-based)Contributing
Contributions are welcome. Open an issue to discuss a change before submitting a PR.
Change checklist
For every new feature or behavior change, update every affected surface in the same PR:
- Worker runtime contract (
src/worker.tsand any shared auth helpers) - SDK types/client/examples when the caller contract changes
- CLI scaffolding/config/help text when new secrets, flags, or env vars are introduced
- README and examples when user-facing behavior changes
- Tests for the new functionality or changed behavior
Avoid partial backend updates. If a change touches auth, routes, examples, or generated usage, review worker, SDK, CLI, and docs together before you call it done.
git clone https://github.com/borgius/freepieces.git
cd freepieces
pnpm install
npm testLicense
MIT © 2026 Victor Borg
