At Zaghop Consulting, we live and breathe Salesforce. Whether we're building custom apps, integrating external systems, or helping clients streamline operations, we always keep one thing in mind: Salesforce is a multitenant platform - and that means rules. Lots of them.
If you're a developer transitioning from traditional web development to Salesforce, these “governor limits” can feel like hidden traps. And even seasoned developers can get burned by edge cases. Here’s a breakdown of the key limits and gotchas we navigate every day - and how to avoid them.
You can only perform 150 DML operations (e.g., insert, update, delete, undelete) per transaction. Sounds like plenty - until you’re working in a loop or invoking multiple triggers.
Gotcha: Bulk triggers that do one DML per record will blow this limit fast.
Fix: Use List<Record>
and perform bulk DML outside the loop.
Salesforce allows up to 100 SOQL queries per transaction.
Gotcha: Triggers calling helper methods that perform queries in loops.
Fix: Move SOQL out of loops, leverage maps to cache lookups, and combine queries where possible.
Each transaction has a CPU time limit - 10 seconds for synchronous Apex.
Gotcha: Complex logic, large data sets, or nested loops can silently push you over this threshold.
Fix: Refactor into queueable or batch jobs, offload to asynchronous methods, and avoid inefficient recursive logic.
This governs how much memory your transaction can use in RAM.
Gotcha: Too many large objects, especially when deserializing JSON or querying rich relationships.
Fix: Query only required fields, paginate data, and clear large objects after use.
You can only enqueue one queueable job from another queueable context.
Gotcha: You can't dynamically spawn multiple queueables from within a queueable job.
Fix: Chain just one, or escalate to batch jobs for more flexibility.
@future
methods are convenient, but you can only call 50 of them in one go.
Gotcha: Calling one @future
method inside a loop can hit this quickly.
Fix: Avoid future-in-loops, use batch/queueable instead, or combine calls into a single method.
You can’t call certain async operations like System.enqueueJob
or Database.executeBatch
after performing a DML operation in the same transaction.
Gotcha: Calling an async method from within a trigger or after a DML statement.
Fix: Move the async call into a post-commit logic layer (like a Platform Event, Change Data Capture, or Scheduled Job).
Gotcha: External integrations that require a callout after save logic.
Fix: Use @future(callout=true)
or move to queueable architecture.
Salesforce doesn’t prevent infinite loops in triggers - it’s on you.
Gotcha: An update trigger causes another update, and so on.
Fix: Use static variables in Apex to track and prevent recursion.
Field history tracking is limited to 20 fields per object.
Gotcha: Clients assume every field can be audited.
Fix: Use custom objects or third-party audit tools for full change logs.
Gotcha: Tests pass locally but fail in deployment due to shared limits.
Fix: Break up tests, use Test.startTest()
and Test.stopTest()
, and always assert selectively.
If you're building managed packages or AppExchange apps, cross-namespace limits apply.
Gotcha: Accessing fields, classes, or methods across namespaces requires explicit exposure.
Fix: Use global
visibility and careful API design for extension.
Salesforce development isn’t about coding without limits - it’s about coding within constraints. That’s the art. At Zaghop, we see these limits not as roadblocks, but as signals - cues to write more efficient, scalable, and platform-native solutions.
Got a Salesforce “gotcha” that burned you? We’ve probably fixed it. Let’s talk.