trigger-refactor-pipeline

Refactor Salesforce triggers into handler patterns with automated test generation and deployment. Use when modernizing legacy triggers with DML/SOQL in loops…

INSTALLATION
npx skills add https://github.com/forcedotcom/afv-library --skill trigger-refactor-pipeline
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

When to Use This Skill

Use this skill when you need to:

  • Modernize legacy triggers with DML/SOQL operations inside loops
  • Refactor triggers that lack clear separation of concerns
  • Implement bulk-safe patterns in existing trigger code
  • Generate comprehensive test coverage for refactored triggers

Prerequisites

Before starting, ensure you have:

  • Salesforce CLI installed and authenticated to your target org
  • Python 3.9 or higher installed
  • The baseline trigger deployed (see Setup section)

Setup

Deploy the baseline anti-pattern trigger to analyze and refactor:

// ❌ Anti-pattern: all logic stuffed into the trigger, with DML/SOQL in loops.

trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {

    // BEFORE INSERT: validate Closed Won w/ low Amount

    if (Trigger.isBefore && Trigger.isInsert) {

        for (Opportunity o : Trigger.new) {

            if (o.StageName == 'Closed Won' &#x26;&#x26; (o.Amount == null || o.Amount < 1000)) {

                o.addError('Closed Won opportunities must have Amount ≥ 1000.');

            }

        }

    }

    // BEFORE UPDATE: if Stage changed, overwrite Description

    if (Trigger.isBefore &#x26;&#x26; Trigger.isUpdate) {

        for (Opportunity o : Trigger.new) {

            Opportunity oldO = Trigger.oldMap.get(o.Id);

            if (o.StageName != oldO.StageName) {

                o.Description = 'Stage changed from ' + oldO.StageName + ' to ' + o.StageName;

            }

        }

    }

    // AFTER UPDATE: when Stage becomes Closed Won, create a follow-up Task

    if (Trigger.isAfter &#x26;&#x26; Trigger.isUpdate) {

        for (Opportunity o : Trigger.new) {

            Opportunity oldO = Trigger.oldMap.get(o.Id);

            if (o.StageName == 'Closed Won' &#x26;&#x26; oldO.StageName != 'Closed Won') {

                Task t = new Task(

                    WhatId     = o.Id,

                    OwnerId    = o.OwnerId,

                    Subject    = 'Send thank-you',

                    Status     = 'Not Started',

                    Priority   = 'Normal',

                    ActivityDate = Date.today()

                );

                insert t; // ❌ DML in a loop

            }

        }

    }

}

Deploy this to your org:

sf project deploy start --source-dir force-app/main/default/triggers

Step 1: Analyze the Trigger

Run the analysis script to identify anti-patterns and generate a report:

python scripts/analyze_trigger.py OpportunityTrigger

The script will output:

  • DML in loops - Line numbers where DML operations occur inside iteration
  • SOQL in loops - Line numbers where SOQL queries occur inside iteration
  • Missing bulkification - Areas where collection-based processing is needed
  • Complexity score - Overall trigger complexity rating (1-10)
  • Recommended approach - Suggested handler pattern based on trigger contexts

Review the analysis report before proceeding to refactoring.

Step 2: Review Handler Patterns

Consult the handler patterns reference to understand:

  • Single-responsibility handlers - One handler class per trigger context
  • Unified handler approach - Single handler with context methods
  • Bulk collection strategies - How to aggregate DML/SOQL outside loops
  • Best practices - Error handling, test boundaries, deployment order

Choose the pattern that best fits your trigger's complexity and team conventions.

Step 3: Refactor the Trigger

Create the handler class using the appropriate pattern from the reference guide:

  • Extract logic into handler methods with descriptive names
  • Implement bulk-safe collections for DML operations
  • Add proper error handling using try-catch or Database methods
  • Update the trigger to delegate only, passing Trigger context variables
  • Preserve behavior - ensure the refactored code produces identical results

The trigger should be reduced to simple delegation:

trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {

    OpportunityTriggerHandler handler = new OpportunityTriggerHandler();

    if (Trigger.isBefore &#x26;&#x26; Trigger.isInsert) {

        handler.beforeInsert(Trigger.new);

    }

    if (Trigger.isBefore &#x26;&#x26; Trigger.isUpdate) {

        handler.beforeUpdate(Trigger.new, Trigger.oldMap);

    }

    if (Trigger.isAfter &#x26;&#x26; Trigger.isUpdate) {

        handler.afterUpdate(Trigger.new, Trigger.oldMap);

    }

}

Step 4: Generate Tests

Use the test template from assets/test_template.apex to scaffold your test class:

  • Copy the template and rename for your handler
  • Implement setup methods to create test data
  • Write unit tests covering each handler method:
  • Positive cases with valid data
  • Negative cases with invalid data
  • Boundary conditions
  • Add bulk tests with 200+ records to verify bulkification
  • Test mixed scenarios where only some records qualify for logic

Required test coverage:

  • Each handler method must have at least 2 test methods (positive + negative)
  • At least one bulk test with 200+ records
  • Overall code coverage must be 100%

Step 5: Deploy and Validate

Deploy the refactored trigger, handler, and tests:

# Deploy all components

sf project deploy start --source-dir force-app/main/default

# Run tests

sf apex test run --class-names OpportunityTriggerHandlerTest --result-format human --code-coverage

# Verify no regressions

sf apex test run --test-level RunLocalTests --result-format human

Validation checklist:

  • All new tests pass with 100% coverage
  • No new governor limit warnings in debug logs
  • Existing functionality remains unchanged
  • Deployment to production planned with rollback strategy

Troubleshooting

Issue: Tests fail with "System.LimitException: Too many DML statements"

  • Solution: Ensure handler methods collect DML operations and execute outside loops

Issue: Code coverage below 100%

  • Solution: Add negative test cases and verify all conditional branches are tested

Issue: Behavior differs from original trigger

  • Solution: Review Trigger context variables (new, old, oldMap) are passed correctly to handler

Next Steps

After successful refactoring:

  • Document the new handler pattern in your team's wiki
  • Update code review checklist to enforce handler patterns for new triggers
  • Identify other legacy triggers for refactoring using this skill
  • Consider implementing a trigger framework if managing many triggers
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card