Read and write the VBA macros inside Excel workbooks, in pure Python.
No external dependencies. No Excel install required. Works on Windows, macOS, and Linux. Python 3.10 or newer.
Supports .xlsm, .xlsb, .xlam, and legacy .xls.
Several excellent Python tools already exist for reading VBA out of Excel files (oletools, olefile, and friends), and they remain a strong choice for forensics, malware analysis, and audit use-cases. pyOpenVBA focuses on the next step: safely writing changes back so the workbook still opens cleanly in Excel.
The write path is the whole point of the library:
- Modify a module's source in place.
- Add a new standard module, class module, or document/UserForm code-behind.
- Rename any module (the CFB stream,
dirrecord,PROJECTdeclaration,PROJECTwmname map, andAttribute VB_Nameare all updated in lockstep). - Delete a module cleanly.
- Save the workbook and have it reopen in Excel with no "we found
a problem with some content" repair dialog. Every supported format
(
.xlsm,.xlsb,.xlam,.xls) is verified against live Excel. - Create new
.xlsmfiles on the fly, and inject VBA code into them.
That makes it a good fit for:
- Version-controlling your VBA in git like normal source code, then pushing edits back without ever opening Excel.
- Diffing two workbooks to see what changed in
Module1. - Generating or updating macros from a script without scripting Excel through COM automation.
- Reading and writing macros on a server (Linux / CI) where Excel is not installed.
- Agentic AI Integration allow your AI agent easy access to both push and pull VBA code in your workbook.
pyOpenVBA is a complete read-and-write library, so it covers the full lifecycle of a VBA project in one place: extract, edit, version, write back, and verify. If you only ever need to read, the existing tools remain a solid choice; if you might ever need to write, start here and you will not have to switch later.
From PyPI:
pip install pyOpenVBARequires Python 3.10 or newer. There are no other dependencies.
After install, the CLI is available either as a module or as a script:
python -m pyopenvba --help
pyopenvba --helpFrom source (for development):
git clone https://github.com/WilliamSmithEdward/pyOpenVBA
cd pyOpenVBA
pip install -e ".[dev]"from pyopenvba import ExcelFile
with ExcelFile("workbook.xlsm") as wb:
# 1. List all VBA modules in the workbook.
print(wb.module_names())
# ['ThisWorkbook', 'Sheet1', 'Module1']
# 2. Read a module's source as a string.
source = wb.get_module("Module1")
print(source)
# 3. Edit a module and save the workbook.
wb.set_module("Module1", 'Sub Hello()\r\n MsgBox "hi"\r\nEnd Sub\r\n')
wb.save() # overwrites the original file
# wb.save("edited.xlsm") # ...or save to a new fileThat is the entire core API. Three methods.
Need a fresh macro-enabled workbook without launching Excel? Use
ExcelFile.create_new():
from pyopenvba import ExcelFile
with ExcelFile.create_new("new_book.xlsm") as wb:
wb.set_module(
"Module1",
'Sub Hello()\r\n MsgBox "hello from python"\r\nEnd Sub\r\n',
)
wb.save()The new workbook ships with ThisWorkbook, Sheet1, and an empty
Module1, and opens cleanly in Excel with no repair prompt.
from pyopenvba import ExcelFile, VBAModuleKind
with ExcelFile("workbook.xlsm") as wb:
project = wb.vba_project()
project.add_module(
"NewModule",
"Sub Hi()\r\n MsgBox \"hi\"\r\nEnd Sub\r\n",
kind=VBAModuleKind.standard,
)
project.rename_module("OldName", "NewName")
project.delete_module("Obsolete")
wb.save("out.xlsm")This is the easiest way to manage VBA in a git repo. Export every module to a folder, edit the files in any text editor, then push the changes back into the workbook.
From the command line:
# 1. Pull every module out of the workbook into ./vba/
python -m pyopenvba pull workbook.xlsm ./vba
# 2. ...edit ./vba/Module1.bas in your editor of choice...
# 3. Push your edits back into the workbook
python -m pyopenvba push ./vba workbook.xlsm
# Bonus: list modules without extracting anything
python -m pyopenvba ls workbook.xlsmFrom Python:
from pyopenvba import pull, push
pull("workbook.xlsm", "./vba")
# ...edit files...
push("./vba", "workbook.xlsm") # in place
push("./vba", "workbook.xlsm", out="edited.xlsm") # to a new fileModule files use the extensions VBA already uses: .bas for standard
modules, .cls for class modules, .frm for UserForm code-behind.
| Extension | What it is | Read | Write |
|---|---|---|---|
.xlsm |
Macro-enabled workbook | yes | yes |
.xlsb |
Binary workbook | yes | yes |
.xlam |
Macro-enabled add-in | yes | yes |
.xls |
Legacy (Excel 97-2003) | yes | yes |
Every save is verified to reopen in Excel without the "we found a problem with some content" repair dialog.
save() refuses to silently produce a broken workbook.
If the VBA project is password-protected, any mutation will raise
VBAProjectError unless you explicitly opt in:
wb.save(allow_protected=True)The library never tries to decrypt or change the password - it just preserves the existing protection bytes verbatim. The resulting workbook still requires the original password to open the VBE.
A digital signature is invalidated by any change to the macros. On
mutation, the library drops the stale signature streams and emits a
UserWarning so you know trust has been removed:
import warnings
warnings.filterwarnings("error", category=UserWarning) # treat as fatal
# ...or silence the warning if you accept the consequence:
wb.save(allow_invalidate_signature=True)This library is intentionally focused on module source code. The following are preserved byte-for-byte but not interpreted:
- UserForm layout (controls, properties, positions). Editing the code-behind of a UserForm works fine; editing the design surface does not.
- VBA project password decryption / re-encryption.
- Re-signing digitally signed projects.
- ActiveX license editing.
See docs/roadmap.md for the full feature matrix.
src/pyopenvba/
__init__.py public API (ExcelFile, pull, push, exceptions)
excel.py ExcelFile facade (ZIP / CFB dispatch, pull/push helpers)
vba.py VBA project parser + MS-OVBA codec
cfb.py MS-CFB (Compound File Binary) parser/writer
exceptions.py custom exception hierarchy
_templates/ baked-in empty .xlsm bytes for ExcelFile.create_new()
__main__.py `python -m pyopenvba {pull,push,ls}` CLI
For deeper documentation:
- docs/architecture.md - internal module layout.
- docs/ms-ovba-implementation-guide_v2.md - language-agnostic guide for re-implementing MS-OVBA in another language.
- docs/roadmap.md - per-feature implementation status.
Bug reports, weird workbooks that break the library, and PRs are all welcome. Please include the workbook (or a minimal redacted version) when filing a parsing bug.
Run the full local check (same as CI):
pip install -e ".[dev]"
pyright src tests
pytest -p no:randomlyCI runs the test matrix on Python 3.10 / 3.11 / 3.12 / 3.13 across
Linux, plus 3.12 on Windows and macOS, on every push and pull request.
Releases are published to PyPI automatically when a v*.*.* tag is
pushed.
MIT.