Forest CLI Commands
The Forest CLI provides commands to help you manage your packages. This page is the full reference; to get the CLI itself, see Installing the Forest CLI.
| Command | What it does |
|---|---|
forest init | Start a new package (or project) in the current directory |
forest install | Install a package, or everything in your forest.json |
forest remove | Remove a package from your project |
forest update | Update dependencies to the newest versions your ranges allow |
forest audit | Check dependencies for updates and license considerations |
forest tree | Show the installed dependency tree |
forest link | Point a dependency at a local folder while you work on it |
forest unlink | Remove a local link and restore the registry version |
forest publish | Publish your package to the registry |
forest login | Log in to your Forest PM account |
forest logout | Log out and clear stored credentials |
forest whoami | Show the currently logged-in user |
forest upgrade | Update the CLI itself to the latest release |
forest override | Force a transitive dependency onto a version range |
forest exclude | Ban versions of a package from being installed |
forest init
$forest initStarts development on a new package in the current directory.
On Roblox, init asks for a package name and the path to your root module (default src/init.luau), creates a starter module at the root path if the file doesn’t exist yet, and writes a forest.json with the name, a starting version (0.1.0), the platform, and the root path. Dependencies you install will live in a Packages/ folder next to your root module (src/Packages/ in the default layout): the same place they sit when a consumer installs your package, so your requires work identically in development and after install. Pass --packages-dir to rename that folder; the name is recorded as packagesDir in forest.json.
On UEFN, init infers what to scaffold from where you run it; see Authoring & Publishing.
$ forest init> Platform: <select your platform>> Package name: my-package> Path to the package's root file (created if missing): src/init.luau🌳 Initialized package "my-package"
-p--platform<platform>- Skip the platform picker:
--platform robloxor--platform uefn. --packages-dir<name>- Roblox only: install dependencies into
<name>instead of the defaultPackages, recorded aspackagesDirinforest.json. --project- Set up a game or project that only consumes packages: writes a bare manifest (platform and an empty dependency list) with a top-level
Packages/folder, no prompts.
forest install
$forest install [package-name]aliasesforest iforest growInstalls a package from Forest PM. Downloads the package and its dependencies into your project’s packages directory. Run without a package name to install everything in your forest.json.
Run in a folder with no forest.json, install offers to create the same bare manifest as forest init --project; pass --init <platform> to skip that prompt in scripts.
-v--version<version>- Install a specific version instead of the latest.
-a--alias<alias>- Install the package under an alias.
-f--force- Reinstall everything from scratch, ignoring installed state.
--init<platform>- Create the bare manifest without prompting when there is no
forest.json, for use in scripts. --links<mode>- How to treat local links:
apply,ignore(install exactly what the lockfile says), orforbid(fail if any links are configured, for strict CI). The default isignoreon CI machines andapplyeverywhere else.
forest remove
$forest remove <package-name>aliasforest chopRemoves a package from your project and updates your forest.json.
forest update
$forest updateMoves every dependency, direct and transitive, to the newest version its declared range allows. This is the safe, routine way to pick up fixes: forest.json is never touched, so a ^1.4.0 range can move to 1.6.2 but never to 2.0.0. The project is re-resolved fresh and reinstalled, and the command prints exactly which packages moved.
$ forest updateℹ forest/signal 2.4.0 -> 2.4.2ℹ forest/sauce 1.1.0 -> 1.2.0🌳 Updated 2 packages within the declared ranges!
To jump past your declared ranges (major versions), use forest audit --update instead, and read its report first. To update the CLI itself, see forest upgrade, which was called forest update before v1.11.
forest audit
$forest audit [package-name]aliasforest outdatedChecks your dependencies for available updates and license considerations. Where forest update stays inside your declared ranges, audit is how you review and cross them: read the report, then re-run with --update.
For each direct dependency, audit reports three versions:
- Current: the version pinned in your lockfile.
- Wanted: the highest version that satisfies the range declared in
forest.json. - Latest: the newest published version, even if your range doesn’t allow it.
After the version table, audit prints a license report over your whole resolved dependency tree (direct and transitive), flagging packages whose license safety rating deserves a look before you ship: each flagged version shows its license, a plain-language severity, and that version’s license caveats. Audit also reports any archived packages in your tree (no longer maintained, with the reason the owner left); installs warn about these with one consolidated line and point here for the details. Archived packages always keep installing, nothing is blocked. Finally, audit lists any overrides and exclusions declared in your manifest, warning about entries that no longer match anything.
Pass a package name to audit just that dependency. The name can even be a transitive dependency, in which case audit checks its installed license.
-u--update- Bump every outdated dependency’s range in
forest.jsonto^latest, then re-resolve and reinstall. Aliases are preserved.
forest tree
$forest tree [package-name]aliasesforest lsforest listPrints the installed dependency tree, so you can see exactly which versions are in your project and which package pulled in which transitive dependency. Pass a package (scope/name, an alias, or a bare name that’s unambiguous) to show only its subtree.
forest link
$forest link [path]Points one of your dependencies at a local folder, so you can iterate on a package inside a real project without publishing on every change. Roblox only for now.
The target folder is a normal package workspace with its own forest.json; Forest reads the package’s identity from it, so you never type a name that could mismatch the path. The package must already be a dependency of your project (a link overrides an installed dependency, it doesn’t add one), which also means it needs at least one published version. Publish a 0.1.0 once, then link and iterate freely.
$ forest link ../signal-devℹ Added .forest/ to .gitignore (link state is machine-local and must not be committed).⚠ 1 package linked locally:⚠ forest/signal -> ../signal-dev (registry pin: 2.4.0, linked: 2.5.0-dev)🌳 Linked forest/signal -> ../signal-dev
While linked, the package’s folder in your Packages/ tree is a live view of the linked source (a directory junction on Windows, a symlink on macOS and Linux): edit a file there and Rojo, Studio, and luau-lsp see the change immediately, no reinstall needed. When a live link isn’t possible (some cloud-synced folders, or a package whose root module isn’t named init.*), Forest copies the folder instead and tells you so; in copy mode, re-run forest install after editing to refresh the copy.
Links are your machine’s state, not your project’s. They live in a gitignored .forest/links.json, and neither forest.json nor forest-lock.json changes: a teammate who pulls your commits gets a completely normal registry install. So you can’t forget a link is active, every install and forest tree prints a banner naming the linked packages, and forest link with no arguments lists them along with how the linked copy has drifted from the pinned registry version.
A few things to know while a link is active:
- The linked package’s own dependencies come from its own folder (run
forest installthere), not from your project’s tree. - CI ignores links by default, so a stray links file can never change what CI builds; see the
--linksflag onforest install. forest publishrefuses to publish your package while any of its dependencies is linked: the tree you tested isn’t the tree consumers would get. Unlink and re-test first.- The linked view exposes the package’s full working folder, including files that publishing would filter out. That’s expected, not a bug.
forest unlink
$forest unlink [package-name]Removes a local link (pass the package or the linked path) and restores the exact registry version your lockfile pins, re-verified from the local cache. Unlinking something that isn’t linked is a no-op, not an error.
$ forest unlink forest/signalℹ Unlinked forest/signal (was -> ../signal-dev)🌳 Restored 1 package from the registry.
--all- Remove every active link.
forest publish
$forest publishPublishes your package to Forest PM. Uploads the package and its metadata to the Forest PM registry, making it available for others to install.
forest login
$forest loginLogs in to your Forest PM account. Two ways to authenticate:
- Browser (recommended): opens forest.dev in your browser; approve the request there and the CLI picks up your session automatically.
- Username/Password: prompts in the terminal. All Forest accounts use two-factor authentication, so you’ll be asked for a code from your authenticator app (or a backup code) after your credentials.
$ forest login? How would you like to log in? Username/Password> Username: your-username> Password: ********ℹ️ Two-factor authentication required> Authenticator code (or backup code): 123456🌳 Logged in as your-username
forest logout
$forest logoutLogs out of your Forest PM account and clears the credentials stored on this machine.
forest whoami
$forest whoamiPrints the account you’re currently logged in as. Useful before publishing from a shared or CI machine.
forest upgrade
$forest upgradeUpdates the Forest CLI itself to the latest release (to update your project’s dependencies, see forest update). It downloads the correct binary for your platform, verifies it against the signed release manifest, and replaces the running executable in place, no reinstall needed.
$ forest upgrade🌳 Updated forest v1.11.0 -> v1.12.0 🌲
--check- Only report whether an update is available, without installing it.
Forest also shows a one-line notice when a newer version is available. That check runs at most once a day and is skipped in CI and non-interactive shells (or set FOREST_NO_UPDATE_CHECK=1 to disable it entirely).
forest override
$forest override [package-name]Forces every transitive occurrence of a package (one you don’t declare yourself) onto a version range of your choosing, recorded in the overrides map of your forest.json. The forced range replaces whatever range each dependent declared, in either direction: pull a stuck dependency forward, or hold it back past a broken release until upstream catches up. Run with no package name to list the overrides currently declared.
Direct dependencies are rejected: their range already lives in dependencies, so change it with forest install <scope>/<name> -v <version> instead. To ban specific versions of any dependency, direct or transitive, use forest exclude.
The interactive wizard validates your range against the package’s published versions (skipping any that your excludes ban) and shows the exact version it resolves to before asking you to confirm. The project is then re-resolved and reinstalled immediately; if resolution fails, forest.json is rolled back untouched.
$ forest override forest/signal> New SemVer range for forest/signal (current: installed 2.4.0): ^2.5.0ℹ Override version will be v2.5.1 (currently 2.4.0).? Accept? Yes🌳 Override set: forest/signal -> ^2.5.0
While an override is active, every install reports how many dependency edges it rewrote (run forest tree to see the affected packages tagged in place), warns if the override matches nothing in the tree, and notes when it is no longer needed because dependencies already resolve inside it on their own.
Overrides apply only to your own project: forest publish does not carry them to consumers.
-r--range<range>- The range to force, skipping the interactive prompt. Fails if no published version satisfies it.
-y--yes- Apply without the confirmation prompt.
--remove- Remove the override for this package.
forest exclude
$forest exclude [package-name]Bans a range of a package’s versions from ever being installed, recorded in the excludes map of your forest.json. Unlike an override, an exclusion applies to direct and transitive dependencies alike: the banned versions are removed from every candidate set, declared ranges are still honored across the versions that remain, and resolution fails loudly (naming the exclusion) if a declared range has no allowed version left. Run with no package name to list the exclusions currently declared.
Before you confirm, the wizard shows exactly which published versions the ban covers, and warns if your range has no upper bound, since that also bans every future version, including the eventual fix. Prefer banning the known-bad set (for example =1.6.0). A range that bans nothing, or that bans every published version, is rejected. As with overrides, the change reinstalls immediately and rolls back forest.json if resolution fails.
$ forest exclude user1/sauce> Versions of user1/sauce to exclude (e.g. =1.6.0): =2.2.0ℹ Bans 1 published version: 2.2.0.ℹ Currently installed 2.2.0 will be replaced on reinstall.? Accept? Yesℹ user1/sauce: 2.2.0 -> 2.1.4🌳 Exclusion set: user1/sauce -> =2.2.0
Installs warn when an exclusion matches nothing in your tree, and let you know once it stops affecting resolution (every range now picks an allowed version on its own, typically because a fixed release shipped), so stale entries don’t linger unnoticed.
Exclusions apply only to your own project: forest publish does not carry them to consumers.
-r--range<range>- The versions to ban (e.g.
=1.6.0), skipping the interactive prompt. -y--yes- Apply without the confirmation prompt.
--remove- Remove the exclusion for this package.