Write GDevelop 5 project files from code, with no editor open: one scene, Sprite and Text objects,
GDevelop's built-in behaviors (such as the platformer ones), instances, image and sound resources and
JavaScript code events. png.mjs makes simple sprite images and wav.mjs simple sound effects in
code (no dependencies), pixelart.mjs draws pixel-art sprites from text grids and tune.mjs writes
music loops, so a whole scene can be generated by a script. harness/ exports a project for the web
and plays the build in headless Chromium from a JSON script of steps. Needs Node.js 18 or later.
Licence: Apache-2.0 (see LICENSE). By MV2 of Munim, Inc. (mv2@munimtech.com). The code, tests and this README were written by AI agents (Anthropic Claude) working for MV2, then tested and revised by the agents in an automated sandbox. gdwrite is not made by or affiliated with the GDevelop team.
Version 0.2.0 is also a download (gdwrite-0.2.0.zip) on https://munimv2.itch.io/jamshell, published on 2026-10-18.
gdwrite ships an agent skill, skills/gdwrite/SKILL.md, that tells a coding agent (Claude Code, Codex,
Cursor and others that read SKILL.md files) when to use gdwrite, how to set it up, how to write a project,
and how to export and play-test it, with the step-script format and the limits. Install it with the open
skills command-line tool:
npx skills add munimtechnologies/gdwrite --skill gdwriteAdd --agent claude-code (or another agent name) to pick the agent, and --copy to copy the file instead
of linking it. Review a skill before you use it: skills run with your agent's permissions.
What was tested:
- 2026-11-08, in a sealed node:20 container:
tools/skill-check/extract.mjspulled the skill's example code and step script out of SKILL.md; the example wrotegame.jsonfrom GDevelop's breakout and platformer examples,harness/export.mjsbuilt it for the web andharness/play.mjsran the step script in headless Chromium: 3 of 3 steps passed, with no page or console errors (details intools/skill-check/README.md). - 2026-11-09, in a sealed node:22 container:
npx skills add munimtechnologies/gdwrite --listfound the one skill, andnpx skills add munimtechnologies/gdwrite --skill gdwrite --agent claude-code --copy -yinstalled it to.claude/skills/gdwrite/SKILL.mdwith the frontmatter intact. That was a single install, made once to check the command works.
pixelart.mjs: sprites from text grids.gridPNG(rows, palette, { scale })turns rows of characters into a PNG (one character per pixel,.transparent, an unknown character throws),sheetPNG(frames, ...)lays equal-size frames side by side,outline(rows, ch)rings the opaque pixels with a 1-pixel outline.tune.mjs:tuneWAV({ bpm, voices })mixes note lists (MIDI numbers and beats;nullis a rest; sine, square, triangle or saw) into a WAV loop with click-free note edges;noiseWAV()makes a seeded, decaying noise burst for hits and steps.harness/export.mjs: exports a project for the web without the editor (needsgdcore-tools).harness/play.mjs: plays an exported build in headless Chromium (needsplaywright) from a JSON script of steps (wait, click, tap, drag, key down / up / press / hold,evalwithexpect,wait_for, screenshots, repeat) and writes a JSON report. It exits 0 only if the page made a canvas, threw no page error and every check passed.harness/expect.mjsholds the comparison (eq,ne,gt,gte,lt,lte,between,truthy,includes).build-artprobe.mjsandsteps/*.json: probe scenes and their scripts (steps/negative.jsonmust fail, to show the harness reports failures).npm testnow runs all three test files.
npm install gdcore-tools@2.0.0-gd-v5.6.269-autobuild playwright@1.47.0
npx playwright install --with-deps chromium
node build-demo.mjs demo breakout.json platformer.json
node harness/export.mjs demo/game.json build-demo
node harness/play.mjs build-demo steps/demo.json out
GDevelop's project format has many fields that change between versions. gdwrite does not hard-code
them: loadTemplates() reads one or more example projects that GDevelop itself saved (for example
the official GDevelop examples) and clones a scene, a layer, a Sprite, a Text object, an instance,
an image resource and every behavior it finds from them. You then fill in only what you want: names,
images, sounds, behavior settings, positions and event code.
import fs from "node:fs";
import { loadTemplates, behaviorTypes, spriteObject, textObject, withBehavior, imageResource, audioResource,
instance, jsEvent, buildProject } from "./gdwrite.mjs";
import { shapePNG } from "./png.mjs";
import { blipWAV } from "./wav.mjs";
const t = loadTemplates(["breakout.json", "platformer.json"].map((f) => JSON.parse(fs.readFileSync(f, "utf8"))));
console.log(behaviorTypes(t)); // the behavior types the examples contain
fs.writeFileSync("player.png", shapePNG(32, 32, [80, 170, 255], "box"));
fs.writeFileSync("ground.png", shapePNG(32, 32, [90, 200, 120], "box"));
fs.writeFileSync("blip.wav", blipWAV({ f0: 520, f1: 880, ms: 120 }));
const project = buildProject(t, {
name: "my game", packageName: "com.example.mygame", width: 800, height: 600, bg: [24, 40, 72],
resources: [imageResource(t, "player.png"), imageResource(t, "ground.png"), audioResource(t, "blip.wav")],
objects: [
withBehavior(t, spriteObject(t, "Player", "player.png"), "PlatformBehavior::PlatformerObjectBehavior", "PlatformerObject"),
withBehavior(t, spriteObject(t, "Ground", "ground.png"), "PlatformBehavior::PlatformBehavior", "Platform"),
textObject(t, "Hud", { text: "Hello" }),
],
instances: [instance(t, "Player", 100, 100, 1), instance(t, "Ground", 80, 400, 2, 1, { width: 640, height: 32 }), instance(t, "Hud", 24, 24, 3)],
events: [jsEvent("if (runtimeScene.getObjects('Player')[0].getBehavior('PlatformerObject').isJumping()) {}")],
});
fs.writeFileSync("game.json", JSON.stringify(project, null, 2));withBehavior(t, object, type, name, props) only accepts properties the example's behavior already
has, so a misspelt setting fails when the project is written, not later in the engine. The scene's
behavior shared data is filled in for you. A sound is played from a JavaScript event with
gdjs.evtTools.sound.playSound(runtimeScene, "blip.wav", false, 60, 1).
Complete examples:
node build-demo.mjs <out dir> <example.json> [...]: a player moved with the arrow keys, coins spawned at runtime, collisions and a score display.node build-platformer.mjs <out dir> <example.json> [...](needs an example with the platformer behaviors, such as GDevelop's "platformer"): a player with the Platformer character behavior on a stretched ground with the Platform behavior, GDevelop's default controls, and a blip on landing.node build-artprobe.mjs <out dir> <example.json> [...]: a pixelart sprite drawn at 4x, a tune.mjs music loop and a noise hit sound.
The example projects are not included; get them from
https://github.com/GDevelopApp/GDevelop-examples (examples/breakout/breakout.json and
examples/platformer/platformer.json were used in the tests below).
A step script is a JSON object with viewport, touch (for tap steps), boot_ms and a list of
steps (the full list of step kinds is at the top of harness/play.mjs). Each step with expect
is compared with harness/expect.mjs; a game exposes its state on a global (the probes use
window.__gdprobe) so the script can read it:
{
"viewport": { "width": 800, "height": 600 },
"boot_ms": 8000,
"steps": [
{ "wait_for": "window.__gdprobe && window.__gdprobe.frames > 30", "timeout_ms": 20000 },
{ "hold": "ArrowRight", "ms": 2500 },
{ "eval": "window.__gdprobe.px", "save": "x_after", "expect": { "gt": 200 } },
{ "shot": "after" }
]
}- 0.3.1 changes only this README, the changelog and the version in package.json; the code is the same as 0.3.0. The agent-skill checks are listed under "Agent skill" above.
- 0.3.0, in an automated sandbox on 2026-11-05 (Node 20.20.2, gdcore-tools
2.0.0-gd-v5.6.269-autobuild, Playwright 1.47.0 with Chromium in software rendering):
node --test test_gdwrite.mjs test_art.mjs harness/test_expect.mjs: 28 tests, 28 passed (16 for gdwrite, PNG and WAV; 8 for pixelart and tune; 4 for the expect comparisons).- The three probe scenes were built with templates from GDevelop's breakout and platformer
examples, exported for the web with
harness/export.mjsand played withharness/play.mjs, with no page errors and no console errors: coin demo 7 of 7 checks passed (the player moved from x = 40 to x = 570 on a held arrow key, a coin was collected, coins spawned at runtime); platformer 11 of 11 (the player landed on the ground, walked right to x = 333, jumped once rising 157 px, landed again, and the blip sound played without an error); art probe 4 of 4 (the 16 x 16 grid sprite drawn at 64 x 64, the music loop was started once and the noise hit sound was played at least twice, with no error caught; headless Chromium has no speakers, so nobody listened to the sounds). steps/negative.jsonfailed as it should: 0 of 3 checks passed (an impossible score, await_forthat never comes true and an expression that throws) andplay.mjsexited 1.
- 0.2.0: in an automated sandbox on 2026-10-18 (Node 20), all 16 tests passed on these files, both from the source folder and again from a clean unzip of gdwrite-0.2.0.zip, and both example scenes were exported and played in headless Chromium with no page errors. The project built from the clean unzip matched the tested one byte for byte.
- gdwrite 0.1.0 output also opened in the GDevelop 5.6.284 desktop editor with no warning. 0.3.0 output has not yet been opened in the desktop editor.
- The step that commits these files to GitHub does not run Node, so the tests were not re-run at
commit time; run
npm testyourself.
- One scene; Sprite and Text objects; image and sound resources; JavaScript events. No regular (non-code) events, fonts, tilemaps or physics yet.
- Behaviors must be ones GDevelop ships with and must appear in an example you supply. Behaviors from custom extensions are not copied, because the extensions themselves are not.
- Fonts are left empty, so text uses GDevelop's default font.
- Templates come from projects you supply; fields GDevelop adds in later versions are whatever those projects contain.
- The play harness was tested at 800 x 600 in software-rendered Chromium only; timings in a step script (holds, waits) depend on the machine, so give checks some slack.
Other people have built tools for working with GDevelop projects from code, among them GDevelop's
own command-line tools, gdcore-tools / gdexporter (exporting without the editor, used in the tests
above) and MCP bridges that let AI assistants edit GDevelop projects. gdwrite is a small, dependency-free
alternative for writing a project file from scratch; it is not the first tool of its kind. Headless
play-test scripts for browser games are common too (Playwright itself, and many game test runners);
harness/play.mjs is a small one tailored to exported GDevelop builds.