How-to
Formatting And Charting Excel Reports With PythonDeep dive

Change the Default Font of an Excel Workbook with Python

Make a generated workbook use your house font everywhere — not just in the cells you styled: set XlsxWriter's default_format_properties (also through pandas), understand why changing the Normal style in openpyxl does nothing to existing cells, use a template so openpyxl inherits the right default, keep bold and coloured cells in the same family, and verify the fonts that actually render.

Brand guidelines say Arial 10, or the company font, everywhere — and a generated report comes out in Calibri 11 except for the few cells the script styled explicitly. Styling every cell by hand is slow and misses empty cells that someone types into later. Excel keeps a workbook-wide default font in the Normal cell style; set that, and every unstyled cell — including cells added later by a person — uses it. How to set it from Python depends on the library: XlsxWriter has an option for it, while openpyxl offers no public switch, and the obvious attempt silently does nothing. This page shows what works with each, how to keep bold and coloured cells in the same font family, and how to verify the result. It belongs to Styling Excel Cells with openpyxl.

Prerequisites

Bash
pip install xlsxwriter openpyxl pandas

LibreOffice (soffice) and pdffonts (from Poppler) are used in Step 5 to check which fonts a rendered file really uses; they are optional.

Step 1 — XlsxWriter: default_format_properties

XlsxWriter sets the default font when the workbook is created:

Python
import xlsxwriter

book = xlsxwriter.Workbook("branded.xlsx", {
    "default_format_properties": {"font_name": "Arial", "font_size": 10},
})
sheet = book.add_worksheet("Summary")
heading = book.add_format({"bold": True, "font_color": "#4338CA"})
sheet.write("A1", "Quarterly summary", heading)
sheet.write_row("A3", ["Region", "Revenue"])
sheet.write_row("A4", ["North", 1200])
book.close()

default_format_properties replaces Calibri 11 in the workbook's base style, so every cell written without a format, every empty cell and every format created with add_format starts from Arial 10. The heading format only adds bold and a colour; the font name is inherited. This is the simplest and most complete way to change the default, and it works for the whole file in one line.

Step 2 — The same through pandas

pandas uses XlsxWriter by default when it is installed, and passes writer options through engine_kwargs:

Python
import pandas as pd

df = pd.DataFrame({"Region": ["North", "South", "East"], "Revenue": [1200, 950, 1430]})
with pd.ExcelWriter("branded_pandas.xlsx", engine="xlsxwriter",
                    engine_kwargs={"options": {"default_format_properties": {"font_name": "Arial", "font_size": 10}}}) as xw:
    df.to_excel(xw, index=False, sheet_name="Data")
print("written with Arial 10 as the default")

The data cells and pandas' own bold header both use Arial 10 — the header format pandas creates inherits the default name and size and only adds bold and a border. Column widths that pandas or autofit() compute later are based on the characters, so a smaller default font leaves columns slightly wider than needed; adjust widths after changing the font if the layout is tight.

Changing the default font with XlsxWriter and openpyxl XlsxWriter has a workbook option, default_format_properties, that changes the base style for all cells and formats. openpyxl has no public option; editing the Normal named style does not change existing cells, so the reliable route is a template whose default font is already set. XlsxWriter default_format_properties option new formats inherit the font works through pandas engine_kwargs new files only openpyxl no public default-font option editing Normal style has no effect inherits the default from a template set name explicitly on new Font objects

Step 3 — Why changing the Normal style in openpyxl does not work

openpyxl does expose the workbook's named styles, and changing the font of Normal looks like it should work:

Python
from openpyxl import Workbook
from openpyxl.styles import Font

wb = Workbook()
ws = wb.active
wb._named_styles["Normal"].font = Font(name="Arial", size=10)
ws["A1"] = "still Calibri?"
print(ws["A1"].font.name, ws["A1"].font.sz)

The cell still reports Calibri 11. Cells do not look up the Normal style when they are written; they point at the workbook's first font entry, which openpyxl creates as Calibri 11 and does not change when the named style is edited. The attribute used here is private (the leading underscore says so) and editing it is a dead end either way. openpyxl simply has no supported switch for the base font of a new workbook.

Step 4 — openpyxl: start from a template with the right default

The reliable openpyxl route is to start from a workbook whose default font is already right — created once by XlsxWriter, or saved from Excel after changing the Normal style — and let openpyxl inherit it:

Python
from openpyxl import load_workbook
from copy import copy

template = xlsxwriter.Workbook("arial_template.xlsx", {"default_format_properties": {"font_name": "Arial", "font_size": 10}})
template.add_worksheet("Report")
template.close()

wb = load_workbook("arial_template.xlsx")
ws = wb["Report"]
ws["A1"] = "Inherited default"
base = ws["A1"].font
ws["A2"] = "Bold, same family"
ws["A2"].font = Font(name=base.name, sz=base.sz, b=True)
ws["A3"] = "Bold, family lost"
ws["A3"].font = Font(b=True)
wb.save("report_from_template.xlsx")
for coord in ("A1", "A2", "A3"):
    print(coord, ws[coord].font.name, ws[coord].font.sz, ws[coord].font.b)

Cells written into the loaded template report Arial 10 without any styling, because the template's first font entry is Arial 10. The two bold cells show the second trap: Font(b=True) creates a font with no name and no size, which Excel and LibreOffice fill in from their own fallbacks — not from your default — so the cell renders in a different family. Build new fonts from the base font's name and size, or copy it with changes (copy(base) and then set b), so emphasised cells stay in the house font. Template workflows are covered in Fill an Excel Template with Python and openpyxl.

What each cell inherits in the template-based workbook A1 with no styling inherits Arial 10 from the template. A2, bold with the base font's name and size, stays Arial. A3, created with Font(b=True) alone, has no font name and renders in the application's fallback font. A B C D E 1 Cell Text Font name Size Bold 2 A1 Inherited default Arial 10 no 3 A2 Bold, same family Arial 10 yes 4 A3 Bold, family lost (none) (none) yes

Step 5 — Check the fonts that actually render

What a cell says its font is and what a reader sees can differ — fonts missing on the reader's machine are substituted. Rendering to PDF and listing the embedded fonts shows the truth:

Python
import subprocess

for name in ("branded.xlsx", "report_from_template.xlsx"):
    subprocess.run(["soffice", "--headless", "--convert-to", "pdf", name], check=True, capture_output=True)
    fonts = subprocess.run(["pdffonts", name.replace(".xlsx", ".pdf")], capture_output=True, text=True).stdout
    used = sorted({line.split()[0].split("+")[-1] for line in fonts.splitlines()[2:] if line.strip()})
    print(f"{name:28} {used}")

On this Linux machine, Arial is not installed, so LibreOffice substitutes Liberation Sans, a metric-compatible replacement — text takes exactly the same width, and the file still says Arial, so it renders in real Arial for Windows and Mac readers. The template-based report shows a second family as well: Caladea, a serif font, appears for the cell created with Font(b=True) — with no name of its own, the cell fell back to LibreOffice's default for the file, which is the problem Step 4 warned about, now visible in the output. In Excel the same cell would fall back to the application default, again not your house font. Checking the font list in a test catches that kind of slip before a branded report goes out.

From the font a cell names to the font a reader sees A cell names a font such as Arial. When the file is opened, the application looks for that font; if it is missing, it substitutes a metric-compatible one such as Liberation Sans. A cell without a font name gets the application's own fallback, which may be a different family. Cell font name Arial (or none) Font installed? on the reader's machine Rendered font Arial · Liberation Sans · fallback

Step 6 — Fonts for charts, comments and headers

The default font covers cells. Other parts of a workbook have their own settings: chart text in XlsxWriter is set per chart (chart.set_title({"name_font": {"name": "Arial"}}) and the axis num_font and name_font options), page headers and footers take a font code such as &"Arial,Bold", and cell comments use Excel's own comment font. If brand rules cover the whole document, set those explicitly too. Number formats and column widths are independent of the font name, but a different font size changes how many characters fit in a column; see Set Column Width and Row Height in openpyxl.

Performance notes

Changing the default font costs nothing at write time: it is one entry in the styles part. It even saves work compared with styling every cell, because each explicitly styled cell needs a style record, while unstyled cells share the default. The template approach adds one small file read per run. The PDF font check takes a couple of seconds for the LibreOffice conversion, so keep it in tests rather than in every production run.

Common pitfalls

  • Editing openpyxl's Normal style. It does not change the font cells use; start from a template instead.
  • Font(b=True) without a name. The cell falls back to the application's font; pass the base name and size.
  • Styling only the cells you write. Empty cells and later additions keep Calibri; change the default instead.
  • Assuming the reader has the font. Unusual fonts are substituted; prefer widely installed fonts or embed a check.
  • Forgetting charts and headers. They do not follow the cell default.

Conclusion

With XlsxWriter, set default_format_properties when creating the workbook — directly or through pandas' engine_kwargs — and every cell and format inherits the font. openpyxl has no public option and ignores edits to the Normal style for cell fonts, so start from a template whose default font is already right, and create emphasised fonts from the base name and size rather than Font(b=True) alone. Render a PDF and list its fonts to confirm what readers will see.

Frequently asked questions

How do I change the default font in an Excel file created with Python? With XlsxWriter, pass {"default_format_properties": {"font_name": "Arial", "font_size": 10}} as the Workbook options. Every cell and every format created afterwards uses that font unless it sets its own.

How do I set the default font when writing Excel with pandas? Use the xlsxwriter engine and pass engine_kwargs={"options": {"default_format_properties": {"font_name": "Arial", "font_size": 10}}} to pd.ExcelWriter. pandas' header format inherits the font too.

Why doesn't changing the Normal style font in openpyxl work? Cells reference the workbook's first font entry directly, and openpyxl does not update it when the Normal named style changes. Start from a template workbook whose default font is already set instead.

Why does my bold cell use a different font after changing the default? It was styled with Font(b=True), which creates a font without a name or size, so the application substitutes its own fallback. Create the bold font with the default's name and size.

More on cell styling & layout

More on cell styling & layout →