Actions have three scopes that determine how they trigger and which records they target. The context object provides access to form values, selected records, and user information.
Action scopes
Scope Targets Triggered from list view Triggered from detail view Single One record at a time When one record is selected ✅ Yes Bulk Multiple selected records When one or more records are selected ✅ Yes Global Your choice among all records ✅ Always available ❌ No
Single scope
Execute on one specific record.
Node.js / Cloud
Ruby
Ruby DSL
collection . addAction ( 'Send email' , {
scope: 'Single' ,
execute : async ( context , resultBuilder ) => {
const user = await context . getRecord ([ 'email' , 'name' ]);
// Process single user
},
});
Use when:
Sending an email to a specific user
Generating an invoice for one order
Viewing details of a single item
Resetting a user’s password
Context methods:
context.getRecord(fieldNames) - Get the selected record
context.getRecordId() - Get the record ID
Bulk scope
Execute on multiple selected records simultaneously.
Node.js / Cloud
Ruby
Ruby DSL
collection . addAction ( 'Archive selected' , {
scope: 'Bulk' ,
execute : async ( context , resultBuilder ) => {
const orders = await context . getRecords ([ 'id' ]);
// Process all selected orders
},
});
Use when:
Archiving multiple items
Updating status of several records
Sending mass emails
Bulk deleting records
Context methods:
context.getRecords(fieldNames) - Get all selected records
context.getRecordIds() - Get array of record IDs
Handle failures gracefully in bulk actions. Decide whether to stop on first error or continue processing remaining records.
Global scope
Execute at collection level without selecting specific records.
Node.js / Cloud
Ruby
Ruby DSL
collection . addAction ( 'Import data' , {
scope: 'Global' ,
execute : async ( context , resultBuilder ) => {
const { file } = context . formValues ;
// Process import for entire collection
},
});
Use when:
Importing data from CSV
Generating collection-wide reports
Syncing with external services
Running maintenance tasks
Context methods:
context.filter - Access current filters and search
context.collection - Query the collection
No specific records are pre-selected
The context object
The context object is passed as the first argument to the execute handler and provides access to all action data.
Access values entered by the user in the form:
const { Amount , Description } = context . formValues ;
// With spaces in label
const firstName = context . formValues [ 'First Name' ];
// By field id (if specified)
const email = context . formValues [ 'email' ];
Selected records
Get data from the records the action is running on:
const user = await context . getRecord ([ 'id' , 'email' , 'name' ]);
console . log ( user . id , user . email , user . name );
// Get just the ID
const userId = await context . getRecordId ();
Current user
Access information about who triggered the action:
const userId = context . caller . id ;
const userEmail = context . caller . email ;
const userRole = context . caller . role ;
const userTeam = context . caller . team ;
const userTimezone = context . caller . timezone ;
Access the collection schema and query interface:
const collectionName = context . collection . name ;
const fields = context . collection . schema . fields ;
// Query the collection
const records = await context . collection . list ( filter , projection );
Filters
For Bulk and Global actions, access current filters from the UI:
const filter = context . filter ;
// Use filter to query matching records
const matchingRecords = await context . collection . list ( filter , projection );
This filter represents the current segment, search, and filters applied in the Forest interface.
Change detection
Check if a form field value has changed (useful for dynamic forms):
if ( context . hasFieldChanged ( 'Status' )) {
// Status field was modified by user
const newStatus = context . formValues . Status ;
}
Examples
Example: Access record data
collection . addAction ( 'Display customer info' , {
scope: 'Single' ,
execute : async ( context , resultBuilder ) => {
// Get specific fields
const customer = await context . getRecord ([
'firstName' ,
'lastName' ,
'email' ,
'company:name' // Relation field
]);
return resultBuilder . success ( 'Customer info' , {
html: `
<p><strong>Name:</strong> ${ customer . firstName } ${ customer . lastName } </p>
<p><strong>Email:</strong> ${ customer . email } </p>
<p><strong>Company:</strong> ${ customer . company . name } </p>
` ,
});
},
});
Example: Update selected records
collection . addAction ( 'Mark as live' , {
scope: 'Single' ,
execute : async ( context , resultBuilder ) => {
// Update using the filter
await context . collection . update ( context . filter , {
status: 'live' ,
liveAt: new Date (),
});
return resultBuilder . success ( 'Company is now live!' );
},
});
Example: Bulk processing with error handling
collection . addAction ( 'Send notifications' , {
scope: 'Bulk' ,
execute : async ( context , resultBuilder ) => {
const users = await context . getRecords ([ 'id' , 'email' , 'name' ]);
const results = { success: [], failed: [] };
for ( const user of users ) {
try {
await sendNotification ( user . email , user . name );
results . success . push ( user . name );
} catch ( error ) {
results . failed . push ( user . name );
}
}
if ( results . failed . length === 0 ) {
return resultBuilder . success (
`Sent ${ results . success . length } notifications`
);
} else {
return resultBuilder . error ( 'Some notifications failed' , {
html: `
<p>Success: ${ results . success . length } </p>
<p>Failed: ${ results . failed . length } </p>
<p>Failed users: ${ results . failed . join ( ', ' ) } </p>
` ,
});
}
},
});
Example: Use user context
collection . addAction ( 'Assign to me' , {
scope: 'Single' ,
execute : async ( context , resultBuilder ) => {
const ticketId = await context . getRecordId ();
// Assign ticket to current user
await context . collection . update ( context . filter , {
assignedTo: context . caller . id ,
assignedAt: new Date (),
});
return resultBuilder . success ( `Ticket assigned to ${ context . caller . email } ` );
},
});
Example: Global action with filters
collection . addAction ( 'Export filtered data' , {
scope: 'Global' ,
generateFile: true ,
execute : async ( context , resultBuilder ) => {
// Get records matching current filters
const records = await context . collection . list (
context . filter ,
[ 'id' , 'name' , 'email' , 'status' ]
);
// Generate CSV
const csv = generateCSV ( records );
return resultBuilder . file ( csv , 'export.csv' , 'text/csv' );
},
});