Excel Conversion Guide

Batch Convert Excel Files While Keeping Macros (2026 Guide)

Sponsored

Published 15 April 2026 · By Joshua Hall · 8 min read

You have 80 XLSM files. Your client needs them in XLSX. You drag them into CloudConvert, come back in 20 minutes, and discover every file converted successfully — with every macro silently deleted.

This happens because batch conversion tools optimise for throughput, not fidelity. They default to XLSX, XLSX cannot contain VBA macros by specification, and no warning is ever raised. The loss is invisible until you open a file and find the automation gone.

This guide shows you the two reliable methods for batch converting Excel files without macro loss.

⚠️ The batch-mode trap: Single-file conversions sometimes work correctly (especially in Excel itself). Batch modes almost never do — they strip macros by default and there is no checkbox to change this behaviour.

Why Batch Converters Destroy Macros

XLSM is an XLSX ZIP archive with one extra file inside: xl/vbaProject.bin. This binary file holds all your VBA modules, class modules, user forms, and the project references. When a converter creates an XLSX output, it simply does not include this file. The conversion "succeeds" from the tool's perspective — the data is all there, formulas work, the file opens cleanly. But the entire VBA project is gone.

The problem is compounded in batch mode because:

Method 1: Python Script (Recommended for Developers)

The cleanest batch conversion approach is a Python script that copies each XLSM's data structure while preserving the vbaProject.bin. The key insight: an XLSM is just a ZIP file, and the VBA project is a binary blob — you can relocate it without parsing it.

Prerequisites

pip install openpyxl

The script

import os
import shutil
import zipfile
from pathlib import Path

def batch_convert_macro_safe(input_dir, output_dir, target_format='xlsm'):
    """
    Convert all XLSM/XLS files in input_dir to target_format
    while preserving VBA macros.
    target_format: 'xlsm' (recommended) or 'xlsb'
    """
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    results = {'success': 0, 'failed': 0, 'macro_preserved': 0, 'no_macro': 0}
    source_files = list(input_path.glob('**/*.xlsm')) + list(input_path.glob('**/*.xlsx'))

    for src_file in source_files:
        out_file = output_path / src_file.with_suffix(f'.{target_format}').name
        try:
            # Copy source to temp
            tmp = out_file.with_suffix('.tmp.zip')
            shutil.copy2(src_file, tmp)

            # Inspect for vbaProject.bin
            has_vba = False
            with zipfile.ZipFile(tmp, 'r') as z:
                has_vba = 'xl/vbaProject.bin' in z.namelist()

            # Rename to target extension
            shutil.move(str(tmp), str(out_file))

            if has_vba:
                results['macro_preserved'] += 1
                print(f"[OK - MACROS] {src_file.name} → {out_file.name}")
            else:
                results['no_macro'] += 1
                print(f"[OK - no macros] {src_file.name} → {out_file.name}")
            results['success'] += 1

        except Exception as e:
            results['failed'] += 1
            print(f"[FAILED] {src_file.name}: {e}")

    print(f"\n=== Batch complete ===")
    print(f"Converted: {results['success']} | Failed: {results['failed']}")
    print(f"Macros preserved: {results['macro_preserved']} | No macros: {results['no_macro']}")
    return results

# Usage
batch_convert_macro_safe('./input', './output', target_format='xlsm')

This script keeps the XLSM extension (which retains the VBA project), reports per-file macro status, and fails loudly rather than silently. Run it with python batch_convert.py from the directory containing your files.

Batch conversion checklist included in the Kit

The Macro-Safe Converter Kit includes the complete batch script, a pre-conversion macro inventory tool, format decision matrix, and post-conversion validation checklist.

Get the Kit — $9

One-time · Instant download · 30-day guarantee

Method 2: LibreOffice Headless CLI

If you prefer not to write Python, LibreOffice's headless mode can batch-convert Excel files while keeping macros — but only if you use the right output filter.

Install LibreOffice (if not already installed)

# macOS
brew install libreoffice

# Ubuntu/Debian
sudo apt install libreoffice

# Windows: download from libreoffice.org

Batch conversion command

# Convert all XLSM in /input to XLSM in /output (macro-safe)
for f in /input/*.xlsm; do
  libreoffice --headless \
    --infilter="Calc MS Excel 2007 XML" \
    --convert-to xlsm:"Calc MS Excel 2007 VBA XML" \
    --outdir /output "$f"
done

The critical detail is the output filter: Calc MS Excel 2007 VBA XML. This is LibreOffice's macro-aware XLSM filter. The default xlsm conversion without the explicit filter sometimes strips macros — always specify the full filter name.

LibreOffice batch: what works and what doesn't

OperationMacro preservationNotes
XLSM → XLSM (VBA XML filter)PreservedUse explicit filter name
XLSM → XLSX (any filter)DestroyedXLSX cannot hold VBA
XLS → XLSM (VBA XML filter)Usually preservedMay fail on complex XLS VBA
XLSM → ODSDestroyedODS uses a different macro system
XLSM → PDFN/APDF has no macro concept; content preserved

Pre-Conversion: Inventory Your Macros

Before running any batch conversion, take a macro inventory. This gives you a before/after comparison point.

import zipfile
from pathlib import Path

def inventory_macros(directory):
    for f in Path(directory).glob('**/*.xlsm'):
        with zipfile.ZipFile(f) as z:
            has_vba = 'xl/vbaProject.bin' in z.namelist()
            vba_size = 0
            if has_vba:
                vba_size = z.getinfo('xl/vbaProject.bin').file_size
            print(f"{f.name}: {'HAS MACROS' if has_vba else 'no macros'} ({vba_size:,} bytes)")

inventory_macros('./input')

Post-Conversion Validation

After batch conversion, always validate. The fastest check is comparing vbaProject.bin presence between source and output:

import zipfile
from pathlib import Path

def validate_batch(input_dir, output_dir):
    issues = 0
    for src in Path(input_dir).glob('*.xlsm'):
        out = Path(output_dir) / src.name
        if not out.exists():
            print(f"[MISSING] {src.name} not found in output")
            issues += 1
            continue
        with zipfile.ZipFile(src) as z_in, zipfile.ZipFile(out) as z_out:
            src_has_vba = 'xl/vbaProject.bin' in z_in.namelist()
            out_has_vba = 'xl/vbaProject.bin' in z_out.namelist()
            if src_has_vba and not out_has_vba:
                print(f"[MACRO LOSS] {src.name}: macros in source, missing from output")
                issues += 1
    if issues == 0:
        print("All macros validated successfully.")
    else:
        print(f"{issues} issue(s) found — check affected files before deleting originals.")

validate_batch('./input', './output')
Never delete originals until validated. Always keep the source XLSM files until the post-conversion validation script passes for every file. Macro loss is irreversible if you have no backup.

Comparison: Batch Conversion Tool Options

ToolBatch supportMacro preservationCost
CloudConvertYesDestroys macrosFree/paid
ZamzarYes (paid)Destroys macrosPaid
iLovePDFYesDestroys macrosFree/paid
Python + zipfileYes (script)PreservedFree
LibreOffice CLI (VBA filter)Yes (shell loop)Usually preservedFree
Excel + VBA macroYes (VBA loop)PreservedExcel licence

FAQ

Can I batch convert XLSM files without losing macros?

Yes, but not with most online or GUI batch converters. These strip macros silently. The reliable methods are a Python script (using the ZIP-level copy approach) or LibreOffice headless CLI with the Calc MS Excel 2007 VBA XML filter explicitly specified.

Why do batch converters destroy macros when single-file converters sometimes don't?

Batch converters prioritise throughput over compatibility. They default to XLSX output and never check whether the source contained macros. Single-file GUI converters sometimes give a warning (Excel does), but this warning disappears in batch/API modes.

How do I validate that macros survived a batch conversion?

Compare the xl/vbaProject.bin file presence between source and output ZIP archives. The validation script above automates this comparison across entire directories in seconds.

Related Guides

Best Macro-Safe Tools 2026Tool comparison VBA Macros Lost? Here's the FixRecovery guide Free Macro Inspector ToolCheck your file first

Stop losing macros on every conversion

The Macro-Safe Converter Kit includes the complete batch script, pre-conversion inventory tool, and post-conversion validation checklist.

Get the Kit — $9

One-time · Instant download · 30-day guarantee