Convert XLSM to XLSX Without Losing Macros
Here's the uncomfortable truth: you cannot convert XLSM to XLSX and keep macros intact. XLSX is structurally incapable of storing VBA code — it's a format limitation, not a software bug.
But there is a safe workflow. You can preserve all VBA code as exported files, create an XLSX copy for sharing, and keep the original XLSM as your working master. This guide walks you through the full process — manually and via script for bulk operations.
vbaProject.bin binary. If you haven't overwritten the original XLSM, you can still recover. See the recovery guide.
Why XLSM → XLSX Always Strips Macros
XLSM stores VBA code in a binary file called vbaProject.bin inside the ZIP archive. XLSX's format specification explicitly prohibits this binary — there's no slot for it. When any tool converts XLSM to XLSX, it must choose:
- Include vbaProject.bin → produces an invalid XLSX that most viewers reject
- Drop vbaProject.bin → produces a valid XLSX with no macros (what every tool does)
The only way to "keep" your macros is to archive them separately before the conversion.
The Safe 5-Step Workflow
Audit What Macros Exist
Before touching the file, know what you're working with. Open the VBA Editor (Alt+F11 in Excel) and look at the Project Explorer panel on the left.
Count and name every item under your workbook's project:
- Modules (Module1, Module2, etc.) — standard VBA code
- Class Modules (clsInvoice, etc.) — object-oriented VBA
- Forms (UserForm1, etc.) — dialog boxes
- ThisWorkbook / Sheet objects — event handlers
Alternatively, use the free Macro Inspector to get a full module list without opening Excel.
Export All VBA Modules
In the VBA Editor Project Explorer, right-click each module/class/form → Export File.
Save to a folder named after the workbook. Use these extensions:
| Module type | Extension | What it contains |
|---|---|---|
| Standard module | .bas | All Sub and Function procedures |
| Class module | .cls | Class definition and methods |
| UserForm | .frm | Form layout (+ .frx binary for controls) |
| ThisWorkbook / Sheet | .cls | Workbook and sheet event handlers |
Also copy any macros from Sheet code modules (right-click the sheet tab → View Code). These are often missed.
Create the XLSX Copy
With the XLSM open in Excel:
- File → Save As
- Change "Save as type" to Excel Workbook (*.xlsx)
- Save to a different location or add
_sharingto the filename - Click Yes when Excel warns that macros will be removed
Archive the Original XLSM
Move the original XLSM (not the new XLSX) to a dated archive folder:
archive/
2026-04-15_report-automation-v3.xlsm
modules/
Module1.bas
DataProcessor.cls
InputForm.frm
InputForm.frx
Zip it. Add a README noting what the macros do and when they were last verified. This is your recovery kit if the XLSX version needs macros re-enabled later.
Verify the XLSX Output
Open the new XLSX and verify:
- All worksheets are present with correct data
- Formulas that relied on VBA-defined functions now show
#NAME?— this is expected - Formatting, tables, and charts transferred correctly
- The file size is smaller than the XLSM (expected — vbaProject.bin removed)
Batch Conversion: Multiple XLSM Files at Once
If you have dozens of XLSM files to convert, doing this manually for each is impractical. The Python script below automates the export + conversion:
"""
batch_xlsm_to_xlsx.py — Safe XLSM → XLSX converter with module archiving
Requires: openpyxl, zipfile (standard library)
Usage: python batch_xlsm_to_xlsx.py /path/to/xlsm/files
"""
import os, sys, shutil, zipfile
from pathlib import Path
def extract_vba_project(xlsm_path, archive_dir):
"""Extract vbaProject.bin from XLSM for archiving."""
archive_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(xlsm_path, 'r') as z:
entries = z.namelist()
vba = [e for e in entries if 'vbaProject' in e]
for entry in vba:
z.extract(entry, archive_dir)
print(f" Archived: {entry}")
return len(vba)
def convert_xlsm_to_xlsx(xlsm_path, output_dir, archive_base):
"""Convert a single XLSM to XLSX with VBA archiving."""
p = Path(xlsm_path)
out_name = p.stem + '.xlsx'
out_path = Path(output_dir) / out_name
archive_dir = Path(archive_base) / p.stem
# Archive VBA binary
vba_count = extract_vba_project(p, archive_dir)
print(f" Archived {vba_count} VBA component(s)")
# Copy XLSM as ZIP, rebuild without vbaProject.bin
with zipfile.ZipFile(p, 'r') as src_zip:
with zipfile.ZipFile(out_path, 'w', zipfile.ZIP_DEFLATED) as dst_zip:
for item in src_zip.infolist():
if 'vbaProject' not in item.filename:
dst_zip.writestr(item, src_zip.read(item.filename))
# Fix Content_Types to remove vba reference
# (simplified — for production, use openpyxl to re-save properly)
print(f" Converted: {out_name}")
return out_path
if __name__ == '__main__':
src_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
output_dir = Path(src_dir) / 'xlsx_output'
archive_dir = Path(src_dir) / 'vba_archive'
output_dir.mkdir(exist_ok=True)
files = list(Path(src_dir).glob('*.xlsm'))
print(f"Found {len(files)} XLSM files to convert")
for f in files:
print(f"Processing: {f.name}")
convert_xlsm_to_xlsx(f, output_dir, archive_dir)
print(f"\nDone. XLSX files in: {output_dir}")
print(f"VBA archives in: {archive_dir}")
xlrd and oletools to extract human-readable module source from the binary.
Re-importing Macros Later
If you later need the macros back in a file, here's how to re-import from your .bas / .cls exports:
- Open the target workbook
- Save as XLSM (it must be XLSM to hold macros)
- Open VBA Editor (Alt+F11)
- File → Import File → select your .bas / .cls files
- For UserForms: import the .frm file (the .frx must be in the same folder)
- Test all macros — some may need worksheet or workbook references updated
Common Mistakes
| Mistake | Consequence | Prevention |
|---|---|---|
| Using Save (not Save As) when converting | Overwrites the XLSM master | Always Save As to a new filename/location |
| Skipping sheet event handlers | Worksheet_Change, Workbook_Open events lost | Export ThisWorkbook and each sheet module explicitly |
| Forgetting .frx files with .frm | UserForms won't import correctly | Export both .frm and .frx; they're always paired |
| Sending XLSX to users who expect automation | VBA-driven formulas return #NAME? | Document which features require the macro version |
Need to convert dozens of XLSM files?
MacroKit includes a complete batch conversion workflow with automated VBA export, conversion scripts, and a post-conversion validation checklist. One purchase covers every file, forever.
Get MacroKit — $9