Custom XLSX Styles for Report Exports
Starting with Version 16, you can customize the appearance of Excel exports for standard Script Reports and Query Reports using the get_xlsx_styles method.
The get_xlsx_styles() method is called only when exporting standard Script Reports or Query Reports to Excel (.xlsx). It is not used for CSV exports.
Basic Usage
Define a get_xlsx_styles() function in your report’s Python file and use the XLSXStyleBuilder class to register and apply styles.
from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder
def get_xlsx_styles(metadata: XLSXMetadata) -> dict:
builder = XLSXStyleBuilder(metadata)
# Add your custom styles here...
return builder.result
Header formatting, filters, currency formatting, and field type styles are automatically applied when XLSXStyleBuilder is initialized.
To disable the default styling, initialize the builder as follows:
builder = XLSXStyleBuilder(metadata, default_styling=False)
XLSXMetadata
The metadata object provides information about the report being exported along with helper methods for working with rows and columns.
| Attribute | Type | Description |
|---|---|---|
report_name |
str |
Name of the report. |
filters |
dict |
Raw filter values. |
column_map |
dict[int, dict] |
Maps column indexes to column definitions. |
row_map |
dict[int, dict | list] |
Maps row indexes to report data. |
applied_filters_map |
dict[int, list] |
Maps filter row indexes to label and value pairs. |
has_total_row |
bool |
Indicates whether the report contains a total row. |
has_indentation |
bool |
Indicates whether indentation styling is available. |
All row and column indexes use 0-based indexing, following the indexing convention used by XlsxWriter.
XLSXStyleBuilder
The XLSXStyleBuilder class is used to register reusable styles and apply them to rows, columns, or individual cells.
Registering Styles
Register a style once and store the returned style ID for reuse.
style_id = builder.register_style({
"bold": True,
"bg_color": "#FFFF00"
})
Applying Styles
After registering a style, it can be applied to different parts of the worksheet.
# Apply to an entire column
builder.style_column(col_idx, style_id)
# Apply to an entire row
builder.style_row(row_idx, style_id)
# Apply to a specific cell
builder.style_cell(row_idx, col_idx, style_id)
Supported Style Properties
Font Styles
Customize text appearance using font-related properties.
builder.register_style({
"bold": True,
"italic": True,
"underline": True,
"font_size": 12,
"font_color": "#FF0000",
"font_name": "Arial"
})
Background Colors
Apply background colors to cells or ranges.
builder.register_style({
"bg_color": "#FFFF00"
})
Borders
Configure border thickness and colors for cells.
builder.register_style({
"border": 1,
"border_color": "#000000"
})
builder.register_style({
"left": 2,
"right": 2,
"top": 1,
"bottom": 3
})
Alignment
Control horizontal alignment, vertical alignment, indentation, and text wrapping.
builder.register_style({
"align": "center",
"valign": "vcenter",
"text_wrap": True,
"indent": 2
})
Number Formats
Specify custom formatting for numeric values.
builder.register_style({
"num_format": "#,##0.00"
})
Styles can be reused across multiple rows, columns, and cells after they are registered.
Complete Example
The following example demonstrates how to register reusable styles, format columns, highlight rows, and apply conditional formatting based on report values.
from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder
def get_xlsx_styles(metadata: XLSXMetadata) -> dict:
builder = XLSXStyleBuilder(metadata)
success_style = builder.register_style({
"font_color": "#006100",
"bg_color": "#C6EFCE"
})
warning_style = builder.register_style({
"font_color": "#9C5700",
"bg_color": "#FFEB9C"
})
error_style = builder.register_style({
"font_color": "#9C0006",
"bg_color": "#FFC7CE"
})
highlight_style = builder.register_style({
"bold": True,
"bg_color": "#DDEBF7"
})
total_row_border = builder.register_style({
"border": 3,
"border_color": "#15137e"
})
right_align = builder.register_style({
"align": "right"
})
amount_col_idx = builder.field_index_map.get("net_amount")
if amount_col_idx is not None:
builder.style_column(amount_col_idx, right_align)
if metadata.has_total_row:
builder.style_row(metadata.get_last_row_index(), total_row_border)
status_col_idx = builder.field_index_map.get("status")
for row_idx, row_data in metadata.row_map.items():
if not isinstance(row_data, dict):
continue
if status_col_idx is not None:
status = row_data.get("status")
if status == "Completed":
builder.style_cell(row_idx, status_col_idx, success_style)
elif status == "Pending":
builder.style_cell(row_idx, status_col_idx, warning_style)
elif status == "Cancelled":
builder.style_cell(row_idx, status_col_idx, error_style)
if amount_col_idx is not None:
amount = row_data.get("net_amount")
if amount and amount > 10000:
builder.style_cell(row_idx, amount_col_idx, highlight_style)
return builder.result
Register each style only once and reuse the returned style ID throughout the report. This reduces duplicate style definitions and improves export performance.
Reference
For the complete list of supported formatting properties, refer to the official XlsxWriter format documentation.
XLSXStyleBuilder supports the same formatting properties available through the XlsxWriter library.