vbapm
v0.11.0
Published
A package manager and build tool for VBA
Readme
vbapm
A package manager and build tool for VBA. It lets you version-control your VBA source code and Excel XML structure as plain text files, then build them back into a working workbook or add-in.
Support for other MS Office file format is planed.
Installation
Option 1: Install as a global npm package
Prerequisites: Node.js v22 or higher.
npm install -g vbapmOption 2: Standalone installer
Windows
In powershell, run the following:
iwr https://raw.githubusercontent.com/vbapm/installer/refs/heads/main/install.ps1 | iexMac
In terminal, run the following:
curl -fsSL https://raw.githubusercontent.com/vbapm/installer/refs/heads/main/install.sh | shFor more recent versions of Office for Mac, you will need to trust access to the VBA project object model for vbapm to work correctly:
If you run into any issues during installation, please see the known issues for the installer or create a new issue with details about what's happening.
:rocket: You're ready to go! Open a new command-line session (cmd / terminal) and try vba --help
macOS users: You must enable "Trust access to the VBA project object model" in Excel → Preferences → Security for vbapm to work.
Getting Started
Initialize a project from an existing workbook
If you already have an .xlsm or .xlam file and want to start managing it with vbapm, use the --from flag:
vba new my-project --from "C:\path\to\existing.xlsm"If you want to start in the current directory instead of creating a new one:
vba initThis creates a new directory my-project/ containing:
my-project/
├── vbaproject.toml # Project manifest (name, target type, sources)
├── src/ # Extracted VBA source files
│ ├── Module1.bas # Standard modules → .bas
│ ├── Sheet1.cls # Document modules → .cls
│ ├── ThisWorkbook.cls # Workbook module → .cls
│ ├── MyClass.cls # Class modules → .cls
│ └── UserForm1.frm # UserForms → .frm
├── target/ # Extracted Excel XML structure
│ ├── [Content_Types].xml
│ └── xl/
│ ├── workbook.xml
│ ├── worksheets/
│ ├── styles.xml
│ └── ...
├── build/ # Built workbook (customizable via build-dir in vbaproject.toml)
│ └── my-project.xlsm
└── .git/, .gitignore, ... # Git version control (by default)vbapm automates two extraction steps:
VBA source code — The vbapm add-in runs a macro that extracts every
VBComponentfrom the workbook as its native file type, along with aproject.jsonlisting non-built-in references.XML structure — The workbook is unzipped and its internal XML (sheets, styles, ribbon, etc.) is extracted to
target/.
Use
vba init --from existing.xlsminstead ofvba newif you want to initialize in the current directory rather than creating a new one.
The basic workflow
Once your project is initialized, the day-to-day loop is:
- Edit your source files in
src/and register new ones withvba add - Build the workbook with
vba build - Test your macros in Excel, or use
vba runfrom the command line - Extract changes back to source with
vba extract, thengit diffand commit
Putting it together
# Start with a new project from an existing file
vba new expense-tracker --from C:\workbooks\budget-2025.xlsm
cd expense-tracker
# Make edits to src/ files, then rebuild
vba build --open
# After tweaking in Excel, capture the changes
vba extract
# Commit your work
git add . && git commit -m "Add expense tracker project"Programmatic Usage
You can also use vbapm as a library (e.g. from a VS Code extension):
const { buildProject, loadProject, env } = require("vbapm");
// Override working directory
env.cwd = "/path/to/project";
const project = await loadProject();
await buildProject(project);Usage
new
Create a new folder with a blank/generated vbapm project inside
Create a folder "project-name" with a blank xlsm project:
vba new project-name.xlsm(equivalent to above)
vba new project-name --target xlsmCreate a folder "from-existing" with a project from an existing workbook:
vba new from-existing --from existing.xlsmCreate a blank package for sharing as a library between projects:
vba new json-converter --packageBy default, vba new initializes a git repository and creates .gitignore, .gitattributes and .editorconfig template files. Use --no-git to skip git init and --no-conf to skip the template files:
vba new project-name.xlsm --no-git
vba new project-name.xlsm --no-confinit
Create a blank/generated vbapm project in the current folder
Create a blank xlsm project with the current folder's name:
vba init --target xlsmCreate a project from an existing workbook:
vba init --from existing.xlsmCreate a blank package:
vba init --packageBy default, vba init initializes a git repository and creates .gitignore, .gitattributes and .editorconfig template files. Use --no-git to skip git init and --no-conf to skip the template files:
vba init --target xlsm --no-git
vba init --target xlsm --no-confadd
Create a new source file in src/ and register it in vbaproject.toml.
Create a standard module:
vba add Module1Create a class module:
vba add JsonParser --type classRegister an existing source file by path (no overwrite):
vba add .\src\Test.basCreate and register a file in a nested path (missing folders are created):
vba add .\src\features\auth\LoginHelper.basAdd a development-only source to [dev-src]:
vba add TestHelpers --devbuild
Build an Excel workbook from the project's source. The built file is written to the directory specified by build-dir in vbaproject.toml (defaults to build/). If a previously built file is found it is moved to a .backup directory under the build directory to protect against losing any previously saved work.
To output the built file in the project root instead, set build-dir = "." in vbaproject.toml.
Build a project:
vba buildBuild and open a project for editing:
vba build --openBuild a package using a blank target:
vba build --target xlsmBuild a project, excluding any development src, dependencies, or references:
vba build --releaseUpdate only the VBA source in an existing built target, without rebuilding the workbook:
vba build --vba-onlyThis is equivalent to vba update and requires the target to already exist in the build directory.
extract
Once you've completed your edits and are ready to commit your changes, extract your project with vba extract.
Extract a project:
vba extractExtract a previously-built package:
vba extract --target xlsmOnly extract the XML files (skip VBA source extraction):
vba extract --xml-onlyOnly extract the VBA source (skip XML extraction):
vba extract --vba-onlyNote:
vba exportis deprecated and will be removed in a future version. Usevba extractinstead.
update
vba update writes the current VBA source directly into an existing built target file (including one currently open in Excel), without going through a full build cycle.
Update VBA source in the built target:
vba updateUpdate VBA in a specific target type:
vba update --target xlsmUpdate excluding dev-src, dev-dependencies, and dev-references:
vba update --releaseUpdate and leave the target open in Excel after updating:
vba update --openopen
vba open opens the current built target file in Excel. This is a convenience shortcut when you want to start editing a target you've already built.
Open the built target:
vba openOpen a specific target type:
vba open --target xlsmclose
vba close closes the built target file that is currently open in Excel. By default changes are discarded — pass --save to keep them.
Close the built target (discard unsaved changes):
vba closeClose and save changes:
vba close --saveClose a specific target type:
vba close --target xlsmrun
vba run opens the built workbook in Excel, runs a public VBA function, captures its return value and prints it to stdout.
Syntax
vba run <Module.Function> [<arg1> <arg2> ...] [--file PATH] [--target TYPE]VBA code conventions
The vba run command calls a public function (not a Sub). The function:
- Must be
Public - Must return a
String(or a type that VBA can coerce to a string) - Can accept up to 10 arguments (passed positionally from the CLI)
- Arguments arrive as
Varianton the VBA side
' (Module: Messages.bas)
Public Function SayHi(Name As Variant) As String
SayHi = "Howdy " & Name & "!"
End Functionvba run Messages.SayHi Tim
Howdy Tim!Passing arguments
Arguments are passed positionally and arrive as Variant in VBA:
# Single argument
vba run ExcelManipulator.WriteCell "Sheet1,5,3,Hello World"
# Multiple arguments
vba run Math.Add 3 7
# Quoted strings with spaces
vba run Messages.SayHi "Tim Hall"Return value
If the VBA function returns a string, it is printed to stdout. This is how you receive data back from Excel.
If the function returns a JSON string matching {"success": true/false, "messages": [...], "errors": [...]} it is parsed as a structured result.
Targeting options
Run against the built project (default — run from project directory):
vba run ExcelManipulator.AddSheet "MyNewSheet"Run against a specific file:
vba run Messages.SayHi Tim --file "C:\path\to\workbook.xlsm"Run against a specific target type (if project has multiple targets):
vba run MyModule.MyFunction arg1 --target xlsmPractical workflow for Excel manipulation
vba add ExcelManipulator
# → edit src/ExcelManipulator.bas with your VBA functions
vba build
vba run ExcelManipulator.AddSheet "Report"
vba run ExcelManipulator.WriteCell "Report,1,1,Title"
vba run ExcelManipulator.GetCell "Report,1,1"
# → output: "Title"Example VBA module for cell manipulation:
Public Function AddSheet(SheetName As Variant) As String
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets.Add
ws.Name = CStr(SheetName)
AddSheet = "Created sheet: " & SheetName
End Function
Public Function WriteCell(Args As Variant) As String
Dim parts() As String
parts = Split(CStr(Args), ",")
ThisWorkbook.Sheets(parts(0)).Cells(CLng(parts(1)), CLng(parts(2))).Value = parts(3)
WriteCell = "Wrote '" & parts(3) & "' to " & parts(0) & "!" & parts(1) & "," & parts(2)
End Function
Public Function GetCell(Args As Variant) As String
Dim parts() As String
parts = Split(CStr(Args), ",")
GetCell = CStr(ThisWorkbook.Sheets(parts(0)).Cells(CLng(parts(1)), CLng(parts(2))).Value)
End FunctionFor complex operations, consider encoding data as JSON strings in arguments and parsing with a VBA JSON library.
Tips
- Build before running —
vba runtargets the built workbook inbuild/, not the source files. - Extract after manipulation — Changes made by macros (new sheets, cell values) live in the built workbook. Run
vba extractto persist them back to source. - Close before rebuilding — If a workbook is open in Excel, close it with
vba close --savebefore runningvba buildagain. - Run from the project root — Commands expect
vbaproject.tomlin the current directory, unless you use--fileto target a specific workbook. - Quote arguments with spaces — Shell escaping is handled by vbapm, but wrap arguments containing spaces in quotes.
Manifest (vbaproject.toml)
The vbapm manifest (vbaproject.toml) serves as the foundation for your project and provides information on your package, source, dependencies, references, and targets, as detailed below.
[project] or [package]
The [package] / [project] section includes general information about your package. You should choose [package] if your project is only intended to be used as a utility inside another project and [project] if your project is a standalone tool.
Here are the main properties:
name(required): The name of the project/packageversion(required for[package]): version number following semver.authors(required for[package]): name(s) of the author(s)target(required for[project]): define what application/extension to use when building your project.codenamecustom VBA project name shown in the VBE (defaults to"VBAProject")
Example 1
[project]
name = "awesome-excel-project"
target = "xlsm"
codename = "AwesomeProject"Example 2
[package]
name = "awesome-vba-package"
authors = ["Me <[email protected]>"]
version = "0.1.0"version
vbapm follows Semantic Versioning. Make sure you adopt a compatible versioning approach if you intend to publish to the repository.
target
target is used to define what application/extension to use when building your project. It can be a string for the extension, in which case target/ includes the source files for creating the target. Otherwise, type and path can be used to define a custom target path.
Example 1:
target = "xlsm"
# equivalent to target = { type = "xlsm", path = "target" }Example 2:
target = { type = "xlam", path = "targets/xlam" }codename
codename sets the VBA project name displayed in the Visual Basic Editor (VBE). When omitted, the default "VBAProject" is used. Only valid for [project] — ignored for [package].
codename = "MyCustomProject"[build-dir]
build-dir specifies where the built .xlsm / .xlam file is written. Defaults to "build" when omitted. Set to "." to output in the project root.
build-dir = "." # output to project root instead of build/[source.files]
Will contain the list of source code files to be included in the VBA-Enabled Document at build time.
Two listing modes are supported:
Individual entries — each file is listed explicitly by name (name = "path" or { path }):
[source.files]
A = "src/A.bas"
B = "src/B.cls"
C = { path = "src/C.bas" }Use this mode with vba init --list-all or vba init --from <workbook> for full control over which files are tracked.
Wildcard entries — groups of files are discovered by glob pattern (the default for vba init):
[source.files]
Objects = "src/Excel Objects/**/*.cls"
Forms = "src/Forms/**/*.frm"
Modules = "src/Modules/**/*.bas"
Classes = "src/Class Modules/**/*.cls"Wildcards are expanded at build time — any file on disk matching the glob is picked up automatically, so you don't need to run vba add after creating a new module. When you delete a module in the VBE and run vba extract, the corresponding file on disk is removed automatically.
Note: Wildcard and individual entries can coexist. Components already covered by an existing wildcard pattern do not get duplicate individual entries added which means the wildcard handles discovery on the next build or extract.
Legacy
[src]: Older manifests use a top-level[src]section. It is still accepted for backwards compatibility, but is deprecated — entries are treated as[source.files]and a warning is shown until the manifest is updated.
[source]
Optional settings that control how source files are organised and written to disk.
[source]
# folder = "src" # Implicit overridable default.
subfolders = { Modules = "Modules", Forms = "Forms", Classes = "Classes" }| Key | Default | Description |
|---|---|---|
| folder | "src" | Base directory for all source files (relative to project root). New components extracted from a workbook are placed under this directory. |
| subfolders | — | Organises source files into type-based subdirectories: .bas → Modules/, .frm → Forms/, .cls → Classes/. Objects maps document modules (ThisWorkbook, sheets) to a subfolder. |
| encoding | — | Default encoding for source files (e.g. "cp1252", "utf-8"). Can be overridden per-source in [source.files]. |
| sort | — | Enforce ordering in the [source.files] section. Options: by-types (group by component type), alphabetical (sort by name). |
| include-empty-objects | true | When false, blank document objects (ThisWorkbook, sheet modules) are skipped during export. |
[dependencies]
The dependencies section lists the packages that your project depends on. Each dependency can be specified in one of three ways:
name = "version" or
versionpathgit(andbranch,tag, orrev)
[dependencies]
a = "1" # Equivalent to ^1
b = "=2.0.0" # Precisely 2.0.0
c = { version = "3" }
d = { path = "./packages/d" }
e = { git = "https://..." } # master
f = { git = "https://...", branch = "dev" }
g = { git = "https://", tag = "bugfix" }
h = { git = "https://", rev = "abc1234" }[references]
version("MAJOR.MINOR")guid("{...}")
[references]
Scripting = { version = "1.0", guid = "{...}" }[dev-src,dependencies,references]
[dev-src], [dev-dependencies], and [dev-references] are included during development and are excluded when building with the --release flag (i.e. vba build --release)
Development
Prerequisites
git clonethis repo- Install Node.js v22 or later
- Note: For CLI builds, Node v23+ only supports Windows x64 (win-x64). 32-bit Windows (win-x86) is no longer available upstream.
- Install node-gyp dependencies for Mac or Windows
Build
- Run
pnpm install - Run
pnpm run format - Run
pnpm run build:cliIt will build the CLI/library inlib, plus ensured vendor node runtime is available. - Run
pnpm run build:addinsIt will build the Excel addin that performs workbook/VBA operations from inside Office.
Test
- Run
pnpm testIt will run unit tests - Run
pnpm run test:e2eIt will run the end-to-end CLI scenarios in excel.e2e.ts, covering workflows like build, extract, new, and version against fixtures. To keep temporary e2e work folders for manual inspection, setKEEP_E2E_TMP=1before running (PowerShell:$env:KEEP_E2E_TMP=1; pnpm run test:e2e, cmd:set KEEP_E2E_TMP=1 && pnpm run test:e2e). To echo each e2e command output even on successful runs, use--verbose(PowerShell:pnpm run test:e2e:background -- --verbose) or setE2E_VERBOSE=1(PowerShell:$env:E2E_VERBOSE=1; pnpm run test:e2e:background).
Install local version
To install the local version you can use the devinstall.ps1 script available in the installer submodule.
# Update submodule
git submodule update --init --recursive installer
# Run devinstall
.\installer\devinstall.ps1Release
- Run
pnpm version - Run
pnpm run release
Acknowledgments
This project is a fork of the original vba-blocks project by Tim Hall.
