Skip to main content

What are Actions?

Actions are operations that users can perform in their flows. They take inputs (props), execute logic, and return outputs. Think of them as functions that can be called from automation workflows. Examples:
  • Send a message to Slack
  • Create an issue in GitHub
  • Upload a file to Google Drive
  • Query a database

Creating Your First Action

Use the CLI to generate an action:
npm run create-action
You’ll be prompted for:
  • Piece name: Which piece to add the action to
  • Action name: Unique identifier (e.g., send_message)
  • Display name: Human-readable name (e.g., Send Message)
  • Description: What the action does

Action Structure

Here’s a real example from the GitHub piece:
packages/pieces/community/github/src/lib/actions/create-issue.ts
import { createAction, Property } from '@activepieces/pieces-framework';
import { githubAuth } from '../auth';
import { githubCommon } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';

export const githubCreateIssueAction = createAction({
  // Authentication requirement
  auth: githubAuth,
  
  // Unique identifier
  name: 'github_create_issue',
  
  // Display information
  displayName: 'Create Issue',
  description: 'Create Issue in GitHub Repository',
  
  // Input properties
  props: {
    repository: githubCommon.repositoryDropdown,
    title: Property.ShortText({
      displayName: 'Title',
      description: 'The title of the issue',
      required: true,
    }),
    description: Property.LongText({
      displayName: 'Description',
      description: 'The description of the issue',
      required: false,
    }),
    labels: githubCommon.labelDropDown(),
    assignees: githubCommon.assigneeDropDown(),
  },
  
  // Execution logic
  async run({ auth, propsValue }) {
    const { title, assignees, labels, description } = propsValue;
    const { owner, repo } = propsValue.repository;

    const issueFields = {
      title,
      body: description,
      labels: labels || [],
      assignees: assignees || [],
    };

    const response = await githubApiCall({
      accessToken: auth.access_token,
      method: HttpMethod.POST,
      resourceUri: `/repos/${owner}/${repo}/issues`,
      body: issueFields,
    });

    return response;
  },
});

Action Configuration

name
string
required
Unique identifier for the action within the piece. Use snake_case.Example: send_message, create_issue, upload_file
displayName
string
required
Human-readable name shown in the UI. Use Title Case.Example: Send Message, Create Issue, Upload File
description
string
required
Brief description of what the action does. Shown as a tooltip.Example: Send a message to a Slack channel
auth
PieceAuth
Authentication configuration. References the piece’s auth.
auth: slackAuth
props
Record<string, Property>
required
Input properties that users configure. See Properties.
props: {
  channel: Property.ShortText({
    displayName: 'Channel',
    required: true,
  }),
  message: Property.LongText({
    displayName: 'Message',
    required: true,
  }),
}
run
function
required
The main execution function. Receives context with auth and prop values.
async run(context) {
  const { auth, propsValue } = context;
  // Your logic here
  return result;
}
test
function
Optional test function for validating the action works. If not provided, run is used for testing.
requireAuth
boolean
default:"true"
Whether authentication is required. Set to false for public APIs.

Complete Action Examples

import { createAction, Property } from '@activepieces/pieces-framework';
import { slackAuth } from '../auth';

export const slackSendMessageAction = createAction({
  auth: slackAuth,
  name: 'send_channel_message',
  displayName: 'Send Message To A Channel',
  description: 'Send message to a channel',
  
  props: {
    channel: Property.Dropdown({
      displayName: 'Channel',
      required: true,
      refreshers: [],
      options: async ({ auth }) => {
        // Fetch channels from Slack API
        const response = await fetch(
          'https://slack.com/api/conversations.list',
          {
            headers: {
              Authorization: `Bearer ${auth.access_token}`,
            },
          }
        );
        const data = await response.json();
        
        return {
          options: data.channels.map((channel) => ({
            label: channel.name,
            value: channel.id,
          })),
        };
      },
    }),
    text: Property.LongText({
      displayName: 'Message',
      description: 'The text of your message',
      required: true,
    }),
    threadTs: Property.ShortText({
      displayName: 'Thread Timestamp',
      description: 'Reply to a thread by providing the parent message timestamp',
      required: false,
    }),
  },
  
  async run(context) {
    const { channel, text, threadTs } = context.propsValue;
    const token = context.auth.access_token;

    const response = await fetch(
      'https://slack.com/api/chat.postMessage',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          channel,
          text,
          thread_ts: threadTs,
        }),
      }
    );

    return await response.json();
  },
});

Action Context

The context object passed to your run function contains:
interface ActionContext {
  // User's authenticated connection
  auth: OAuth2PropertyValue | string | CustomAuthValue;
  
  // User-configured property values
  propsValue: {
    [key: string]: any;
  };
  
  // Platform services
  store: {
    get<T>(key: string): Promise<T | null>;
    put<T>(key: string, value: T): Promise<void>;
    delete(key: string): Promise<void>;
  };
  
  // File operations
  files: {
    write(data: Buffer): Promise<string>;
  };
  
  // Server configuration
  server: {
    apiUrl: string;
    publicUrl: string;
  };
}

Using Context

// OAuth2
const token = context.auth.access_token;
const userData = context.auth.data;

// Secret Text (API Key)
const apiKey = context.auth;

// Custom Auth
const { apiKey, apiSecret } = context.auth;

Error Handling

Provide clear error messages to help users debug issues:
async run(context) {
  try {
    const response = await fetch(url, options);
    
    if (!response.ok) {
      // Provide specific error message
      const error = await response.json();
      throw new Error(`Failed to create issue: ${error.message}`);
    }
    
    return await response.json();
  } catch (error) {
    // Re-throw with helpful context
    if (error.message.includes('404')) {
      throw new Error('Repository not found. Please check the repository name.');
    }
    if (error.message.includes('401')) {
      throw new Error('Authentication failed. Please reconnect your account.');
    }
    throw error;
  }
}

Testing Actions

Test your action during development:
1

Start Dev Server

npm run dev
2

Create Test Flow

  1. Go to http://localhost:4200
  2. Create a new flow
  3. Add your action as a step
3

Configure & Test

  1. Fill in the required properties
  2. Click “Test” button
  3. Check the output
4

Iterate

Make changes to your action code - they’ll be reflected immediately thanks to hot reloading

Best Practices

Return structured data that can be used in subsequent steps:
// Good: Structured output
return {
  id: response.id,
  url: response.url,
  created_at: response.created_at,
};

// Bad: Raw response
return response;
Check inputs before making API calls:
if (!context.propsValue.email.includes('@')) {
  throw new Error('Invalid email address');
}

if (context.propsValue.quantity < 1) {
  throw new Error('Quantity must be at least 1');
}
Make property names clear and self-documenting:
// Good
props: {
  channelId: Property.ShortText({
    displayName: 'Channel ID',
    description: 'The Slack channel ID (e.g., C1234567890)',
    required: true,
  }),
}

// Bad
props: {
  id: Property.ShortText({
    displayName: 'ID',
    required: true,
  }),
}
Implement retry logic for rate-limited APIs:
import { httpClient } from '@activepieces/pieces-common';

const response = await httpClient.sendRequest({
  method: HttpMethod.POST,
  url: apiUrl,
  headers: headers,
  body: body,
  // Automatically retry on rate limit
  retries: 3,
  retryDelay: 1000,
});

Next Steps

Create Triggers

Learn how to create triggers

Properties Guide

Explore all property types

Testing

Write tests for your actions