This is a "cheat sheet" for sfgml, a Haxe->GameMaker compiler.

Release builds can be found on

Source code can be found on BitBucket (1, 2).

An up-to-date version of this manual is always available online.

Click on sections to expand/collapse them.
Quick display controls: Categories · Sections · Everything ·

Setting up

Pre-setup (if you haven't used Haxe before)
  • Download Haxe (preferably the recent preview build, but 3.4.7 should work too)
  • Download an IDE with Haxe support (list)
    If you're on Windows, FlashDevelop/HaxeDevelop is usually the best bet.
Initial setup

Install sfhx and sfgml libraries from the downloaded package.

To do so, use haxelib's dev command to set paths to both libraries

haxelib dev sfhx <downloaded directory>/sfhx/sfhx-dev
haxelib dev sfgml <downloaded directory>/sfgml/sfgml-dev

If you install the package from app, you can set this to itch subdirectory to have the libraries auto-update.

You can also just copy the sfgml/sfhx directories to Haxe's lib directory (when updating, probably remove the old copies first).

Per-project setup
  • Add a new extension to your project (right-click on Extensions section) and add a blank GML file to it. The extension and it's file can be named anyhow.
  • If using FlashDevelop/HaxeDevelop, create a Haxe - JavaScript project, open menu:Project - Properties, and set Output File to point to the earlier made extension's file (.yy / Then add ._ suffix to the path to prevent the file from being overwritten by Haxe compiler. So your Output File path might look like so on GMS1\extensions\

    and like so on GMS2


    Then switch to Compiler Options tab, and add the following Additional Compiler Options:

     -dce full

    And add the folowing to Libraries:


    If targetting GMS2, add the following to Directives:


    Directives is also where you add your option=value pairs for toggling things from Compiler directives section.

  • If not using FlashDevelop, build your project like

    haxe -debug -lib sfhx -lib sfgml -cp SourceDirectory -js ExtensionPath._ -main MainClass -dce full

    and add any additional -D directives (e.g. -D sfgml-next) in there.

  • Upon compiling the Haxe project, the GML extension in the GM project will be updated with new code, and you'll be able to use it's functions.


As in, examples of using the library, and also examples of what output looks like.

Hello world

Let's take a look at a simple program,

class Main {
	static function main() {
		trace("Hello world!");

compiling it would produce something like the following,

#define sftest

#define Main_main
/// Main_main()
trace("Main.hx:3:", "Hello world!");

so you have an initialization script (which is empty because we don't have any variables) and a script for the actual entrypoint function.

If your setup does not benefit much from this (like here), you can have the entrypoint script merged into initialization script by marking it as inline,

class Main {
	static inline function main() {
		trace("Hello world!");

which would shorten the output to just

#define sftest
trace("Main.hx:3:", "Hello world!");

The other thing to note here is the trace script - it is a global function used for quick logging in Haxe (documentation).

GML does not have a multi-argument debug log function, so you have to make one if you want to use trace from Haxe. A simple variant would be just about:

/// trace(...values)
var r = argument[0];
for (var i = 1; i < argument_count; i++) {
    r += " " + string(argument[i]);

And if you were to get fancy, you could also add the string to a list for display in-game, or the first argument (position information) in release builds, or whatnot.


Let's suppose you want an function that produces a random number between 1..6.

With Haxe's standard library functions you could do something like

class Main {
	static function rollD6() {
		return Std.random(6) + 1;
	static inline function main() {
		trace("You rolled " + rollD6() + "!");

which would produce

#define sftest
trace("Main.hx:6:", "You rolled " + string(Main_rollD6()) + "!");

#define Main_rollD6
/// Main_rollD6()->int
return irandom(5) + 1;

So far so good. You can see the subtle modifications here like Haxe's call becoming an irandom call while accounting for difference between the two, and number getting wrapped in a string call before adding it to a string.

People often argue about whether it's justified to have such single-line scripts or not. Usually you'd be choosing between stuffing the script into a macro instead and typing out/copy-pasting the line whenever it's needed, but with Haxe you don't have to - if you change the function declaration to be

	static inline function rollD6() {
		return Std.random(6) + 1;

It would insert the contents at location of use, producing

#define sftest
trace("Main.hx:6:", "You rolled " + string(irandom(5) + 1) + "!");

which is pretty nice.

Structures / "lightweight objects"

Let's suppose you need some two-item point structures.

You declare an anonymous structure typedef and use it:

class Main {
	static function showPoint(q:Point) {
		trace(q.x + ", " + q.y);
	static inline function main() {
		var p:Point = { x: 10, y: 20 };
typedef Point = { x:Float, y:Float };

Now, as you might be aware, GML doesn't have lightweight objects. So what happens here? Well, since fields are known on access, structure is stored as an array instead:

#define sftest
var p = array_create(2);
p[1,0] = "Point";
p[0/* x */] = 10;
p[1/* y */] = 20;

#define Main_showPoint
/// Main_showPoint(q:Point)
var q = argument[0];
trace("Main.hx:3:", string(q[0/* x */]) + ", " + string(q[1/* y */]));

The lonely item on second row is there purely to make it a little easier to tell structures apart in the debugger and can be stripped by prefixing the typedef declaration with @:nativeGen.


Most of the externs for built-in GML structures include iterators.

So, for example, you can do

class Main {
	static inline function main() {
		var arr = [1, 2, 3];
		for (item in arr) trace(item);

and this would produce

#define sftest
var arr = [1, 2, 3];
var _g = 0;
while (_g < array_length_1d(arr)) {
	var item = arr[_g];
	_g += 1;
	trace("Main.hx:4:", item);

On GMS1, [...] syntax did not yet exist, so a helper script is utilized,

#define sftest
var arr = haxe_boot_decl(1, 2, 3);
var _g = 0;
while (_g < array_length_1d(arr)) {
	var item = arr[_g];
	_g += 1;
	trace("Main.hx:5:", item);

#define haxe_boot_decl
/// haxe_boot_decl(...values:T)->array<T>
var i = argument_count;
var r = array_create(i);
while (--i >= 0) {
	r[i] = argument[i];
return r;

Same goes for data structures - if you were to do

import gml.ds.ArrayList;
class Main {
	static inline function main() {
		var list = new ArrayList();
		list.add(1, 2, 3);
		for (item in list) trace(item);

you would get

#define sftest
var list = ds_list_create();
ds_list_add(list, 1, 2, 3);
var _g_list = list;
var _g_index = 0;
while (_g_index < ds_list_size(_g_list)) {
	trace("Main.hx:6:", _g_list[|_g_index++]);
Iterating over instances

GML has a special bit of syntax for quickly iterating over instances (with statement).

To make use of this in Haxe, you can use gml.NativeScope.with

import gml.NativeScope.with;
import gml.Assets;
class Main {
	static inline function main() {
		for (inst in with(Assets.obj_test)) {

which is then translated during compilation to a with-statement:

with (obj_test) trace("Main.hx:6:", self.x);

(self.x is implicit like this.x in Haxe and results in no overhead)

the syntax supports break and continue statements, as well as nested loops.

Inline functions

While the "proper" inline functions remain to be on GameMaker roadmap for the time being (local scope sharing between functions is no joke), cases where the inline function only uses it's own scope can be handled smoothly:

class Main {
	static inline function main() {
		var fn = (i) -> i + 4; // or `function(i) return i + 4;`
		trace(fn(4), fn(5));

The compiler does what you would yourself - puts the inline function in a script:

#define sftest
trace("Main.hx:4:", Main_main_fn(4), Main_main_fn(5));

#define Main_main_fn
/// Main_main_fn(i:int)->int
var i = argument[0];
return i + 4;

The pleasant difference here is that this is done automatically.

Of course, as with anything else, you can also mark that local function as inline and it would be gone from generated code completely.

Working with resources

sfgml provides externs for all common asset functions in gml.assets package, and you also have an option to reference project resources conveniently.

First, you would need to let the compiler know where your project file is.

If using FlashDevelop, you would add a line to menu:Project - Properties - Compiler Options - General - Directives box - e.g.\

If using hxml, you would add the same declaration but prefixed with -D to build file;
For GMS2 you would have the path point at the YYP file.

Once that is done, you can import the gml.Assets class and access your resources from there, e.g.

import gml.Assets;
class Main {
	static inline function main() {

which would produce

#define sftest
trace("Main.hx:3:", sprite_get_name(object_get_sprite(obj_test)));

if you need your assets even easier to access, you can do import gml.Assets.*; to have assets accessible by name without "Assets." prefix.

Calling GML from Haxe

In order,

  • Common GML functions have externs written for them under gml.* package.
  • Uncommon GML functions can be accessed via gml.RawAPI (which has auto-generated bindings for all functions in their original snake_case format).
  • If project file path was specified (see Working with resources), project scripts can be accessed using gml.Scripts class:

  • Custom externs can be written easily - for example,

    @:native("some") extern class Some {
    	static function thing():Float;

    would have Some.thing() be translated to some_thing().

Calling Haxe from GML

General behaviour is as following:

  • Haxe paths are translated to GML by "flattening" them - so pkg.Some.func becomes pkg_Some_func; You can override paths for classes and field names using @:native("<new name>") metadata or set type-less paths for fields using @:expose("<new name>") metadata.
  • Haxe static variables become GML global variables;
    Initialization (if any) happens in entrypoint script;
    If marked with @:doc or @:expose metadata, they will be given a macro to appear in GML auto-completion.
  • Haxe static methods become GML scripts;
    If marked with @:doc or @:expose metadata, they will be marked as visible and have documentation set for auto-completion.
  • Haxe instance methods become GML scripts that take this as first argument
    (much like GML built-in functions do when they apply to something)
    Similarly, can be marked with @:doc/@:expose to expose them to GML code.
  • Marking instance variables with @:doc/@:expose will have the compiler generate helper macros for their indexes.
  • Marking a class with @:doc/@:expose will consider all of it's members to be marked with @:doc
  • To prevent fields and types from being removed by DCE, they can be marked with @:keep (same as on Haxe-JS).

That said, pretty much any Haxe stucture can be made accessible from GML side.


The following are things to keep in mind when writing code for GML

Method inheritance

If you've worked with C++ before, this might seem familiar.

Since method calls are flattened to complete paths by default, casting a child to a parent type would reference the parent's method. Say, if you have

class Main {
	static inline function main() {
		var b:A = new B();
class A {
	public function test() trace("A");
class B extends A {
	public function new() { }
	override public function test() trace("B");

this would output

var b = B_create();

Methods can be marked as "virtual" by declaring them as dynamic or @:isVar,

// ...
class A {
	public dynamic function test() trace("A");
class B extends A {
	public function new() { }
	override public function test() trace("B");

which will store the script reference in class instance itself,

var b = B_create();
script_execute(b[0/* test */], b);

You can also force this setting for all instance methods using sfgml_dyn_methods directive.

Memory management

GML's native APIs (, gml.ds.*, etc.) are usually faster than pure-Haxe implementations, but are largely explicitly managed - don't forget to call destroy() when you are done using them.

Bit operations

GameMaker's implicit integer type is 64-bit so this can have minor effects on how sign bit works or behaviours related to shifting bits out of 32-bit bounds.

That to say, you probably shouldn't be relying on overflow behaviour with Int type anyway.

String manipulation

GML stores strings as raw UTF-8 data so string operations gradually get slower as strings get longer.

If you need to do string manipulations with overly large strings, consider writing them into a or and then peeking bytes out of there.

Type checking

As of now, many GML types (such as data structures, resource indexes, buffers...) are referenced by index rather than by reference. As result, you cannot check type of them, and attempting to Std.string them will usually just yield a number.

Haxe-generated types include metadata for type checking unless marked with @:nativeGen.


Exception handling

GML doesn't have exception handling (yet) so you can't use that from Haxe either.

Dynamic access

Since instance variables are stored sequentially in arrays, you cannot perform dynamic access on something without it being cast to a specific type - otherwise lookup tables would have to be generated for each type. For same reason, most of Reflect is currently not supported.

Regular expressions

GML doesn't have those (yet?) so there's nothing to map them to in sfgml.

However, you can still use pure-Haxe solutions (like HRE) or GML-speciic native extensions implementation PCRE or other systems.

Haxe standard library coverage

Since GML's standard library is largely game-oriented rather than software-oriented, some of the functions cannot be implemented. See package overview to see what to expect.

Package overview

Brief overview and notes on what works and what doesn't.
If something isn't mentioned here but doesn't use system-specific APIs, it might still work, but wasn't actively tested.



This package houses externs for various GameMaker asset types.
In each case C-style API is mapped to a more object-oriented one
(object_get_name(obj) ->



A small set of random externs for GMS1-specific d3d functions,



Wraps all of GM's data structure related functions in OO interfaces

ArrayList (ds_list_)
Color (color_)
Grid (ds_grid_)
HashMap (ds_map_)
PriorityQueue (ds_priority_)
Queue (ds_queue_)
Stack (ds_stack_)


Houses wrappers for async events


Has an extern for async_load map and that's it.


Has a number of static properties for getting network event data out of async_load without "magic strings".


Wraps various input and device related functions into static interfaces.



Wraps GML-specific I/O functions into OO interfaces.



Wraps GML-specific networking functions.

GML's network API is strictly async-only with separate data events so it cannot be mapped to* automatically.



If compiler directive is set (doc), this class is automatically populated with externs for your project's resources.


Automatically populated with raw GML API entries in case you need something doesn't have proper externs for yet.


Represents GameMaker object instances and can be extended to wrap classes around object types.


Provides a few JS-style functions (raw trace, getTimer) and a few GML-specific macros.


Provides externs for GML-specific math functions.


Provides externs for GML-specific array functions and operations.


Allows raw access to self/other variables and with iterator.


Provides externs for GML-specific string functions (note: 1-based char indexes)


Akin to Assets, but is populated with externs for project's scripts.
Argument information is also extracted where possible (doc).



If sfgml_native_bytes is set, bytes and inputs/outputs wrap
Otherwise the data will be stored in an array at some cost of memory efficiency.

Bytes (OK)
BytesInput (OK)
BytesOutput (OK)
Eof (amiss)
Input (OK)
Output (OK
Path (OK)
StringInput (OK)


BalancedTree (OK)
Either (OK)
EnumValueMap (OK)
GenericStack (OK)
IntMap (OK)
List (OK)

Only maps with Int and String keys are supported at this time.

ObjectMap (amiss)

There is no clean way of doing this while having object instances based on arrays.

Option (OK)
StringMap (OK)
Vector (OK)
WeakMap (amiss)


You get the classic problem of there not being a zlib inflate implementation but there's a native extension by me that wraps zlib.


As mentioned earlier, integers in GML are already 64-bit, so this is fairly implicit.

Json (amiss)

GML lacks dynamic access so decoding JSON into arbitrary structures is not possible. You can still use any custom JSON implementation that produces maps and arrays though.


Compiler code resides in this package.



File (OK)
FileInput (OK)
FileOutput (OK)
FileSeek (OK)


GML's API is limited to what is warranted to work everywhere, so you get

  • exists
  • rename
  • isDirectory
  • createDirectory
  • deleteFile
  • deleteDirectory
  • readDirectory

Any (OK)

All standard functions are supported except for pop/shift/remove/splice - GML arrays cannot be contracted.

Class (OK)
Date (OK)
Enum (OK)
EnumValue (OK)
EReg (amiss)

See regular expressions in Limitations section.

IntIterator (OK)

Where possible, iterators will be transformed to C-style for-loops or GML specific repeat (times) block loops for minor performance boost and readability.

Lambda (amiss)

Lambda class relies on types having a callable iterator method, which isn't always the case with GML.

List (OK)

Since GML's hashmap/hashtable functions (ds_map_) aren't garbage-collected, by default this uses a simple custom hashtable implementation. You can opt in to using non-GC versions using -D sfgml_native_map or gml.ds.HashTable directly.

Math (OK)

GML doesn't expose NaN/+Inf/-Inf in an obvious way but that doesn't mean that you can't use them


As per dynamic access in Limitations section, only a few things work:

  • isFunction: since scripts in GML are just indexes (doc), this checks if the value is an index that points to a valid script.
  • compare: OK - GML happens to have a way of consistently comparing arbitrary values (including arrays).
  • isObject, isEnumValue: since everything is stored as arrays, these check if a value is an array.
  • copy: works for classes/typedefs/enums (that are based on arrays)

Offers several non-standard functions for manipulating enum values - getting parameter at index, getting number of parameters, setting parameter at index (since enum values are stored as array and thus technically mutable) and shallow-copying contents of one enum value to other.


A wrapper for allowing trailing arguments in functions.

So, for example, if you have

static function show(label:String, values:SfRest<Dynamic>) {
	var out = "[" + label + "]";
	for (i in 0 ... values.length) {
		out += " " + values[i];

this will compie to

var label = argument[0];
var out = "[" + label + "]";
var i = 0;
for (var _g = (argument_count - 1); i < _g; i += 1) {
	out += " " + string(argument[i + 1]);

and would be callable with arbitrary number of trailing arguments from GML.

(in Haxe you'd cal it as show("Some", [1, 2, 3]) and it would be unwrapped compile-time)


Wraps several common macros that compiler can specifically optimize.


Std.parseInt/Std.parseFloat return 0 when failing to parse because that's what underlying functions do and speed is usually favored for these things.

Std.string uses the GML's built-in string() function for the lack of prototypes with overrideable properties.


A note here: if you want to be able to use iterators after casting them to Iterator<T>, their hasNext/next functions must be declared as "dynamic" before member fields so that they line up with Iterator typedef, e.g.

@:nativeGen private class ArrayIterator<T> {
	public function new(arr:Array<T>) {
		this.array = arr;
		this.index = 0;
	public dynamic function hasNext():Bool {
		return index < array.length;
	public dynamic function next():T {
		return array[index++];
	public var array:Array<T>;
	public var index:Int;
String (OK)
StringBuf (OK)

Implemented except for things that require a purpose-specific parser (urlEncode, urlDecode, quoteWinArg, quoteUnixArg).


GML API doesn't cover everything standard for Haxe, so this is limited to

  • println
  • args
  • getEnv
  • sleep (simple busy sleep)
  • getCwd (returns empty string - GameMaker maps relative paths to correct directory automatically)
  • systemName
  • exit (exitcode is unused)
  • time
  • programPath

As per limitations, only several functions are supported here:

  • getClass
  • enumConstructor
  • enumParameters
  • enumIndex

Ideally this could do with a custom parser (see notes on string manipulation) but it does work perfectly fine after replacing it's single use of regular expressions with a StringTools.replace call.

Target-specific metadata

@:doc <type|field>

If used on a field, exposes it to GML, displaying it in auto-completion.

For functions with a doc-comment (/** ... */) this will also display the comment as suffix in documentation help line.

If a parameter is specified (@:doc("text")), overrides the help line shown in auto-completion for functions.

If used on a type, exposes all of it's fields.

@:native("name") <type|field>

Allows to override name for a type/field in generated code.

This can be used to control names for GML-accessible API and to control prefixes for externs.

If a type name is blank, it's field names will be printed without any prefix, making up for an easy way to work with "top-level" items.

Also see sfgml_snake_case.

@:expose("name") <type|field>

Works more or less like the equivalent Haxe JS metadata.

If used without arguments (@:expose), exposes a type/field with current path (much like @:doc).

If used with a parameter, overrides a field path completely.
So, for example, if you have

class Log {
	@:expose("log") static function value(v:Dynamic) {
		// ...

Log.value would be accessible from GML as just log rather than Log_log as it would if renamed via @:native.

@:expose2("name") <field>

Same as @:expose but only applies when sfgml_next is set.

Can be used to easier map extern items that have different naming scheme between GMS1 and GMS2.

@:docName("name") <type>

Allows to set a custom name for a type when displayed in auto-completion for argument types or return values.

@:nativeGen <type>

Not too unlike normal @:nativeGen metadata in Haxe, specifying this causes a type to be generated without Haxe-specific fields. This allows for much more concise output (on par with handwritten code) but strips you of ability to use or other type-related functions to accurately distinguish the type of instances of the class.

@:snakeCase <type>

Like sfgml_snake_case but only applies to the type it's flagged with.

@:isVar <function field>

This metadata is conventionally used to mark property fields to generate an instance variable, and in sfgml it can also be used to mark a function as dynamic without dynamic keyword.

As a minor advantage over dynamic keyword, if functions in child classes override it, they will automatically be considered to have this metadata.

@:remove <type|field>

Purges the given field/type from generated code.

Compiler directives


Has the compiler utilize GMS2-specific syntax features like ternary operators and array declarations.


If set, maps and Input/Output to use instead of storing byte data in an array.

Faster and more memory-efficient, but requires explicit deallocation - Bytes gets a destroy() method, andd Inputs/Outputs destroy their buffer upon close() (except for BytesOutput, which returns the buffer when calling getBytes()).


If set, has StringBuf use a that is destroyed upon calling toString() or destroy(). This is a little faster than a GC-compatible implementation.


If set, Map/haxe.ds.Map becomes an abstract around gml.ds.HashTable and gets an explicit destroy() method.


If set, has all instance methods generate as dynamic fields. Can fix compatibility errors but is going to be a little slower/less memory efficient.


If set, automatically defaults type/field paths to snake_case format, so pkg.Some.doThing becomes pkg_some_do_thing without you having to do anything.

Will not touch types/fields already renamed with @:native.


Allows to set the name of "entrypoint" script that will be doing initialization.


Can be set to a number (e.g. sfgml_version=1763) if specifically targetting an older version of GMS1 - enables workarounds for select bugs and missing functions.

Isn't used for GMS2 at this time.


Allows to set custom header text to be shown at the start of the generated file.

Supports linebreaks via \n.

Can be used for copyright notes or version numbers or anything.


Sets a prefix for all local variables - for example, sfgml_local=l_ would have var some; output

var l_some;

can be used to avoid potential name clashes between your local variables and user's resources due to everything being in global scope in GML.


If you have multiple GML files in an extension and want a specific one to be updated by Haxe, you can use this to do so (e.g. sfgml_ext_file=some.gml).


If set, each class field is preceded by a field bearing it's name.

Good for debugging as you can see field-value pairs more clearly.

sfgml_print_if=<gml condition>

If set, wraps the contents of every Haxe-generated script to be inside a said condition.

When set to a macro, can be used to easily strip out the generated code out of final builds entirely by changing the macro's value.


Can be set to path to a file for use with a few auto-generating classes (gml.Assets, gml.Scripts)


If your extension script references get very mysteriously messed up (bugs #24929, #29203), setting this option will have Haxe generate a lookup map and then peek script indexes out of it using a lookup function.


Changes the GML script to be called when you call the top-level trace function in Haxe.

General options:

If set, code is prettyprinted for readability. Otherwise spacing is used only for indentation and where required by syntax.

By default, this is enabled if Debug mode is on.


If set, any instance field references will include a comment denoting the field name alongside with the index "magic number".

By default, this is enabled if Debug mode is on.


If set, initialization and per-type declarations will be grouped together with //{..//} comments.

By default, this is enabled if Debug mode is on.


If set, will print hexadecimal constants and "<char>".code as $hex/ord("<char>") for additional readability.


If set, prefixes all classes with @:std metadata by the given package.

This is convenient if you are making an extension and don't want your Haxe standard library entries to clash with any from other Haxe-compiled extensions.


If set, a sf.sfdump file is created containing a complete dump of program AST in Haxe-like format. Option can be set to following:

  • sf_dump: makes a dump when code generation finishes or if a generation error is encountered.
  • sf_dump=pre: makes a dump prior to any AST transformations.
  • sf_dump=post: makes a dump only after compilation finishes.