GJSify
Native desktop apps,
in TypeScript
One framework where the Node.js, Web and GNOME ecosystems meet. Write TypeScript once, run it natively on GJS, Node.js, Deno or Bun — across Linux, macOS and Windows. No embedded browser.
$ gjs -m express-webserver.js
Express.js blog running on GJS
➜ Local: http://localhost:3000/ ➜ API: http://localhost:3000/api/posts
Press Ctrl+C to stop
[12:34:56] GET /[12:34:56] GET /style.css[12:34:56] GET /app.js[12:34:56] GET /api/runtime[12:34:56] GET /api/posts A real Express.js
blog, running on GJS
import '@gjsify/node-globals';import express from 'express';import { posts } from './data/posts.js';
const app = express();app.use(express.json());
app.get('/api/posts', (_req, res) => { res.json({ count: posts.length, posts });});
app.get('/api/posts/:slug', (req, res) => { const post = posts.find((p) => p.slug === req.params.slug); if (!post) return res.status(404).json({ error: 'Not found' }); res.json(post);});
app.use(express.static('public'));
app.listen(3000, () => { console.log('Express server running at http://localhost:3000');}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase express-webservernpx @gjsify/cli@latest showcase express-webserver --runtime nodebunx @gjsify/cli@latest showcase express-webserver --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase express-webserver --runtime deno Create stories for your GTK widgets
just as you would in web development
import { ControlType, type StoryMeta } from '@gjsify/stories';
// Author a widget's story once — renderer-free metadata + controls.// The native GTK storybook (gjsify storybook) and the browser one// both render it from this same source, so the two stay in lockstep.export const switchRowMeta: StoryMeta = { title: 'Boxed Lists/Switch Row', description: 'Adw.SwitchRow — a row with a trailing switch.', controls: [ { name: 'title', label: 'Title', type: ControlType.TEXT, defaultValue: 'Automatic updates' }, { name: 'active', label: 'Active', type: ControlType.BOOLEAN, defaultValue: true }, ],}; See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase adwaita-storybooknpx @gjsify/cli@latest showcase adwaita-storybook --runtime nodebunx @gjsify/cli@latest showcase adwaita-storybook --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase adwaita-storybook --runtime deno This WebGL demo runs
natively on Linux!
import Adw from 'gi://Adw?version=1';import { WebGLBridge } from '@gjsify/webgl';import * as THREE from 'three';import { RenderPixelatedPass } from 'three/addons/postprocessing/RenderPixelatedPass.js';import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
const app = new Adw.Application({ application_id: 'gjsify.examples.pixel' });app.connect('activate', () => { const glArea = new WebGLBridge(); glArea.onReady((canvas) => { const renderer = new THREE.WebGLRenderer({ canvas }); const scene = new THREE.Scene(); const camera = new THREE.OrthographicCamera(); const composer = new EffectComposer(renderer); composer.addPass(new RenderPixelatedPass(6, scene, camera)); });}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase three-postprocessing-pixelnpx @gjsify/cli@latest showcase three-postprocessing-pixel --runtime nodebunx @gjsify/cli@latest showcase three-postprocessing-pixel --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-postprocessing-pixel --runtime deno Real Three.js,
powered by GJS
import Adw from 'gi://Adw?version=1';import { WebGLBridge } from '@gjsify/webgl';import * as THREE from 'three';import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
const app = new Adw.Application({ application_id: 'gjsify.examples.teapot' });app.connect('activate', () => { const glArea = new WebGLBridge(); glArea.onReady((canvas) => { const renderer = new THREE.WebGLRenderer({ canvas }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45); scene.add(new THREE.Mesh( new TeapotGeometry(50, 20), new THREE.MeshPhongMaterial({ color: 0xc0c0c0 }) )); });}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase three-geometry-teapotnpx @gjsify/cli@latest showcase three-geometry-teapot --runtime nodebunx @gjsify/cli@latest showcase three-geometry-teapot --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-geometry-teapot --runtime deno A model streamed
from disk over fetch
import Adw from 'gi://Adw?version=1';import { WebGLBridge } from '@gjsify/webgl';import * as THREE from 'three';import { LDrawLoader } from 'three/addons/loaders/LDrawLoader.js';
const app = new Adw.Application({ application_id: 'gjsify.examples.ldraw' });app.connect('activate', () => { const glArea = new WebGLBridge(); glArea.onReady((canvas) => { const renderer = new THREE.WebGLRenderer({ canvas }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45);
new LDrawLoader().load('models/car.ldr_Packed.mpd', (model) => { model.rotation.x = Math.PI; scene.add(model); }); });}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase three-loader-ldrawnpx @gjsify/cli@latest showcase three-loader-ldraw --runtime nodebunx @gjsify/cli@latest showcase three-loader-ldraw --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-loader-ldraw --runtime deno The web game engine
Excalibur.js, natively
in GJS — no WebView
import Adw from 'gi://Adw?version=1';import { WebGLBridge } from '@gjsify/webgl';import * as ex from 'excalibur';import { startGame } from './game.js';
const app = new Adw.Application({ application_id: 'gjsify.examples.jelly-jumper',});app.connect('activate', () => { const glArea = new WebGLBridge(); glArea.onReady(async (canvas) => { const game = await startGame(canvas); // game.mute(); // toggle audio // game.pause(); // toggle pause });}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase excalibur-jelly-jumpernpx @gjsify/cli@latest showcase excalibur-jelly-jumper --runtime nodebunx @gjsify/cli@latest showcase excalibur-jelly-jumper --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase excalibur-jelly-jumper --runtime deno Canvas 2D, drawn
with Cairo
import Adw from 'gi://Adw?version=1';import { Canvas2DBridge } from '@gjsify/canvas2d';import { start } from './fireworks.js';
const app = new Adw.Application({ application_id: 'gjsify.examples.fireworks' });app.connect('activate', () => { const canvasWidget = new Canvas2DBridge(); canvasWidget.installGlobals(); canvasWidget.onReady((canvas) => { const demo = start(canvas); // demo.effectController.particleCount = 60; // demo.effectController.autoInterval = 150; });}); See for yourself —
on any runtime!
on any runtime!
Terminal
gjsify showcase canvas2d-fireworksnpx @gjsify/cli@latest showcase canvas2d-fireworks --runtime nodebunx @gjsify/cli@latest showcase canvas2d-fireworks --runtime bundeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase canvas2d-fireworks --runtime denoOne app. Every desktop.
The same TypeScript, the same Adwaita widgets, the same gjsify
commands. GJSify talks to the real GTK 4 libraries on each system — no
embedded browser, no per-platform rewrite.
Mon 14:32
14:32
My App File Edit View Mon 17 Aug 14:32
GNOME Native GJS on the system libadwaita.
The same application is shown running on GNOME, Windows 11, macOS.
Get started in seconds
Terminal
# See every command, from create to shipgjsify --help
# Scaffold a new appgjsify create my-app# See every command, from create to shipnpx @gjsify/cli@latest --help
# Scaffold a new appnpx @gjsify/cli@latest create my-app# See every command, from create to shipbunx @gjsify/cli@latest --help
# Scaffold a new appbunx @gjsify/cli@latest create my-app# See every command, from create to shipdeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest --help
# Scaffold a new appdeno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest create my-app