For Zoho services only


I'm currently part of a wider delivery team at Ascent Business Solutions, recognised as a leading Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions support organisations with everything from targeted technical fixes through to full Zoho CRM implementations and long-term platform adoption. Working as a team rather than a one-person consultancy allows projects to move forward consistently, with access to the right skills at each stage.

The team I manage specialises in API integrations between Zoho and third-party finance and commerce platforms such as Xero, Shopify, WooCommerce, and eBay. Much of our work involves solving integration challenges that fall outside standard documentation, supporting new ideas, new sectors, and evolving business models.

Success is measured through practical outcomes and return on investment, ranging from scaling small operations into high-turnover businesses to delivering rapid gains through online payments, automation, and streamlined digital workflows.

If you are looking for structured Zoho expertise backed by an established consultancy, you can contact Ascent Business Solutions on 0121 392 8140 (UK), email info@ascentbusiness.co.uk, or visit https://www.ascentbusiness.co.uk.
Zoho CRM Client Script: Map Quote to Invoice

Zoho CRM Client Script: Map Quote to Invoice

What?
This is an article to apply an automation that when creating an invoice in CRM from a related quote, it maps in the fields.

Why?
The process should be that you go to the CRM quote record and click on "Convert" > then select "Invoice", and it should map the fields over. Great! But what if someone skips the process, in other words, goes to the quote record, and clicks on the plus icon next to "Invoices" in the left sidebar?

This script will map over the core fields, address, and line item. This took me a while to get the right syntax for writing to the invoice line items as well as setting the value of lookup fields so I thought it was worth an article and may help someone else (or even myself) in the future.

At time of print (9th October 2025), for some reason, if you click on the plus icon next to Sales Order (creates a new sales order) on the quote record, the fields auto-map. The client script below does the same for invoice as the quote lookup is a custom field on the invoice.

How?
First, we'll setup a client script specifying what triggers it (on load of the page when creating an invoice) then I'll just put the client script I used to map over all the applicable field values from the quote to the invoice within ZohoCRM.

Set up the Client Script
  1. Login to ZohoCRM > Setup (cog icon next to profile photo) > Developer Hub > Client Script > New Script
  2. Give it a name, I'm calling mine Invoice TBC
  3. Give it a description. I said "Sets the subject/name of the invoice to TBC when first created. Maps values from the related quote if loaded from somewhere other than converting quote to invoice.". Note that I have a workflow that when saved, uses an auto-number field, pads it with zeros, and prefixes it with "INV-" overwriting the invoice subject.
  4. Category is Module, Page is Create Page (Standard) (change this depending on if you're using a canvas or not)
  5. Module is Invoices
  6. Type is Page Event
  7. Event is onLoad
  8. Click on the button Next

the Script
copyraw
/* *******************************************************************************
	Function:       Invoice TBC
	Label:          Invoice TBC
	Page Details
        Category    Module
        Page        Create Page (Standard)
        Module      Invoices
        Layout      Standard
    Event Details
        Event Type  Page Event
        Event       onLoad
	Purpose:	 	Loads in a "TBC" so that the the naming of the invoice can happen on save
					Maps other fields to fill in the blanks
	Inputs:         -
	Outputs:        -

	Date Created:   2025-10-07 (Joel Lipman)
					- Initial release
	Date Modified:	???
					- ???

	More Information:
					https://www.zohocrm.dev/explore/client-script/webapi/Modules#fetchById

******************************************************************************* */

// for debugging
console.clear();

// set the value of the subject
ZDK.Page.getField('Subject').setValue('TBC');

// get the quote values (if linked - note this is a custom lookup field on the invoice)
var o_InvoicedQuote = ZDK.Page.getField('Quote').getValue();

// go down this route if a quote is specified (they were on the quote and clicked on the plus next to 'invoice')
if (o_InvoicedQuote != null) {

	// check on the quote associated to this invoice
	var o_QuoteDetails = ZDK.Apps.CRM.Quotes.fetchById(o_InvoicedQuote.id);
	console.log(o_QuoteDetails);

	// consider map over deal from quote to invoice lookup field (if quote deal is not blank)
	if (o_QuoteDetails.Deal_Name_Lookup_Id != null) {

		// check on the deal record on this invoice
		var o_LinkedDeal = ZDK.Page.getField('Deal_Name__s');

		// if linked deal is blank, then see if we can find a relevant deal
		if (o_LinkedDeal.getValue() == null) {

			// if deal on the quote is not blank, then let's associate that one to this invoice
			if (o_QuoteDetails.Deal_Name_Lookup_Id != null) {

				// retrieve the deal record
				var o_OppDetails = ZDK.Apps.CRM.Deals.fetchById(o_QuoteDetails.Deal_Name_Lookup_Id);
				console.log(o_OppDetails);

				// set the deal lookup value on this page
				ZDK.Page.getField('Deal_Name__s').setValue({
					id: o_QuoteDetails.Deal_Name_Lookup_Id,
					name: o_OppDetails._Deal_Name
				});
			}
		}
	}


	// check on the sales order record on this invoice
	var o_LinkedSO = ZDK.Page.getField('Sales_Order');

	// if linked SO is blank, then see if we can find a relevant SO
	if (o_LinkedSO.getValue() == null) {

		// find sales orders related to this quote (fetchRelatedRecords doesn't seem to work for me)
		var a_QuotedSalesOrders = ZDK.Apps.CRM.Sales_Orders.searchByCriteria("(Quote_Name:equals:" + o_QuoteDetails.id + ")");

		// convert to a usable array
		var a_QuotedSalesOrders = typeof a_QuotedSalesOrders === "string" ? JSON.parse(a_QuotedSalesOrders) : a_QuotedSalesOrders || [];

		// set first valid sales order ID
		var v_FirstSalesOrderID = null;

		// loop through related sales orders
		for (var i = 0; i < a_QuotedSalesOrders.length; i++) {

			// check status is neither cancelled nor expired
			if (a_QuotedSalesOrders[i].Status !== "Cancelled" && a_QuotedSalesOrders[i].Status !== "Expired") {

				// found one so let's bail
				v_FirstSalesOrderID = a_QuotedSalesOrders[i].id;
				break;
			}
		}

		// if not null (was found) then let's assign it to this invoice
		if (v_FirstSalesOrderID != null) {

			// retrieve the deal record
			var o_SalesOrderDetails = ZDK.Apps.CRM.Sales_Orders.fetchById(v_FirstSalesOrderID);
			console.log(o_SalesOrderDetails);

			// set the deal lookup value on this page
			ZDK.Page.getField('Sales_Order').setValue({

				id: o_SalesOrderDetails.id,
				name: o_SalesOrderDetails.Subject

			});
		}

	}


	// this is on create of the invoice, so let's map over the quote fields irrespectively
	ZDK.Page.getField('Billing_City').setValue(o_QuoteDetails._Billing_City);
	ZDK.Page.getField('Billing_Country').setValue(o_QuoteDetails._Billing_Country);
	ZDK.Page.getField('Billing_State').setValue(o_QuoteDetails._Billing_State);
	ZDK.Page.getField('Billing_Code').setValue(o_QuoteDetails._Billing_Code);
	ZDK.Page.getField('Billing_Street').setValue(o_QuoteDetails._Billing_Street);

	ZDK.Page.getField('Shipping_City').setValue(o_QuoteDetails._Shipping_City);
	ZDK.Page.getField('Shipping_Country').setValue(o_QuoteDetails._Shipping_Country);
	ZDK.Page.getField('Shipping_State').setValue(o_QuoteDetails._Shipping_State);
	ZDK.Page.getField('Shipping_Code').setValue(o_QuoteDetails._Shipping_Code);
	ZDK.Page.getField('Shipping_Street').setValue(o_QuoteDetails._Shipping_Street);

	ZDK.Page.getField('Currency').setValue(o_QuoteDetails._Currency);
	ZDK.Page.getField('Terms_and_Conditions').setValue(o_QuoteDetails._Terms_and_Conditions);
	ZDK.Page.getField('Adjustment').setValue(o_QuoteDetails._Adjustment);

	// field mapping of quoted items to invoiced items (i console logged out the quote record and used the API names of the invoiced items)
	// declare new array to store line items for invoice
	var a_InvoiceLineItems = [];

	// loop through quoted line items
	for (i = 0; i < o_QuoteDetails._Product_Details.length; i++) {

		// initialize new line item
		var o_NewLineItem = {};

		// store this line item as an object
		o_ThisLineItem = o_QuoteDetails._Product_Details[i];
		console.log(o_ThisLineItem);

		// assigning to a lookup field (Product)
		var o_ProductDetails = ZDK.Apps.CRM.Products.fetchById(o_ThisLineItem._product_Lookup_Id);
		o_NewLineItem.Product_Name = { id: o_ProductDetails.id, name: o_ProductDetails.Product_Name };

		// other fields for this line item
		o_NewLineItem.Description = o_ThisLineItem._product_description;
		o_NewLineItem.Discount = o_ThisLineItem._Discount;
		o_NewLineItem.List_Price = o_ThisLineItem._list_price;
		o_NewLineItem.Quantity = o_ThisLineItem._quantity;
		o_NewLineItem.Tax = o_ThisLineItem._Tax;

		// going to create a new set of line items
		a_InvoiceLineItems.push(o_NewLineItem);
	}

	// write back the new line items to the invoice
	ZDK.Page.getField('Invoiced_Items').setValue(a_InvoiceLineItems);
}
  1.  /* ******************************************************************************* 
  2.      Function:       Invoice TBC 
  3.      Label:          Invoice TBC 
  4.      Page Details 
  5.          Category    Module 
  6.          Page        Create Page (Standard) 
  7.          Module      Invoices 
  8.          Layout      Standard 
  9.      Event Details 
  10.          Event Type  Page Event 
  11.          Event       onLoad 
  12.      Purpose:         Loads in a "TBC" so that the the naming of the invoice can happen on save 
  13.                      Maps other fields to fill in the blanks 
  14.      Inputs:         - 
  15.      Outputs:        - 
  16.   
  17.      Date Created:   2025-10-07 (Joel Lipman) 
  18.                      - Initial release 
  19.      Date Modified:    ??? 
  20.                      - ??? 
  21.   
  22.      More Information: 
  23.                      https://www.zohocrm.dev/explore/client-script/webapi/Modules#fetchById 
  24.   
  25.  ******************************************************************************* */ 
  26.   
  27.  // for debugging 
  28.  console.clear()
  29.   
  30.  // set the value of the subject 
  31.  ZDK.Page.getField('Subject').setValue('TBC')
  32.   
  33.  // get the quote values (if linked - note this is a custom lookup field on the invoice) 
  34.  var o_InvoicedQuote = ZDK.Page.getField('Quote').getValue()
  35.   
  36.  // go down this route if a quote is specified (they were on the quote and clicked on the plus next to 'invoice') 
  37.  if (o_InvoicedQuote != null) { 
  38.   
  39.      // check on the quote associated to this invoice 
  40.      var o_QuoteDetails = ZDK.Apps.CRM.Quotes.fetchById(o_InvoicedQuote.id)
  41.      console.log(o_QuoteDetails)
  42.   
  43.      // consider map over deal from quote to invoice lookup field (if quote deal is not blank) 
  44.      if (o_QuoteDetails.Deal_Name_Lookup_Id != null) { 
  45.   
  46.          // check on the deal record on this invoice 
  47.          var o_LinkedDeal = ZDK.Page.getField('Deal_Name__s')
  48.   
  49.          // if linked deal is blank, then see if we can find a relevant deal 
  50.          if (o_LinkedDeal.getValue() == null) { 
  51.   
  52.              // if deal on the quote is not blank, then let's associate that one to this invoice 
  53.              if (o_QuoteDetails.Deal_Name_Lookup_Id != null) { 
  54.   
  55.                  // retrieve the deal record 
  56.                  var o_OppDetails = ZDK.Apps.CRM.Deals.fetchById(o_QuoteDetails.Deal_Name_Lookup_Id)
  57.                  console.log(o_OppDetails)
  58.   
  59.                  // set the deal lookup value on this page 
  60.                  ZDK.Page.getField('Deal_Name__s').setValue({ 
  61.                      id: o_QuoteDetails.Deal_Name_Lookup_Id, 
  62.                      name: o_OppDetails._Deal_Name 
  63.                  })
  64.              } 
  65.          } 
  66.      } 
  67.   
  68.   
  69.      // check on the sales order record on this invoice 
  70.      var o_LinkedSO = ZDK.Page.getField('Sales_Order')
  71.   
  72.      // if linked SO is blank, then see if we can find a relevant SO 
  73.      if (o_LinkedSO.getValue() == null) { 
  74.   
  75.          // find sales orders related to this quote (fetchRelatedRecords doesn't seem to work for me) 
  76.          var a_QuotedSalesOrders = ZDK.Apps.CRM.Sales_Orders.searchByCriteria("(Quote_Name:equals:" + o_QuoteDetails.id + ")")
  77.   
  78.          // convert to a usable array 
  79.          var a_QuotedSalesOrders = typeof a_QuotedSalesOrders === "string" ? JSON.parse(a_QuotedSalesOrders) : a_QuotedSalesOrders || []
  80.   
  81.          // set first valid sales order ID 
  82.          var v_FirstSalesOrderID = null
  83.   
  84.          // loop through related sales orders 
  85.          for (var i = 0; i < a_QuotedSalesOrders.length; i++) { 
  86.   
  87.              // check status is neither cancelled nor expired 
  88.              if (a_QuotedSalesOrders[i].Status !== "Cancelled" && a_QuotedSalesOrders[i].Status !== "Expired") { 
  89.   
  90.                  // found one so let's bail 
  91.                  v_FirstSalesOrderID = a_QuotedSalesOrders[i].id; 
  92.                  break; 
  93.              } 
  94.          } 
  95.   
  96.          // if not null (was found) then let's assign it to this invoice 
  97.          if (v_FirstSalesOrderID != null) { 
  98.   
  99.              // retrieve the deal record 
  100.              var o_SalesOrderDetails = ZDK.Apps.CRM.Sales_Orders.fetchById(v_FirstSalesOrderID)
  101.              console.log(o_SalesOrderDetails)
  102.   
  103.              // set the deal lookup value on this page 
  104.              ZDK.Page.getField('Sales_Order').setValue({ 
  105.   
  106.                  id: o_SalesOrderDetails.id, 
  107.                  name: o_SalesOrderDetails.Subject 
  108.   
  109.              })
  110.          } 
  111.   
  112.      } 
  113.   
  114.   
  115.      // this is on create of the invoice, so let's map over the quote fields irrespectively 
  116.      ZDK.Page.getField('Billing_City').setValue(o_QuoteDetails._Billing_City)
  117.      ZDK.Page.getField('Billing_Country').setValue(o_QuoteDetails._Billing_Country)
  118.      ZDK.Page.getField('Billing_State').setValue(o_QuoteDetails._Billing_State)
  119.      ZDK.Page.getField('Billing_Code').setValue(o_QuoteDetails._Billing_Code)
  120.      ZDK.Page.getField('Billing_Street').setValue(o_QuoteDetails._Billing_Street)
  121.   
  122.      ZDK.Page.getField('Shipping_City').setValue(o_QuoteDetails._Shipping_City)
  123.      ZDK.Page.getField('Shipping_Country').setValue(o_QuoteDetails._Shipping_Country)
  124.      ZDK.Page.getField('Shipping_State').setValue(o_QuoteDetails._Shipping_State)
  125.      ZDK.Page.getField('Shipping_Code').setValue(o_QuoteDetails._Shipping_Code)
  126.      ZDK.Page.getField('Shipping_Street').setValue(o_QuoteDetails._Shipping_Street)
  127.   
  128.      ZDK.Page.getField('Currency').setValue(o_QuoteDetails._Currency)
  129.      ZDK.Page.getField('Terms_and_Conditions').setValue(o_QuoteDetails._Terms_and_Conditions)
  130.      ZDK.Page.getField('Adjustment').setValue(o_QuoteDetails._Adjustment)
  131.   
  132.      // field mapping of quoted items to invoiced items (i console logged out the quote record and used the API names of the invoiced items) 
  133.      // declare new array to store line items for invoice 
  134.      var a_InvoiceLineItems = []
  135.   
  136.      // loop through quoted line items 
  137.      for (i = 0; i < o_QuoteDetails._Product_Details.length; i++) { 
  138.   
  139.          // initialize new line item 
  140.          var o_NewLineItem = {}
  141.   
  142.          // store this line item as an object 
  143.          o_ThisLineItem = o_QuoteDetails._Product_Details[i]
  144.          console.log(o_ThisLineItem)
  145.   
  146.          // assigning to a lookup field (Product) 
  147.          var o_ProductDetails = ZDK.Apps.CRM.Products.fetchById(o_ThisLineItem._product_Lookup_Id)
  148.          o_NewLineItem.Product_Name = { id: o_ProductDetails.id, name: o_ProductDetails.Product_Name }
  149.   
  150.          // other fields for this line item 
  151.          o_NewLineItem.Description = o_ThisLineItem._product_description; 
  152.          o_NewLineItem.Discount = o_ThisLineItem._Discount; 
  153.          o_NewLineItem.List_Price = o_ThisLineItem._list_price; 
  154.          o_NewLineItem.Quantity = o_ThisLineItem._quantity; 
  155.          o_NewLineItem.Tax = o_ThisLineItem._Tax; 
  156.   
  157.          // going to create a new set of line items 
  158.          a_InvoiceLineItems.push(o_NewLineItem)
  159.      } 
  160.   
  161.      // write back the new line items to the invoice 
  162.      ZDK.Page.getField('Invoiced_Items').setValue(a_InvoiceLineItems)
  163.  } 
Category: Zoho CRM :: Article: 436

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com