Installing & Requiring Packages on Roblox
This page covers how Roblox packages are installed, required, and mounted in your project. For the platform-neutral rules (version ranges, the lockfile, private access), see Concepts.
Prerequisites
Before you can install packages, you’ll need the Forest CLI installed.
Installing packages
Suppose your project needs three packages: forest/pasta, user1/sauce, and user1/meatballs. Install each with forest install (alias forest i; every flag is covered in the CLI reference):
forest i forest/pasta
forest i user1/sauce
forest i user1/meatballsThis downloads each package into your project’s Packages/ folder (next to forest.json) and records it in your manifest.
Requiring packages
Each installed package is a folder with an init module at its top, so in Roblox it is a ModuleScript. You require it by its instance path, from wherever the Packages folder lives in your datamodel:
local Packages = game:GetService("ReplicatedStorage").Packages
local Pasta = require(Packages.pasta)
local Sauce = require(Packages.sauce)
local Meatballs = require(Packages.meatballs)
-- Do something with our now-imported ingredientsThe folder name you require is the package’s name without its scope (forest/pasta becomes pasta), or the alias you chose at install time (forest i user2/pasta -a pasta2 becomes pasta2).
Whatever a package’s root module returns is its entire public API. See Package Anatomy.
Using packages this way keeps every part of your project modular. You can update, replace, or share each piece independently.
Where packages live
On the filesystem, Forest installs into a Packages/ folder next to your forest.json, the project’s default mount. A project can add more mounts for server-only or dev-only packages. Forest does not touch your place file or decide datamodel locations.
(If your forest.json has a root field, meaning you’re authoring a package rather than a game, the Packages/ folder lives next to the root module instead, e.g. src/Packages/. See Package Anatomy.)
In the Roblox datamodel, you choose where each folder is mounted. If you use Rojo , map it in your project file. ReplicatedStorage.Packages is the common choice:
{
"name": "my-game",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"Packages": { "$path": "Packages" }
}
}
}All requires inside the installed tree are relative (script.Parent-style), so the Packages folder works wherever you mount it: ReplicatedStorage, ServerScriptService, or anywhere else.
Packages don’t declare a realm: where a package’s code lives is your project’s choice. Everything in a folder mounted under ReplicatedStorage replicates to clients, so every package’s source there is visible to clients, including packages you only use on the server. Keep those in a server-only mount, and don’t put secrets (API keys, webhook URLs, anti-cheat logic you want hidden) in package code either way. This is by design: every published public package’s code is meant to be visible to all consumers, so the exposure of source is expected and intentional.
Server-only and dev-only packages
Sometimes one folder isn’t what you want. A private package full of server logic, or an anti-cheat library, shouldn’t replicate to clients just because everything else lives in ReplicatedStorage.
Give those packages their own mount: a second dependency folder, declared in the same forest.json and pinned in the same forest-lock.json.
forest mount create ServerPackages
forest i my-studio/anticheat --mount ServerPackagesThen map each folder on its own side of the datamodel:
{
"name": "my-game",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"Packages": { "$path": "Packages" }
},
"ServerScriptService": {
"ServerPackages": { "$path": "ServerPackages" }
}
}
}Packages under ServerScriptService (or ServerStorage) never replicate, so their source is not visible to clients. Server code requires from ServerScriptService.ServerPackages, shared and client code from ReplicatedStorage.Packages. A DevPackages mount does the same job for test frameworks you don’t want in your game.
Things to know about mounts:
- Each mount is independent. Every mount resolves and installs on its own, so a package needed in both is installed twice, with separate state at runtime. For pure libraries this is harmless; each side only ever requires its own folder.
- This changes where code lives, not what it can do. Server-mounted packages are hidden from clients, but any installed package still runs with your game’s full permissions. Visibility and trust are separate questions.
- One
forest.jsonper realm still works. Projects that split realms into separate folders, each with its own manifest, keep working; mounts do the same job from one manifest and one lockfile.
See Mounts for the forest.json field, the --mount flag on each command, and the rules mounts follow.
Forest owns its dependency folders
Everything at the top level of a dependency folder belongs to Forest. On every install, anything there that isn’t one of that folder’s declared packages is removed, files and _-prefixed folders included, and Forest reports in one line anything it removed that it didn’t install. Only entries whose names start with . (such as .gitkeep) are left alone. Keep your own modules somewhere else.
That’s how Forest takes over a folder Wally used to fill: the first install removes Wally’s _Index folder and its link modules and puts Forest’s packages in their place.
To keep a wrong path from deleting your code, Forest won’t take over a folder it has never installed into while that folder holds files neither Forest nor Wally put there: install stops with an error naming them, and forest mount create refuses the same folders. Move the files out, or fix the path in forest.json.
Package Dependencies
When you install a package, Forest automatically installs any dependencies it requires.
For example, if forest/pasta depends on forest/flour, Forest installs both when you run:
forest i forest/pasta
After installation, your project’s file tree looks like this:
- script.lua
- init.lua
- LICENSE
- init.lua
- LICENSE
In the example file tree, the root Packages folder contains the three packages we installed. Inside the pasta package, there’s another Packages folder with its dependency, flour.
Since we didn’t install flour directly, it doesn’t appear in the root Packages folder. That means it isn’t available for you to require() in your project.
This structure keeps dependencies isolated. Each package manages its own requirements without affecting the main project or other packages. Forest generates small pointer modules (return require(script.Parent...)) so version-conflicted or transitive-only packages nested inside a dependent’s own Packages/ folder are always found at a predictable relative location. Every link is a relative instance path, which is why the whole tree can be mounted anywhere in the datamodel. If you do want to use flour directly, install it yourself with:
forest i forest/flour
For exactly how versions are chosen, how conflicts between shared dependencies resolve, and how the lockfile pins your tree, see Dependencies & Versioning.