wasm-git: libgit2 compiled to WebAssembly, giving browsers a real Git engine
GIT for nodejs and the browser using https://libgit2.org compiled to WebAssembly with https://emscripten.org
At a glance
- What is it?
- wasm-git ports libgit2 to WebAssembly with Emscripten and exposes it through lg2, libgit2's own example command line program, so a browser can clone, commit and push against a git server without any new API to learn. It ships six build variants covering synchronous, asynchronous and four different Origin Private File System strategies, each solving a different persistence and isolation constraint.
- Who is it for?
- Adopt wasm-git when you want an application to store user data locally with full change history and push or pull to a git server on the user's instruction, and you want git semantics rather than a bespoke sync layer. Do not adopt it if you need a programmatic git API, because the project deliberately exposes none, or if your browser cannot host a Web Worker or a cross-origin isolated context.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the project is, and the bet behind it
wasm-git is Git for Node.js and the browser, using libgit2 compiled to WebAssembly with Emscripten. The README opens with the picture it is trying to make real.
Imagine that every app you use kept a complete journal of your data: every change recorded, a backup on your own computer that is truly yours, and synchronization between your devices whenever you want it. Software developers have had exactly this for decades, and the tool is called Git. wasm-git brings that same engine to web applications, so you can store user data locally in the browser with full change history and push or pull to a remote when, and only when, the user chooses. The application does not need to expose any Git terminology: a single synchronize data button can do all of it behind the scenes.
That last sentence is the design brief, and it explains the most unusual decision in the project. wasm-git adds no API of its own. Instead it extends lg2, libgit2's example command-line program, so that it resembles the git command line, and you invoke it with lg.callMain(['clone', url, 'repo']) in JavaScript, or lg2 clone url repo in a WASI runtime.
The rationale is stated plainly: Git's command language is already designed, documented and widely known, so wasm-git adds no API of its own to learn or maintain, and it follows libgit2 upstream closely. lg2 covers a subset of git's commands and options, and missing ones are added by extending the examples in the libgit2patchedfiles/examples/ directory. If you need a programmatic API, the README's advice is to build it on top of these commands or to compile libgit2 to wasm with your own bindings.
That is a deliberate refusal to be a nicer library, and it buys something real: the project tracks libgit2 upstream instead of maintaining a parallel API surface of its own. The latest release, v0.0.17, records libgit2 1.9.4 and Emscripten 6.0.3.
Six build variants, and the constraint each one solves
The variants table is the heart of the project, because each row is a different answer to a browser constraint.
The sync variant, lg2.js, runs in a Web Worker only in the browser, on the main thread or on worker_threads in Node.js. It persists to MEMFS, IDBFS or NODEFS. It is the smallest binary and uses a synchronous callMain, and it needs a worker in the browser because of synchronous HTTP and long-running operations.
The async variant, lg2_async.js, runs on the browser main thread or in a worker, and in Node.js. It is an Asyncify build, so you await lg.callMain(...). It is a larger binary with the simplest client code.
Then four Origin Private File System variants, all of which need a Web Worker. The OPFS pthreads build, lg2_opfs.js, is the fastest OPFS variant and needs cross-origin isolation, meaning COOP and COEP headers for SharedArrayBuffer. The OPFS JSPI build, lg2_opfs_jspi.js, needs no isolation but requires a JSPI-capable browser. The OPFS Asyncify build, lg2_opfs_async.js, needs no isolation and works everywhere, at the cost of being the largest binary. The OPFS auto-loader, lg2_opfs_auto.js, picks the best supported OPFS build at runtime and exposes a uniform API.
The two axes are clear. The first axis is how the WebAssembly module is allowed to yield to the event loop, with Asyncify and JSPI being the two ways to do that, plus pthreads which avoids the question by using real threads. The second axis is where the files live, with MEMFS being memory only, IDBFS backed by IndexedDB, NODEFS giving Node.js direct filesystem access, and OPFS being the browser's own private file system.
For browser persistence with the sync and async variants, the README says to mount IDBFS and call FS.syncfs, or to use the OPFS variants, which persist through the filesystem itself with better performance and quota management.
That last point is the reason the OPFS variants exist at all. IDBFS is a compatibility layer over IndexedDB and it is slow, because every write is structured-cloned into a database. OPFS is a real filesystem in the browser, so a git repository with thousands of loose objects is a directory rather than a key-value blob.
Using it: a git repository in the browser filesystem
The quick start installs the package:
npm install wasm-gitThen you load a variant and drive it through callMain. The async variant is the easiest to start with, since it runs on the browser main thread or in Node.js with plain async and await:
import initGit from 'wasm-git/lg2_async.js';
const lg = await initGit();
const FS = lg.FS;
// Git needs to know who you are before committing
FS.writeFile('/home/web_user/.gitconfig',
'[user]\nname = Your Name\nemail = [email protected]');
// Create a repository, add a file, and commit
FS.mkdir('/myrepo');
FS.chdir('/myrepo');
await lg.callMain(['init', '.']);
FS.writeFile('data.json', JSON.stringify({ hello: 'world' }));
await lg.callMain(['add', 'data.json']);
await lg.callMain(['commit', '-m', 'first commit']);Two details in that example carry the whole approach. The FS object is the Emscripten filesystem, so writing files means writing into the WebAssembly module's virtual filesystem, which is why the gitconfig has to be written before the first commit: git needs an identity to commit as. And the working directory is changed with FS.chdir, because lg2 is a command-line program that operates on the current directory, exactly as git does.
Cloning and pushing work the same way:
await lg.callMain(['clone', 'https://your-git-server.example.com/repo.git', 'repo']);
FS.chdir('repo');
// ...edit files, add, commit...
await lg.callMain(['push']);There is one constraint on the browser side that is not optional. When cloning from the browser, the git server must allow cross-origin requests, or be served from the same origin. The README links githttpserver as a ready-made reference server, and its live instance as an open playground for trying cloning, editing, committing and pushing directly in the browser, with a polite request not to abuse it.
The files can also be loaded from public CDNs such as unpkg or jsDelivr, with the example URL given for the async variant.
What is actually built with it
The Built with wasm-git section lists two users, and they use it for two quite different things.
Ariz-Portfolio tracks crypto asset balances, profits and losses on the NEAR protocol blockchain. It uses wasm-git to store portfolio data with full change history on the user's own machine, and lets the user download a complete copy, history included, to their computer. Its optional cloud sync is end-to-end encrypted with encrypted-git-storage, so repositories are encrypted before upload with keys only the user holds.
That is the pattern the README's opening image describes exactly: a local journal with history, on the user's machine, with the user choosing when to sync.
Y42 is a data pipeline platform, a DataOps product, where every integration, model and dashboard is versioned in git. In a talk called Using GIT as a NoSQL Database, the founder Hung Dang explains that under the hood they developed their own git client for the browser using wasm-git, a WebAssembly compiled version of libgit2, and recommends looking at wasm-git.
The Y42 case is the more interesting one technically, because it is a company using git as a database rather than as a version control system. Versioning every integration, model and dashboard in git gives you history and branching for operational data, which is the same property the README sells, applied to a different domain.
The repository structure shows the build infrastructure behind all of this: emscriptenbuild/ and wasibuild/ directories for the two build paths, libgit2patchedfiles/ holding the patched upstream examples, examples/opfs/ for the OPFS example, CMakeLists.txt, a setup.sh and a preparepublishnpm.sh, and the test and test-browser directories the README points to for working examples of every variant.
Limits: no programmatic API, browser constraints and an upstream dependency
The limits are worth being explicit about, because the project's central decision has consequences in both directions.
The first is the absence of an API. wasm-git adds none by design, and the README says so. If your application needs to manipulate a repository programmatically, abstracting the commit operation behind your own function, you have to either build that layer on top of the command calls or compile libgit2 yourself with your own bindings. For an application whose only requirement is a synchronize button, the command line is exactly right. For anything that needs to introspect the repository, work with blobs, or interleave git operations with other state, it is a constraint.
The second is that lg2 covers a subset of git's commands and options. The workaround is to extend the patched upstream examples, which means forking the build, and the README's phrasing, that missing ones are added by extending the examples, tells you that is a supported activity but not a trivial one.
The third is the browser constraints, and they are real. The sync variant needs a Web Worker because of synchronous HTTP and long-running operations. The OPFS pthreads variant needs cross-origin isolation with COOP and COEP headers, which means a server configuration change and a hosting provider that supports it. The JSPI variant needs a browser with JSPI support, which is still relatively new. The Asyncify OPFS variant works anywhere but is the largest binary. There is no variant that is small, universal, fast and persistent, because the platform does not offer one.
The fourth is the dependency on upstream libgit2, which is the price of the design. Tracking upstream means the project's own maintenance burden is small, and it also means the project cannot add features that libgit2 does not have, and inherits libgit2's bugs and gaps.
The fifth is maturity. The version is 0.0.17, with a single tagged release listed, and the licence is a custom one GitHub cannot classify, with the text in the COPYING file. For a library that handles user data and, in the Ariz-Portfolio case, encryption keys, the licence position is worth reading rather than assuming.
wasm-git against a JavaScript git implementation and against a purpose-built sync service
Two comparisons make the choice clearer.
The first is against a JavaScript-only git implementation such as isomorphic-git. The difference in approach is what the engine underneath is. isomorphic-git reimplements Git's data model in JavaScript, which means it is a pure JavaScript package with no WebAssembly, no filesystem shim and no cross-origin isolation requirement, and it runs in any runtime including a browser without a Worker. The price is that it is a reimplementation, with the same consequence libgit2 has: it is not the server's engine, so edge cases can diverge.
wasm-git's advantage is that it is the same C library the server uses, so its behaviour on a malformed repository or an unusual object matches what a real git server would do. Its cost is the toolchain: Emscripten, a build step, six variants and browser constraints.
The second comparison is against a purpose-built sync service, where the application talks to a proprietary API and the client library handles versioning. That is simpler, and for most applications it is the better choice, because the service does the conflict resolution, the transport and the storage. The difference in approach is where the truth lives. With a sync service, your data is in the service's database and the service defines the semantics. With wasm-git, your data is a git repository on the user's device and the semantics are Git's, which means a user can take their history and use any other git tool on it.
That property is the whole argument in the README's opening paragraph, and it is a real one for applications holding personal data. A user with a local journal of every change has a backup they control, and the option to leave your service without losing their history.
The practical framing is that wasm-git is for the case where local-first data ownership is a requirement rather than a feature request. If you simply need a sync button, a sync service is less work and fewer browser constraints.
Editorial conclusion
Adopt wasm-git when you want an application to store user data locally with full change history and push or pull to a git server on the user's instruction, and you want git semantics rather than a bespoke sync layer. Do not adopt it if you need a programmatic git API, because the project deliberately exposes none, or if your browser cannot host a Web Worker or a cross-origin isolated context. Verify first which build variant your deployment supports, since the OPFS variants differ in their requirements around SharedArrayBuffer and cross-origin isolation, and that your git server allows cross-origin requests.
Frequently asked questions
What is wasm-git and how do you use it?
It is libgit2 compiled to WebAssembly with Emscripten, exposed through lg2, libgit2's example command-line program, so you call it with lg.callMain(['clone', url, 'repo']) in JavaScript or lg2 clone url repo in a WASI runtime. The package is installed with npm install wasm-git and ships prebuilt binaries in several variants.
Which build variant of wasm-git should I use in a browser?
The async variant lg2_async.js is the easiest to start with, running on the main thread or in a worker with plain await, persisting to MEMFS, IDBFS or NODEFS. For browser persistence the OPFS variants are faster, and among them the pthreads build is fastest but needs cross-origin isolation with COOP and COEP headers, the JSPI build needs a JSPI-capable browser, the Asyncify build works everywhere but is the largest, and the auto-loader picks the best supported OPFS build at runtime.
Does wasm-git provide a JavaScript API for git operations?
No, and that is deliberate. The README says Git's command language is already designed, documented and widely known, so wasm-git adds no API of its own to learn or maintain and follows libgit2 upstream closely. If you need a programmatic API, the README suggests building it on top of these commands or compiling libgit2 to wasm with your own bindings.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/petersalomonsen-wasm-git)Was this useful?