For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at https://ascentbusiness.co.uk/zoho-services/uk-zoho-support.  For larger projects, talk to our experts and receive dedicated support from our hands-on project consultants at https://ascentbusiness.co.uk/zoho-services/zoho-crm-implementation.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at https://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 :: Article: 912

Add comment

Your rating:

Submit

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

Accreditation

Badge - Zoho Creator Certified Developer Associate
Badge - Zoho Deluge Certified Developer
Badge - Certified Zoho CRM Developer

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF

Please publish modules in offcanvas position.