Environment Setup
Node.js#
Use the Node.js version pinned in .nvmrc at the root of the repository. With nvm, nvm install run inside the repository installs and activates it.
Setting up pnpm#
Trilium uses the pnpm package manager in order to better manage its mono-repo structure. Unlike npm which comes by default with Node.js, pnpm needs to be installed separately. The expected version is the packageManager field of the root package.json; a newer release of the same major works too, since pmOnFail: ignore in pnpm-workspace.yaml tolerates the difference.
Install it with npm:
npm install -g pnpmpnpm 12 is a native executable that its install script links into place, so the command needs lifecycle scripts allowed (npm's default). Under a version manager such as nvm, a global package belongs to one Node.js version, so run the command again after switching to another one. The standalone installer is an alternative that does not depend on the Node.js version.
After that, run pnpm in a new terminal to see if it is working.
As a quick heads-up of some differences when compared to npm:
- Generally instead of
npm runwe havepnpm runinstead. - Instead of
npxwe havepnpm exec.
Installing dependencies#
Run pnpm i at the top of the Trilium repository to install the dependencies.
IDE#
Our recommended IDE for working on Trilium is Visual Studio Code (or VSCodium if you are looking for a fully open-source alternative).
By default we include a number of suggested extensions which should appear when opening the repository in VS Code. Most of the extensions are for integrating various technologies we are using such as Playwright and Vitest for testing or for Internationalisation / Translations.
TypeScript#
The root package.json declares both typescript (6.x) and @typescript/native (an alias of typescript@7). This is deliberate — do not "deduplicate" them by bumping typescript to 7:
typescript6.x is the library. TypeScript 7 is the native Go port and its package no longer exports the JS compiler API (exports["."]is just a version stub). Everything that doesrequire("typescript")needs 6.x: TypeDoc, typescript-eslint, and — the one that also ships to users —packages/codemirror, which runs the real language service in the browser for script-note IntelliSense.@typescript/nativeis the compiler binary, used only byscripts/filter-tsc-output.mtsbehindpnpm typecheck. It builds the whole project graph in roughly a seventh of the time 6.x takes.- pnpm gives
node_modules/.bin/tscto the alias, so a baretscon the command line is 7, not the 6.x that tooling loads. That is also what keeps.tsbuildinfoin one format — the two majors cannot read each other's, and mixing them forces a full rebuild every time.
Do not switch to @typescript/typescript6. Microsoft's documented side-by-side layout aliases typescript to that compatibility shim so the native compiler can own the tsc bin name. It does not fit here, for two reasons that only show up at build time:
- The shim ships five files and no
lib.*.d.ts, so the 96typescript/lib/lib.*.d.ts?rawimports inpackages/codemirror/src/type_completion/ts_lib_files.tsfail to resolve and the client build dies. - Working around that by keeping a real
typescriptunderpackages/codemirrorsplits resolution:@typescript/vfsand@valtown/codemirror-tsare hoisted to the root and follow the shim, while codemirror's own source follows its nested copy. Two physical paths means the 3.3 MB compiler is bundled twice into the lazy script-note chunk (measured: clientdist69 M → 72 M).
The official layout assumes the only consumer of the typescript name is tooling. This repo also bundles it into a browser app, so the plain package has to stay.