Every Business Central extension we ship declares dependencies: [] in its app.json. Zero required AppSource dependencies. Not one. Across twelve production extensions today, this is the single design decision that we have never once regretted, and the one that gets the most puzzled reactions from other AppSource ISVs when we describe it.
This post is about why.
What "dependencies" means in AppSource
The dependencies array in a BC extension's app.json names other AppSource apps that must be installed before yours will install. If your SFDBank app depends on SFSpendFlow, then a customer installing SFDBank will be blocked until SFSpendFlow is also present in the tenant, at a compatible version. The BC installer resolves this by fetching the dependency graph and installing the transitive closure. It works. It is how many enterprise apps compose.
It is also how you paint yourself into a corner.
The three ways the corner arrives
First, an upstream break. When your dependency ships a new major version — perhaps with an object-range rename, a permission-set rework, or an event-subscriber signature change — every downstream app that depends on it needs to release a compatible version before the customer can upgrade either. If you take a few weeks to certify against the new version, the customer sits on the old one, potentially for months. If the upstream vendor stops maintaining the dependency, you are exposed.
Second, a scope creep. You depend on a library today because you use three of its codeunits. Two years and three releases later, the library has grown to sixteen codeunits, most of which you do not use, but all of which now ship into every customer's tenant. The customer's admin sees pages and roles they never installed, cannot explain them, and cannot remove them without uninstalling your app.
Third, a certification refusal. Microsoft AppSource has been getting stricter about transitive dependency chains — cycles, orphan libraries, and shims that only exist to bridge two apps that don't quite fit together. A dependency chain that seemed harmless a year ago can fail today's AppSourceCop. Your submission bounces. You cannot ship until the upstream fixes their metadata.
We have watched partners walk through each of these. It is a bad walk.
The alternative we picked
Every SourceForge BC extension declares dependencies: [] — the empty array, not "just Microsoft base app." The extension composes only with base Business Central. Any interaction with a sibling app happens through runtime discovery, not compile-time dependency.
Concretely, that discovery is done through a small pattern we call ISiblingIntegration, ITaxPack, IBankingProvider and IAIProvider — each one an interface declared inside the extension that ships it. If another SourceForge extension is installed alongside, and it implements one of those interfaces, our app finds it at runtime through the extensibility framework and calls into it. If the sibling is not installed, the interface returns a no-op implementation and everything keeps working.
The customer installs any combination of our twelve extensions — one, three, all twelve — in any order, and every combination works. There is no dependency chain to resolve, no upstream break to wait through, no certification refusal from a shim.
What runtime discovery costs
Nothing at compile time. The interface files sit in the extension that declares them. The consumer never sees the implementation source. When the consumer needs to call the sibling, it asks the extensibility framework for an implementation of the interface. Framework returns one if it can find one, returns null (or a default no-op) if it cannot.
At runtime, the cost is one interface lookup per call, which is dominated by the ambient noise of a BC transaction. We have never measured a customer-visible impact.
The engineering discipline it demands is: define the interface once, treat it as a contract, version it as carefully as you would version a public API. That is worth it. It costs nothing to be careful about the shape of a function signature.
Where this shows up in the product family
- SpendFlow ships an
ITaxPackinterface with W1, EU, UK, GCC and US implementations. New country packs are new implementations; the SpendFlow core does not know about them. - Direct Banking ships an
IBankingProviderinterface with Ponto, TrueLayer, Lean, Plaid, Setu and Basiq implementations. A partner can ship a seventh connector without any change to Direct Banking. - Planning Studio ships an
IAIProviderinterface with a Claude implementation and a NoOp implementation. Disable AI at the licence layer and the NoOp resolver kicks in; the app keeps working. - All twelve extensions ship an
ISiblingIntegrationinterface that lets other SourceForge extensions register callbacks — for example, SpendFlow can push a trip folder into a sibling calendar sync, if such a sync exists in the tenant.
Every one of these is discovered at runtime. Every one is optional.
Why the pattern generalises
The reason zero-dependency composition works for us is not because our extensions are small or simple. Two of them are twenty-plus modules; several have thirty-plus tables. It works because we drew the same architectural line inside each extension that AppSource draws around each extension. Inside an extension, sub-modules communicate through interfaces and event subscribers, not through direct calls. Between extensions, the same pattern applies.
The unit that pays the cost of coupling is the customer's upgrade cycle. Making that unit zero-coupling by design is the single largest reduction of failure surface we have ever made in a BC portfolio.
What we would tell another ISV starting from scratch
If you are building a Business Central extension today and thinking about whether to declare a dependency on another AppSource app: don't. Almost certainly you can achieve the same customer outcome with a runtime-discovered interface, at the cost of writing one small no-op implementation. That cost is trivial. The cost of having your certification blocked because the upstream vendor pushed a bad release last Tuesday is not.
We have twelve extensions in market and zero dependencies. It is not a slogan. It is a working architecture, and it is the biggest quiet win of our BC practice.
