Raylib for Dart (FFI)
A Dart-first, modular set of FFI bindings for raylib.
⚠️ Stability warningYou should not rely on regular git pulls or updates to be non-breaking. If you use this project, consider vendoring or pinning a specific commit.
The API is not stabilized. Method signatures, type names, and module boundaries are subject to change without deprecation cycles. This applies especially to the
D(Dart) layer, which is newer and less battle-tested than the raw FFI bindings.In particular, the
D(Dart) layer may still contain undetected memory safety issues - dangling pointers, double-free bugs, or ownership ambiguity that has not yet surfaced. The fact that all provided examples run correctly is not proof that theDlayer is safe or correct in general. If you are doing anything non-trivial, audit the relevantDwrappers before trusting them.
This project exposes raylib in an idiomatic Dart API while staying very close to the original C design. Most bindings are generated using ffigen, but this project goes significantly beyond a raw ffigen output.
In short: ffigen gets you the raw function table. This project gives you something you can actually use.

Installation
This package currently assumes you already have raylib compiled for your platform.
Place the compiled library in a folder anywhere in your project tree (or a parent directory). The expected filenames are platform-specific:
| Platform | raylib | raygui (optional) |
|---|---|---|
| Linux | libraylib.so |
libraygui.so |
| Windows | raylib.dll |
raygui.dll |
| macOS | libraylib.dylib |
libraygui.dylib |
Then pass the folder path to findRaylib:
final rl = findRaylib('raylib-5.5_linux_amd64/lib');
This walks up from the current working directory until it finds a folder matching that name, so the path is relative and does not need to be absolute. If raygui is present in the same folder it will be loaded automatically; if not, it is silently skipped.
For explicit control over paths, use Raylib constructor directly:
final rl = Raylib(
core: '/absolute/path/to/libraylib.so',
gui: '/absolute/path/to/libraygui.so', // optional
);
The D (Dart) API - Recommended Starting Point
The D wrappers are the recommended way to use this library. They provide idiomatic, Dart-friendly objects that own their data, manage their own memory, and free you from thinking about pointer lifetimes, null terminators, and native struct layout.
Each raw FFI module has a corresponding D counterpart accessible on the same Raylib instance. For example:
| Raw FFI | D layer |
|---|---|
rl.Core |
rl.CoreD |
rl.Rlgl |
rl.RlglD |
rl.Gui |
rl.GuiD |
You are not forced to pick one or the other - both layers are available simultaneously and it is perfectly normal to mix them. The D layer covers the common case well, but sometimes the raw layer is the right tool:
// `rl.CoreD.IsKeyPressed` works directly with KeyboardKey enum,
// so we can't pass an arbitrary int - fall back to raw FFI in this case
rl.Core.IsKeyPressed('Z'.codeUnitAt(0))
See any dart example in example/<category>/dart/.
Or start here: core/dart/core_basic_window.dart
Raw FFI API - For When You Want Full Control
If you really want to manage your own pointers, squeeze every nanosecond out of the hot path, or stay as close as possible to the original C raylib API, the raw FFI layer is still fully available and is what the D wrappers are built on top of.
It is also the right choice if you are porting existing C raylib examples directly - the mapping is almost 1:1.
Quick Start (Raw FFI)
See any c example in example/<category>/c/.
Or start here: core/c/core_basic_window.dart
Advanced Loading & RaylibTemp
Native allocations are unavoidable when working with FFI. This project introduces RaylibTemp, a managed temporary memory pool designed for high-performance, short-lived native structs.
Why RaylibTemp exists
- Avoids repeated
calloc/free - Prevents memory leaks
- Makes FFI-heavy loops predictable and fast
- Allows both rotating (only for Strings) and named pointer slots
String Handling
Raylib expects null-terminated UTF-8 C strings. Dart strings are not automatically managed when passed through FFI, so string handling requires special care.
Any call that converts a Dart String into a native pointer allocates native memory. Dart DOES NOT track or free this memory for you.
❌ .toUnsafeC()
'Hello'.toUnsafeC()
- Allocates a new native UTF-8 string
- Does NOT free it automatically
- Safe only for one-time or startup calls
- Must not be used inside loops or per-frame code
This method exists purely for convenience and quick experiments.
✅ rl.Temp.str() or rl.Temp.strAt()
For runtime and per-frame usage, use the temporary allocator:
rl.Core.DrawText(
rl.Temp.str(text),
50, 50, 30, rl.C.RAYWHITE,
);
- Reuses native memory
- Avoids per-frame allocations
- Prevents memory leaks
- Is automatically freed when
rl.dispose()is called
This is the preferred and safest way to pass strings to raylib during normal execution when using the raw FFI layer.
String slots
rl.Temp.str() uses a rotating slot system, it maintains a fixed number of native string buffers and cycles through them in order. The slot count is configured via RaylibTempOptions:
final rl = Raylib(
core: '/absolute/path/to/libraylib.so',
gui: '/absolute/path/to/libraylib.so', // optional
tempOptions: RaylibTempOptions(
stringCount: 4, // default
),
);
If you call rl.Temp.str() more times than stringCount at a single call site, earlier strings will be silently overwritten, the rotating index wraps around and reuses the same slot. For example, with stringCount: 4, a fifth str() call overwrites the buffer used by the first.
In practice, rl.Temp.str() alone is sufficient for the vast majority of use cases. The overwrite issue only arises when multiple strings need to be alive simultaneously within a single expression or function call, which is uncommon. rl.Temp.strAt() is a low-level escape hatch for that specific situation and you will rarely need to reach for it.
When passing multiple strings to a single function call, you can use rl.Temp.strAt(key, [text]) to pin each string to a specific slot and avoid unintended overwrites:
rl.Core.DrawTextEx(
font,
rl.Temp.strAt('label', label),
rl.Temp.strAt('suffix', suffix),
fontSize, spacing, rl.C.RAYWHITE,
);
abbr.dart
Since only one Raylib instance is allowed at a time, abbr.dart lets you skip one layer of namespacing and save some keystrokes. The Raylib instance remains accessible via Raylib.instance:
import 'package:raylib_dartified/raylib.dart';
import 'package:raylib_dartified/abbr.dart';
void main() {
// Either of these works:
findRaylib('path/to/raylib');
// Raylib(
// core: '/absolute/path/to/libraylib.so',
// )
CoreD.InitWindow(800, 600, 'Title');
// ... and so on
disposeRaylib();
}
Safety Notes
- This is not memory-safe Dart - you are responsible for correct usage when using the raw FFI layer
- Passing invalid pointers will crash the VM
Guiand other optional modules will be uninitialized if not loaded- The
Dwrappers handle most of this for you - use them if in doubt
If you want guardrails, build them on top of this layer - or just use the D API.
Examples
This repository includes ported raylib examples, rewritten to use the Dart FFI API exposed here.
The examples closely follow the original raylib examples, making it easy to cross-reference C code with Dart FFI usage.
Each example exists in two versions:
- Raw FFI version - written against
rl.Core,rl.Temp, and the low-level pointer-based API. Useful for understanding what theDlayer is doing under the hood, or for direct comparison with the original C source. Dlayer version - written againstDmodules (likerl.CoreD) and the Dart-managed wrapper types. Useful for understanding idiomatic usage and how to avoid manual memory management in practice.
Reading both versions of the same example side by side is the fastest way to understand the tradeoffs between the two layers.
These examples are the recommended starting point to understand real-world usage, correct memory handling, and idiomatic patterns for this binding.
See the example/ directory for runnable code.
WARNING: Some examples require
resources/from the original Raylib source.
In that case just copy-paste theresources/folder intoexample/<category>/
See Also
License
This project is released under the zlib/libpng license.
It contains bindings and rewritten components derived from raylib, which is also licensed under zlib.
See LICENSE for details.
Acknowledgements
- raylib by Ramon Santamaria
- Dart FFI &
ffigen
PRs, issues, and experiments are welcome.