Documentation menu

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"
LanguageThe archive shipsInstalls in
luauLuauevery project
alloyits .aly, .alx, and .d.aly sources, and the Luau that alloy build writesAlloy projects only
loomits sources, and the Luau that loom build writesLoom projects only
luwuLuwuLuwu 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:

  • .aly is a module.
  • .alx is markup.
  • .d.aly holds 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:

  • alloy is not on PATH, or the build fails.
  • main is 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 build in each installed Alloy package that ships an Alloy config, alloy.toml or .config.aly. The project then runs code that its own alloy built 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 .luwu files as code, the way it counts .luau and .lua files.
  • embr publish runs 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.