# Lead to Opportunity Conversion Workflow in ERPNext: How Customer Data Flows Through the CRM Pipeline

> Understand the ERPNext Lead to Opportunity conversion workflow. See how customer data flows seamlessly through the CRM pipeline, from Lead to Opportunity, ensuring connected records.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**The Lead to Opportunity conversion workflow in ERPNext maps core customer fields from the Lead DocType to a new Opportunity document using the `make_opportunity` function in [`erpnext/crm/doctype/lead/lead.py`](https://github.com/frappe/erpnext/blob/main/erpnext/crm/doctype/lead/lead.py), then copies linked Address and Contact records via Dynamic Links to ensure complete customer data persistence.**

The Lead to Opportunity conversion workflow is a critical CRM process in ERPNext that ensures sales prospects seamlessly transition through the pipeline. When a sales representative clicks **Convert to Opportunity** on a Lead record, ERPNext executes a server-side mapping operation that preserves customer identity, contact details, and ownership information. This article examines the exact mechanics of how the `frappe/erpnext` codebase handles this data transformation.

## The Entry Point: `make_opportunity` Function

The workflow begins at the whitelisted `make_opportunity` function defined in [`erpnext/crm/doctype/lead/lead.py`](https://github.com/frappe/erpnext/blob/main/erpnext/crm/doctype/lead/lead.py). This function serves as the gateway for converting Lead documents into Opportunities, accepting a `source_name` parameter that identifies the specific Lead to be converted and an optional `target_doc` parameter.

```python
@frappe.whitelist()
def make_opportunity(source_name: str, target_doc: str | Document | None = None):

```

The implementation leverages `frappe.model.mapper.get_mapped_doc` to instantiate a new Opportunity document while applying a predefined field mapping configuration. This approach ensures type safety and consistent data structure across the conversion process.

## Field Mapping Between Lead and Opportunity

The conversion relies on a dictionary-based field map that translates Lead schema fields into their Opportunity equivalents. This eliminates manual re-entry and maintains data integrity throughout the Lead to Opportunity conversion workflow.

### Core Identity Fields

The mapping establishes the foundational relationship between the source Lead and the new Opportunity:

- **`name` → `party_name`** – Links the Opportunity to the Lead's unique identifier, establishing the party relationship.
- **`company_name` → `customer_name`** – Carries the organization name that will become the **Customer** name when the Opportunity is later converted.
- **`doctype` → `opportunity_from`** – Sets the source document type indicator on the Opportunity.

### Contact and Ownership Data

Customer communication details and responsibility assignments transfer through these mappings:

- **`email_id` → `contact_email`** – Preserves the primary email address.
- **`mobile_no` → `contact_mobile`** – Maintains mobile contact information.
- **`lead_owner` → `opportunity_owner`** – Transfers sales person responsibility to the Opportunity.
- **`notes` → `notes`** – Carries forward any free-text annotations.

## Copying Linked Address and Contact Records

Beyond simple field mapping, the workflow preserves relational data through the `_set_missing_values` helper method. This function queries the `Dynamic Link` table to locate **Address** and **Contact** documents linked to the original Lead, then assigns them to the new Opportunity's `customer_address` and `contact_person` fields.

```python
address = frappe.get_all(
    "Dynamic Link",
    {"link_doctype": source.doctype, "link_name": source.name, "parenttype": "Address"},
    ["parent"],
    limit=1,
)
contact = frappe.get_all(
    "Dynamic Link",
    {"link_doctype": source.doctype, "link_name": source.name, "parenttype": "Contact"},
    ["parent"],
    limit=1,
)
if address: target.customer_address = address[0].parent
if contact: target.contact_person = contact[0].parent

```

This guarantees the Opportunity inherits the same billing/shipping addresses and primary contact records attached to the original Lead.

## Pre-Conversion Validation Checks

The Lead class implements validation methods to prevent duplicate conversions and ensure data consistency. The `has_opportunity` method checks for existing active Opportunities linked to the Lead, while `has_customer` and `has_quotation` provide additional pipeline visibility.

The UI layer disables the **Convert to Opportunity** button when `has_opportunity` returns true for a non-Lost Opportunity, preventing users from creating multiple active Opportunities for the same Lead inadvertently.

## From Opportunity to Customer

The same architectural pattern extends to the final conversion stage in the sales pipeline. The `make_customer` function (also located in [`erpnext/crm/doctype/lead/lead.py`](https://github.com/frappe/erpnext/blob/main/erpnext/crm/doctype/lead/lead.py)) reuses the mapping logic to transform Opportunity data into a **Customer** record. This creates a consistent data lineage from initial Lead through closed sale, ensuring the `customer_name` field populated during the first conversion becomes the actual Customer document name.

## Implementation Examples

### Server-Side Python Conversion

You can trigger the Lead to Opportunity conversion workflow programmatically from server scripts or custom apps:

```python
import frappe

lead_name = "LEAD-00015"

# Convert the lead into an opportunity

opportunity_doc = frappe.get_doc("Lead", lead_name).make_opportunity(lead_name)

# Optionally, immediately convert to a customer

customer_doc = frappe.get_doc("Opportunity", opportunity_doc.name).make_customer(opportunity_doc.name)

```

### Client-Side JavaScript API Call

For custom UI components or client-side automation, call the whitelisted method directly:

```javascript
frappe.call({
    method: "erpnext.crm.doctype.lead.lead.make_opportunity",
    args: { source_name: "LEAD-00015" },
    callback: function(r) {
        if (r.message) {
            console.log("Created Opportunity:", r.message.name);
        }
    }
});

```

## Summary

- The conversion workflow initiates in [`erpnext/crm/doctype/lead/lead.py`](https://github.com/frappe/erpnext/blob/main/erpnext/crm/doctype/lead/lead.py) via the whitelisted `make_opportunity` function.
- **Field mapping** uses `get_mapped_doc` to transfer Lead data to Opportunity fields including `party_name`, `customer_name`, `contact_email`, and `opportunity_owner`.
- **Address and Contact preservation** occurs through Dynamic Link queries in the `_set_missing_values` helper method.
- **Validation methods** (`has_opportunity`, `has_customer`) prevent duplicate records and maintain data integrity.
- The same mapping pattern extends to **Customer creation** via the `make_customer` function, ensuring seamless CRM pipeline progression.

## Frequently Asked Questions

### What file contains the Lead to Opportunity conversion logic in ERPNext?

The core logic resides in [`erpnext/crm/doctype/lead/lead.py`](https://github.com/frappe/erpnext/blob/main/erpnext/crm/doctype/lead/lead.py), specifically within the `make_opportunity` function and its helper `_set_missing_values`. This file also contains the validation methods and the field mapping configuration that drives the conversion process.

### How does ERPNext handle existing addresses when converting a Lead to an Opportunity?

The system queries the `Dynamic Link` table for Address and Contact records linked to the Lead via the `link_doctype` and `link_name` filters. It assigns the first matching Address to `customer_address` and the first Contact to `contact_person` on the new Opportunity, ensuring billing and shipping information persists through the conversion.

### Can a Lead be converted to an Opportunity if it already has an active Opportunity?

No. The `has_opportunity` method checks for existing Opportunities with a status other than "Lost". If an active Opportunity exists, the UI disables the conversion button. Programmatic implementations should call this validation method before invoking `make_opportunity` to prevent duplicate entries.

### Which specific fields are mapped from Lead to Opportunity during conversion?

Key mappings include the Lead's `name` to the Opportunity's `party_name`, `company_name` to `customer_name`, `email_id` to `contact_email`, `mobile_no` to `contact_mobile`, and `lead_owner` to `opportunity_owner`. The system also maps `doctype` to `opportunity_from` and copies the `notes` field verbatim.