Skip to main content
Code-based actions let you implement custom business logic in your back-end code. They provide full control over action behavior, forms, validation, and results. Use code-based actions when you need complex workflows, dynamic forms, external API integrations, file generation, or custom validation that goes beyond what no-code actions can do.
Custom action displayed in a table view

Basic structure

Here’s what a code-based action looks like:
agent.customizeCollection('companies', collection =>
  collection.addAction('Mark as Live', {
    scope: 'Single',
    description: 'Mark the company as live',
    form: [
      {
        type: 'Date',
        label: 'Live date',
        isRequired: true,
      },
      {
        type: 'String',
        label: 'Notes',
        widget: 'TextArea',
      },
    ],
    execute: async (context, resultBuilder) => {
      // Access form values
      const { 'Live date': liveDate, Notes: notes } = context.formValues;

      // Access selected record
      const company = await context.getRecord(['id', 'name']);

      // Perform business logic
      await markCompanyAsLive(company.id, liveDate, notes);

      // Return result
      return resultBuilder.success(`${company.name} is now live!`);
    },
  }),
);

Common examples

Send email

collection.addAction('Send Welcome Email', {
  scope: 'Single',
  form: [
    { type: 'String', label: 'Subject', isRequired: true },
    { type: 'String', label: 'Message', widget: 'TextArea', isRequired: true },
  ],
  execute: async (context, resultBuilder) => {
    const user = await context.getRecord(['email', 'name']);
    const { Subject, Message } = context.formValues;

    try {
      await sendEmail({
        to: user.email,
        subject: Subject,
        body: Message,
      });
      return resultBuilder.success(`Email sent to ${user.email}`);
    } catch (error) {
      return resultBuilder.error(`Failed to send email: ${error.message}`);
    }
  },
});

Charge credit card

collection.addAction('Charge Credit Card', {
  scope: 'Single',
  form: [
    { type: 'Number', label: 'Amount', isRequired: true },
    { type: 'String', label: 'Description', isRequired: true },
  ],
  execute: async (context, resultBuilder) => {
    const customer = await context.getRecord(['stripeId', 'email']);
    const { Amount, Description } = context.formValues;

    try {
      const charge = await stripe.charges.create({
        amount: Amount * 100,
        currency: 'usd',
        customer: customer.stripeId,
        description: Description,
      });

      return resultBuilder.success('Charge successful', {
        html: `
          <p>Charged $${Amount} to ${customer.email}</p>
          <p>Transaction ID: ${charge.id}</p>
        `,
      });
    } catch (error) {
      return resultBuilder.error(`Charge failed: ${error.message}`);
    }
  },
});

Bulk status update

collection.addAction('Update Status', {
  scope: 'Bulk',
  form: [
    {
      type: 'Enum',
      label: 'New Status',
      enumValues: ['pending', 'approved', 'rejected'],
      isRequired: true,
    },
  ],
  execute: async (context, resultBuilder) => {
    const orders = await context.getRecords(['id']);
    const newStatus = context.formValues['New Status'];

    const ids = orders.map(o => o.id);
    await Order.update({ status: newStatus }, { where: { id: ids } });

    return resultBuilder.success(`Updated ${ids.length} orders to ${newStatus}`);
  },
});

Generate report

collection.addAction('Generate Report', {
  scope: 'Global',
  generateFile: true,
  form: [
    { type: 'DateOnly', label: 'Start Date', isRequired: true },
    { type: 'DateOnly', label: 'End Date', isRequired: true },
  ],
  execute: async (context, resultBuilder) => {
    const { 'Start Date': startDate, 'End Date': endDate } = context.formValues;

    const data = await fetchReportData(startDate, endDate);
    const pdf = await generatePDF(data);

    return resultBuilder.file(
      pdf,
      `report-${startDate}-${endDate}.pdf`,
      'application/pdf'
    );
  },
});

Learn more

Forms

Form fields, validation, and dynamic behavior

Result types

All the ways to return feedback to users

Context & scope

Understanding scopes and the context object

Related data invalidation

Refresh related data after actions