Docs / Trigger Framework
Trigger Framework

Trigger, Event & Scheduler Bindings

Connect pipelines to Salesforce record events, Platform Events, and cron schedules — all through declarative metadata. One line of trigger code, zero pipeline-specific Apex.

The idea: Instead of writing custom trigger logic for every pipeline, FlowMason uses binding records (Custom Metadata) to declare which pipeline runs in response to which event. The trigger itself is a one-liner — all routing and orchestration happens in the framework.

Record triggers — bind a pipeline to SObject events

First, write the trigger. This is the only trigger code you write — ever — for any FlowMason pipeline on this SObject:

Apex — AccountTrigger.trigger
// The only trigger code you'll ever write for FlowMason:
trigger AccountTrigger on Account (after insert, after update, after delete) {
    FMTriggerFramework.dispatch();
}

// That's it. The binding in FM_Trigger_Binding__mdt takes it from here.

Then create a binding record in FM_Trigger_Binding__mdt. You can do this through Setup → Custom Metadata Types, or deploy it as SFDX metadata:

XML — FM_Trigger_Binding.Account_Summarize_OnInsert.md-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <label>Account AI Summary on Insert</label>
    <values>
        <field>SObject_API_Name__c</field>
        <value xsi:type="xsd:string">Account</value>
    </values>
    <values>
        <field>Trigger_Event__c</field>
        <value xsi:type="xsd:string">after_insert,after_update</value>
    </values>
    <values>
        <field>Pipeline_Id__c</field>
        <value xsi:type="xsd:string">account-summarize-v1</value>
    </values>
    <values>
        <field>Error_Mode__c</field>
        <value xsi:type="xsd:string">continue</value>
    </values>
    <values>
        <field>Execution_Mode__c</field>
        <value xsi:type="xsd:string">async</value>
    </values>
    <values>
        <field>Is_Active__c</field>
        <value xsi:type="xsd:boolean">true</value>
    </values>
</CustomMetadata>

Binding fields reference

FieldRequiredValues
SObject_API_Name__cYesAccount, Case, My_Object__c, etc.
Trigger_Event__cYesComma-separated: after_insert, after_update, after_delete, before_insert, before_update
Pipeline_Id__cYesThe pipeline's DeveloperName or custom object Id
Execution_Mode__cNoasync (default) — enqueue Queueable per record; sync — run inline; bulk — single Queueable for the whole batch
Error_Mode__cNocontinue (default) — log and skip; abort — throw and rollback; mark — call addError on the record
Is_Active__cYestrue / false

The trigger input shape

Inside your pipeline, the trigger provides a rich input context — no SOQL query needed to get the triggering record's fields:

Pipeline template expressions
// Inside your pipeline, the trigger provides this input shape:
// {{input.recordId}}         — Id of the triggering record
// {{input.record.Name}}      — field value from the triggering record
// {{input.record.Stage__c}}  — any field on the new record state
// {{input.oldRecord.Stage__c}} — field value before the update (for update events)
// {{input.operation}}        — "insert" | "update" | "delete"
// {{input.objectType}}       — "Account" | "Case" | etc.

// Example pipeline stage config using trigger input:
// prompt: "Summarize this account: {{input.record.Name}} - {{input.record.Industry}}"

Execution modes explained

  • async (default): The framework enqueues one PipelineQueueable per record. Each runs in its own transaction, with its own governor limits. This is the right choice for most AI workloads — callouts aren't allowed in the synchronous trigger context anyway.
  • sync: Runs the pipeline inline within the trigger transaction. Only viable for pipelines with no HTTP callouts (pure data transformation). Watch governor limits closely.
  • bulk: Enqueues a single PipelineQueueable for the whole batch of records. Saves callout budget on bulk data loads. The pipeline receives {{input.records}} as a list.

Platform Events — react to published events

Connect a pipeline to any Platform Event using FM_Event_Binding__mdt and FMEventFramework.dispatch(). The pattern is identical to trigger bindings.

XML — FM_Event_Binding.Order_Placed_Fulfillment.md-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <label>Order Placed to Fulfillment Pipeline</label>
    <values>
        <field>Event_API_Name__c</field>
        <value xsi:type="xsd:string">OrderPlaced__e</value>
    </values>
    <values>
        <field>Pipeline_Id__c</field>
        <value xsi:type="xsd:string">order-fulfillment-v1</value>
    </values>
    <values>
        <field>Execution_Mode__c</field>
        <value xsi:type="xsd:string">async</value>
    </values>
    <values>
        <field>Is_Active__c</field>
        <value xsi:type="xsd:boolean">true</value>
    </values>
</CustomMetadata>
Apex — Platform Event trigger
// One-liner trigger on the Platform Event:
trigger OrderPlacedTrigger on OrderPlaced__e (after insert) {
    FMEventFramework.dispatch();
}

// Inside the pipeline, the event provides:
// {{input.event.OrderId__c}}     — event field
// {{input.eventName}}            — "OrderPlaced__e"
// {{input.replayId}}             — platform event replay Id

Scheduled runs — cron-driven pipelines

Schedule any pipeline to run on a cron expression. This is useful for nightly enrichment jobs, weekly report generation, or any periodic AI workload. The scheduler reads from FM_Schedule_Binding__mdt to resolve which pipeline to run at fire time.

Apex — FMScheduler
// Schedule a pipeline to run on a cron expression:
FMScheduler.schedule('nightly-lead-enrichment', '0 0 2 * * ?');

// List what's scheduled:
List<FMScheduler.JobInfo> jobs = FMScheduler.listScheduled();
for (FMScheduler.JobInfo job : jobs) {
    System.debug(job.jobName + ' — next fire: ' + job.nextFireTime);
}

// Stop a schedule:
FMScheduler.unschedule('nightly-lead-enrichment');
XML — FM_Schedule_Binding.Nightly_Lead_Enrichment.md-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <label>Nightly Lead Enrichment</label>
    <values>
        <field>Job_Name__c</field>
        <value xsi:type="xsd:string">nightly-lead-enrichment</value>
    </values>
    <values>
        <field>Pipeline_Id__c</field>
        <value xsi:type="xsd:string">lead-enrich-v1</value>
    </values>
    <values>
        <field>Input_JSON__c</field>
        <value xsi:type="xsd:string">{"batchSize": 200, "segment": "new"}</value>
    </values>
    <values>
        <field>Is_Active__c</field>
        <value xsi:type="xsd:boolean">true</value>
    </values>
</CustomMetadata>

Error modes

When a pipeline fails inside a trigger, Error_Mode__c controls what happens next:

  • continue (default): Log the error to Pipeline_Stage_Log__c and move on to the next record. The triggering transaction succeeds. Good for non-critical enrichment (summarization, tagging).
  • abort: Rethrow the exception, rolling back the transaction. Use when the AI step is load-bearing for data integrity.
  • mark: Call addError() on the specific record that failed. Blocks only that record in the batch — others succeed. Useful when you need to surface the failure to the user in real time.

Troubleshooting

Pipeline isn't firing on record save
  • Check that Is_Active__c is true on the binding record
  • Verify the Trigger_Event__c value matches the actual operation (after_insert for new records, after_update for edits)
  • Confirm the trigger on the SObject calls FMTriggerFramework.dispatch()
Can't see the pipeline execution in PipelineExecution__c
Async executions are created by a Queueable that runs after the trigger transaction commits. There may be a small delay. Check the Apex Jobs page in Setup to see if the Queueable is queued or running.
GovernorLimitException in sync execution mode
Sync mode runs inside the trigger transaction and shares its governor limits. LLM callouts require async mode — HTTP callouts aren't allowed in synchronous trigger contexts. Switch Execution_Mode__c to async.

What's next