DEV Community

Cover image for Sharing private Flutter packages shouldn't cost this much (so I built a free registry)
Timophei Lemeshchenko
Timophei Lemeshchenko

Posted on

Sharing private Flutter packages shouldn't cost this much (so I built a free registry)

Disclosure: I built Publy, the tool this post ends up recommending. It started as a fix for my own problem, not a product — and this is the honest version of how it happened.

If you've ever tried to share private Dart or Flutter code across more than one repo, you already know the two roads in front of you, and you already know both of them hurt.

Road one: a paid private pub registry. It works, dart pub is happy, and the bill climbs every time your team or your package count grows.

Road two: git dependencies. It's free, and it quietly turns your pubspec.yaml into a minefield.

I spent a long time on both. Here's what actually went wrong, and what I did about it.

The git-dependency trap

For a while my team went the free route — pull internal packages straight from git:

dependencies:
  app_core:
    git:
      url: git@github.com:our-org/app-core.git
      ref: main
Enter fullscreen mode Exit fullscreen mode

This looks fine on day one. It falls apart on week three.

The moment you reference a package by branch or tag instead of a version, you throw away everything pub's version solver does for you. There are no version constraints — ^1.4.0 means nothing when you're pinned to ref: main. Two apps depending on the same internal package at two different commits? The solver can't reconcile that, because as far as it's concerned they're the same unversioned thing.

So you end up doing this:

dependency_overrides:
  app_core:
    git:
      url: git@github.com:our-org/app-core.git
      ref: a3f9c21   # the "known good" commit, do not touch, nobody remembers why
Enter fullscreen mode Exit fullscreen mode

dependency_overrides is meant to be a temporary, local escape hatch. When it becomes the only way your dependency graph resolves, that's not a workaround anymore — that's your architecture, and it's a bad one. Every new package multiplied the pain. Across two internal SDK monorepos we had 50+ packages depending on each other, and the git approach turned every version bump into a manual archaeology dig through commit hashes.

The whole point of semantic versioning and pub's solver is that you don't do this. Git dependencies opt you out of the one thing that makes Dart's package management good.

The paid-registry trap

So we did the sensible thing and moved to a hosted private registry. And it was genuinely better — real versions, dart pub publish, the solver working again.

Then the invoice showed up.

I won't name numbers, but the pricing scaled per seat, and even with a small team of 3 developers plus CI/CD, hosting our internal packages had become one of those line items you keep meaning to "look into." For a handful of private packages that just needed a place to live, the cost-to-value ratio stopped making sense. I was also paying out of pocket to host packages for my own side projects — and that math was even harder to justify.

I did not want a fancy product. I wanted the boring thing: a place to dart pub publish internal packages, with real versions, that didn't charge me per human.

So I built Publy

I'm a Flutter dev, not a SaaS founder, and I built this because the alternatives annoyed me — so it's deliberately small. Publy is a private Dart & Flutter package registry that implements the standard pub hosted repository spec. That last part matters: there's no custom CLI, no plugin, no wrapper. dart pub publish and dart pub get work exactly like they do against pub.dev, because to dart it is just a hosted registry.

Setup is three commands and one pubspec block.

1. Authenticate the CLI (after signing in with GitHub and creating an org + token):

dart pub token add https://publy.dev/o/your-org
Enter fullscreen mode Exit fullscreen mode

2. Tell a package where to publish — in that package's pubspec.yaml:

publish_to: https://publy.dev/o/your-org
Enter fullscreen mode Exit fullscreen mode
dart pub publish
Enter fullscreen mode Exit fullscreen mode

3. Consume it from another repo like any hosted dependency — real version constraints, back at last:

dependencies:
  app_core:
    hosted: https://publy.dev/o/your-org
    version: ^1.4.0
Enter fullscreen mode Exit fullscreen mode

That ^1.4.0 is the whole reason this exists. The solver is doing its job again. No ref, no commit hash, no dependency_overrides graveyard.

What it is and isn't

Being honest about scope, since I hate posts that oversell:

  • It's free. Not free-trial, not free-tier-with-an-asterisk — the plan you'd use for a small team costs nothing. I built it so I'd stop paying for this, so charging small teams would defeat the point.
  • Auth is GitHub OAuth for the web UI and bearer tokens for the CLI / CI.
  • Versions are immutable — republishing the same name@version is a 409, never a silent overwrite.
  • It's young. No per-package ACLs, no download analytics, no billing gymnastics. If you need enterprise compliance features today, this isn't that yet.

If you're a small-to-medium Flutter team quietly bleeding money on a private registry, or worse, drowning in git-dependency overrides — this is aimed squarely at you.

Try it

You can be publishing in about five minutes: publy.dev, and the getting-started guide is the same three commands above with pictures.

If you try it and something's broken or missing, tell me — genuinely. It exists because I hit a wall, and the fastest way to make it good is other people hitting different walls.

What's your current setup for private Flutter packages — paid registry, git deps, or something cleverer? Curious what everyone else landed on.

Top comments (0)