Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/g-js-api/G.js/llms.txt

Use this file to discover all available pages before exploring further.

The best way to understand G.js is to build something that actually moves. This guide walks through the complete example from the README — a text object that slides left and right forever with a background flash on every cycle — line by line. By the end you will have seen all of the core building blocks: $.exportConfig, objects, groups, trigger functions, context management, and looping.
Geometry Dash uses small-step units internally. One “big block” in the editor equals 30 small steps. The coordinate values you pass to .with(obj_props.X, 45) are in small-step units, so X: 45 places an object 1.5 big blocks from the origin. When working with group.move(), the distance arguments are also in small steps — move(30, 0, 0.5) moves an object exactly one big block to the right over 0.5 seconds.

Steps

1
Import G.js and configure the export
2
Every G.js script starts with an import followed immediately by $.exportConfig(). The config call is async, so your script needs to await it before doing anything else GD-related.
3
import '@g-js-api/g.js';

await $.exportConfig({
  type: 'savefile',
  options: { info: true }
});
4
type: 'savefile' tells G.js to write the finished level directly into your GD save file. If you would rather inspect the raw levelstring first, use type: 'levelstring' instead and capture the return value. The info: true option prints a summary — object count, group count, and so on — when the script finishes.
5
Change type to 'live_editor' to stream objects into GD in real time while you iterate, using the WSLiveEditor mod.
6
Reserve a group ID with unknown_g()
7
Before placing an object you need a group ID to target it with move triggers later. unknown_g() allocates the next available group and returns it as a $group value.
8
let my_text = unknown_g();
9
This is equivalent to clicking “Next Free” in the GD editor. G.js tracks all allocated IDs so you never accidentally reuse one.
10
Create and place a text object
11
GD text objects can be built from a plain JavaScript string using the .to_obj() extension method. Chain .with(prop, value) calls to set object properties, then finish with .add() to place the object in the level.
12
'Hello, World!'
  .to_obj()
  .with(obj_props.X, 45)
  .with(obj_props.Y, 45)
  .with(obj_props.GROUPS, my_text)
  .add();
13
obj_props is a dictionary of every GD object property key. Using obj_props.X, obj_props.Y, and obj_props.GROUPS instead of raw numbers makes the code readable and keeps you safe against magic-number errors.
14
The object is positioned at X 45, Y 45 (in small-step units) and assigned the group ID stored in my_text so that move triggers can target it later.
15
Wrap the loop logic in a trigger function
16
A trigger function is a named group of GD triggers you can spawn on demand. You define one by passing a callback to trigger_function(). The callback runs at compile time to collect the triggers; the returned TriggerFunctionGroup value is what you call at runtime.
17
let moveloop = trigger_function(() => {
  // triggers defined here are placed inside the group
});
18
Everything inside the callback represents a sequence of GD triggers that will execute in order when the group is called.
19
Add the move triggers, flash, and loop back
20
Inside the trigger function, use $.trigger_fn_context() to capture the group ID of the current context before any spawn delays advance it. Then add two move triggers, a background flash inside ignore_context_change(), and a recursive call to create the loop.
21
let moveloop = trigger_function(() => {
  // Capture the original context group before any delays
  let my_context = $.trigger_fn_context();

  // Move right 30 small steps over 0.5 s, then back
  my_text.move(30, 0, 0.5);
  my_text.move(-30, 0, 0.5);

  // Flash the background WITHOUT advancing the context
  ignore_context_change(() => log.runtime.flash());

  // Call the original context group to loop
  my_context.call();
});
22
Why $.trigger_fn_context()? Each wait or sequential move trigger advances the internal spawn-delay context, giving later triggers a different group ID. If you called moveloop directly at the end, G.js would target the new context group — not the one the loop started in. Capturing it upfront with $.trigger_fn_context() and calling that specific group closes the loop correctly.
23
Why ignore_context_change()? The flash should fire at the same time as the second move rather than waiting for it to complete. Wrapping it in ignore_context_change() tells G.js not to switch the active context for the triggers inside, so the flash runs in parallel with the surrounding sequence.
24
Call the loop to start it
25
The trigger function is defined but not yet active. Call .call() on the returned group to spawn it once — after that it loops itself forever via my_context.call().
26
moveloop.call();

Complete Example

Here is the full script assembled:
import '@g-js-api/g.js';

// Must be called before any GD content
await $.exportConfig({
  type: 'savefile',
  options: { info: true }
});

// Reserve a group for the text object
let my_text = unknown_g();

// Place the text object in the level
'Hello, World!'
  .to_obj()
  .with(obj_props.X, 45)
  .with(obj_props.Y, 45)
  .with(obj_props.GROUPS, my_text)
  .add();

// Define the looping move sequence
let moveloop = trigger_function(() => {
  let my_context = $.trigger_fn_context();

  my_text.move(30, 0, 0.5);   // slide right
  my_text.move(-30, 0, 0.5);  // slide back

  ignore_context_change(() => log.runtime.flash()); // flash the BG

  my_context.call(); // loop
});

// Kick off the loop
moveloop.call();
Run it with:
node level.js
G.js will write the level to your GD savefile and print a summary if info: true is set.

Export Type Reference

The type field in $.exportConfig() controls how the level reaches GD:
typeBehaviour
levelstringReturns the raw GD levelstring as a string — useful for debugging or piping into other tools
savefileWrites directly to your local GD save file — the level appears in your editor immediately
live_editorStreams objects to a running GD session via the WSLiveEditor mod for real-time preview
gmdProduces a .gmd file compatible with GD World and external level editors

What’s Next

This example only scratches the surface. Explore the concept guides to go deeper:

Objects and Triggers

Learn how object(), trigger(), .with(), and .add() work in detail.

Trigger Functions and Contexts

Understand contexts, spawn delays, and how $.trigger_fn_context() keeps loops correct.

Groups, Colors, and Blocks

Deep dive into ID allocation, unknown_g(), and how groups target objects.

Counters and Conditions

Use counter(), arithmetic methods, and while_loop for dynamic level logic.

Build docs developers (and LLMs) love