Custom Actions & Row Operations in actions/*.ts
#
Custom Listing Actions allow developers to attach executable server-side TypeScript functions to custom listing pages. Actions can operate on individual records (via table row dropdown menus) or execute batch operations across multiple selected records (via the floating bulk action bar).
Like fetchers and dynamic filter resolvers, all actions execute inside secure, memory-capped V8 isolate sandboxes with full access to the dolfin CRM & HTTP SDK.
Action Types: Row vs. Bulk (Rows) #
When creating an action in the Custom Functions IDE (actions folder), you select one of two execution modes:
| Action Mode | Trigger Location | Context Input | Best Used For |
|---|---|---|---|
Single-Row Action ("row") | Table row kebab menu (...) | context.row (single record) | Changing status, sending an email to a lead, syncing a single record to an ERP, opening details. |
Bulk Action ("rows") | Floating Bulk Action Bar | context.rows (array of selected records) | Batch status updates, mass tagging, exporting selected records, batch webhook dispatch. |
Primary Key Configuration (isPrimaryKey)
#
For the listing table to track selected rows, render multi-select checkboxes, and correctly identify records during action execution, at least one column in your filters.ts should be designated as the primary key using isPrimaryKey: true:
// filters.ts
export const columns = defineColumns([
{ key: "id", label: "ID", type: "number", width: 80, isPrimaryKey: true },
{ key: "name", label: "Customer Name", type: "text" },
{ key: "email", label: "Email Address", type: "text" },
{ key: "status", label: "Status", type: "badge" },
]);
If no column has isPrimaryKey: true, the table defaults to using the id field. Always mark isPrimaryKey: true explicitly when using custom unique identifiers (e.g. uuid, code, or leadId).
Single-Row Actions (type: "row")
#
Single-row actions export an asynchronous run function that receives CustomListRowActionContext and DolfinSdk.
Context Signature #
interface CustomListRowActionContext {
type: "row";
/** Strongly typed single record data inferred from columns schema in filters.ts */
row: __ListingRow;
/** Active filter values currently selected in the UI */
filter: __ListingFilters;
/** Active tenant identifier */
tenantId: number;
/** Target module identifier (or null for standalone listing pages) */
moduleId: number | null;
/** Decrypted secrets scoped to this listing page */
secrets: CustomSecrets;
}
Single-Row Action Example: Qualify Lead & Log Note #
// actions/qualify-lead.ts
export async function run(
context: CustomListRowActionContext,
dolfin: DolfinSdk
): Promise<CustomListActionResponse> {
const { row, moduleId } = context;
if (!moduleId) {
throw new Error("Module ID is required to execute this action.");
}
// 1. Access module CRM API
const crm = await dolfin.crm.module(moduleId);
// 2. Update lead status in CRM
const updatedLead = await crm.leads.update(Number(row.id), {
meta: { status: "qualified" },
});
// 3. Append internal note
await crm.leads.notes.add(
Number(row.id),
`Lead marked as Qualified by custom listing action.`
);
// 4. Return user feedback & trigger table refresh
return {
success: true,
message: `Lead "${row.name}" successfully qualified!`,
refresh: true, // Automatically reloads the listing table data
};
}
Bulk Actions (type: "rows")
#
Bulk actions execute when users select one or more records using the table checkboxes and click an action in the floating action bar.
Context Signature #
interface CustomListRowsActionContext {
type: "rows";
/** Strongly typed array of selected records inferred from columns schema in filters.ts */
rows: __ListingRow[];
/** Active filter values currently selected in the UI */
filter: __ListingFilters;
/** Active tenant identifier */
tenantId: number;
/** Target module identifier (or null for standalone listing pages) */
moduleId: number | null;
/** Decrypted secrets scoped to this listing page */
secrets: CustomSecrets;
}
Bulk Action Example: Batch Webhook Sync #
// actions/bulk-sync.ts
export async function run(
context: CustomListRowsActionContext,
dolfin: DolfinSdk
): Promise<CustomListActionResponse> {
const { rows } = context;
if (rows.length === 0) {
return { success: false, message: "No rows selected." };
}
// 1. Dispatch batch webhook to external synchronization service
const response = await dolfin.http.post(
"https://api.external-erp.example.com/v1/batch-sync",
{
items: rows.map((r) => ({
id: r.id,
name: r.name,
email: r.email,
})),
},
{
headers: {
"Authorization": `Bearer ${context.secrets.ERP_API_KEY}`,
"Content-Type": "application/json",
},
timeoutMs: 8000,
}
);
// 2. Return toast notification and trigger table refresh
return {
success: true,
message: `Successfully synchronized ${rows.length} records to external ERP!`,
refresh: true,
};
}
Action Response Contract (CustomListActionResponse)
#
Your action's run function should return a CustomListActionResponse object to provide user feedback:
interface CustomListActionResponse {
/** Indicates whether the action completed successfully (defaults to true) */
success?: boolean;
/** Message displayed to the user in a toast notification */
message?: string;
/** When true, the frontend automatically reloads the table data after execution */
refresh?: boolean;
/** Optional URL path to navigate the user to after completion */
redirectUrl?: string;
}
Toast Feedback & Table Reloading #
- When
success: true(or omitted), the frontend displays a Success Toast with your custommessage. - When
success: falseor if an unhandled error is thrown, the frontend displays an Error Toast with the failure message. - Setting
refresh: truecauses the listing table to automatically refetch data using the active filters, reflecting any database changes instantly.
Authoring Actions in the IDE #
- Open your Custom Listing Page in the Custom Functions IDE.
- In the explorer sidebar, expand the
actionsfolder under your listing page. - Click the
+(New Action) icon on theactionsfolder. - Fill in the action details in the modal:
- Action Name: Human-readable label (e.g. Qualify Lead or Export Selected).
- Action Key / File: Filename identifier (e.g.
qualify-lead.ts). - Action Mode: Choose Single Row (
row) or Bulk Selected Rows (rows).
- The IDE generates a starter template pre-typed for your action mode.
- Write your custom logic and press
Ctrl+Sto save and compile.