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
},
});
collection. add_action (
'Send email' ,
BaseAction . new ( scope: ActionScope :: SINGLE ) do | context , result_builder |
user = context. get_record ([ 'email' , 'name' ])
# Process single user
end
)
collection. action 'Send email' , scope: :single do
execute do
user = record ([ 'email' , 'name' ])
# Process single user
end
end
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
},
});
collection. add_action (
'Archive selected' ,
BaseAction . new ( scope: ActionScope :: BULK ) do | context , result_builder |
orders = context. get_records ([ 'id' ])
# Process all selected orders
end
)
collection. action 'Archive selected' , scope: :bulk do
execute do
orders = records ([ 'id' ])
# Process all selected orders
end
end
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
},
});
collection. add_action (
'Import data' ,
BaseAction . new ( scope: ActionScope :: GLOBAL ) do | context , result_builder |
file = context. form_values [ 'file' ]
# Process import for entire collection
end
)
collection. action 'Import data' , scope: :global do
execute do
file = form_value ( :file )
# Process import for entire collection
end
end
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 ();
const users = await context . getRecords ([ 'id' , 'email' ]);
// Returns array of records
const ids = await context . getRecordIds ();
// Returns array of IDs: [1, 2, 3, ...]
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>
` ,
});
},
});
collection. add_action (
'Display customer info' ,
BaseAction . new ( scope: ActionScope :: SINGLE ) do | context , result_builder |
# Get specific fields
customer = context. get_record ([
'firstName' ,
'lastName' ,
'email' ,
'company:name' # Relation field
])
result_builder. 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>"
)
end
)
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!' );
},
});
collection. add_action (
'Mark as live' ,
BaseAction . new ( scope: ActionScope :: SINGLE ) do | context , result_builder |
# Update using the filter
context. collection . update (context. filter , {
'status' => 'live' ,
'liveAt' => Time . now
})
result_builder. success ( 'Company is now live!' )
end
)
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>
` ,
});
}
},
});
collection. add_action (
'Send notifications' ,
BaseAction . new ( scope: ActionScope :: BULK ) do | context , result_builder |
users = context. get_records ([ 'id' , 'email' , 'name' ])
results = { success: [], failed: [] }
users. each do | user |
begin
send_notification (user[ 'email' ], user[ 'name' ])
results[ :success ] << user[ 'name' ]
rescue => error
results[ :failed ] << user[ 'name' ]
end
end
if results[ :failed ]. empty?
result_builder. success ( "Sent #{ results[ :success ]. length } notifications" )
else
result_builder. error (
'Some notifications failed' ,
html: "<p>Success: #{ results[ :success ]. length } </p>
<p>Failed: #{ results[ :failed ]. length } </p>
<p>Failed users: #{ results[ :failed ]. join ( ', ' ) } </p>"
)
end
end
)
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 } ` );
},
});
collection. add_action (
'Assign to me' ,
BaseAction . new ( scope: ActionScope :: SINGLE ) do | context , result_builder |
ticket_id = context. get_record_id
# Assign ticket to current user
context. collection . update (context. filter , {
'assignedTo' => context. caller . id ,
'assignedAt' => Time . now
})
result_builder. success ( "Ticket assigned to #{ context. caller . email } " )
end
)
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' );
},
});
collection. add_action (
'Export filtered data' ,
BaseAction . new (
scope: ActionScope :: GLOBAL ,
is_generate_file: true
) do | context , result_builder |
# Get records matching current filters
records = context. collection . list (
context. filter ,
[ 'id' , 'name' , 'email' , 'status' ]
)
# Generate CSV
csv = generate_csv (records)
result_builder. file ( content: csv, name: 'export.csv' , mime_type: 'text/csv' )
end
)