Skip to content

ERPNext glossary

Child Table

Also called Child DocType, Table field, istable

Definition

A Child Table in Frappe and ERPNext is a DocType marked Is Child Table whose records only exist as rows inside a parent document, such as the items on a Sales Order. The parent holds the rows through a Table field, and each row stores its parent, parenttype, parentfield and idx.

Line items are everywhere in ERPNext. The items on a Sales Invoice, the taxes below them, the payment schedule, the components on a BOM. Each of those grids is a child table, and each has its own DocType with Is Child Table ticked (the flag is stored as istable).

How a row knows where it belongs

A child DocType gets its own database table like any other, tabSales Order Item for example. What makes it different is four columns that tie every row to a document:

  • parent: the name of the parent document, like SAL-ORD-2026-00012
  • parenttype: the parent’s DocType, like Sales Order
  • parentfield: the Table field on the parent that holds the row, like items
  • idx: the row’s position, starting at 1

On the parent side, you add a field of type Table and set its options to the child DocType. One child DocType can sit under several parents, and parenttype keeps their rows apart. The Table MultiSelect field type also stores its values in a child DocType.

Reading and adding rows in Python

When you load a parent, its rows come with it as a list on the Table field’s name:

import frappe

so = frappe.get_doc("Sales Order", "SAL-ORD-2026-00012")  # a draft order

for row in so.items:
    print(row.idx, row.item_code, row.qty)

so.append("items", {
    "item_code": "BOLT-M8",
    "qty": 200,
    "delivery_date": so.delivery_date,
})
so.save()

Saving the parent saves the rows. It also deletes any row in the database that’s no longer in the list, so removing an entry from so.items and saving is how you delete a line. When the parent is submitted or cancelled, Frappe copies its docstatus onto every row.

What a child table doesn’t get

Frappe strips permission rules from a child DocType. Access is always checked against the parent, which is why a direct frappe.get_list on a child DocType needs a parent_doctype argument before the permission check will pass. A child DocType can’t be imported on its own through Data Import, and when you create one in developer mode Frappe generates the Python controller but skips the JS form script and the test file.

In List View means something different here. On a child table field it controls which columns appear in the grid on the parent form.

Where people trip up

Editing rows behind the parent’s back. frappe.db.set_value on a child row writes straight to the table and skips the parent’s validate. Totals and taxes don’t get recalculated. Load the parent, change the row, save the parent.

Putting row checks in the wrong place. We put row validation in the parent controller’s validate, where every row and the header are available together, such as rejecting duplicate item codes.

Changing standard child tables in core files. Need an extra column on Sales Order Item? Add a custom field to the child DocType from your own app rather than editing ERPNext’s JSON. That’s how erpfly handles it: the field is exported as a fixture and ships in a pull request against your app. The ERPNext customization page covers how those changes survive upgrades, and the custom DocType walkthrough shows creating a child table from scratch.

Sources

The official documentation and source code this page was checked against.

  1. 01 Child DocType, Frappe Framework documentation docs.frappe.io
  2. 02 frappe/core/doctype/doctype/doctype.py (version-15), frappe/frappe on GitHub github.com
  3. 03 frappe/model/document.py (version-15), frappe/frappe on GitHub github.com

Where Child Table comes up

Terms used on this page

More ERPNext terms

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

ERPNext v16

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.