One loader for every file in your addon’s folder. An image, a font, a 3D model and a data file come through the same door, typed by the file’s extension. hafen.asset() is the collection of the files this addon holds. Unprotected: an asset is a client-side file you shipped.
local icon = hafen.asset():get("icon.png") -- an image
local face = hafen.asset():get("fonts/Inter.ttf") -- a font
local chair = hafen.asset():get("props/chair.glb") -- a mesh
local theme = hafen.asset():get("theme.json") -- data (its text)
local song = hafen.asset():get("songs/air.mid") -- data (its bytes)
One flow for a local file: load once, keep the handle, hand the handle to whatever uses it. The use sites take a handle and nothing else. A path string passed to a sprite or an object raises pointing back here.
| Extensions | asset:type() |
You get | Use it with |
|---|---|---|---|
.png .jpg .jpeg .gif .bmp |
"image" |
A GPU texture, alpha preserved. | graphics:image/graphics:aimage, a sprite, a button’s face, a menu entry’s icon. |
.ttf .otf |
"font" |
A FontHandle whose family is registered, so $font[…] works. |
font =, rule:font, widget:rule(). |
.glb .gltf |
"mesh" |
Parsed glTF 2.0 static geometry and its textures. | An object. |
| Any other | "data" |
The file’s bytes, and its text decoded as UTF-8. | hafen.json():parse, string.byte, anything that takes a string. |
| Rule | Detail |
|---|---|
| Recommended formats | PNG for images (transparency), .glb for models (a single file). Every file outside the three decoded kinds is data. |
| Size limit | A file is read whole into memory: 128 MB. A larger one raises naming its size before being read. |
| One argument, always | :get(path) takes a path and no options table. Loading a file is expensive and happens once. Configuring a use is cheap and happens many times (face:derive():size(11), face:derive():size(24):bold(true)). |
| Synchronous | Call :get from setup code (Load, SessionEnteredWorld, a command), never inside a draw callback. It is one call from Lua’s side however big the file, so the instruction budget cannot see it. A large model or image is a dropped frame, timed by where you called :get. |
| Rule | Detail |
|---|---|
| Relative to your addon folder | "icon.png", "img/sign.png", "props/tree.glb". Absolute paths and .. escapes are rejected. An internal a/../b normalises and lands inside. |
| The check is on the file reached | A link inside your folder pointing outside is refused like any other outside path. External .gltf buffers and textures resolve relative to the model file through the same check. :files(dir) lists only what the same check passes. |
The same path is the same handle, for every type: the key is the resolved path, so get("./icon.png") == get("img/../icon.png"), and repeating a load is free. asset:path() answers the spelling of the first load.
local function get(path) return hafen.asset():get(path) end
assert(get("icon.png") == get("icon.png"))
assert(get("./icon.png") == get("img/../icon.png")) -- the key is the resolved path
Identity is stable while the asset is alive.
hafen.asset():remove(asset)drops it from the cache. The next:get(path)re-reads the file into a new object, never equal to the old handle. A freed asset is never served or listed again.
| Not here | Where |
|---|---|
| A remote file | hafen.http, protected by a host allowlist the user approves. A remote asset would be an async load in a synchronous API, an untrusted binary in the font and texture paths, and a tracking channel. :get handed a URL says so rather than reporting a missing file. |
A .res image (action icons, HUD art) |
graphics:resource(name, x, y). |
A .res image as a theme’s own art |
{res = name} in a rule’s bg, border or picture. |
| A minimap drawing of ground you explored | grid:image(level). |
A .res sound |
hafen.sound():get(name). |
A .res prop in the world |
hafen.virtual. |
| A built-in font | hafen.font():get(name). |
The client already owns every .res and addresses it by name: no sandbox, no cache of yours, no lifetime. This namespace is your files.
| Page | Covers |
|---|---|
| Collection | Loading, listing, freeing, and naming the files a folder of yours holds, with every refusal. |
| Handles | What each kind of loaded file answers: the shared verbs, and those an image, a font, a mesh and a data file add. |
hafen.virtual — stand an image or a mesh in the world.graphics:image and graphics:aimage draw an image asset on screen.hafen.font — what a font asset does once you have it, and the built-ins that are not assets.hafen.json — turning a data asset’s :text() into a table.