20 July 2026·8 min read·Drafted with Claude · human-edited

Standalone by architecture: why every SourceForge BC extension declares dependencies: []

The single design decision behind our twelve Business Central extensions — zero required AppSource dependencies. Why we picked runtime discovery over compile-time coupling, and what it saves at customer upgrade time.

SourceForge BC Practice
Business Central engineering

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 ITaxPack interface 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 IBankingProvider interface 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 IAIProvider interface 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 ISiblingIntegration interface 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.

Share
Written by
SourceForge BC PracticeBusiness Central engineering

Published 20 July 2026 by SourceForge Software Services Pvt Ltd. Replies, corrections and follow-up questions: info@sourceforge.in.

Have a project that touches what you just read?

The blog exists because we'd rather show our thinking than pitch it. If something here resonated, let's talk about how it applies to your situation.

WhatsAppCall us