Documentation menu

The manifest

[dependencies] & [indices]

Last updated October 23, 2026

[dependencies] records what the project uses; [indices] names where dependencies can come from.

[indices]
wally = "https://github.com/UpliftGames/wally-index"
pesde = "https://github.com/pesde-pkg/index"

[dependencies]
Promise = { name = "evaera/promise", version = "^4.0.0", index = "wally" }
Bin     = { name = "chief/bin", version = "^" }
Traits  = { workspace = "chief/traits", version = "^" }

The two dependency forms#

  • Registry: { name = "scope/pkg", version = "…", index = "key" } — index is a key into [indices]; absent means the default index (below). name follows the package name rules.
  • Workspace: { workspace = "scope/pkg", version = "^" } — another member of the same workspace, linked live during development. version is optional and defaults to ^.

Aliases are the keys#

The [dependencies] key is the dependency's alias: it names the generated link file (so it is the name you require), and it is how overrides address edges — case-sensitively, exactly as spelled. Only declared dependencies get a top-level link; transitive packages are reached through each package's own nested links. Aliases containing dots or whitespace cannot be addressed by overrides.

A key can also be the lowercase scope/name of a package, in quotes. embr add --scoped writes that form. Two packages with the same short name, such as mayari/core and chief/core, can then both be dependencies:

[dependencies]
"mayari/core" = { name = "mayari/core", version = "^0.8" }
"chief/core"  = { name = "chief/core", version = "^0.2" }

Install links a scoped key in a folder named after the scope, at packages/<environment>/<scope>/<name>.luau.

  • A key must be a plain file name, or a lowercase scope/name. A key that names no link file, such as "../x" or "a/b/c", fails before install changes the disk.
  • A plain key and a scope folder with the same name in one tree are refused, because the two names would mean two things:
Dependency keys 'quill' and 'quill/init' collide in the roblox tree: 'quill.luau' and the 'quill' scope folder share a name; rename one of them

Version requirements#

Standard semver requirements (^4.0.0, ~1.2, =2.0.0, >=4, <5). A bare ^ or * (or an empty string) means "whatever is newest". embr install re-resolves on every full run, so ^ picks up new releases; embr install --locked replays the lockfile instead.

In the workspace form, version means something different: it is a version type (^, ~, =, *) or a full requirement applied to the member's current version only at publish time. During development it is ignored entirely — you always get the member's current code. See Workspaces.

entry#

Which module inside the package its link file points at. Most packages name their own, and Ember works the rest out (see the detection order) — including a guess for packages that name nothing at all, which the install tells you about:

warning: synttx/vow names no entry point; guessed src/vow (set `entry` on the dependency to choose another)

entry is how you correct that guess, or supply one where Ember refused to guess. With or without the file extension:

[dependencies]
Vow = { name = "synttx/vow", version = "^", index = "wally", entry = "src/vow" }

It is stated per package, not per alias, so the same value is used wherever that package turns up in the install — including a dependent's nested link to it. A transitive package Ember cannot find an entry for is fixed by declaring it yourself with an entry.

An entry that isn't there is reported and ignored, falling back to detection, rather than writing a link file that points at nothing. Inside an overrides value the key parses but has no effect.

target#

Which environment tree the dependency installs under, overriding the one it would land in. It takes any of the seven environments: roblox, roblox_server, lune, lute, seal, zune, or luau. The old names still work: shared for roblox, and server for roblox_server. A package published for Roblox can be pulled into the server root instead of the project's own. It also picks the matching target of a multi-target index entry.

[dependencies]
Lapis = { name = "brinkokevin/lapis", version = "^", target = "roblox_server" }
Path  = { name = "acme/path", version = "^", target = "lune" }

A target also takes a dependency for another environment on purpose. Without it, that dependency fails the install. Ember then checks the packages under the dependency against the environment that target names. A target cannot cross a language. See What a project installs. A typo is refused, naming the entry that stated it.

Direct dependencies only, and that is a rule about the tree rather than a shortcut. Every other entry reaches the resolver with a context inherited from the root of its subtree, and moving one out of that tree would strand its dependents: they link what is installed alongside them and nothing else. So a target Ember only ever meets transitively — an overrides value, or a workspace member's own dependency while the root installs — has no effect. It is honored when that member installs itself, where the same entry is a direct dependency.

Named indices#

[indices] maps a name to a git repository. Three formats are auto-detected: wally (root config.json), pesde (root config.toml), and Ember's own index (pesde format whose entries carry a baked download URL). Any git repository in one of those formats works as a custom index.

Forest is the one index that is not a git repository. Its URL is its API, https://api.forest.dev, and Ember knows it by that host:

[indices]
forest = "https://api.forest.dev"

[dependencies]
DataStream = { name = "stratiz/datastream", version = "^2", index = "forest" }

Forest packages install into roblox. A package that Forest copies from Wally keeps its wally.toml, so its realm picks roblox or roblox_server.

embr index add manages the table, and the names wally, pesde, and forest are shorthands there: it fills in their URLs automatically. In the manifest itself there are no shorthand values; every entry is a full URL.

The key default is special: it overrides Ember's own registry (github.com/ember-luau/index) for dependencies that name no index.

Bare dependencies go to Ember's registry unless [indices] default says otherwise.