Back to monorepo orchestration Target affected workspaces Configure turbo pipelines Speed up type-checking

Splitting a Package Out of a Monorepo

Sometimes a package outgrows the monorepo it was born in: it becomes an open-source project that needs its own issue tracker and contributors, a different team takes ownership with a different release cadence, or licensing and access requirements demand a separate repository. Extracting it well means keeping its history, keeping its npm name and version line, and replacing every internal workspace: dependency with a normal published dependency — without breaking the applications that still use it. This guide walks through the extraction with git filter-repo, the dependency changes on both sides, and the release handover.

Signals that a package should leave

  • Its audience is outside the monorepo. An open-source library needs public issues, external contributors and a repository people can star and clone on its own.
  • Its release cadence differs. A package released weekly by one team while the monorepo releases daily (or the reverse) creates friction in shared release tooling.
  • Access or compliance differs. Code that must be private to a subset of engineers, or public while the rest is private, is easier to govern in its own repository.
  • It no longer changes with anything else. If a year of history shows no pull requests that touched this package and another package together, the monorepo is not buying anything for it.

If none of these apply, keep it in the monorepo — splitting reintroduces version bumps and cross-repository coordination for every change. The broader trade-offs are covered in Monorepo Migration and Adoption.

Extracting the history

The goal is a new repository whose root is the package folder, with every commit that touched that folder and nothing else.

# 1. Fresh clone of the monorepo (filter-repo rewrites it)
git clone https://git.example.com/acme/acme.git /tmp/acme-sdk
cd /tmp/acme-sdk

# 2. Keep only the package folder and move it to the repository root
git filter-repo --subdirectory-filter packages/sdk

# 3. Keep only this package's release tags, renamed back to plain versions
git tag -l | grep -v '^@acme/sdk@' | xargs -r git tag -d
git filter-repo --tag-rename '@acme/sdk@':'v' --force

# 4. Push to the new repository
git remote add origin https://git.example.com/acme/acme-sdk.git
git push origin main --tags

--subdirectory-filter is the inverse of the --to-subdirectory-filter used when merging repositories in — see Merging Repositories While Preserving Git History. Commits that touched only other packages disappear; commits that touched this package and others keep only this package's part of the diff.

Extracting a package into its own repository Clone the monorepo fresh, filter to the package subdirectory, clean up tags, push to the new repository, then replace workspace dependencies in both repositories. fresh clone monorepo is untouched --subdirectory-filter packages/sdk becomes root tags + push keep v* tags for this package fix dependencies workspace: to real ranges release handover publish next version from new repo
History is extracted by rewriting a fresh clone; the monorepo itself is never rewritten.

Making the extracted package self-contained

Inside the monorepo, the package leaned on shared infrastructure. In the new repository it needs its own:

  • Dependencies on other internal packages. Any workspace: range in its package.json must become a published version range ("@acme/utils": "^3.2.0"). If a dependency was never published, either publish it, or copy the needed code in and remove the dependency.
  • Shared configuration. extends: "@acme/tsconfig/library.json" and the shared ESLint config either become published config packages or are inlined into the new repository.
  • Tooling and scripts — build, test and release scripts that relied on the monorepo's task runner and release tool.
  • CI — a new pipeline for build, test and publish, including provenance if the package had it.
  • Lockfile — generate a fresh one with a clean install; the monorepo's lockfile does not apply.
package.json before and after extraction Left panel shows the package manifest in the monorepo with workspace and catalog references; right panel shows the extracted manifest with published version ranges and its own scripts. In the monorepo "dependencies": { "@acme/utils": "workspace:^", "zod": "catalog:" }, "devDependencies": { "@acme/tsconfig": "workspace:*" } Extracted repository "dependencies": { "@acme/utils": "^3.2.0", "zod": "^3.24.1" }, "devDependencies": { "typescript": "5.7.2" }
Every workspace:, catalog: and shared-config reference must become something that resolves outside the monorepo.

Updating the monorepo

Consumers left in the monorepo now depend on a package that is no longer local. Change their dependency from workspace:^ to a published range, and remove the package folder in the same pull request:

# In the monorepo
git rm -r packages/sdk
pnpm --filter "@acme/*" update @acme/sdk@^4.1.0   # consumers now use the registry version
pnpm install

Do this only after the extracted repository has published a version that the consumers can use — ideally the same code as the last monorepo release, published as the next patch version from the new repository. That way, the switch from local to registry changes nothing functionally.

Also remove the package from release configuration (Changesets fixed or linked groups, CODEOWNERS entries, task-runner overrides) so nothing references a folder that no longer exists.

Release handover

Version continuity matters to consumers. The new repository should publish the next version after the last one released from the monorepo, under the same npm name, with the same dist-tags. Check npm view @acme/sdk versions --json and continue from the highest. If the monorepo used a release tool that tracks versions through changeset files or tags, make sure the new repository's release tool starts from the correct current version — for Changesets, the version field in package.json is the source of truth; for semantic-release, the imported v* tags are.

Transfer publishing rights too: the new repository's CI needs publish access (a token or trusted publishing configured for the new repository), and the monorepo's release pipeline should lose it for that package. Trusted publishing is described in Publishing from CI with npm Trusted Publishing.

Handing over releases to the new repository The monorepo publishes the last in-repo version; the new repository publishes the next patch; monorepo consumers switch to the registry range; the package folder is removed. monorepo npm registry acme-sdk repo last release: 4.1.0 first release from new repo: 4.1.1 consumers install ^4.1.1 remove packages/sdk; link README
Consumers move to the registry only after the new repository has published a compatible version.

Alternatives to a full split

Before extracting, consider whether a lighter change solves the underlying problem.

Mirroring for open source. If the goal is public visibility while development stays in the monorepo, a read-only mirror repository updated by CI (git subtree split --prefix=packages/sdk pushed to the mirror on every release) gives the public a repository to browse and clone without changing where work happens. External contributions then need a process to bring pull requests back into the monorepo, which works for occasional contributors but not for an active community.

Separate release configuration. If the friction is release cadence, most release tools support per-package configuration inside a monorepo: independent versioning, separate changeset groups, or a dedicated release workflow triggered only by that package's changes.

Access control within the monorepo. If the friction is ownership, CODEOWNERS and required reviews may be enough, as covered in Setting Up CODEOWNERS for Monorepo Packages. Genuine confidentiality — code some engineers must not read — does require a separate repository.

Keeping the two sides compatible after the split

Once the package lives elsewhere, monorepo applications consume it through the registry like any third-party dependency. That brings back version management: a fix in the SDK reaches the applications only after a release and an update. Configure your dependency bot to watch the package with an auto-merge rule for patch releases, so fixes flow quickly, and add a contract test in the monorepo — a small test that exercises the SDK's public API the way applications use it — so a breaking change in a minor release is caught at the update pull request rather than in production. If changes routinely need to land in both repositories at once, that is strong evidence the split was premature.

Worked example: open-sourcing an SDK

A company decides to open-source its JavaScript SDK, which lives in its private monorepo alongside internal applications. They extract packages/sdk with --subdirectory-filter, scan the extracted history for secrets and internal hostnames (finding and rewriting two internal URLs in old test fixtures), replace its dependency on the private @acme/http package by inlining the forty lines it used, and publish 4.1.1 from the new public repository with provenance. The internal applications switch from workspace:^ to ^4.1.1. Contributors outside the company can now open issues and pull requests, and internal teams consume the SDK like any other dependency.

Prevention and guardrails

  • Extract from a fresh clone, never by rewriting the monorepo.
  • Scan the extracted history for secrets and internal references before pushing it anywhere public.
  • Publish from the new repository before switching consumers.
  • Continue the version line and npm name, and move publish rights explicitly.

Frequently Asked Questions

Can I keep developing the package in both places for a while? Avoid it. Two sources of truth guarantee divergence. Pick a cut-over date, freeze changes in the monorepo folder, extract, and make the new repository the only home.

What if the package depends on unpublished internal packages? Publish them, inline the code, or keep the package in the monorepo. An extracted package must install from public (or private registry) sources alone.

Does extraction affect the monorepo's history? No. The monorepo keeps every commit, including the package's history up to the removal. Only the fresh clone is rewritten.

How do I point people from the old folder to the new repository? Leave a short README.md in the monorepo at the old path for one release cycle, stating where the package now lives, then remove it. Update links in documentation and the package's repository field in package.json so the npm page points to the new home.

Related

Monorepo Migration and Adoption