ERPNext Foundation ERPNext Cloud User Manual Blog Discuss Frappé* Donate

Custom Script - Serial Number Generator (and batch generator in reply)



This generates a serial number based on set parameters.

Custom Script:
Enter this into your custom script file for ‘Stock Entry’

frappe.ui.form.on("Stock Entry Detail", {
	generate_serial_sequence: function(frm, cdt, cdn) {
		var d = locals[cdt][cdn];
		var serial_number = d.starting_number;
		var serial_array = [];
		var sequence_number = 0;
		if(d.starting_number > d.end_number) {
			msgprint("Starting serial sequence number must not be larger than end number.");
		} else if((d.end_number - d.starting_number) > 100) {
			msgprint("Maximum 100 serial numbers.");
		} else {
		while ( serial_number <= d.end_number) {
    		serial_array.push(d.prefix + serial_number);
			if(!d.serial_no) {
				frappe.model.set_value(d.doctype,, 'serial_no', serial_array[sequence_number]);
			} else{
				frappe.model.set_value(d.doctype,, 'serial_no', d.serial_no + "\n" + serial_array[sequence_number]);
			}			serial_number++;

Custom Fields:
Copy this into a text editor and save as .csv, then use the import tool to upload.

Data Import Template,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Table:,Custom Field,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Please do not change the template headings.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
First data column must be blank.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
"If you are uploading new records, leave the ""name"" (ID) column blank.",,,,,,,,,,,,,,,,,,,,,,,,,,,,,
"If you are uploading new records, ""Naming Series"" becomes mandatory, if present.",,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
"For updating, you can update only selective columns.",,,,,,,,,,,,,,,,,,,,,,,,,,,,,
You can only upload upto 5000 records in one go. (may be less in some cases),,,,,,,,,,,,,,,,,,,,,,,,,,,,,
DocType:,Custom Field,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Column Labels:,ID,Document,Field Type,Label,Fieldname,Insert After,Precision,Options,Collapsible,Collapsible Depends On,Default Value,Depends On,Field Description,Permission Level,Width,Is Mandatory Field,Unique,Read Only,Ignore User Permissions,Hidden,Print Hide,Print Hide If No Value,No Copy,Allow on Submit,In Report Filter,In List View,In Standard Filter,Report Hide,Ignore XSS Filter
Column Name:,name,dt,fieldtype,label,fieldname,insert_after,precision,options,collapsible,collapsible_depends_on,default,depends_on,description,permlevel,width,reqd,unique,read_only,ignore_user_permissions,hidden,print_hide,print_hide_if_no_value,no_copy,allow_on_submit,in_filter,in_list_view,in_standard_filter,report_hide,ignore_xss_filter
Type:,Data (text),Link,Select,Data,Data,Select,Select,Text,Check,Code,Text,Code,Text,Int,Data,Check,Check,Check,Check,Check,Check,Check,Check,Check,Check,Check,Check,Check,Check
Info:,,Valid DocType,"One of: Attach, Attach Image, Button, Check, Code, Column Break, Currency, Data, Date, Datetime, Dynamic Link, Float, HTML, Image, Int, Link, Long Text, Password, Percent, Read Only, Section Break, Select, Small Text, Table, Text, Text Editor, Time",,,,"One of: 1, 2, 3, 4, 5, 6, 7, 8, 9",,0 or 1,,,,,Integer,,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1,0 or 1
Start entering data below this line,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,"""Stock Entry Detail-serial_number_generator""",Stock Entry Detail,Section Break,Serial Number Generator,serial_number_generator,transfer_qty,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-prefix""",Stock Entry Detail,Data,Prefix,prefix,serial_number_generator,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-cbsng2""",Stock Entry Detail,Column Break,,cbsng2,end_number,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-starting_number""",Stock Entry Detail,Int,Starting Number,starting_number,cbsng1,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-end_number""",Stock Entry Detail,Int,End Number,end_number,starting_number,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-cbsng1""",Stock Entry Detail,Column Break,,cbsng1,prefix,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0
,"""Stock Entry Detail-generate_serial_sequence""",Stock Entry Detail,Button,Generate Serial Sequence,generate_serial_sequence,cbsng2,,,0,,,,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0

This limits you to generating 100 serial numbers at a time, in case of a typo.



is this an issue prevailing in your ERPNext account? Can you share the relevant screenshots along?


Auto generation of serial numbers is already possible through item settings. What’s the use case of the custom script?
Can we auto generate batch numbers in similar way? That will be really useful.


@umair I was just sharing a custom script that I created.

@Mukesh_Variyani The serial number series through item settings is fine, but our company is not creating serials most of the time, but imputing ones they have. One stock entry to the next will likely not be sequential to each other. They need a way to manually specify the serial numbers being recorded.

As for the batch, I have created this script:

//Creates new batches for line items.
frappe.ui.form.on("Purchase Order", {
    validate: function(frm, cdt, cdn) {
        console.log("Batch Creator function triggered");
        var att = [];
        var d = locals[cdt][cdn];
        var ic = [];
        frm.doc.items.forEach(function(d) {
        if (frm.doc.workflow_state == "Submitted") {
// If you would like the batch numbers to start with a number other than 0, change the value of the variable 'i' on the next line.
            for (var i = 0; i < ic.length; i++) {
                console.log("Creating batch for " + ic[i]);
                doc = {
                    "doctype": "Batch",
                    "batch_id": ( + "-" + i),
                    "item": ic[i],

                    "method": "frappe.client.insert",
                    "args": {
                        "doc": doc

This will create batches for PO line items. it should be easily adaptable for other documents. If the PO is 601, then the batches will be 601-0, 601-1, 601-2, etc. for each line item


This script requires a link field called purchase_order in the batch.

The following code in the purchase receipt filters to only allow entry of the batch that matches the purchase order:

cur_frm.fields_dict['items'].grid.get_field('batch_no').get_query = function(doc, cdt, cdn) {
	var d = locals[cdt][cdn];
	return {
		filters: [
			['Batch', 'purchase_order', '=', d.purchase_order],
			['Batch', 'item', '=', d.item_code]


Oh great, Thanks a lot for sharing…:smile:


Hi @cpurbaugh,

Looks like a great script. However, I encountered an error.

Traceback (most recent call last):
File “/home/frappe/frappe-bench/apps/frappe/frappe/”, line 62, in application
response = frappe.handler.handle()
File “/home/frappe/frappe-bench/apps/frappe/frappe/”, line 22, in handle
data = execute_cmd(cmd)
File “/home/frappe/frappe-bench/apps/frappe/frappe/”, line 53, in execute_cmd
return, **frappe.form_dict)
File “/home/frappe/frappe-bench/apps/frappe/frappe/”, line 939, in call
return fn(*args, **newargs)
TypeError: runserverobj() takes at least 1 argument (1 given)

Please advice.



cur_frm.fields_dict[‘items’].grid.get_field(‘batch_no’).get_query = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
return {
filters: [
[‘Batch’, ‘purchase_order’, ‘=’, d.purchase_order],
[‘Batch’, ‘item’, ‘=’, d.item_code]

Hello, how to convert this code into parent? thanks :blush: