Skip to content

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.

Terminal
$ 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
express-webserver.ts
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');
});
Full example →
Terminal
Terminal
gjsify showcase express-webserver
Terminal
npx @gjsify/cli@latest showcase express-webserver --runtime node
Terminal
bunx @gjsify/cli@latest showcase express-webserver --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase express-webserver --runtime deno
switch-row.meta.ts
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 },
],
};
Full example →
Terminal
Terminal
gjsify showcase adwaita-storybook
Terminal
npx @gjsify/cli@latest showcase adwaita-storybook --runtime node
Terminal
bunx @gjsify/cli@latest showcase adwaita-storybook --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase adwaita-storybook --runtime deno
pixel-demo.ts
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));
});
});
Full example →
Terminal
Terminal
gjsify showcase three-postprocessing-pixel
Terminal
npx @gjsify/cli@latest showcase three-postprocessing-pixel --runtime node
Terminal
bunx @gjsify/cli@latest showcase three-postprocessing-pixel --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-postprocessing-pixel --runtime deno
teapot-demo.ts
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 })
));
});
});
Full example →
Terminal
Terminal
gjsify showcase three-geometry-teapot
Terminal
npx @gjsify/cli@latest showcase three-geometry-teapot --runtime node
Terminal
bunx @gjsify/cli@latest showcase three-geometry-teapot --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-geometry-teapot --runtime deno
ldraw-demo.ts
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);
});
});
});
Full example →
Terminal
Terminal
gjsify showcase three-loader-ldraw
Terminal
npx @gjsify/cli@latest showcase three-loader-ldraw --runtime node
Terminal
bunx @gjsify/cli@latest showcase three-loader-ldraw --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase three-loader-ldraw --runtime deno
jelly-jumper.ts
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
});
});
Full example →
Terminal
Terminal
gjsify showcase excalibur-jelly-jumper
Terminal
npx @gjsify/cli@latest showcase excalibur-jelly-jumper --runtime node
Terminal
bunx @gjsify/cli@latest showcase excalibur-jelly-jumper --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase excalibur-jelly-jumper --runtime deno
fireworks.ts
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;
});
});
Full example →
Terminal
Terminal
gjsify showcase canvas2d-fireworks
Terminal
npx @gjsify/cli@latest showcase canvas2d-fireworks --runtime node
Terminal
bunx @gjsify/cli@latest showcase canvas2d-fireworks --runtime bun
Terminal
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest showcase canvas2d-fireworks --runtime deno

One 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.

GNOME Native GJS on the system libadwaita.

The same application is shown running on GNOME, Windows 11, macOS.

Get started in seconds

Terminal
Terminal
# See every command, from create to ship
gjsify --help
# Scaffold a new app
gjsify create my-app
Terminal
# See every command, from create to ship
npx @gjsify/cli@latest --help
# Scaffold a new app
npx @gjsify/cli@latest create my-app
Terminal
# See every command, from create to ship
bunx @gjsify/cli@latest --help
# Scaffold a new app
bunx @gjsify/cli@latest create my-app
Terminal
# See every command, from create to ship
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest --help
# Scaffold a new app
deno run -A --reload --min-dep-age 0 npm:@gjsify/cli@latest create my-app