Guides
Languages
Last updated October 23, 2026
[target] language says what a package's files are written in. It is
separate from the environment, so an Alloy package for
Lune sets both:
[target]
environment = "lune"
language = "alloy"
main = "out/init.luau"
| Language | The archive ships | Installs in |
|---|---|---|
luau | Luau | every project |
alloy | its .aly, .alx, and .d.aly sources, and the Luau that alloy build writes | Alloy projects only |
loom | its sources, and the Luau that loom build writes | Loom projects only |
luwu | Luwu | Luwu projects only |
luau is the default, and Ember leaves it out of the file. Any other
value fails the manifest parse.
A project takes packages in its own language, or in plain Luau. A Luau project takes no Alloy, Loom, or Luwu package. The environment has its own rule. See What a project installs.
Alloy#
Alloy is a strict superset of Luau. Its source files come in three kinds:
.alyis a module..alxis markup..d.alyholds declarations. It writes no output and feeds the type check.
Publish an Alloy package#
embr publish runs alloy build in the package
folder before it packs the archive. Then it checks [target] main. The
publish stops when:
alloyis not on PATH, or the build fails.mainis missing, does not end in.luau, or does not exist after the build.
The archive ships its .aly, .alx, and .d.aly sources with the typed Luau
output. A --dry-run builds too, so its file list shows what a real
publish ships.
Alloy projects#
An Alloy project sets language = "alloy" in its [target]. It installs
and publishes with embr, the same as every other project:
embr add chief/core
embr install --locked
embr publish --dry-run
Ember applies these rules to an Alloy project:
- It takes Alloy packages and Luau packages. Any other package fails the install, and the error gives the reason.
- After an install, Ember runs
alloy buildin each installed Alloy package that ships an Alloy config,alloy.tomlor.config.aly. The project then runs code that its ownalloybuilt from the Alloy sources. - When a package ships several targets,
an Alloy project gets the package's Alloy target for its environment,
such as
roblox-alloy, when one exists. Otherwise it gets the Luau target of its environment.
A failed alloy build does not stop the install. Ember prints a
warning, and the package keeps the Luau that it was published with. The
same happens when alloy is not on PATH.
Loom#
Loom is a Roblox DSL that compiles to Luau. embr publish runs
loom build first, with the same checks as for Alloy. The archive ships the
sources with the Luau in dist/. A Loom project takes Loom and Luau
packages.
Use a Luau package from Loom#
Loom code cannot require a Luau module. It imports one through a
.d.loom file, which declares the module's types and writes no code.
loom build copies the .luau file of the same name into the output in
its place, so the import finds the real module.
For an installed Luau package, put the two files in your source folder.
The .luau file requires the package's link file, and the .d.loom file
declares what the package exports:
-- src/clamp.luau
return require("@packages/clamp")
// src/clamp.d.loom
export declare fn clamp(n: number, min: number, max: number): number;
// src/main.loom
import { clamp } from "./clamp";
Loom checks only what the .d.loom file declares. When the package
changes its exports, change the .d.loom file too.
Luwu#
Luwu is a fork of Luau with its own compiler and VM. .luwu files run only
on a Luwu host.
- Luwu can run Luau, so a Luwu project takes Luwu and Luau packages.
- Nothing else runs Luwu, so a Luwu package needs a Luwu project.
- Ember counts
.luwufiles as code, the way it counts.luauand.luafiles. embr publishruns no build step for Luwu.
A Luwu package in a project that is not Luwu fails the install. See What a project installs.
Find packages by language#
The search page has a Language filter. A package card shows a badge for every language except plain Luau.