Change the Default Font of an Excel Workbook with Python
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
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:
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:
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.
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:
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:
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.
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:
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.
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.
Related
- Up one level: Styling Excel Cells with openpyxl — fonts, fills and alignment.
- Apply a Reusable Style Theme Across an Excel Report — consistent styles beyond the font.
- Write a Formatted Excel Report with XlsxWriter — formats built on the default.
- Save a Workbook as an Excel Template with Python — keeping the default in a template.
More on cell styling & layout
- How-toWrite Rich Text with Mixed Formats in One Cell with xlsxwriter
- How-toCopy Cell Styles from One Cell to Another with openpyxl
- How-toAuto-Fit Column Widths When Writing with pandas
- How-toExport a Styled pandas DataFrame to Excel
- How-toGenerate a Staff Rota in Excel with Python
- How-toAdd a Table of Contents Sheet with Hyperlinks in Excel