A Beginner's Guide to Lerna with Yarn Workspaces

When coupled together, Lerna and Yarn Workspaces can ease and optimize the management of working with multi-package repositories.

Lerna makes versioning and publishing packages to an NPM Org a painless experience by providing helpful utility commands for handling the execution of tasks across multiple packages.

Yarn Workspaces manages our dependencies. Rather than having multiple node_modules directories, it intelligently optimizes the installing of dependencies together and allows for the cross-linking of dependencies in a monorepo. Yarn Workspaces provide tools, like Lerna, the low-level primitives it needs to manage multi-package repositories.

In order to begin, let’s enable Yarn Workspaces

Now we are able to illustrate these concepts by creating a dummy project

that we initialize

and add Lerna as a dev dependency

you’ll then want to initialize Lerna, which will create a lerna.json and a packages directory

In order to set up Lerna with Yarn workspaces, we need to configure the lerna.json

Let’s add yarn as our npmClient and specify that we’re using yarn workspaces. For this tutorial we’ll be versioning our packages independently.

At this point we should only have a root package.json. In this root package.json we need to add workspaces and private to true. Setting private to true will prevent the root project from being published to NPM.

Flow for Creating a New Package

New packages need to be created under the packages directory. Let’s create a dummy form package

Once we’re in the correct directory, we can create and cd into our new package

then we create a new package.json by running yarn init:

The new name should follow our NPM Org scope ex. @my-scope-name

It’s also important to have the new package start at a version like 0.0.0 because once we do our first publish using Lerna, it’ll be published at 0.1.0 or 1.0.0.

If you have an NPM Org Account which supports private packages, you can add the following to your module’s individual package.json

Adding a Local Sibling Dependency to a Specific Package

Now that we know the flow for creating new packages, let’s say we ended up with a structure like:

If we wanted to add the my-design-system-button as a dependency to our my-design-system-form and have Lerna symlink them, we can do so by cd into that package

and then running the following:

This will update the package.json of @my-scope-name/my-design-system-form.

Our package.json should look like:

Now you can reference this local dependency in index.js like

Adding a “common” dependency to ALL packages

Doing this is similar to the previous command. This would be for /packages/*. It doesn’t matter if they’re local sibling dependencies or from NPM

If you have common dev dependencies, it’s better to specify them in the workspace root package.json. For instance, this can be dependencies like Jest, Husky, Storybook, Eslint, Prettier, etc.

*Adding the -W flag makes it explicit that we’re adding the dependency to the workspace root.

Removing Dependencies

If there’s a dependency that all packages use but that you want to remove, Lerna has the exec command that runs an arbitrary command in each package. With this knowledge, we can use exec to remove a dependency on all packages.

Running Tests

Lerna provides the run command which will run an npm script in each package that contains that script.

For instance, say all of our packages follow the structure of my-design-system-form:

and in each package.json we have the test npm script

Then Lerna can execute each test script by running:

*The — stream flag just provides output from the child processes

Publishing to NPM


First, you need to make sure you’re logged in. You can verify that you’re logged in by doing

If you’re not logged in, run the following and follow the prompts:

Once logged in you can have Lerna publish by running:

Lerna will prompt you to update the version numbers.


Lerna supports the use of the Conventional Commits Standard to automate Semantic Versioning in a CI environment.

This gives developers the ability to commit messages like

Then in a CI environment, the packages version’s can be updated and published to NPM based on commits like the one above. This is done by configuring your CI environment to run:

If you don’t want to pass flags, add the following to your lerna.json file

Enforcing Conventional Commits

If you want to enforce the Conventional Commits Standard, I recommend adding commitlint to the ROOT of the project

Then create a release script in the root package.json

This release script will be run in a CI Environment. Note, that we configured the conventional commits and “yes” flag in the lerna.json file. Since, this CI environment will be committing the Version changes, we don’t want to trigger the linting of the commit message. We do this by adding an Environment Variable named HUSKY_BYPASS which we’ll set to true by using cross-env.

We still need to add further configuration in the root package.json

For husky, we add a commit-msg hook that will check the HUSKY_BYPASS Environment Variable we added above, if this is falsy then we lint the commit message by using @commitlint/config-conventional

Split Versioning and Publishing

If for whatever reason you want full control of the versioning, Lerna has the ability to split versioning and publishing into two commands.

You can manually run:

Then follow the prompts to update the individual version numbers.

Then you can have a step that will read the latest tag (that was manually updated) to publish to NPM:

Local Development with Multiple Contributors

Anytime a new contributor does a git clone of your project or you need to pull your team’s latest changes you have to run the yarn command:

In most Lerna tutorials, it is advocated to use the lerna bootstrap command, however when yarn workspaces is enabled this is unecessary and redundant.

lerna bootstrap when you're using Yarn workspaces is literally redundant? All lerna bootstrap --npm-client yarn --use-workspaces (CLI equivalent of your lerna.json config) does is call yarn install in the root. — Issue 1308

See https://github.com/lerna/lerna/issues/1308 for more info.

Cross Project Local Development

In our example, we are building a multi-package design system. If a developer wanted to create a new component in the design system but also test it out in a local client application before it’s published, they can do so by using yarn’s link command.

To symlink a local dependency

Say we want to use our local my-design-system-core in my-client-app

We first CD into the package we want to use in another project

Then we create a symlink

You should see output like:

Now that our package is symlinked, we can go into my-client-app to use:

Any changes in /packages/my-design-system-core will be reflected in my-client-app. Now a developer can easily do local development on both projects and see it reflected.

To unlink a local dependency

When the developer is finished and no longer wants to use the local package we need to unlink.

CD into the package we want to unlink

Run unlink to remove the local symlink

You’ll see output like:

Now we can cd into my-client-app to unlink


Lerna coupled with yarn workspaces is a great combination. Lerna adds utility functionality on top of Yarn Workspaces for working with multiple packages. Yarn workspaces make it so that all dependencies can be installed together, making caching and installing faster. It allows us to easily release dependencies on NPM with a single command, automatically updates the package.json of sibling dependencies when a dependency version changes, and generally makes installing, versioning, and publishing a painless experience.



Senior Software Engineer @Netflix Prev. @NBCUniversal. #000 Coffee Addict. #webperf enthusiast

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store

Senior Software Engineer @Netflix Prev. @NBCUniversal. #000 Coffee Addict. #webperf enthusiast