Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,109 @@
---
title: 'Create Action'
icon: 'circle-5'
description: ''
---
## Action Definition
Now let's create first action which fetch random ice-cream flavor.
```bash
npm run cli actions create
```
You will be asked three questions to define your new piece:
1. `Piece Folder Name`: This is the name associated with the folder where the action resides. It helps organize and categorize actions within the piece.
2. `Action Display Name`: The name users see in the interface, conveying the action's purpose clearly.
3. `Action Description`: A brief, informative text in the UI, guiding users about the action's function and purpose.
Next, Let's create the action file:
**Example:**
```bash
npm run cli actions create
? Enter the piece folder name : gelato
? Enter the action display name : get icecream flavor
? Enter the action description : fetches random icecream flavor.
```
This will create a new TypeScript file named `get-icecream-flavor.ts` in the `packages/pieces/community/gelato/src/lib/actions` directory.
Inside this file, paste the following code:
```typescript
import {
createAction,
Property,
PieceAuth,
} from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { gelatoAuth } from '../..';
export const getIcecreamFlavor = createAction({
name: 'get_icecream_flavor', // Must be a unique across the piece, this shouldn't be changed.
auth: gelatoAuth,
displayName: 'Get Icecream Flavor',
description: 'Fetches random icecream flavor',
props: {},
async run(context) {
const res = await httpClient.sendRequest<string[]>({
method: HttpMethod.GET,
url: 'https://cloud.activepieces.com/api/v1/webhooks/RGjv57ex3RAHOgs0YK6Ja/sync',
headers: {
Authorization: context.auth, // Pass API key in headers
},
});
return res.body;
},
});
```
The createAction function takes an object with several properties, including the `name`, `displayName`, `description`, `props`, and `run` function of the action.
The `name` property is a unique identifier for the action. The `displayName` and `description` properties are used to provide a human-readable name and description for the action.
The `props` property is an object that defines the properties that the action requires from the user. In this case, the action doesn't require any properties.
The `run` function is the function that is called when the action is executed. It takes a single argument, context, which contains the values of the action's properties.
The `run` function utilizes the httpClient.sendRequest function to make a GET request, fetching a random ice cream flavor. It incorporates API key authentication in the request headers. Finally, it returns the response body.
## Expose The Definition
To make the action readable by Activepieces, add it to the array of actions in the piece definition.
```typescript
import { createPiece } from '@activepieces/pieces-framework';
// Don't forget to add the following import.
import { getIcecreamFlavor } from './lib/actions/get-icecream-flavor';
export const gelato = createPiece({
displayName: 'Gelato',
logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png',
authors: [],
auth: gelatoAuth,
// Add the action here.
actions: [getIcecreamFlavor], // <--------
triggers: [],
});
```
# Testing
By default, the development setup only builds specific components. Open the file `packages/server/api/.env` and include "gelato" in the `AP_DEV_PIECES`.
For more details, check out the [Piece Development](./development-setup) section.
Once you edit the environment variable, restart the backend. The piece will be rebuilt. After this process, you'll need to **refresh** the frontend to see the changes.
<Tip>
If the build fails, try debugging by running `npx nx run-many -t build --projects=gelato`.
It will display any errors in your code.
</Tip>
To test the action, use the flow builder in Activepieces. It should function as shown in the screenshot.
![Gelato Action](/resources/screenshots/gelato-action.png)

View File

@@ -0,0 +1,137 @@
---
title: 'Create Trigger'
icon: 'circle-6'
description: ''
---
This tutorial will guide you through the process of creating trigger for a Gelato piece that fetches new icecream flavor.
## Trigger Definition
To create trigger run the following command,
```bash
npm run cli triggers create
```
1. `Piece Folder Name`: This is the name associated with the folder where the trigger resides. It helps organize and categorize triggers within the piece.
2. `Trigger Display Name`: The name users see in the interface, conveying the trigger's purpose clearly.
3. `Trigger Description`: A brief, informative text in the UI, guiding users about the trigger's function and purpose.
4. `Trigger Technique`: Specifies the trigger type - either [polling](../piece-reference/triggers/polling-trigger) or [webhook](../piece-reference/triggers/webhook-trigger).
**Example:**
```bash
npm run cli triggers create
? Enter the piece folder name : gelato
? Enter the trigger display name : new flavor created
? Enter the trigger description : triggers when a new icecream flavor is created.
? Select the trigger technique: polling
```
This will create a new TypeScript file at `packages/pieces/community/gelato/src/lib/triggers` named `new-flavor-created.ts`.
Inside this file, paste the following code:
```ts
import { gelatoAuth } from '../../';
import {
DedupeStrategy,
HttpMethod,
HttpRequest,
Polling,
httpClient,
pollingHelper,
} from '@activepieces/pieces-common';
import {
PiecePropValueSchema,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import dayjs from 'dayjs';
const polling: Polling<
PiecePropValueSchema<typeof gelatoAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const request: HttpRequest = {
method: HttpMethod.GET,
url: 'https://cloud.activepieces.com/api/v1/webhooks/aHlEaNLc6vcF1nY2XJ2ed/sync',
headers: {
authorization: auth,
},
};
const res = await httpClient.sendRequest(request);
return res.body['flavors'].map((flavor: string) => ({
epochMilliSeconds: dayjs().valueOf(),
data: flavor,
}));
},
};
export const newFlavorCreated = createTrigger({
auth: gelatoAuth,
name: 'newFlavorCreated',
displayName: 'new flavor created',
description: 'triggers when a new icecream flavor is created.',
props: {},
sampleData: {},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});
```
The way polling triggers usually work is as follows:
`Run`:The run method executes every 5 minutes, fetching data from the endpoint within a specified timestamp range or continuing until it identifies the last item ID. It then returns the new items as an array. In this example, the httpClient.sendRequest method is utilized to retrieve new flavors, which are subsequently stored in the store along with a timestamp.
## Expose The Definition
To make the trigger readable by Activepieces, add it to the array of triggers in the piece definition.
```typescript
import { createPiece } from '@activepieces/pieces-framework';
import { getIcecreamFlavor } from './lib/actions/get-icecream-flavor';
// Don't forget to add the following import.
import { newFlavorCreated } from './lib/triggers/new-flavor-created';
export const gelato = createPiece({
displayName: 'Gelato Tutorial',
logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png',
authors: [],
auth: gelatoAuth,
actions: [getIcecreamFlavor],
// Add the trigger here.
triggers: [newFlavorCreated], // <--------
});
```
# Testing
By default, the development setup only builds specific components. Open the file `packages/server/api/.env` and include "gelato" in the `AP_DEV_PIECES`.
For more details, check out the [Piece Development](./development-setup) section.
Once you edit the environment variable, restart the backend. The piece will be rebuilt. After this process, you'll need to **refresh** the frontend to see the changes.
To test the trigger, use the load sample data from flow builder in Activepieces. It should function as shown in the screenshot.
![Gelato Action](/resources/screenshots/gelato-trigger.png)

View File

@@ -0,0 +1,50 @@
---
title: 'Development setup'
icon: 'circle-2'
---
## Prerequisites
- Node.js v18+
- npm v9+
## Instructions
1. Setup the environment
```bash
node tools/setup-dev.js
```
2. Start the environment
This command will start activepieces with sqlite3 and in memory queue.
```bash
npm start
```
<Note>
By default, the development setup only builds specific pieces.Open the file
`packages/server/api/.env` and add comma-separated list of pieces to make
available.
For more details, check out the [Piece Development](/build-pieces/building-pieces/development-setup#pieces-development) section.
</Note>
3. Go to **_localhost:4200_** on your web browser and sign in with these details:
Email: `dev@ap.com`
Password: `12345678`
## Pieces Development
When [`AP_SYNC_MODE`](https://github.com/activepieces/activepieces/blob/main/packages/server/api/.env#L17) is set to `OFFICIAL_AUTO`, all pieces are automatically loaded from the cloud API and synced to the database on first launch. This process may take a few seconds to several minutes depending on your internet connection.
For local development, pieces are loaded from your local `dist` folder instead of the database. To enable this, set the [`AP_DEV_PIECES`](https://github.com/activepieces/activepieces/blob/main/packages/server/api/.env#L4) environment variable with a comma-separated list of pieces. For example, to develop with `google-sheets` and `cal-com`:
```sh
AP_DEV_PIECES=google-sheets,cal-com npm start
```

View File

@@ -0,0 +1,31 @@
---
title: 'Overview'
description: 'This section helps developers build and contribute pieces.'
icon: 'hand-wave'
---
Building pieces is fun and important; it allows you to customize Activepieces for your own needs.
<Tip>
We love contributions! In fact, most of the pieces are contributed by the community. Feel free to open a pull request.
</Tip>
<Tip>
**Friendly Tip:**
For the fastest support, we recommend joining our Discord community. We are dedicated to addressing every question and concern raised there.
</Tip>
<CardGroup cols={2}>
<Card title="Code with TypeScript" icon="code">
Build pieces using TypeScript for a more powerful and flexible development process.
</Card>
<Card title="Hot Reloading" icon="cloud-bolt">
See your changes in the browser within 7 seconds.
</Card>
<Card title="Open Source" icon="earth-americas">
Work within the open-source environment, explore, and contribute to other pieces.
</Card>
<Card title="Community Support" icon="people">
Join our large community, where you can ask questions, share ideas, and develop alongside others.
</Card>
</CardGroup>

View File

@@ -0,0 +1,38 @@
---
title: 'Add Piece Authentication'
icon: 'circle-4'
description: ''
---
### Piece Authentication
Activepieces supports multiple forms of authentication, you can check those forms [here](../piece-reference/authentication)
Now, let's establish authentication for this piece, which necessitates the inclusion of an API Key in the headers.
Modify `src/index.ts` file to add authentication,
```ts
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
export const gelatoAuth = PieceAuth.SecretText({
displayName: 'API Key',
required: true,
description: 'Please use **test-key** as value for API Key',
});
export const gelato = createPiece({
displayName: 'Gelato',
logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png',
auth: gelatoAuth,
authors: [],
actions: [],
triggers: [],
});
```
<Note>
Use the value **test-key** as the API key when testing actions or triggers for
Gelato.
</Note>

View File

@@ -0,0 +1,51 @@
---
title: 'Create Piece Definition'
icon: 'circle-3'
description: ''
---
This tutorial will guide you through the process of creating a Gelato piece with an action that fetches random icecream flavor and trigger that fetches new icecream flavor created. It assumes that you are familiar with the following:
- [Activepieces Local development](./development-setup) Or [GitHub Codespaces](../misc/codespaces).
- TypeScript syntax.
## Piece Definition
To get started, let's generate a new piece for Gelato
```bash
npm run cli pieces create
```
You will be asked three questions to define your new piece:
1. `Piece Name`: Specify a name for your piece. This name uniquely identifies your piece within the ActivePieces ecosystem.
2. `Package Name`: Optionally, you can enter a name for the npm package associated with your piece. If left blank, the default name will be used.
3. `Piece Type`: Choose the piece type based on your intention. It can be either "custom" if it's a tailored solution for your needs, or "community" if it's designed to be shared and used by the broader community.
**Example:**
```bash
npm run cli pieces create
? Enter the piece name: gelato
? Enter the package name: @activepieces/piece-gelato
? Select the piece type: community
```
The piece will be generated at `packages/pieces/community/gelato/`,
the `src/index.ts` file should contain the following code
```ts
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
export const gelato = createPiece({
displayName: 'Gelato',
logoUrl: 'https://cdn.activepieces.com/pieces/gelato.png',
auth: PieceAuth.None(),
authors: [],
actions: [],
triggers: [],
});
```

View File

@@ -0,0 +1,18 @@
---
title: 'Fork Repository'
icon: "circle-1"
---
To start building pieces, we need to fork the repository that contains the framework library and the development environment. Later, we will publish these pieces as `npm` artifacts.
Follow these steps to fork the repository:
1. Go to the repository page at https://github.com/activepieces/activepieces.
2. Click the `Fork` button located in the top right corner of the page.
![Fork Repository](/resources/screenshots/fork-repository.jpg)
<Tip>
If you are an enterprise customer and want to use the private pieces feature, you can refer to the tutorial on how to set up a [private fork](../misc/private-fork).
</Tip>

View File

@@ -0,0 +1,41 @@
---
title: 'Start Building'
icon: 'hammer'
description: ''
---
This section guides you in creating a Gelato piece, from setting up your development environment to contributing the piece. By the end of this tutorial, you will have a piece with an action that fetches a random ice cream flavor and a trigger that fetches newly created ice cream flavors.
<Info>
These are the next sections. In each step, we will do one small thing. This tutorial should take around 30 minutes.
</Info>
## Steps Overview
<Steps>
<Step title="Fork Repository" icon="code-branch">
Fork the repository to create your own copy of the codebase.
</Step>
<Step title="Setup Development Environment" icon="code">
Set up your development environment with the necessary tools and dependencies.
</Step>
<Step title="Create Piece Definition" icon="gear">
Define the structure and behavior of your Gelato piece.
</Step>
<Step title="Add Piece Authentication" icon="lock">
Implement authentication mechanisms for your Gelato piece.
</Step>
<Step title="Create Action" icon="ice-cream">
Create an action that fetches a random ice cream flavor.
</Step>
<Step title="Create Trigger" icon="ice-cream">
Create a trigger that fetches newly created ice cream flavors.
</Step>
<Step title="Sharing Pieces" icon="share">
Share your Gelato piece with others.
</Step>
</Steps>
<Card title="Contribution" icon="gift" iconType="duotone" color="#6e41e2">
Contribute a piece to our repo and receive +1,400 tasks/month on [Activepieces Cloud](https://cloud.activepieces.com).
</Card>

View File

@@ -0,0 +1,38 @@
---
title: 'Build Custom Pieces'
icon: 'box'
---
You can use the CLI to build custom pieces for the platform. This process compiles the pieces and exports them as a `.tgz` packed archive.
### How It Works
The CLI scans the `packages/pieces/` directory for the specified piece. It checks the **name** in the `package.json` file. If the piece is found, it builds and packages it into a `.tgz` archive.
### Usage
To build a piece, follow these steps:
1. Ensure you have the CLI installed by cloning the repository.
2. Run the following command:
```bash
npm run build-piece
```
You will be prompted to enter the name of the piece you want to build. For example:
```bash
? Enter the piece folder name : google-drive
```
The CLI will build the piece and you will be given the path to the archive. For example:
```bash
Piece 'google-drive' built and packed successfully at dist/packages/pieces/community/google-drive
```
You may also build the piece non-interactively by passing the piece name as an argument. For example:
```bash
npm run build-piece google-drive
```

View File

@@ -0,0 +1,31 @@
---
title: 'GitHub Codespaces'
icon: 'github'
description: ''
---
GitHub Codespaces is a cloud development platform that enables developers to write, run, and debug code directly in their browsers, seamlessly integrated with GitHub.
### Steps to setup Codespaces
1. Go to [Activepieces repo](https://github.com/activepieces/activepieces).
2. Click Code `<>`, then under codespaces click create codespace on main.
![Create Codespace](/resources/screenshots/development-setup_codespaces.png)
<Note>
By default, the development setup only builds specific pieces.Open the file
`packages/server/api/.env` and add comma-separated list of pieces to make
available.
For more details, check out the [Piece Development](/build-pieces/building-pieces/development-setup#pieces-development) section.
</Note>
3. Open the terminal and run `npm start`
4. Access the frontend URL by opening port 4200 and signing in with these details:
Email: `dev@ap.com`
Password: `12345678`

View File

@@ -0,0 +1,101 @@
---
title: 'Create New AI Provider'
icon: 'sparkles'
---
ActivePieces currently supports the following AI providers:
- OpenAI
- Anthropic
To create a new AI provider, you need to follow these steps:
## Implement the AI Interface
Create a new factory that returns an instance of the `AI` interface in the `packages/pieces/community/common/src/lib/ai/providers/your-ai-provider.ts` file.
```typescript
export const yourAiProvider = ({
serverUrl,
engineToken,
}: { serverUrl: string, engineToken: string }): AI<YourAiProviderSDK> => {
const impl = new YourAiProviderSDK(serverUrl, engineToken);
return {
provider: "YOUR_AI_PROVIDER" as const,
chat: {
text: async (params) => {
try {
const response = await impl.chat.text(params);
return response;
} catch (e: any) {
if (e?.error?.error) {
throw e.error.error;
}
throw e;
}
}
},
};
};
```
## Register the AI Provider
Add the new AI provider to the `AiProviders` enum in `packages/pieces/community/common/src/lib/ai/providers/index.ts` file.
```diff
export const AiProviders = [
+ {
+ logoUrl: 'https://cdn.activepieces.com/pieces/openai.png',
+ defaultBaseUrl: 'https://api.your-ai-provider.com',
+ label: 'Your AI Provider' as const,
+ value: 'your-ai-provider' as const,
+ models: [
+ { label: 'model-1', value: 'model-1' },
+ { label: 'model-2', value: 'model-2' },
+ { label: 'model-3', value: 'model-3' },
+ ],
+ factory: yourAiProvider,
+ },
...
]
```
## Define Authentication Header
Now we need to tell ActivePieces how to authenticate to your AI provider. You can do this by adding an `auth` property to the `AiProvider` object.
The `auth` property is an object that defines the authentication mechanism for your AI provider. It consists of two properties: `name` and `mapper`. The `name` property specifies the name of the header that will be used to authenticate with your AI provider, and the `mapper` property defines a function that maps the value of the header to the format that your AI provider expects.
Here's an example of how to define the authentication header for a bearer token:
```diff
export const AiProviders = [
{
logoUrl: 'https://cdn.activepieces.com/pieces/openai.png',
defaultBaseUrl: 'https://api.your-ai-provider.com',
label: 'Your AI Provider' as const,
value: 'your-ai-provider' as const,
models: [
{ label: 'model-1', value: 'model-1' },
{ label: 'model-2', value: 'model-2' },
{ label: 'model-3', value: 'model-3' },
],
+ auth: authHeader({ bearer: true }), // or authHeader({ name: 'x-api-key', bearer: false })
factory: yourAiProvider,
},
...
]
```
## Test the AI Provider
To test the AI provider, you can use a **universal AI** piece in a flow. Follow these steps:
- Add the required headers from the admin console for the newly created AI provider. These headers will be used to authenticate the requests to the AI provider.
![Configure AI Provider](/resources/screenshots/configure-ai-provider.png)
- Create a flow that uses our **universal AI** pieces. And select **"Your AI Provider"** as the AI provider in the **Ask AI** action settings.
![Configure AI Provider](/resources/screenshots/use-ai-provider.png)

View File

@@ -0,0 +1,58 @@
---
title: 'Dev Containers'
icon: 'docker'
description: ''
---
## Using Dev Containers in Visual Studio Code
The project includes a dev container configuration that allows you to use Visual Studio Code's [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension to develop the project in a consistent environment. This can be especially helpful if you are new to the project or if you have a different environment setup on your local machine.
## Prerequisites
Before you can use the dev container, you will need to install the following:
- [Visual Studio Code](https://code.visualstudio.com/).
- The [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension for Visual Studio Code.
- [Docker](https://www.docker.com/).
## Using the Dev Container
To use the dev container for the Activepieces project, follow these steps:
1. Clone the Activepieces repository to your local machine.
2. Open the project in Visual Studio Code.
3. Press `Ctrl+Shift+P` and type `> Dev Containers: Reopen in Container`.
4. Run `npm start`.
5. The backend will run at `localhost:3000` and the frontend will run at `localhost:4200`.
<Note>
By default, the development setup only builds specific pieces.Open the file
`packages/server/api/.env` and add comma-separated list of pieces to make
available.
For more details, check out the [Piece Development](/build-pieces/building-pieces/development-setup#pieces-development) section.
</Note>
The login credentials are:
Email: `dev@ap.com`
Password: `12345678`
## Exiting the Dev Container
To exit the dev container and return to your local environment, follow these steps:
1. In the bottom left corner of Visual Studio Code, click the `Remote-Containers: Reopen folder locally` button.
2. Visual Studio Code will close the connection to the dev container and reopen the project in your local environment.
## Troubleshoot
One of the best trouble shoot after an error occur is to reset the dev container.
1. Exit the dev container
2. Run the following
```sh
sh tools/reset-dev.sh
```
3. Rebuild the dev container from above steps

View File

@@ -0,0 +1,82 @@
---
title: 'Custom Pieces CI/CD'
icon: 'hammer'
---
You can use the CLI to sync custom pieces. There is no need to rebuild the Docker image as they are loaded directly from npm.
### How It Works
Use the CLI to sync items from `packages/pieces/custom/` to instances. In production, Activepieces acts as an npm registry, storing all piece versions.
The CLI scans the directory for `package.json` files, checking the **name** and **version** of each piece. If a piece isn't uploaded, it packages and uploads it via the API.
### Usage
To use the CLI, follow these steps:
1. Generate an API Key from the Admin Interface. Go to Settings and generate the API Key.
2. Install the CLI by cloning the repository.
3. Run the following command, replacing `API_KEY` with your generated API Key and `INSTANCE_URL` with your instance URL:
```bash
AP_API_KEY=your_api_key_here bun run sync-pieces -- --apiUrl https://INSTANCE_URL/api
```
### Developer Workflow
1. Developers create and modify the pieces offline.
2. Increment the piece version in their corresponding `package.json`. For more information, refer to the [piece versioning](../piece-reference/piece-versioning) documentation.
3. Open a pull request towards the main branch.
4. Once the pull request is merged to the main branch, manually run the CLI or use a GitHub/GitLab Action to trigger the synchronization process.
### GitHub Action
```yaml
name: Sync Custom Pieces
on:
push:
branches:
- main
workflow_dispatch:
jobs:
sync-pieces:
runs-on: ubuntu-latest
steps:
# Step 1: Check out the repository code with full history
- name: Check out repository code
uses: actions/checkout@v3
with:
fetch-depth: 0
# Step 2: Set up Bun
- name: Set up Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
# Step 3: Cache Bun dependencies
- name: Cache Bun dependencies
uses: actions/cache@v3
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lockb') }}
restore-keys: |
bun-
# Step 4: Install dependencies using Bun
- name: Install dependencies
run: bun install --no-save
# Step 5: Sync Custom Pieces
- name: Sync Custom Pieces
env:
AP_API_KEY: ${{ secrets.AP_API_KEY }}
run: bun run sync-pieces -- --apiUrl ${{ secrets.INSTANCE_URL }}/api
```

View File

@@ -0,0 +1,84 @@
---
title: 'Setup Private Fork'
icon: "code-branch"
---
<Tip>
**Friendly Tip #1:** If you want to experiment, you can fork or clone the public repository.
</Tip>
<Tip>
For private piece installation, you will need the paid edition. However, you can still develop pieces, contribute them back, **OR** publish them to the public npm registry and use them in your own instance or project.
</Tip>
## Create a Private Fork (Private Pieces)
By following these steps, you can create a private fork on GitHub, GitLab or another platform and configure the "activepieces" repository as the upstream source, allowing you to incorporate changes from the "activepieces" repository.
1. **Clone the Repository:**
Begin by creating a bare clone of the repository. Remember that this is a temporary step and will be deleted later.
```bash
git clone --bare git@github.com:activepieces/activepieces.git
```
2. **Create a Private Git Repository**
Generate a new private repository on GitHub or your chosen platform. When initializing the new repository, do not include a README, license, or gitignore files. This precaution is essential to avoid merge conflicts when synchronizing your fork with the original repository.
3. **Mirror-Push to the Private Repository:**
Mirror-push the bare clone you created earlier to your newly created "activepieces" repository. Make sure to replace `<your_username>` in the URL below with your actual GitHub username.
```bash
cd activepieces.git
git push --mirror git@github.com:<your_username>/activepieces.git
```
4. **Remove the Temporary Local Repository:**
```bash
cd ..
rm -rf activepieces.git
```
5. **Clone Your Private Repository:**
Now, you can clone your "activepieces" repository onto your local machine into your desired directory.
```bash
cd ~/path/to/directory
git clone git@github.com:<your_username>/activepieces.git
```
6. **Add the Original Repository as a Remote:**
If desired, you can add the original repository as a remote to fetch potential future changes. However, remember to disable push operations for this remote, as you are not permitted to push changes to it.
```bash
git remote add upstream git@github.com:activepieces/activepieces.git
git remote set-url --push upstream DISABLE
```
You can view a list of all your remotes using `git remote -v`. It should resemble the following:
```
origin git@github.com:<your_username>/activepieces.git (fetch)
origin git@github.com:<your_username>/activepieces.git (push)
upstream git@github.com:activepieces/activepieces.git (fetch)
upstream DISABLE (push)
```
> When pushing changes, always use `git push origin`.
### Sync Your Fork
To retrieve changes from the "upstream" repository, fetch the remote and rebase your work on top of it.
```bash
git fetch upstream
git merge upstream/main
```
Conflict resolution should not be necessary since you've only added pieces to your repository.

View File

@@ -0,0 +1,57 @@
---
title: 'Publish Custom Pieces'
icon: 'upload'
---
You can use the CLI to publish custom pieces to the platform. This process packages the pieces and uploads them to the specified API endpoint.
### How It Works
The CLI scans the `packages/pieces/` directory for the specified piece. It checks the **name** and **version** in the `package.json` file. If the piece is not already published, it builds, packages, and uploads it to the platform using the API.
### Usage
To publish a piece, follow these steps:
1. Ensure you have an API Key. Generate it from the Admin Interface by navigating to Settings.
2. Install the CLI by cloning the repository.
3. Run the following command:
```bash
npm run publish-piece-to-api
```
4. You will be asked three questions to publish your piece:
- `Piece Folder Name`: This is the name associated with the folder where the action resides. It helps organize and categorize actions within the piece.
- `API URL`: This is the URL of the API endpoint where the piece will be published (ex: https://cloud.activepieces.com/api).
- `API Key Source`: This is the source of the API key. It can be either `Env Variable (AP_API_KEY)` or `Manually`.
In case you choose `Env Variable (AP_API_KEY)`, the CLI will use the API key from the `.env` file in the `packages/server/api` directory.
In case you choose `Manually`, you will be asked to enter the API key.
Examples:
```bash
npm run publish-piece-to-api
? Enter the piece folder name : google-drive
? Enter the API URL : https://cloud.activepieces.com/api
? Enter the API Key Source : Env Variable (AP_API_KEY)
```
```bash
npm run publish-piece-to-api
? Enter the piece folder name : google-drive
? Enter the API URL : https://cloud.activepieces.com/api
? Enter the API Key Source : Manually
? Enter the API Key : ap_1234567890abcdef1234567890abcdef
```

View File

@@ -0,0 +1,133 @@
---
title: "Piece Auth"
description: "Learn about piece authentication"
icon: 'key'
---
Piece authentication is used to gather user credentials and securely store them for future use in different flows.
The authentication must be defined as the `auth` parameter in the `createPiece`, `createTrigger`, and `createAction` functions.
This requirement ensures that the type of authentication can be inferred correctly in triggers and actions.
<Warning>
The auth parameter for `createPiece`, `createTrigger`, and `createAction` functions can take an array, but you cannot have more than one auth property of the same type, i.e two OAUTH2 properties.
</Warning>
### Secret Text
This authentication collects sensitive information, such as passwords or API keys. It is displayed as a masked input field.
**Example:**
```typescript
PieceAuth.SecretText({
displayName: 'API Key',
description: 'Enter your API key',
required: true,
// Optional Validation
validate: async ({auth}) => {
if(auth.startsWith('sk_')){
return {
valid: true,
}
}
return {
valid: false,
error: 'Invalid Api Key'
}
}
})
```
### Username and Password
This authentication collects a username and password as separate fields.
**Example:**
```typescript
PieceAuth.BasicAuth({
displayName: 'Credentials',
description: 'Enter your username and password',
required: true,
username: {
displayName: 'Username',
description: 'Enter your username',
},
password: {
displayName: 'Password',
description: 'Enter your password',
},
// Optional Validation
validate: async ({auth}) => {
if(auth){
return {
valid: true,
}
}
return {
valid: false,
error: 'Invalid Api Key'
}
}
})
```
### Custom
This authentication allows for custom authentication by collecting specific properties, such as a base URL and access token.
**Example:**
```typescript
PieceAuth.CustomAuth({
displayName: 'Custom Authentication',
description: 'Enter custom authentication details',
props: {
base_url: Property.ShortText({
displayName: 'Base URL',
description: 'Enter the base URL',
required: true,
}),
access_token: PieceAuth.SecretText({
displayName: 'Access Token',
description: 'Enter the access token',
required: true
})
},
// Optional Validation
validate: async ({auth}) => {
if(auth){
return {
valid: true,
}
}
return {
valid: false,
error: 'Invalid Api Key'
}
},
required: true
})
```
### OAuth2
This authentication collects OAuth2 authentication details, including the authentication URL, token URL, and scope.
**Example:**
```typescript
PieceAuth.OAuth2({
displayName: 'OAuth2 Authentication',
grantType: OAuth2GrantType.AUTHORIZATION_CODE,
required: true,
authUrl: 'https://example.com/auth',
tokenUrl: 'https://example.com/token',
scope: ['read', 'write']
})
```
<Tip>
Please note `OAuth2GrantType.CLIENT_CREDENTIALS` is also supported for service-based authentication.
</Tip>

View File

@@ -0,0 +1,55 @@
---
title: "Enable Custom API Calls"
description: "Learn how to enable custom API calls for your pieces"
icon: 'webhook'
---
Custom API Calls allow the user to send a request to a specific endpoint if no action has been implemented for it.
This will show in the actions list of the piece as `Custom API Call`, to enable this action for a piece, you need to call the `createCustomApiCallAction` in your actions array.
## Basic Example
The example below implements the action for the OpenAI piece. The OpenAI piece uses a `Bearer token` authorization header to identify the user sending the request.
```typescript
actions: [
...yourActions,
createCustomApiCallAction({
// The auth object defined in the piece
auth: openaiAuth,
// The base URL for the API
baseUrl: () => {
'https://api.openai.com/v1'
},
// Mapping the auth object to the needed authorization headers
authMapping: async (auth) => {
return {
'Authorization': `Bearer ${auth}`
}
}
})
]
```
## Dynamic Base URL and Basic Auth Example
The example below implements the action for the Jira Cloud piece. The Jira Cloud piece uses a dynamic base URL for it's actions, where the base URL changes based on the values the user authenticated with. We will also implement a Basic authentication header.
```typescript
actions: [
...yourActions,
createCustomApiCallAction({
baseUrl: (auth) => {
return `${(auth as JiraAuth).instanceUrl}/rest/api/3`
},
auth: jiraCloudAuth,
authMapping: async (auth) => {
const typedAuth = auth as JiraAuth
return {
'Authorization': `Basic ${typedAuth.email}:${typedAuth.apiToken}`
}
}
})
]
```

View File

@@ -0,0 +1,31 @@
---
title: "Piece Examples"
description: "Explore a collection of example triggers and actions"
icon: 'brackets-curly'
---
To get the full benefit, it is recommended to read the tutorial first.
## Triggers:
**Webhooks:**
- [New Form Submission on Typeform](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts)
**Polling:**
- [New Completed Task On Todoist](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts)
## Actions:
- [Send a message On Discord](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/discord/src/lib/actions/send-message-webhook.ts)
- [Send an mail On Gmail](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/gmail/src/lib/actions/send-email-action.ts)
## Authentication
**OAuth2:**
- [Slack](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/slack/src/index.ts)
- [Gmail](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/gmail/src/index.ts)
**API Key:**
- [Sendgrid](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/sendgrid/src/index.ts)
**Basic Authentication:**
- [Twilio](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/twilio/src/index.ts)

View File

@@ -0,0 +1,20 @@
---
title: "External Libraries"
icon: 'npm'
description: "Learn how to install and use external libraries."
---
The Activepieces repository is structured as a monorepo, employing Nx as its build tool.
To keep our main `package.json` as light as possible, we keep libraries that are only used for a piece in the piece `package.json` . This means when adding a new library you should navigate to the piece folder and install the library with our package manager `bun`
```bash
cd packages/pieces/<piece-path>
bun install --save <library-name>
```
- Import the library into your piece.
Guidelines:
- Make sure you are using well-maintained libraries.
- Ensure that the library size is not too large to avoid bloating the bundle size; this will make the piece load faster in the sandbox.

View File

@@ -0,0 +1,25 @@
---
title: "Files"
icon: 'file'
description: "Learn how to use files object to create file references."
---
The `ctx.files` object allow you to store files in local storage or in a remote storage depending on the run environment.
## Write
You can use the `write` method to write a file to the storage, It returns a string that can be used in other actions or triggers properties to reference the file.
**Example:**
```ts
const fileReference = await files.write({
fileName: 'file.txt',
data: Buffer.from('text')
});
```
<Tip>
This code will store the file in the database If the run environment is testing mode since it will be required to test other steps, other wise it will store it in the local temporary directory.
</Tip>
For Reading the file If you are using the file property in a trigger or action, It will be automatically parsed and you can use it directly, please refer to `Property.File` in the [properties](./properties#file) section.

View File

@@ -0,0 +1,65 @@
---
title: 'Flow Control'
icon: 'Joystick'
description: 'Learn How to Control Flow from Inside the Piece'
---
Flow Controls provide the ability to control the flow of execution from inside a piece. By using the `ctx` parameter in the `run` method of actions, you can perform various operations to control the flow.
## Stop Flow
You can stop the flow and provide a response to the webhook trigger. This can be useful when you want to terminate the execution of the piece and send a specific response back.
**Example with Response:**
```typescript
context.run.stop({
response: {
status: context.propsValue.status ?? StatusCodes.OK,
body: context.propsValue.body,
headers: (context.propsValue.headers as Record<string, string>) ?? {},
},
});
```
**Example without Response:**
```typescript
context.run.stop();
```
## Pause Flow and Wait for Webhook
You can pause flow and return HTTP response, also provide a callback to URL that you can call with certain payload and continue the flow.
**Example:**
```typescript
ctx.run.pause({
pauseMetadata: {
type: PauseType.WEBHOOK,
response: {
callbackUrl: context.generateResumeUrl({
queryParams: {},
}),
},
},
});
```
## Pause Flow and Delay
You can pause or delay the flow until a specific timestamp. Currently, the only supported type of pause is a delay based on a future timestamp.
**Example:**
```typescript
ctx.run.pause({
pauseMetadata: {
type: PauseType.DELAY,
resumeDateTime: futureTime.toUTCString()
}
});
```
These flow hooks give you control over the execution of the piece by allowing you to stop the flow or pause it until a certain condition is met. You can use these hooks to customize the behavior and flow of your actions.

View File

@@ -0,0 +1,36 @@
---
title: 'Piece i18n'
description: 'Learn about translating pieces to multiple locales'
icon: 'globe'
---
<Steps>
<Step title="Generate">
Run the following command to create a translation file with all the strings that need translation in your piece
```bash
npm run cli pieces generate-translation-file PIECE_FOLDER_NAME
```
</Step>
<Step title="Translate">
Make a copy of `packages/pieces/<community_or_custom>/<your_piece>/src/i18n/translation.json`, name it `<locale>.json` i.e fr.json and translate the values.
<Tip>
For open source pieces, you can use the [Crowdin project](https://crowdin.com/project/activepieces) to translate to different languages. These translations will automatically sync back to your code.
</Tip>
</Step>
<Step title="Test Locally">
After following the steps to [setup your development environment](/build-pieces/building-pieces/development-setup), click the small cog icon next to the logo in your dashboard and change the locale.
![Locales](/resources/i18n-pieces.png)
<br></br>
In the builder your piece will now appear in the translated language:
![French Webhooks](/resources/french-webhooks.png)
</Step>
<Step title="Publish">
Follow the docs here to [publish your piece](/build-pieces/sharing-pieces/overview)
</Step>
</Steps>

View File

@@ -0,0 +1,45 @@
---
title: "Persistent Storage"
icon: 'database'
description: "Learn how to store and retrieve data from a key-value store"
---
The `ctx` parameter inside triggers and actions provides a simple key/value storage mechanism. The storage is persistent, meaning that the stored values are retained even after the execution of the piece.
By default, the storage operates at the flow level, but it can also be configured to store values at the project level.
<Tip>
The storage scope is completely isolated. If a key is stored in a different scope, it will not be fetched when requested in different scope.
</Tip>
## Put
You can store a value with a specified key in the storage.
**Example:**
```typescript
ctx.store.put('KEY', 'VALUE', StoreScope.PROJECT);
```
## Get
You can retrieve the value associated with a specific key from the storage.
**Example:**
```typescript
const value = ctx.store.get<string>('KEY', StoreScope.PROJECT);
```
## Delete
You can delete a key-value pair from the storage.
**Example:**
```typescript
ctx.store.delete('KEY', StoreScope.PROJECT);
```
These storage operations allow you to store, retrieve, and delete key-value pairs in the persistent storage. You can use this storage mechanism to store and retrieve data as needed within your triggers and actions.

View File

@@ -0,0 +1,43 @@
---
title: 'Piece Versioning'
icon: 'code-compare'
description: 'Learn how to version your pieces'
---
Pieces are npm packages and follows **semantic versioning**.
## Semantic Versioning
The version number consists of three numbers: `MAJOR.MINOR.PATCH`, where:
- **MAJOR** It should be incremented when there are breaking changes to the piece.
- **MINOR** It should be incremented for new features or functionality that is compatible with the previous version, unless the major version is less than 1.0, in which case it can be a breaking change.
- **PATCH** It should be incremented for bug fixes and small changes that do not introduce new features or break backward compatibility.
## Engine
The engine will use the most up-to-date compatible version for a given piece version during the **DRAFT** flow versions. Once the flow is published, all pieces will be locked to a specific version.
**Case 1: Piece Version is Less Than 1.0**:
The engine will select the latest **patch** version that shares the same **minor** version number.
**Case 2: Piece Version Reaches Version 1.0**:
The engine will select the latest **minor** version that shares the same **major** version number.
## Examples
<Tip>
when you make a change, remember to increment the version accordingly.
</Tip>
### Breaking changes
- Remove an existing action.
- Add a required `action` prop.
- Remove an existing action prop, whether required or optional.
- Remove an attribute from an action output.
- Change the existing behavior of an action/trigger.
### Non-breaking changes
- Add a new action.
- Add an optional `action` prop.
- Add an attribute to an action output.
i.e., any removal is breaking, any required addition is breaking, everything else is not breaking.

View File

@@ -0,0 +1,78 @@
---
title: "Props Validation"
description: "Learn about different types of properties validation "
icon: 'magnifying-glass'
---
Activepieces uses Zod for runtime validation of piece properties. Zod provides a powerful schema validation system that helps ensure your piece receives valid inputs.
To use Zod validation in your piece, first import the validation helper and Zod:
<Warning>
Please make sure the `minimumSupportedRelease` is set to at least `0.36.1` for the validation to work.
</Warning>
```typescript
import { createAction, Property } from '@activepieces/pieces-framework';
import { propsValidation } from '@activepieces/pieces-common';
import { z } from 'zod';
export const getIcecreamFlavor = createAction({
name: 'get_icecream_flavor', // Unique name for the action.
displayName: 'Get Ice Cream Flavor',
description: 'Fetches a random ice cream flavor based on user preferences.',
props: {
sweetnessLevel: Property.Number({
displayName: 'Sweetness Level',
required: true,
description: 'Specify the sweetness level (0 to 10).',
}),
includeToppings: Property.Checkbox({
displayName: 'Include Toppings',
required: false,
description: 'Should the flavor include toppings?',
defaultValue: true,
}),
numberOfFlavors: Property.Number({
displayName: 'Number of Flavors',
required: true,
description: 'How many flavors do you want to fetch? (1-5)',
defaultValue: 1,
}),
},
async run({ propsValue }) {
// Validate the input properties using Zod
await propsValidation.validateZod(propsValue, {
sweetnessLevel: z.number().min(0).max(10, 'Sweetness level must be between 0 and 10.'),
numberOfFlavors: z.number().min(1).max(5, 'You can fetch between 1 and 5 flavors.'),
});
// Action logic
const sweetnessLevel = propsValue.sweetnessLevel;
const includeToppings = propsValue.includeToppings ?? true; // Default to true
const numberOfFlavors = propsValue.numberOfFlavors;
// Simulate fetching random ice cream flavors
const allFlavors = [
'Vanilla',
'Chocolate',
'Strawberry',
'Mint',
'Cookie Dough',
'Pistachio',
'Mango',
'Coffee',
'Salted Caramel',
'Blackberry',
];
const selectedFlavors = allFlavors.slice(0, numberOfFlavors);
return {
message: `Here are your ${numberOfFlavors} flavors: ${selectedFlavors.join(', ')}`,
sweetnessLevel: sweetnessLevel,
includeToppings: includeToppings,
};
},
});
```

View File

@@ -0,0 +1,449 @@
---
title: 'Props'
description: 'Learn about different types of properties used in triggers / actions'
icon: 'input-pipe'
---
Properties are used in actions and triggers to collect information from the user. They are also displayed to the user for input. Here are some commonly used properties:
## Basic Properties
These properties collect basic information from the user.
### Short Text
This property collects a short text input from the user.
**Example:**
```typescript
Property.ShortText({
displayName: 'Name',
description: 'Enter your name',
required: true,
defaultValue: 'John Doe',
});
```
### Long Text
This property collects a long text input from the user.
**Example:**
```typescript
Property.LongText({
displayName: 'Description',
description: 'Enter a description',
required: false,
});
```
### Checkbox
This property presents a checkbox for the user to select or deselect.
**Example:**
```typescript
Property.Checkbox({
displayName: 'Agree to Terms',
description: 'Check this box to agree to the terms',
required: true,
defaultValue: false,
});
```
### Markdown
This property displays a markdown snippet to the user, useful for documentation or instructions. It includes a `variant` option to style the markdown, using the `MarkdownVariant` enum:
- **BORDERLESS**: For a minimalistic, no-border layout.
- **INFO**: Displays informational messages.
- **WARNING**: Alerts the user to cautionary information.
- **TIP**: Highlights helpful tips or suggestions.
The default value for `variant` is **INFO**.
**Example:**
```typescript
Property.MarkDown({
value: '## This is a markdown snippet',
variant: MarkdownVariant.WARNING,
}),
```
<Tip>
If you want to show a webhook url to the user, use `{{ webhookUrl }}` in the
markdown snippet.
</Tip>
### DateTime
This property collects a date and time from the user.
**Example:**
```typescript
Property.DateTime({
displayName: 'Date and Time',
description: 'Select a date and time',
required: true,
defaultValue: '2023-06-09T12:00:00Z',
});
```
### Number
This property collects a numeric input from the user.
**Example:**
```typescript
Property.Number({
displayName: 'Quantity',
description: 'Enter a number',
required: true,
});
```
### Static Dropdown
This property presents a dropdown menu with predefined options.
**Example:**
```typescript
Property.StaticDropdown({
displayName: 'Country',
description: 'Select your country',
required: true,
options: {
options: [
{
label: 'Option One',
value: '1',
},
{
label: 'Option Two',
value: '2',
},
],
},
});
```
### Static Multiple Dropdown
This property presents a dropdown menu with multiple selection options.
**Example:**
```typescript
Property.StaticMultiSelectDropdown({
displayName: 'Colors',
description: 'Select one or more colors',
required: true,
options: {
options: [
{
label: 'Red',
value: 'red',
},
{
label: 'Green',
value: 'green',
},
{
label: 'Blue',
value: 'blue',
},
],
},
});
```
### JSON
This property collects JSON data from the user.
**Example:**
```typescript
Property.Json({
displayName: 'Data',
description: 'Enter JSON data',
required: true,
defaultValue: { key: 'value' },
});
```
### Dictionary
This property collects key-value pairs from the user.
**Example:**
```typescript
Property.Object({
displayName: 'Options',
description: 'Enter key-value pairs',
required: true,
defaultValue: {
key1: 'value1',
key2: 'value2',
},
});
```
### File
This property collects a file from the user, either by providing a URL or uploading a file.
**Example:**
```typescript
Property.File({
displayName: 'File',
description: 'Upload a file',
required: true,
});
```
### Array of Strings
This property collects an array of strings from the user.
**Example:**
```typescript
Property.Array({
displayName: 'Tags',
description: 'Enter tags',
required: false,
defaultValue: ['tag1', 'tag2'],
});
```
### Array of Fields
This property collects an array of objects from the user.
**Example:**
```typescript
Property.Array({
displayName: 'Fields',
description: 'Enter fields',
properties: {
fieldName: Property.ShortText({
displayName: 'Field Name',
required: true,
}),
fieldType: Property.StaticDropdown({
displayName: 'Field Type',
required: true,
options: {
options: [
{ label: 'TEXT', value: 'TEXT' },
{ label: 'NUMBER', value: 'NUMBER' },
],
},
}),
},
required: false,
defaultValue: [],
});
```
## Dynamic Data Properties
These properties provide more advanced options for collecting user input.
### Dropdown
This property allows for dynamically loaded options based on the user's input.
**Example:**
```typescript
Property.Dropdown({
displayName: 'Options',
description: 'Select an option',
required: true,
auth: yourPieceAuth,
refreshers: ['auth'],
refreshOnSearch: false,
options: async ({ auth }, { searchValue }) => {
// Search value only works when refreshOnSearch is true
if (!auth) {
return {
disabled: true,
};
}
return {
options: [
{
label: 'Option One',
value: '1',
},
{
label: 'Option Two',
value: '2',
},
],
};
},
});
```
<Tip>
When accessing the Piece auth, be sure to use exactly `auth` as it is
hardcoded. However, for other properties, use their respective names.
</Tip>
### Multi-Select Dropdown
This property allows for multiple selections from dynamically loaded options.
**Example:**
```typescript
Property.MultiSelectDropdown({
displayName: 'Options',
description: 'Select one or more options',
required: true,
refreshers: ['auth'],
auth: yourPieceAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
};
}
return {
options: [
{
label: 'Option One',
value: '1',
},
{
label: 'Option Two',
value: '2',
},
],
};
},
});
```
<Tip>
When accessing the Piece auth, be sure to use exactly `auth` as it is
hardcoded. However, for other properties, use their respective names.
</Tip>
### Dynamic Properties
This property is used to construct forms dynamically based on API responses or user input.
**Example:**
```typescript
import {
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
Property.DynamicProperties({
description: 'Dynamic Form',
displayName: 'Dynamic Form',
required: true,
refreshers: ['auth'],
auth: yourPieceAuth,
props: async ({auth}) => {
const apiEndpoint = 'https://someapi.com';
const response = await httpClient.sendRequest<{ values: [string[]][] }>({
method: HttpMethod.GET,
url: apiEndpoint ,
//you can add the auth value to the headers
});
const properties = {
prop1: Property.ShortText({
displayName: 'Property 1',
description: 'Enter property 1',
required: true,
}),
prop2: Property.Number({
displayName: 'Property 2',
description: 'Enter property 2',
required: false,
}),
};
return properties;
},
});
```
### Custom Property (BETA)
<Warning>
This feature is still in BETA and not fully released yet, please let us know if you use it and face any issues and consider it a possibility could have breaking changes in the future
</Warning>
This is a property that lets you inject JS code into the frontend and manipulate the DOM of this content however you like, it is extremely useful in case you are [embedding](/embedding/overview) Activepieces and want to have a way to communicate with the SaaS embedding it.
It has a `code` property which is a function that takes in an object parameter which will have the following schema:
| Parameter Name | Type | Description |
| --- | --- | --- |
| onChange | `(value:unknown)=>void` | A callback you call to set the value of your input (only call this inside event handlers)|
| value | `unknown` | Whatever the type of the value you pass to onChange|
| containerId | `string` | The ID of an HTML element in which you can modify the DOM however you like |
| isEmbedded | `boolean` | The flag that tells you if the code is running inside an [embedded instance](/embedding/overview) of Activepieces |
| projectId | `string` | The project ID of the flow the step that contains this property is in |
| disabled | `boolean` | The flag that tells you whether or not the property is disabled |
| property | `{ displayName:string, description?: string, required: boolean}` | The current property information|
- You can return a clean up function at the end of the `code` property function to remove any listeners or HTML elements you inserted (this is important for development mode, the component gets [mounted twice](https://react.dev/reference/react/useEffect#my-effect-runs-twice-when-the-component-mounts)).
- This function must be pure without any imports from external packages or variables outside the function scope.
- **Must** mark your piece `minimumSupportedRelease` property to be at least `0.58.0` after introducing this property to it.
Here is how to define such a property:
```typescript
Property.Custom({
code:(({value,onChange,containerId})=>{
const container = document.getElementById(containerId);
const input = document.createElement('input');
input.classList.add(...['border','border-solid', 'border-border', 'rounded-md'])
input.type = 'text';
input.value = `${value}`;
input.oninput = (e: Event) => {
const value = (e.target as HTMLInputElement).value;
onChange(value);
}
container!.appendChild(input);
const windowCallback = (e:MessageEvent<{type:string,value:string,propertyName:string}>) => {
if(e.data.type === 'updateInput' && e.data.propertyName === 'YOUR_PROPERTY_NAME'){
input.value= e.data.value;
onChange(e.data.value);
}
}
window.addEventListener('message', windowCallback);
return ()=>{
window.removeEventListener('message', windowCallback);
container!.removeChild(input);
}
}),
displayName: 'Custom Property',
required: true
})
```
- If you would like to know more about how to setup communication between Activepieces and the SaaS that's embedding it, check the [window postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).

View File

@@ -0,0 +1,76 @@
---
title: 'Overview'
description: ''
---
This tutorial explains three techniques for creating triggers:
- `Polling`: Periodically call endpoints to check for changes.
- `Webhooks`: Listen to user events through a single URL.
- `App Webhooks (Subscriptions)`: Use a developer app (using OAuth2) to receive all authorized user events at a single URL (Not Supported).
to create new trigger run following command,
```bash
npm run cli triggers create
```
1. `Piece Folder Name`: This is the name associated with the folder where the trigger resides. It helps organize and categorize triggers within the piece.
2. `Trigger Display Name`: The name users see in the interface, conveying the trigger's purpose clearly.
3. `Trigger Description`: A brief, informative text in the UI, guiding users about the trigger's function and purpose.
4. `Trigger Technique`: Specifies the trigger type - either polling or webhook.
# Trigger Structure
```typescript
export const createNewIssue = createTrigger({
auth: PieceAuth | undefined
name: string, // Unique name across the piece.
displayName: string, // Display name on the interface.
description: string, // Description for the action
sampleData: null,
type: TriggerStrategy.WEBHOOK | TriggerStrategy.POLLING | TriggerStrategy.APP_WEBHOOK,
props: {}; // Required properties from the user.
// Run when the user enable or publish the flow.
onEnable: (ctx) => {},
// Run when the user disable the flow or
// the old flow is deleted after new one is published.
onDisable: (ctx) => {},
// Trigger implementation, It takes context as parameter.
// should returns an array of payload, each payload considered
run: async run(ctx): unknown[] => {}
})
```
<Tip>
It's important to note that the `run` method returns an array. The reason for
this is that a single polling can contain multiple triggers, so each item in
the array will trigger the flow to run.
</Tip>
## Context Object
The Context object contains multiple helpful pieces of information and tools that can be useful while developing.
```typescript
// Store: A simple, lightweight key-value store that is helpful when you are developing triggers that persist between runs, used to store information like the last polling date.
await context.store.put('_lastFetchedDate', new Date());
const lastFetchedData = await context.store.get('_lastFetchedDate', new Date());
// Webhook URL: A unique, auto-generated URL that will trigger the flow. Useful when you need to develop a trigger based on webhooks.
context.webhookUrl;
// Payload: Contains information about the HTTP request sent by the third party. It has three properties: status, headers, and body.
context.payload;
// PropsValue: Contains the information filled by the user in defined properties.
context.propsValue;
```
**App Webhooks (Not Supported)**
Certain services, such as `Slack` and `Square`, only support webhooks at the developer app level.
This means that all authorized users for the app will be sent to the same endpoint. While this technique will be supported soon, for now, a workaround is to perform polling on the endpoint.

View File

@@ -0,0 +1,111 @@
---
title: "Polling Trigger"
description: "Periodically call endpoints to check for changes"
---
The way polling triggers usually work is as follows:
**On Enable:**
Store the last timestamp or most recent item id using the context store property.
**Run:**
This method runs every **5 minutes**, fetches the endpoint between a certain timestamp or traverses until it finds the last item id, and returns the new items as an array.
**Testing:**
You can implement a test function which should return some of the most recent items. It's recommended to limit this to five.
**Examples:**
- [New Record Airtable](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts)
- [New Updated Item Salesforce](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/salesforce/src/lib/trigger/new-updated-record.ts)
# Polling library
There multiple strategy to implement polling triggers, and we have created a library to help you with that.
## Strategies
**Timebased:**
This strategy fetches new items using a timestamp. You need to implement the items method, which should return the most recent items.
The library will detect new items based on the timestamp.
The polling object's generic type consists of the props value and the object type.
```typescript
const polling: Polling<Polling<AppConnectionValueForAuthProperty<typeof auth>> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ propsValue, lastFetchEpochMS }) => {
// Todo implement the logic to fetch the items
const items = [ {id: 1, created_date: '2021-01-01T00:00:00Z'}, {id: 2, created_date: '2021-01-01T00:00:00Z'}];
return items.map((item) => ({
epochMilliSeconds: dayjs(item.created_date).valueOf(),
data: item,
}));
}
}
```
**Last ID Strategy:**
This strategy fetches new items based on the last item ID. To use this strategy, you need to implement the items method, which should return the most recent items.
The library will detect new items after the last item ID.
The polling object's generic type consists of the props value and the object type
```typescript
const polling: Polling<AppConnectionValueForAuthProperty<typeof auth>, Record<string,any>> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ propsValue }) => {
// Implement the logic to fetch the items
const items = [{ id: 1 }, { id: 2 }];
return items.map((item) => ({
id: item.id,
data: item,
}));
}
}
```
## Trigger Implementation
After implementing the polling object, you can use the polling helper to implement the trigger.
```typescript
export const newTicketInView = createTrigger({
name: 'new_ticket_in_view',
displayName: 'New ticket in view',
description: 'Triggers when a new ticket is created in a view',
type: TriggerStrategy.POLLING,
props: {
authentication: Property.SecretText({
displayName: 'Authentication',
description: markdownProperty,
required: true,
}),
},
sampleData: {},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
store: context.store,
propsValue: context.propsValue,
auth: context.auth
})
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
store: context.store,
propsValue: context.propsValue,
auth: context.auth
})
},
run: async (context) => {
return await pollingHelper.poll(polling, context);
},
test: async (context) => {
return await pollingHelper.test(polling, context);
}
});
```

View File

@@ -0,0 +1,38 @@
---
title: 'Webhook Trigger'
description: 'Listen to user events through a single URL'
---
The way webhook triggers usually work is as follows:
**On Enable:**
Use `context.webhookUrl` to perform an HTTP request to register the webhook in a third-party app, and store the webhook Id in the `store`.
**On Handshake:**
Some services require a successful handshake request usually consisting of some challenge. It works similar to a normal run except that you return the correct challenge response. This is optional and in order to enable the handshake you need to configure one of the available handshake strategies in the `handshakeConfiguration` option.
**Run:**
You can find the HTTP body inside `context.payload.body`. If needed, alter the body; otherwise, return an array with a single item `context.payload.body`.
**Disable:**
Using the `context.store`, fetch the webhook ID from the enable step and delete the webhook on the third-party app.
**Testing:**
You cannot test it with Test Flow, as it uses static sample data provided in the piece.
To test the trigger, publish the flow, perform the event. Then check the flow runs from the main dashboard.
**Examples:**
- [New Form Submission on Typeform](https://github.com/activepieces/activepieces/blob/main/packages/pieces/community/typeform/src/lib/trigger/new-submission.ts)
<Warning>
To make your webhook accessible from the internet, you need to configure the backend URL. Follow these steps:
1. Install ngrok.
2. Run the command `ngrok http 4200`.
3. Replace the `AP_FRONTEND_URL` environment variable in `packages/server/api/.env` with the ngrok URL.
4. Go to /packages/react-ui/vite.config.ts, uncomment allowedHosts and set the value to what ngrok gives you.
Once you have completed these configurations, you are good to go!
</Warning>

View File

@@ -0,0 +1,32 @@
---
title: "Community (Public NPM)"
description: "Learn how to publish your piece to the community."
---
You can publish your pieces to the npm registry and share them with the community. Users can install your piece from Settings -> My Pieces -> Install Piece -> type in the name of your piece package.
<Steps>
<Step title="Login to npm">
Make sure you are logged in to npm. If not, please run:
```bash
npm login
```
</Step>
<Step title="Rename Piece">
Rename the piece name in `package.json` to something unique or related to your organization's scope (e.g., `@my-org/piece-PIECE_NAME`). You can find it at `packages/pieces/PIECE_NAME/package.json`.
<Tip>
Don't forget to increase the version number in `package.json` for each new release.
</Tip>
</Step>
<Step title="Publish">
<Tip>
Replace `PIECE_FOLDER_NAME` with the name of the folder.
</Tip>
Run the following command:
```bash
npm run publish-piece PIECE_FOLDER_NAME
```
</Step>
</Steps>
**Congratulations! You can now import the piece from the settings page.**

View File

@@ -0,0 +1,17 @@
---
title: "Contribute"
description: "Learn how to contribute a piece to the main repository."
---
<Steps>
<Step title="Open a pull request">
- Build and test your piece.
- Open a pull request from your repository to the main fork.
- A maintainer will review your work closely.
</Step>
<Step title="Merge the pull request">
- Once the pull request is approved, it will be merged into the main branch.
- Your piece will be available within a few minutes.
- An automatic GitHub action will package it and create an npm package on npmjs.com.
</Step>
</Steps>

View File

@@ -0,0 +1,9 @@
---
title: "Overview"
description: "Learn the different ways to publish your own piece on activepieces."
---
## Methods
- [Contribute Back](/build-pieces/sharing-pieces/contribute): Publish your piece by contributing back your piece to main repository.
- [Community](/build-pieces/sharing-pieces/community): Publish your piece on npm directly and share it with the community.
- [Private](/build-pieces/sharing-pieces/private): Publish your piece on activepieces privately.

View File

@@ -0,0 +1,34 @@
---
title: "Private"
description: "Learn how to share your pieces privately."
---
<Snippet file="enterprise-feature.mdx" />
This guide assumes you have already created a piece and created a private fork of our repository, and you would like to package it as a file and upload it.
<Tip>
Friendly Tip: There is a CLI to easily upload it to your platform. Please check out [Publish Custom Pieces](../misc/publish-piece).
</Tip>
<Steps>
<Step title="Build Piece">
Build the piece using the following command. Make sure to replace `${name}` with your piece name.
```bash
npm run pieces -- build --name=${name}
```
<Info>
More information about building pieces can be found [here](../misc/build-piece).
</Info>
</Step>
<Step title="Upload Tarball">
Upload the generated tarball inside `dist/packages/pieces/${name}`from Activepieces Platform Admin -> Pieces
![Manage Pieces](/resources/screenshots/install-piece.png)
</Step>
</Steps>