apex-dml-patterns
Use when choosing between DML statements and Database class methods, handling partial success, managing savepoints, or using Database.DMLOptions for assignment rules and duplicate handling. Trigger keywords: 'Database.insert allOrNone false', 'partial DML success apex', 'SaveResult isSuccess', 'database merge apex', 'DML exception handling'. NOT for SOQL query patterns (use soql-fundamentals or apex-soql-relationship-queries), NOT for sharing model setup (use apex-managed-sharing-patterns).
Best use case
apex-dml-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when choosing between DML statements and Database class methods, handling partial success, managing savepoints, or using Database.DMLOptions for assignment rules and duplicate handling. Trigger keywords: 'Database.insert allOrNone false', 'partial DML success apex', 'SaveResult isSuccess', 'database merge apex', 'DML exception handling'. NOT for SOQL query patterns (use soql-fundamentals or apex-soql-relationship-queries), NOT for sharing model setup (use apex-managed-sharing-patterns).
Teams using apex-dml-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/apex-dml-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How apex-dml-patterns Compares
| Feature / Agent | apex-dml-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when choosing between DML statements and Database class methods, handling partial success, managing savepoints, or using Database.DMLOptions for assignment rules and duplicate handling. Trigger keywords: 'Database.insert allOrNone false', 'partial DML success apex', 'SaveResult isSuccess', 'database merge apex', 'DML exception handling'. NOT for SOQL query patterns (use soql-fundamentals or apex-soql-relationship-queries), NOT for sharing model setup (use apex-managed-sharing-patterns).
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Apex DML Patterns
Use this skill when selecting between DML statements (`insert`/`update`/`delete`/`upsert`) and `Database` class methods for bulk data operations — especially when partial success, savepoints, duplicate suppression, or assignment rule triggering is needed.
---
## Before Starting
Gather this context before working on anything in this domain:
- Confirm whether partial success is acceptable: if any row failure must roll back all rows, use DML statements; if individual row failures should be tolerated, use `Database.insert(list, false)`.
- Check the 150 DML operations limit: one `Database.insert(list)` call with 200 records counts as **1 DML operation**, not 200.
- Identify whether assignment rules, email headers, or duplicate rules need to be controlled — these require `Database.DMLOptions`.
---
## Core Concepts
### DML statements vs. Database class methods
DML statements (`insert list`) use `allOrNone = true` by default: any row failure throws `DmlException` and rolls back the entire batch. `Database.insert(list, false)` enables partial success: failures on individual rows do **not** roll back the successful rows, but you must check each `SaveResult.isSuccess()` to determine which rows failed.
The governor limit is 150 DML **operations** per transaction — not rows. One `Database.insert(200-record list)` = 1 DML operation. This is the single most common misconception.
### SaveResult, UpsertResult, and error collection
`Database.insert(list, false)` returns `List<Database.SaveResult>`. For each result:
```apex
List<Database.SaveResult> results = Database.insert(records, false);
List<String> errors = new List<String>();
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
for (Database.Error err : sr.getErrors()) {
errors.add(err.getMessage() + ' [' + err.getStatusCode() + ']');
}
}
}
```
`Database.upsert` returns `List<Database.UpsertResult>` with an additional `isCreated()` method distinguishing inserts from updates. `Database.delete` returns `List<Database.DeleteResult>`.
### Savepoint and rollback
`Savepoint sp = Database.setSavepoint()` creates a named rollback point. `Database.rollback(sp)` undoes all DML since that savepoint. Use this for multi-step operations where you want to rollback a logical unit without aborting the entire transaction:
```apex
Savepoint sp = Database.setSavepoint();
try {
Database.insert(headersRecords);
Database.insert(lineItemRecords);
} catch (DmlException e) {
Database.rollback(sp);
throw e;
}
```
Savepoints count against the DML statement limit.
### Database.DMLOptions
`Database.DMLOptions` controls side-effect behavior on a DML operation:
- `opt.assignmentRuleHeader.useDefaultRule = true` — fires the active assignment rule
- `opt.emailHeader.triggerAutoResponseEmail = false` — suppresses auto-response emails
- `opt.duplicateRuleHeader.allowSave = true` — bypasses duplicate rules (use with care)
- `opt.optAllOrNone = false` — equivalent to `allOrNone = false` flag
Pass options as the second argument: `Database.insert(list, opt)`.
---
## Common Patterns
### Bulk insert with partial success and error logging
**When to use:** Mass-creating records (e.g., from an external feed) where some rows may have invalid data and individual failures should not stop valid rows.
**How it works:**
```apex
List<Contact> contacts = buildContactsFromFeed(feedRows);
List<Database.SaveResult> results = Database.insert(contacts, false);
List<ProcessingError__c> errorLog = new List<ProcessingError__c>();
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error err : results[i].getErrors()) {
errorLog.add(new ProcessingError__c(
SourceRow__c = feedRows[i].externalId,
Message__c = err.getMessage(),
StatusCode__c = String.valueOf(err.getStatusCode())
));
}
}
}
if (!errorLog.isEmpty()) {
Database.insert(errorLog, false);
}
```
**Why not DML statement:** `insert contacts` throws on the first bad row and rolls back all successfully validated rows.
### Upsert with external ID field
**When to use:** Syncing records from an external system using a known external identifier.
**How it works:**
```apex
List<Account> accounts = buildAccountsFromSource(sourceData);
// ExternalId__c must be an indexed, external-ID-marked custom field
List<Database.UpsertResult> results =
Database.upsert(accounts, Account.ExternalId__c, false);
for (Database.UpsertResult ur : results) {
if (ur.isSuccess()) {
System.debug(ur.isCreated() ? 'Inserted: ' : 'Updated: ' + ur.getId());
}
}
```
**Why not insert:** Upsert atomically handles both new and existing records without a pre-query, saving SOQL and DML operations.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Any row failure must roll back all | `insert list` / DML statement | allOrNone=true (default) |
| Partial success acceptable | `Database.insert(list, false)` | Per-row error collection without rollback |
| Need assignment rule on insert | `Database.DMLOptions` with `assignmentRuleHeader` | DML statement does not expose this control |
| Suppress duplicate rule | `DMLOptions.duplicateRuleHeader.allowSave = true` | Bypass without disabling org-wide rule |
| Match on external Id for sync | `Database.upsert(list, ExternalId__c, false)` | Avoids pre-query, atomic insert/update |
| Hard delete (bypass Recycle Bin) | `Database.emptyRecycleBin(ids)` | Permanent delete, not a DML operation |
| Merge duplicate records | `Database.merge(master, duplicateIds)` | Account/Contact/Lead only |
---
## Recommended Workflow
1. **Determine allOrNone policy**: decide whether partial success is acceptable and choose DML statement (allOrNone=true) or `Database.insert(list, false)` accordingly.
2. **Build bulk list**: collect all records to process into a single list — never call DML inside loops.
3. **Select method**: use `Database` class if you need partial success, DMLOptions, or explicit result inspection; use DML statement for simpler all-or-nothing cases.
4. **Collect errors**: iterate `SaveResult`/`UpsertResult` list and collect failures into an error log or exception list.
5. **Apply savepoints** where multi-step DML must be atomically rolled back on failure.
6. **Set DMLOptions** if assignment rules, email suppression, or duplicate bypass is needed.
7. **Validate DML count**: confirm total DML operations in the transaction stay within 150; each `Database.insert(list)` call = 1 operation regardless of list size.
---
## Review Checklist
- [ ] No DML inside loops — all records collected into lists first
- [ ] DML operation count (not row count) verified under 150
- [ ] `SaveResult.isSuccess()` checked per row when `allOrNone=false`
- [ ] `DmlException` caught and re-thrown or logged at appropriate level
- [ ] `Database.DMLOptions` used where assignment rules or duplicate suppression is needed
- [ ] `Database.merge` only called for Account, Contact, or Lead
- [ ] Savepoints used when multi-step DML must be transactionally consistent
---
## Salesforce-Specific Gotchas
1. **DML limit counts operations, not rows** — one `Database.insert(200Records)` = 1 DML operation, not 200. The 150 limit applies to the number of distinct DML calls, not the total records processed. Confusion here leads to over-splitting lists unnecessarily.
2. **Partial success does NOT roll back successful rows** — when using `allOrNone=false`, rows that succeed are committed immediately. If you later discover you need to undo them, you must delete them explicitly; `Database.rollback()` only works within the same transaction.
3. **`Database.merge` is restricted to three objects** — `Database.merge` only supports Account, Contact, and Lead. Attempting to merge any other object type throws a `DmlException` at runtime with no compile-time warning.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| `List<Database.SaveResult>` | Per-row success/failure details from `Database.insert/update/delete` |
| Error log records | Custom error object populated with failure messages for audit trail |
| `check_apex_dml_patterns.py` | Validator confirming required method coverage in SKILL.md |
---
## Related Skills
- soql-fundamentals — for query patterns that precede DML in lookup-and-update flows
- apex-transaction-finalizers — for cleanup logic after Queueable DML failures
- apex-batch-chaining — for chaining batch jobs that each perform bulk DML
- callout-and-dml-transaction-boundaries — for the callout-before-DML restrictionRelated Skills
mfa-enforcement-patterns
Design MFA enforcement: auto-enablement, Salesforce Authenticator rollout, exceptions, service accounts, API-only users, SSO interop, and audit. Trigger keywords: MFA, multi-factor, two-factor, Salesforce Authenticator, MFA exception, MFA SSO, api-only MFA. Does NOT cover: end-user password policies, device-trust posture, or non-Salesforce IdP configuration.
encrypted-field-query-patterns
Design SOQL, filters, reporting, and indexes against Shield Platform Encryption fields. Trigger keywords: Shield Platform Encryption, encrypted field query, probabilistic vs deterministic encryption, encrypted SOQL filter, encrypted field index. Does NOT cover: Classic Encryption (deprecated), field-level security policy, or tenant secret key rotation.
apex-managed-sharing-patterns
Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.
omnistudio-testing-patterns
Use when testing or validating OmniStudio components — OmniScript preview, Integration Procedure step debugging, DataRaptor field-mapping validation, and end-to-end UTAM-based automation. NOT for Apex unit testing or standard Flow debugging.
omnistudio-error-handling-patterns
Use when designing fault behavior across Integration Procedures, DataRaptors, OmniScripts, and FlexCards — error routing, user-facing messaging, retry semantics, and idempotency. Triggers: 'omnistudio error', 'integration procedure fault', 'dataraptor error handling', 'omniscript retry', 'flexcard action failure'. NOT for general Apex exception design or Flow fault paths.
omnistudio-ci-cd-patterns
Use when designing or implementing CI/CD pipelines for OmniStudio components — DataPack export/import, versioning, environment promotion, and automated deployment. NOT for standard Salesforce metadata CI/CD or Apex-only pipelines.
omniscript-design-patterns
Use when designing or reviewing OmniScripts for guided experiences, step structure, branching, save/resume, and the boundary between OmniScript, Integration Procedures, DataRaptors, and custom LWCs. Triggers: 'omniscript design', 'too many steps in omniscript', 'save and resume omniscript', 'branching in omniscript', 'when should this be an integration procedure'. NOT for deep Integration Procedure or DataRaptor design when the guided interaction layer is not the main concern.
integration-procedure-cacheable-patterns
Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers cache key design, TTL selection, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss/stale. Does NOT cover general IP authoring (see omnistudio-error-handling-patterns) or LWC client-side caching.
flexcard-design-patterns
Use when designing, building, or reviewing OmniStudio FlexCards — including data source selection, card states, actions, conditional visibility, flyout configuration, and child card iteration. Triggers: 'FlexCard', 'card template', 'flyout', 'card action', 'card state', 'data source', 'child card', 'conditional visibility'. NOT for OmniScript design, standalone LWC development, or Apex controller architecture outside the FlexCard context.
dataraptor-patterns
Use when designing or reviewing OmniStudio DataRaptors, especially Extract versus Turbo Extract versus Transform versus Load, field mapping strategy, performance tradeoffs, and when to move work into Integration Procedures or Apex. Triggers: 'DataRaptor Extract', 'Turbo Extract', 'DataRaptor Load', 'DataRaptor Transform', 'OmniStudio data mapping'. NOT for overall OmniScript journey design or Integration Procedure sequencing when the main question is not the DataRaptor shape itself.
wire-service-patterns
Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.
message-channel-patterns
Use when implementing Lightning Message Service (LMS) to enable cross-DOM communication between LWC, Aura, and Visualforce components on the same Lightning page, using message channels. Triggers: 'communicate between unrelated LWC components', 'send data between Visualforce and LWC', 'lightning message service not working', 'APPLICATION_SCOPE vs default scope', 'message channel metadata deployment'. NOT for parent-child component communication (use component-communication) or server-side events.