lead-conversion-customization

Use when writing Apex to customize lead conversion: controlling which records are created, copying custom field values post-conversion, firing related record logic on convert, or structuring LeadConvert calls. Triggers: 'lead conversion apex', 'Database.LeadConvert', 'custom field mapping conversion', 'convert lead trigger', 'lead convert opportunity', 'LeadConvertResult'. NOT for configuring lead conversion field mapping in Setup UI, managing lead assignment rules, or building web-to-lead forms — use admin/lead-management-and-conversion for those.

Best use case

lead-conversion-customization is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when writing Apex to customize lead conversion: controlling which records are created, copying custom field values post-conversion, firing related record logic on convert, or structuring LeadConvert calls. Triggers: 'lead conversion apex', 'Database.LeadConvert', 'custom field mapping conversion', 'convert lead trigger', 'lead convert opportunity', 'LeadConvertResult'. NOT for configuring lead conversion field mapping in Setup UI, managing lead assignment rules, or building web-to-lead forms — use admin/lead-management-and-conversion for those.

Teams using lead-conversion-customization 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

$curl -o ~/.claude/skills/lead-conversion-customization/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/lead-conversion-customization/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/lead-conversion-customization/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How lead-conversion-customization Compares

Feature / Agentlead-conversion-customizationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when writing Apex to customize lead conversion: controlling which records are created, copying custom field values post-conversion, firing related record logic on convert, or structuring LeadConvert calls. Triggers: 'lead conversion apex', 'Database.LeadConvert', 'custom field mapping conversion', 'convert lead trigger', 'lead convert opportunity', 'LeadConvertResult'. NOT for configuring lead conversion field mapping in Setup UI, managing lead assignment rules, or building web-to-lead forms — use admin/lead-management-and-conversion for those.

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.

Related Guides

SKILL.md Source

# Lead Conversion Customization

This skill activates when a practitioner needs Apex to control, extend, or react to lead conversion — including custom field transfer, selective record creation, and post-conversion side effects. It covers the `Database.LeadConvert` API and the Apex trigger patterns that fire during and after conversion.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm the converted LeadStatus API name. `Database.LeadConvert.setConvertedStatus()` requires the exact API name of a picklist value where `IsConverted = true`. Passing a display label causes a runtime error.
- Verify whether the org already has an `after update` trigger on Lead that fires on conversion. Conversion fires `before update` and `after update` on Lead, `before insert`/`after insert` on Contact, Account, and (optionally) Opportunity. Adding logic in multiple places without coordination causes duplicate processing.
- Know the batch size. `Database.convertLead()` accepts a maximum of 100 `Database.LeadConvert` instances per call. Exceeding this limit throws a `System.LimitException` at runtime.
- Understand which custom fields are mapped in Setup (Object Manager > Lead > Map Lead Fields). Only mapped fields are copied automatically. All others silently drop their values unless handled in Apex post-conversion.

---

## Core Concepts

### Database.LeadConvert and LeadConvertResult

`Database.LeadConvert` is the Apex object used to configure a single lead conversion. You build a list of these, one per lead, set properties on each, and pass the list to `Database.convertLead()`. The method returns a `List<Database.LeadConvertResult>`.

Key setters on `Database.LeadConvert`:

| Method | Purpose |
|---|---|
| `setLeadId(Id)` | Required. Specifies the Lead to convert. |
| `setConvertedStatus(String)` | Required. API name of a converted LeadStatus value. |
| `setDoNotCreateOpportunity(Boolean)` | Pass `true` to skip Opportunity creation. |
| `setOpportunityName(String)` | Sets the Opportunity name. Defaults to company name if omitted. |
| `setAccountId(Id)` | Merge the Lead into an existing Account instead of creating a new one. |
| `setContactId(Id)` | Merge the Lead into an existing Contact instead of creating a new one. |
| `setOwnerId(Id)` | Assign the resulting records to a specific user. |
| `setSendNotificationEmail(Boolean)` | Whether to send the lead owner notification email. |

`Database.LeadConvertResult` exposes `getAccountId()`, `getContactId()`, `getOpportunityId()`, `isSuccess()`, and `getErrors()`. You cannot instantiate this class directly in test code — see the Testing Gotcha below.

### Custom Field Transfer Is Not Automatic

Salesforce automatically copies Lead field values to Contact, Account, and Opportunity only for fields that are explicitly mapped in Setup > Object Manager > Lead > Map Lead Fields. Custom fields that are not mapped are silently dropped at conversion time. The Lead record remains in the database with the original field values, so recovery is possible, but it requires a follow-up query against the converted Lead.

The correct pattern is to perform a post-conversion DML update: after calling `Database.convertLead()`, query the converted Lead for the unmapped custom fields, then update the resulting Contact, Account, or Opportunity records with those values.

### Conversion Fires Triggers on Multiple Objects

A single `Database.convertLead()` call fires triggers on:

| Object | Trigger contexts |
|---|---|
| Lead | `before update` and `after update` (with `IsConverted` flipping from `false` to `true`). |
| Account | `before insert` / `after insert` (if a new Account is created) or `before update` / `after update` (if merged into existing). |
| Contact | `before insert` / `after insert` (if a new Contact is created) or `before update` / `after update` (if merged). |
| Opportunity | `before insert` / `after insert` (if Opportunity creation is not suppressed). |

All of this happens in a single transaction. Governor limits are shared across all trigger fires. SOQL queries and DML operations performed in any one trigger count against the same transaction limits.

### The 100-Lead Batch Limit

`Database.convertLead()` enforces a hard limit of 100 `Database.LeadConvert` objects per invocation. When processing more than 100 leads, split the input list into chunks of 100 and call `convertLead()` multiple times. In a Batch Apex `execute()` method, set batch size to 100 or fewer to stay within this limit without manual chunking.

---

## Common Patterns

### Pattern 1: Controlled Bulk Lead Conversion Service

**When to use:** A user action, Flow, or scheduled job needs to convert leads in bulk with custom configuration — suppressing opportunity creation, targeting specific accounts, or running post-conversion field mapping.

**How it works:**

```apex
public class LeadConversionService {

    public static void convertLeads(List<Id> leadIds) {
        // Fetch the converted status once
        String convertedStatus = [
            SELECT MasterLabel FROM LeadStatus
            WHERE IsConverted = true
            LIMIT 1
        ].MasterLabel;

        List<Database.LeadConvert> conversions = new List<Database.LeadConvert>();
        for (Id leadId : leadIds) {
            Database.LeadConvert lc = new Database.LeadConvert();
            lc.setLeadId(leadId);
            lc.setConvertedStatus(convertedStatus);
            lc.setDoNotCreateOpportunity(true);
            lc.setSendNotificationEmail(false);
            conversions.add(lc);
        }

        // Enforce 100-lead batch limit
        List<Database.LeadConvertResult> results = new List<Database.LeadConvertResult>();
        for (Integer i = 0; i < conversions.size(); i += 100) {
            List<Database.LeadConvert> batch = conversions.subList(i,
                Math.min(i + 100, conversions.size()));
            results.addAll(Database.convertLead(batch));
        }

        // Collect converted record Ids for post-conversion field mapping
        List<Id> contactIds = new List<Id>();
        for (Database.LeadConvertResult r : results) {
            if (r.isSuccess()) {
                contactIds.add(r.getContactId());
            }
        }
        // Continue with custom field transfer...
    }
}
```

**Why not the alternative:** Calling `convertLead()` one record at a time in a loop hits governor limits quickly and cannot be bulkified across a trigger batch. Building a list-based service method keeps the conversion atomic and governor-safe.

### Pattern 2: Post-Conversion Custom Field Transfer via After-Update Trigger on Lead

**When to use:** Unmapped custom fields on Lead must be transferred to Contact, Account, or Opportunity every time a conversion occurs — including conversions triggered from the UI, from Flow, or from Apex.

**How it works:**

Use an `after update` trigger on Lead. When `IsConverted` flips to `true`, query the Lead's custom fields, then update the related Contact.

```apex
trigger LeadTrigger on Lead (before insert, before update, after insert, after update) {
    if (!TriggerControl.isActive('Lead')) return;
    LeadTriggerHandler handler = new LeadTriggerHandler();

    if (Trigger.isBefore && Trigger.isInsert)  handler.onBeforeInsert(Trigger.new);
    if (Trigger.isBefore && Trigger.isUpdate)  handler.onBeforeUpdate(Trigger.new, Trigger.oldMap);
    if (Trigger.isAfter  && Trigger.isInsert)  handler.onAfterInsert(Trigger.new);
    if (Trigger.isAfter  && Trigger.isUpdate)  handler.onAfterUpdate(Trigger.new, Trigger.oldMap);
}
```

```apex
public with sharing class LeadTriggerHandler {

    public void onAfterUpdate(List<Lead> newLeads, Map<Id, Lead> oldMap) {
        List<Id> convertedLeadIds = new List<Id>();
        for (Lead l : newLeads) {
            if (l.IsConverted && !oldMap.get(l.Id).IsConverted) {
                convertedLeadIds.add(l.Id);
            }
        }
        if (convertedLeadIds.isEmpty()) return;

        // Re-query because ConvertedContactId is not in Trigger.new
        Map<Id, Lead> leads = new Map<Id, Lead>([
            SELECT Id, ConvertedContactId, Custom_Score__c, Demo_Requested__c
            FROM Lead
            WHERE Id IN :convertedLeadIds
        ]);

        List<Contact> contactsToUpdate = new List<Contact>();
        for (Lead l : leads.values()) {
            if (l.ConvertedContactId != null) {
                contactsToUpdate.add(new Contact(
                    Id = l.ConvertedContactId,
                    Lead_Score__c = l.Custom_Score__c,
                    Demo_Requested__c = l.Demo_Requested__c
                ));
            }
        }

        if (!contactsToUpdate.isEmpty()) {
            update contactsToUpdate;
        }
    }
}
```

**Why not the alternative:** Doing the update inside the Apex service that calls `convertLead()` works only for programmatic conversions. The trigger-based approach also covers UI conversions, Flow-driven conversions, and API conversions from external systems.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Need to suppress Opportunity creation | `setDoNotCreateOpportunity(true)` on `Database.LeadConvert` | Only reliable way; UI flows and Flow always create an Opp unless this flag is set programmatically |
| Custom fields must transfer on every conversion (UI + Apex) | `after update` trigger on Lead detecting `IsConverted` flip | Fires for all conversion paths, not just programmatic ones |
| Custom fields must transfer only for programmatic conversion | Post-`convertLead()` DML in the service class | Simpler, avoids trigger complexity, but misses UI-driven conversions |
| Converting >100 leads in a batch job | Batch Apex with batch size 100, or manual chunking | Hard 100-record limit on `convertLead()`; exceeding it throws `LimitException` |
| Need to merge lead into an existing account | `setAccountId(existingAccountId)` | Platform merges data rather than creating a duplicate account |
| Need to test LeadConvertResult | JSON deserialization workaround | Cannot construct `LeadConvertResult` in test code; must deserialize from JSON |
| Assign converted records to a specific user | `setOwnerId(userId)` on `Database.LeadConvert` | Applies to all resulting records (Account, Contact, Opportunity) |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner working on this task:

1. **Gather context** — Identify the converted LeadStatus API name, which custom fields need to be transferred, whether Opportunity creation should be suppressed, and whether an existing Account or Contact should be targeted. Confirm the org edition and whether a trigger framework is already in use.
2. **Check for existing Lead triggers** — Query the org for any `after update` triggers on Lead. If one exists, add conversion logic inside the existing handler rather than creating a second trigger. One trigger per object is non-negotiable.
3. **Build or extend the conversion service** — Implement `Database.LeadConvert` list construction with correct setters. Add chunking logic for batches over 100. Use `allOrNone = false` with `Database.convertLead(conversions, false)` only if partial success is acceptable, and handle individual errors from `LeadConvertResult.getErrors()`.
4. **Add post-conversion field mapping** — For unmapped custom fields, either add logic after `convertLead()` in the service (programmatic-only path) or add an `after update` trigger on Lead detecting `IsConverted` flip (covers all paths). Do not mix both without a guard to prevent double-processing.
5. **Write the test class** — Create a Lead, convert it using `Database.convertLead()`, retrieve the resulting `LeadConvertResult` from the return value, and assert on `isSuccess()` and resulting record Ids. Do not attempt to instantiate `LeadConvertResult` directly; use the returned instance.
6. **Run the checker script and validate** — Execute `python3 scripts/skill_sync.py --skill skills/apex/lead-conversion-customization` and then `python3 scripts/validate_repo.py` before marking the work complete.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] Converted LeadStatus API name is fetched dynamically or validated — not hardcoded as a display label
- [ ] `Database.convertLead()` calls are chunked to 100 leads per invocation
- [ ] Custom field transfer logic runs post-conversion, not inside the `convertLead()` call itself
- [ ] No second Lead trigger has been created if one already exists — logic added to existing handler
- [ ] Test class verifies `isSuccess()` on `LeadConvertResult` and does not instantiate the result class directly
- [ ] Post-conversion DML uses `with sharing` and respects FLS for the target fields
- [ ] Error handling covers partial failures when `allOrNone = false` is used

---

## Salesforce-Specific Gotchas

1. **`LeadConvertResult` cannot be instantiated in test code** — The class has no public constructor. Attempting `new Database.LeadConvertResult()` fails at compile time. In tests, call `Database.convertLead()` with a real or test Lead and use the returned result instances. If mock results are needed, deserialize from a JSON string using `(Database.LeadConvertResult) JSON.deserialize(...)`.

2. **`IsConverted` flip fires `after update`, not a special event** — There is no dedicated conversion trigger event. The only reliable detection in a trigger is checking `l.IsConverted && !oldMap.get(l.Id).IsConverted` in `after update`. Logic placed in `before update` cannot read `ConvertedContactId` or `ConvertedAccountId` yet — those fields are populated only after the conversion DML completes.

3. **Unmapped custom fields silently drop their values** — Salesforce does not warn or error when a custom Lead field has no mapping. The data disappears at conversion time. The original Lead record retains its values but the Contact, Account, and Opportunity do not receive the data without explicit post-conversion DML.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `LeadConversionService.cls` | Bulkified Apex service that builds `Database.LeadConvert` objects, chunks at 100, and calls `convertLead()` |
| `LeadTriggerHandler.cls` | Handler class with `onAfterUpdate` method detecting the `IsConverted` flip and performing post-conversion field mapping |
| `LeadTrigger.trigger` | Minimal trigger body delegating to the handler with an activation guard |
| `LeadConversionServiceTest.cls` | Test class covering success path, partial failure path, and custom field transfer assertions |

---

## Related Skills

- `admin/lead-management-and-conversion` — Use for configuring lead conversion field mapping in Setup, managing lead status picklist values, and building assignment rules. This Apex skill assumes Setup configuration is already in place.
- `apex/trigger-framework` — Use when deciding how to structure the Lead trigger handler or when a trigger framework is already in the org and must be followed.
- `apex/batch-apex-patterns` — Use when converting more than a few hundred leads — Batch Apex handles the 100-per-call chunking cleanly at scale.
- `apex/mixed-dml-and-setup-objects` — Conversion is a DML-heavy operation; be aware of mixed-DML constraints if post-conversion logic touches Setup objects.

Related Skills

experience-cloud-search-customization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring or extending search on an Experience Cloud site — covering Search Manager scope configuration, LWR vs Aura search component selection, federated search setup, guest user search access, and custom search result components. NOT for SOSL/SOQL query development. NOT for internal Salesforce global search or Einstein Search for agents.

lead-data-import-and-dedup

8
from PranavNagrecha/AwesomeSalesforceSkills

Lead data import quality and deduplication strategy: matching rule design for Leads, Data Import Wizard dedup behavior, Web-to-Lead data quality controls, cross-object Lead-to-Contact fuzzy dedup, and enrichment patterns. Use when importing leads from CSV/list files, configuring duplicate detection for incoming leads, diagnosing why duplicate leads are bypassing rules, or designing web form data quality workflows. Triggers: leads from a trade show import are creating duplicates, web-to-lead form submissions are bypassing duplicate rules, deduplicating leads against existing contacts before conversion, setting up matching rules for lead imports. NOT for Data Loader mechanics or Bulk API setup (use data/bulk-api-patterns). NOT for large-scale merge execution across millions of records (use data/large-scale-deduplication). NOT for post-conversion record merge behavior (use data/record-merge-implications).

quote-pdf-customization

8
from PranavNagrecha/AwesomeSalesforceSkills

Customizing Salesforce Quote PDFs using Visualforce: custom VF-based quote templates, dynamic section rendering, multi-language layouts, logo placement via static resources, and programmatic PDF generation with PageReference.getContentAsPDF(). Use when standard declarative quote templates are insufficient or when CPQ/OmniStudio is not licensed. NOT for LWC-based document generation (use omnistudio/document-generation-omnistudio). NOT for OmniStudio DocGen templates. NOT for standard quote template drag-and-drop editor.

commerce-search-customization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill to configure and tune search in B2B Commerce or D2C Commerce storefronts: searchable attributes, facetable attributes, sort rules, search index rebuilds, Einstein product recommendations, and BuyerGroup entitlement visibility. Trigger keywords: commerce search ranking, faceted navigation commerce, search index rebuild, Einstein recommendations storefront, product not appearing in search. NOT for SOSL query development, Experience Cloud federated search, Salesforce Search for non-commerce objects, or Einstein Search for Service Cloud.

mcae-lead-scoring-and-grading

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring MCAE (Account Engagement / Pardot) lead scoring models, grading profiles, score decay rules, or automation rules that fire on score/grade thresholds. Covers: scoring point values per activity, score decay for inactivity, Profiles for fit grading, combined MQL definitions (Score + Grade), and automation rules vs completion actions. NOT for Einstein Lead Scoring (Sales Cloud), not for Marketing Cloud Engagement journey scoring, not for Salesforce native lead scoring without Account Engagement.

lead-scoring-requirements

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when designing or documenting a lead scoring model in Salesforce Sales Cloud or Account Engagement: qualifying criteria, MQL/SQL threshold definitions, scoring dimensions (demographic, firmographic, behavioral), and sales handoff SLA. NOT for Einstein Lead Scoring (AI-based predictive scoring) or Account Engagement (Pardot) automation rule configuration.

lead-nurture-journey-design

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when designing or configuring lead nurture journeys in MCAE (Account Engagement / Pardot) Engagement Studio — including mapping content to funnel stages, defining behavioral trigger rules, building branching program paths, and establishing MQL handoff criteria. Covers funnel stage mapping (Awareness, Consideration, Decision), Engagement Studio program structure, rule-based branching on score/grade/activity, content inventory prerequisites, and program execution schedule. NOT for Journey Builder implementation in Marketing Cloud Engagement (MCE), NOT for Sales Engagement cadences in Sales Cloud, NOT for Einstein Lead Scoring configuration, NOT for initial MCAE Business Unit setup or CRM connector provisioning.

lead-management-and-conversion

8
from PranavNagrecha/AwesomeSalesforceSkills

Configuring Salesforce lead management and conversion: lead settings, web-to-lead, conversion field mapping, lead queues, auto-response rules, lead processes. Use when setting up lead capture, routing, or conversion for Sales Cloud. Trigger keywords: web-to-lead, lead conversion, lead field mapping, lead settings, lead process, lead queue, lead auto-response. NOT for lead assignment rule logic (use assignment-rules). NOT for duplicate rule configuration (use data-quality-and-governance).

xss-and-injection-prevention

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).

transaction-security-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.

sso-saml-troubleshooting

8
from PranavNagrecha/AwesomeSalesforceSkills

Diagnosing broken SAML SSO into Salesforce — IdP-initiated vs SP-initiated flows, signing-certificate validity / expiry, NameID format mismatches, RelayState handling, audience / entityId / issuer mismatches, clock skew, the SAML Assertion Validator in Setup, the Login History debug log, and the My Domain prerequisite for SSO. Covers the standard diagnostic loop: read the SAML response, identify which check failed, fix at the IdP or SP. NOT for OAuth / OpenID Connect SSO (see security/oauth-openid-troubleshooting), NOT for setting up SSO from scratch (see security/sso-saml-setup).