A comprehensive solution for creating advanced master-detail views in Frappe/ERPNext using DevExpress DataGrid and TreeList components. This app provides powerful grid features including expandable detail rows, nested grids, tabbed content, filtering, grouping, and much more.
- Features
- Installation
- Quick Start
- Configuration
- Usage Examples
- Advanced Features
- Work Bundle Integration
- API Reference
- Customization
- Troubleshooting
- Browser Compatibility
- License
- β Master-Detail Grid: Expandable rows with nested detail views
- β TreeList Support: Hierarchical data with parent-child relationships
- β Tabbed Detail Views: Multiple tabs in expanded detail section
- β Nested Grids: Display related child tables within detail rows
- β Inline Editing: Edit data directly in the grid with validation
- β Advanced Filtering: Column filters, search panel, filter rows
- β Grouping & Sorting: Multi-column grouping and sorting
- β Data Export: Export to Excel/CSV with formatting
- β Column Management: Show/hide, resize, reorder columns
- β Responsive Design: Mobile-friendly layouts
- β Real-time Sync: Changes sync with Frappe backend automatically
- β dxDataGrid: Advanced data grid with rich features
- β dxTreeList: Hierarchical tree-based data grid
- β Master-Detail Pattern: Expandable rows with custom detail templates
- β Work Bundle Integration: Add Work Bundle items to Project WBS
- β Dynamic Configuration: JSON-based configuration system
- β Permission-Aware: Respects Frappe permissions
- β Multi-CDN Loader: Automatic fallback for DevExpress library loading
- Frappe Framework v14 or higher
- ERPNext (optional, for demo features)
- Internet connection (for CDN libraries)
# Navigate to your bench directory
cd /path/to/frappe-bench
# Get the app
bench get-app https://github.com/your-org/master_detail_grid.git
# Install on your site
bench --site your-site.local install-app master_detail_grid
# Run migrations
bench --site your-site.local migrate
# Build assets
bench build --app master_detail_grid
# Restart
bench restart# Clone into apps directory
cd frappe-bench/apps
git clone https://github.com/your-org/master_detail_grid.git
# Install on site
bench --site your-site.local install-app master_detail_grid
# Migrate
bench --site your-site.local migrate
# Build and restart
bench build --app master_detail_grid
bench restart# Check if app is installed
bench --site your-site.local console
# In Python console:
>>> import master_detail_grid
>>> print(master_detail_grid.__version__)After installation, explore the demo DocTypes:
-
Sales Order Demo
- Navigate to:
Sales Order Demolist - Create new document
- See master-detail grid in action
- Navigate to:
-
Project WBS
- Navigate to:
Project WBSlist - Create/open a document
- See nested master-detail grids with tabs
- Navigate to:
Step 1: Add HTML field to your DocType
In DocType customization:
- Add a new field
- Field Type:
HTML - Fieldname:
master_detail_grid - Options: Configuration JSON (see below)
Step 2: Configure the field
Add this JSON to the HTML field's Options:
{
"child_doctype": "Your Child DocType",
"child_table_field": "your_child_table_fieldname",
"detail_tabs": [
{
"title": "Details",
"type": "details",
"fields": ["field1", "field2", "field3"]
}
]
}Step 3: Save and reload
- Save the DocType
- Reload the form
- The master-detail grid will appear
The app provides a Master Detail Grid Config DocType for advanced, reusable configurations.
- Navigate to: Master Detail Grid Config list
- Click New
- Fill in configuration details
| Field | Type | Description | Required |
|---|---|---|---|
| Config Name | Data | Unique identifier for this configuration | β Yes |
| Description | Text | Purpose/usage of this configuration | No |
| Parent DocType | Link | The parent DocType (e.g., "Project WBS") | β Yes |
| Child DocType | Link | The child DocType (e.g., "WBS Grid") | β Yes |
| Parent Field | Data | Parent link field in child table | β Yes |
| Grid Type | Select | TreeList or DataGrid |
β Yes |
Grid Type Options:
- TreeList: Use for hierarchical data with parent-child relationships
- DataGrid: Use for flat tabular data
Add tabs to define what appears in the expanded detail row:
| Field | Description |
|---|---|
| Tab Title | Display name of the tab |
| Tab Index | Order of tabs (0-based) |
| Source DocType | DocType to fetch data from |
| Grid Filter Field | Field to filter by (e.g., "rfp") |
| Grid Filter Value | Parent field value to use for filtering |
| Filter Configuration | Additional JSON filter (optional) |
Filter Configuration Example:
{
"field": "category",
"value": "Materials"
}This filters the nested grid to only show rows where category = "Materials".
Method 1: Reference by Name
In your DocType's HTML field options:
{
"config_name": "project_wbs_grid"
}Method 2: Inline Configuration
{
"child_doctype": "WBS Grid",
"child_table_field": "wbs_grid",
"grid_type": "TreeList",
"detail_tabs": [...]
}Configure which fields appear in the grid and how they behave.
{
"child_doctype": "Sales Order Item",
"child_table_field": "items",
"columns": [
{
"dataField": "item_code",
"caption": "Item Code",
"dataType": "string",
"width": 150,
"allowEditing": true,
"allowFiltering": true,
"allowSorting": true
},
{
"dataField": "qty",
"caption": "Quantity",
"dataType": "number",
"format": "#,##0.##",
"width": 100,
"allowEditing": true
},
{
"dataField": "rate",
"caption": "Rate",
"dataType": "number",
"format": "currency",
"width": 120,
"allowEditing": true
}
]
}| Property | Type | Description | Default |
|---|---|---|---|
dataField |
String | Field name from child DocType | Required |
caption |
String | Column header text | Field label |
dataType |
String | Data type: string, number, date, boolean |
Auto-detect |
width |
Number | Column width in pixels | Auto |
allowEditing |
Boolean | Allow inline editing | true |
allowFiltering |
Boolean | Show filter in header | true |
allowSorting |
Boolean | Allow column sorting | true |
allowGrouping |
Boolean | Allow grouping by this column | true |
visible |
Boolean | Show/hide column | true |
format |
String | Display format (numbers, dates) | None |
alignment |
String | left, right, center |
Auto |
fixed |
Boolean | Fix column position | false |
fixedPosition |
String | left or right |
None |
Numbers:
{
"dataField": "amount",
"format": "#,##0.00" // 1,234.56
}Currency:
{
"dataField": "total",
"format": "currency" // Uses system currency
}Dates:
{
"dataField": "delivery_date",
"format": "yyyy-MM-dd" // 2025-11-05
}Percentages:
{
"dataField": "tax_rate",
"format": "#0.##%" // 15.5%
}Define tabs that appear when a row is expanded.
1. Details Tab - Display field values
{
"title": "Item Details",
"type": "details",
"fields": [
"item_code",
"item_name",
"description",
"quantity",
"rate",
"amount"
]
}2. Info Tab - Static HTML content
{
"title": "Help",
"type": "info",
"content": "<h4>Instructions</h4><p>Enter item details...</p>"
}3. Grid Tab - Nested grid
{
"title": "Materials",
"type": "grid",
"source_doctype": "WBS Grid",
"grid_filter_field": "rfp",
"grid_filter_value": "name",
"filter": {
"field": "category",
"value": "Materials"
}
}| Property | Type | Description | Required |
|---|---|---|---|
title |
String | Tab display name | β Yes |
type |
String | details, info, or grid |
β Yes |
fields |
Array | Fields to display (details type) | For details |
content |
String | HTML content (info type) | For info |
source_doctype |
String | DocType for nested grid | For grid |
grid_filter_field |
String | Field to filter nested grid | For grid |
grid_filter_value |
String | Parent field for filter value | For grid |
filter |
Object | Additional filter | Optional |
Apply filters to nested grids or main grid.
{
"field": "status",
"value": "Active"
}{
"field": "category",
"operator": "in",
"value": ["Materials", "Tools", "Equipment"]
}=oreq: Equals!=orne: Not equals>orgt: Greater than<orlt: Less than>=orgte: Greater than or equal<=orlte: Less than or equalin: In arraynot in: Not in arraycontains: String containsstartswith: String starts withendswith: String ends with
{
"child_doctype": "Sales Order Item",
"child_table_field": "items",
"detail_tabs": [
{
"title": "Item Info",
"type": "details",
"fields": [
"item_code",
"item_name",
"description",
"qty",
"rate",
"amount"
]
}
]
}{
"config_name": "project_wbs_grid",
"child_doctype": "WBS Item",
"child_table_field": "wbs_item",
"grid_type": "TreeList",
"parent_field": "parent_wbs_item",
"detail_tabs": [
{
"title": "Materials",
"type": "grid",
"source_doctype": "WBS Grid",
"grid_filter_field": "rfp",
"grid_filter_value": "name",
"filter": {
"field": "category",
"value": "Materials"
}
},
{
"title": "Tools",
"type": "grid",
"source_doctype": "WBS Grid",
"grid_filter_field": "rfp",
"grid_filter_value": "name",
"filter": {
"field": "category",
"value": "Tools"
}
},
{
"title": "Equipment",
"type": "grid",
"source_doctype": "WBS Grid",
"grid_filter_field": "rfp",
"grid_filter_value": "name",
"filter": {
"field": "category",
"value": "Equipment"
}
}
]
}{
"child_doctype": "Sales Invoice Item",
"child_table_field": "items",
"columns": [
{
"dataField": "item_code",
"caption": "Item",
"width": 150
},
{
"dataField": "qty",
"caption": "Quantity",
"dataType": "number",
"format": "#,##0.##",
"width": 100
},
{
"dataField": "rate",
"caption": "Rate",
"dataType": "number",
"format": "currency",
"width": 120
},
{
"dataField": "amount",
"caption": "Amount",
"dataType": "number",
"format": "currency",
"width": 120,
"allowEditing": false
}
],
"detail_tabs": [
{
"title": "Item Details",
"type": "details",
"fields": [
"item_code",
"item_name",
"description",
"image"
]
},
{
"title": "Pricing",
"type": "details",
"fields": [
"qty",
"uom",
"rate",
"discount_percentage",
"discount_amount",
"amount"
]
},
{
"title": "Accounting",
"type": "details",
"fields": [
"income_account",
"expense_account",
"cost_center"
]
},
{
"title": "Delivery",
"type": "details",
"fields": [
"delivery_date",
"warehouse",
"actual_qty",
"stock_uom"
]
}
]
}{
"child_doctype": "BOM Item",
"child_table_field": "items",
"grid_type": "TreeList",
"parent_field": "parent_bom_item",
"columns": [
{
"dataField": "item_code",
"caption": "Item Code",
"width": 200
},
{
"dataField": "qty",
"caption": "Quantity",
"dataType": "number",
"format": "#,##0.####",
"width": 100
},
{
"dataField": "rate",
"caption": "Rate",
"dataType": "number",
"format": "currency",
"width": 120
},
{
"dataField": "is_group",
"caption": "Is Group",
"dataType": "boolean",
"width": 100
}
],
"detail_tabs": [
{
"title": "Item Info",
"type": "details",
"fields": [
"item_code",
"item_name",
"description",
"stock_uom",
"qty",
"rate",
"amount"
]
},
{
"title": "Operations",
"type": "grid",
"source_doctype": "BOM Operation",
"grid_filter_field": "bom_item",
"grid_filter_value": "name"
}
]
}For hierarchical data (parent-child relationships):
{
"grid_type": "TreeList",
"parent_field": "parent_item",
"key_field": "name",
"parent_id_field": "parent_item",
"has_items_field": "is_group"
}TreeList Properties:
parent_field: Field linking to parent rowkey_field: Unique identifier fieldparent_id_field: Field containing parent IDhas_items_field: Boolean field indicating if row has childrenexpanded: Auto-expand rows (boolean or array of keys)
// In custom script
// Toggle detail row programmatically
frm.fields_dict.master_detail_grid.grid_instance.toggle_detail_row(rowKey, rowData);
// Access opened row
let openedRow = window._current_opened_master_detail_row;Define custom HTML templates for detail rows:
{
"detail_template": "<div class='custom-detail'>{{item_name}}: {{description}}</div>"
}Refresh grid data after updates:
// Refresh main grid
frm.fields_dict.master_detail_grid.grid_instance.refresh();
// Refresh nested grid
frm.fields_dict.master_detail_grid.refresh_nested_grid(gridId);Configure export behavior:
{
"export": {
"enabled": true,
"formats": ["xlsx", "pdf"],
"fileName": "export_{{docname}}"
}
}Configure pagination and virtual scrolling:
{
"paging": {
"enabled": true,
"pageSize": 20,
"pageSizes": [10, 20, 50, 100]
},
"scrolling": {
"mode": "virtual",
"rowRenderingMode": "virtual"
}
}The app includes special integration for Work Bundles in Project WBS.
- Add Work Bundle items to Project WBS
- Select from Work Bundle libraries (Materials, Tools, Equipment, Technicians)
- Filter and select specific items
- Automatically creates WBS Grid records
- Refreshes nested grid after insertion
- Open Project WBS document
- Expand RFP row in the master grid
- Click "Add Bundle" button
- Select Work Bundle from the list
- Choose category (Materials/Tools/Equipment/Technicians)
- Select items using checkboxes
- Click "Insert Selected Items"
- Items are added to WBS Grid and grid refreshes
Get Work Bundles:
bundles = frappe.call(
'master_detail_grid.master_detail_grid.doctype.project_wbs.project_wbs.get_work_bundles_for_project_wbs'
)Get Work Bundle Items:
items = frappe.call(
'master_detail_grid.master_detail_grid.doctype.project_wbs.project_wbs.get_work_bundle_items_for_project_wbs',
bundle_name='WB-001'
)Insert Items:
result = frappe.call(
'master_detail_grid.master_detail_grid.doctype.project_wbs.project_wbs.insert_bundle_items_to_project_wbs',
project_wbs_name='PWS-001',
selected_items=items_json,
parent_rfp_name='RFP-001'
)from master_detail_grid.api import get_child_table_data
data = get_child_table_data(
parent_doctype="Sales Order",
parent_name="SO-2025-00001",
child_table_field="items"
)from master_detail_grid.api import add_child_table_row
new_row = add_child_table_row(
parent_doctype="Sales Order",
parent_name="SO-2025-00001",
child_table_field="items",
child_doctype="Sales Order Item",
data={
"item_code": "ITEM-001",
"qty": 10,
"rate": 100
}
)from master_detail_grid.api import update_child_table_row
updated_row = update_child_table_row(
child_doctype="Sales Order Item",
child_name="SOI-00001",
data={
"qty": 15,
"rate": 120
}
)from master_detail_grid.api import delete_child_table_row
result = delete_child_table_row(
child_doctype="Sales Order Item",
child_name="SOI-00001"
)// Get grid instance
let gridInstance = frm.fields_dict.master_detail_grid.grid_instance;
// Get DevExpress grid component
let dxGrid = gridInstance.getGridInstance();// Toggle detail row
gridInstance.toggle_detail_row(rowKey, rowData);
// Show detail row
gridInstance.show_detail_row(rowKey, rowData);
// Remove detail row
gridInstance.remove_detail_row(rowKey);// Refresh main grid
gridInstance.refresh();
// Refresh nested grid
gridInstance.refresh_nested_grid(gridId);// Get selected row keys
let selectedKeys = dxGrid.getSelectedRowKeys();
// Get selected row data
let selectedData = dxGrid.getSelectedRowsData();All API methods are whitelisted and can be called via frappe.call():
frappe.call({
method: 'master_detail_grid.api.get_child_table_data',
args: {
parent_doctype: 'Sales Order',
parent_name: 'SO-2025-00001',
child_table_field: 'items'
},
callback: function(r) {
console.log(r.message);
}
});Add custom styles in your app or custom app:
/* Custom grid container */
.master-detail-grid-container {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
}
/* Custom header */
.dx-datagrid-headers {
background: #f8f9fa;
font-weight: bold;
}
/* Custom detail row */
.dx-master-detail-row {
background: #fafafa;
padding: 20px;
}
/* Custom tab panel */
.dx-tabpanel {
margin-top: 15px;
}Extend the grid component:
frappe.ui.form.on('Your DocType', {
refresh: function(frm) {
// Access grid instance
let grid = frm.fields_dict.master_detail_grid.grid_instance;
// Add custom toolbar button
grid.addCustomButton('Custom Action', function() {
// Your custom logic
});
// Listen to grid events
grid.on('row_expanded', function(e) {
console.log('Row expanded:', e.key);
});
}
});Override field rendering in detail tabs:
frappe.ui.MasterDetailGrid.fieldRenderers = {
'custom_field': function(value, row) {
return `<span class="badge">${value}</span>`;
}
};Provide custom template function:
frm.fields_dict.master_detail_grid.detailTemplate = function(rowData) {
return `
<div class="custom-detail-content">
<h4>${rowData.item_name}</h4>
<p>${rowData.description}</p>
<ul>
<li>Qty: ${rowData.qty}</li>
<li>Rate: ${format_currency(rowData.rate)}</li>
</ul>
</div>
`;
};Symptoms: HTML field shows empty or loading indicator
Solutions:
- Check browser console for JavaScript errors
- Verify DevExpress library loaded:
console.log(typeof DevExpress); // Should be "object"
- Clear cache and rebuild:
bench build --app master_detail_grid bench --site your-site.local clear-cache
- Check field configuration JSON is valid
- Verify child DocType and field names are correct
Symptoms: Console error about DevExpress not found
Solutions:
- Check internet connection (CDN libraries)
- Verify no firewall blocking CDN URLs
- Check
devexpress_loader.jsis included in hooks.py - Try different CDN (loader has automatic fallback)
- Download DevExpress locally if CDN blocked
Check CDN Status:
curl -I https://cdn.jsdelivr.net/npm/devextreme@23.2.3/bundles/dx.all.js
# Should return HTTP 200Symptoms: Changes in grid don't save to backend
Solutions:
- Check form is saved (unsaved forms can't update child tables)
- Verify user has write permission on child DocType
- Check for validation errors in console
- Ensure child table field name matches configuration
- Check parent document is not submitted/cancelled
Symptoms: Detail row expands but nested grid is empty
Solutions:
- Verify
source_doctypeexists and is correct - Check filter configuration matches data
- Verify data exists for the filter criteria
- Check console for errors
- Test filter query manually:
frappe.get_all('WBS Grid', filters={'rfp': 'RFP-001', 'category': 'Materials'})
Symptoms: Grid slow to load or laggy
Solutions:
- Enable virtual scrolling:
{ "scrolling": { "mode": "virtual", "rowRenderingMode": "virtual" } } - Reduce page size:
{ "paging": { "pageSize": 20 } } - Limit visible columns
- Use server-side filtering
- Optimize child table queries
Symptoms: Grid looks broken or misaligned
Solutions:
- Clear browser cache (Ctrl+Shift+Del)
- Rebuild with hard refresh:
bench build --hard
- Check for CSS conflicts with other apps
- Verify DevExpress CSS loaded
- Test in incognito mode
Symptoms: Export button doesn't work
Solutions:
- Check ExcelJS library loaded
- Verify export permissions
- Check browser allows downloads
- Look for console errors
- Try different export format
| Browser | Version | Support | Notes |
|---|---|---|---|
| Chrome | 90+ | β Full | Recommended |
| Edge | 90+ | β Full | Chromium-based |
| Firefox | 88+ | β Full | All features work |
| Safari | 14+ | β Full | macOS/iOS |
| Opera | 76+ | β Full | Chromium-based |
| IE 11 | - | β Not Supported | Use modern browser |
- β Responsive Design: Adapts to mobile screens
- β Touch Support: Works with touch gestures
- β Mobile Browsers: iOS Safari, Chrome Android supported
β οΈ Feature Limitation: Some advanced features (drag-drop) limited on mobile
- Frappe Framework: v14.0.0 or higher
- DevExtreme: v23.2.3 (loaded via CDN)
- ERPNext: For demo features and Work Bundle integration
- ExcelJS: For Excel export (loaded via CDN)
- FileSaver: For file download (loaded via CDN)
The app automatically loads these from CDN with fallback:
-
DevExtreme
- Primary:
cdn.jsdelivr.net - Fallback:
unpkg.com - Fallback:
cdnjs.cloudflare.com
- Primary:
-
ExcelJS (for export)
- Primary:
cdnjs.cloudflare.com - Fallback:
unpkg.com
- Primary:
-
FileSaver (for downloads)
- Primary:
cdnjs.cloudflare.com - Fallback:
unpkg.com
- Primary:
We welcome contributions! Please follow these guidelines:
# Clone repository
git clone https://github.com/your-org/master_detail_grid.git
cd master_detail_grid
# Install pre-commit hooks
pre-commit install
# Make changes
# Run tests
# Commit with clear messageThis app uses pre-commit for code quality:
- ruff: Python linting and formatting
- eslint: JavaScript linting
- prettier: Code formatting
- pyupgrade: Python syntax modernization
- Use clear, descriptive commit messages
- Follow conventional commits format
- Reference issue numbers
- Keep commits atomic
- Fork the repository
- Create feature branch
- Make changes with tests
- Ensure pre-commit passes
- Submit pull request
- Wait for review
MIT License
Copyright (c) 2025 Accurate Systems
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- Issues: https://github.com/your-org/master_detail_grid/issues
- Documentation: https://your-org.github.io/master_detail_grid
- Forum: https://discuss.erpnext.com/
- Email: support@accuratesystems.com.sa
- Frappe Framework team for the excellent framework
- DevExpress for the powerful grid components
- ERPNext community for feedback and contributions
Made with β€οΈ by Accurate Systems