Skip to content

Getting Started

Pick your toolchain — GJSify’s own Node-free CLI (recommended), or the same CLI through npm, Bun or Deno:

Create my-app
Terminal
curl -fsSL https://github.com/gjsify/gjsify/releases/latest/download/install.mjs \
-o /tmp/g.mjs && gjs -m /tmp/g.mjs && rm /tmp/g.mjs
gjsify create my-app
cd my-app
gjsify install
gjsify run dev
Terminal
npx @gjsify/cli create my-app
cd my-app && npm install
npm run dev
Terminal
bunx @gjsify/cli create my-app
cd my-app && bun install
bun run dev
Terminal
deno run -A npm:@gjsify/cli create my-app
cd my-app && deno install
deno task dev

Whichever you pick: a GTK 4 window running your TypeScript, natively on Linux. See the Install guide for ~/.local/bin PATH setup, gjsify self-update, and gjsify uninstall.

Tip: Run gjsify --help (or npx @gjsify/cli --help) to see all available subcommands.

You need a few system packages:

  • GJS 1.86+ — the GNOME JavaScript runtime (SpiderMonkey 140)
  • GTK 4 — the UI toolkit
  • libsoup3 — HTTP, WebSocket and fetch at runtime
  • Node.js 24+, Bun or Denooptional, only needed if you prefer a package-manager workflow over the Node-free bootstrap above
Install commands

Fedora:

Terminal
sudo dnf install gjs gtk4 libsoup3

Debian/Ubuntu:

Terminal
sudo apt install gjs libgtk-4-1 libsoup-3.0-0

Not sure if everything is in place?

System check
Terminal
gjsify system-check
Terminal
npx @gjsify/cli system-check
Terminal
bunx @gjsify/cli system-check
Terminal
deno run -A npm:@gjsify/cli system-check

gjsify create generates a minimal GTK 4 project:

my-app
my-app/
├── src/
│ └── index.ts # Gtk.Application entry point
├── package.json # build/start/dev scripts wired to gjsify CLI
└── tsconfig.json

The build script uses --globals auto by default — no manual list to maintain:

JSON
"scripts": {
"build": "gjsify build src/index.ts --outfile dist/index.js",
"start": "gjsify run dist/index.js"
}

You can also scaffold via npx @gjsify/create-app my-app directly.

The scaffolded scripts run with whichever toolchain you picked:

Build & run
Terminal
gjsify run build # gjsify build src/index.ts --outfile dist/index.js
gjsify run start # gjsify run dist/index.js
gjsify run dev # build + run in one step
Terminal
npm run build # gjsify build src/index.ts --outfile dist/index.js
npm start # gjsify run dist/index.js
npm run dev # build + run in one step
Terminal
bun run build # gjsify build src/index.ts --outfile dist/index.js
bun run start # gjsify run dist/index.js
bun run dev # build + run in one step
Terminal
deno task build # gjsify build src/index.ts --outfile dist/index.js
deno task start # gjsify run dist/index.js
deno task dev # build + run in one step

gjsify run automatically sets LD_LIBRARY_PATH and GI_TYPELIB_PATH for any native prebuilds (e.g. @gjsify/webgl).

Notice none of the build scripts above pass --app explicitly — gjsify build defaults its target to whichever runtime is running the CLI (gjs for the Node-free bootstrap, node for the npm/Bun/Deno paths), so the same package.json script works unmodified across all four toolchains.

Write standard Node.js and Web API code — the Rolldown plugin handles everything:

  1. Auto aliasingimport { readFileSync } from 'node:fs' rewrites to @gjsify/fs (backed by Gio)
  2. Auto globalsfetch, Buffer, process, URL etc. are detected and injected automatically
TypeScript
import { readFileSync } from 'node:fs'
import { createServer } from 'node:http'
const html = readFileSync('index.html', 'utf-8')
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
})
server.listen(parseInt(process.env.PORT ?? '8080'), () => {
console.log('Server running on http://localhost:8080')
})

node:fs is backed by Gio.File, node:http by Soup.Server, process.env by GLib.getenv(). The same code works on Node.js and GJS depending on the --app target.

TypeScript
const response = await fetch('https://api.example.com/data')
const data = await response.json()
const ws = new WebSocket('wss://echo.example.com')
ws.addEventListener('message', (event) => console.log(event.data))
Auto detection missed a global?

This is rare, but happens with libraries that wrap globalThis in another object (hiding the access from static analysis). Keep auto on and add the missing identifier:

Terminal
gjsify build --globals auto,matchMedia
# or use a group:
gjsify build --globals auto,dom

See the CLI Reference for the full list of supported identifiers.