Over winter break, I kept noticing the same shape show up in my side projects.
I had built dev-agent during our November vibeathon. Then I started sketching Auth HI!, a Chrome extension for injecting auth headers. Then I started thinking about Experience SDK, a personalization runtime. Different products, different surfaces, same architecture trying to happen underneath: plugins, events, configuration, clear boundaries, optional capabilities.
If I keep rebuilding the same thing three times, I either understand the pattern well enough to name it, or I don't understand it at all and I'm just getting lucky.
So I went back to the source I trusted most: JSTag.
why JSTag was worth studying
JSTag is Lytics' web SDK. It has been in production long enough to earn its confidence. It has lived through scale, third-party integrations, consent constraints, transport decisions, years of product pressure, and all the boring things that quietly destroy elegant architecture if the architecture isn't actually good.
That was what interested me.
I wasn't trying to copy the product. I was trying to understand which parts of its shape were there because Lytics is Lytics, and which parts were there because SDKs under real pressure keep needing the same infrastructure.
the repeat-pattern moment
I wasn't studying JSTag to learn "how to build a CDP SDK." I was studying it because I kept seeing the same coordination problems show up in completely different projects.
Auth HI! needed to coordinate storage, pattern matching, and request interception inside the weird constraints of a Chrome extension.
Experience SDK needed optional feature surfaces, explainable decisions, and the ability to load only what a marketer actually uses.
Neither of those products is JSTag. But the underlying problems rhyme.
What in JSTag is product opinion, and what in JSTag is reusable infrastructure?
Once I started looking through that lens, the architecture opened up.
what JSTag was actually teaching
What JSTag gave me was a way of composing behavior.
At the center is a small core. Plugins register themselves through use(). Each plugin is a function. No class hierarchy. And that function receives what it needs explicitly: capabilities, instance access, configuration.
function use(closure: PluginClosure): SDK {
const plugin = { ns, defaults, expose, emit, on };
closure(plugin, this, this.config);
return this;
}
What I liked in it was what it refused to do.
It refuses hidden dependencies. It refuses ambient global state. It refuses tight coupling as the default coordination model.
Three patterns inside JSTag turned out to be the ones worth keeping.
minimal core
The core is small: plugin registration and a few utilities. Everything else comes from plugins.
That buys you a kind of stability that is easy to underestimate. The core does not need to know about analytics, storage, consent, transport, or whatever the next product decision turns out to be. It just needs to provide a dependable place where those things can attach.
For SDKs, that matters a lot. Product needs change. Integrations multiply. The thing you want least is a center that has to be rewritten every time the edge gets more complicated.
capability injection
Plugins get the capabilities they need as arguments. That sounds like a dry design point until you work on systems where the opposite is true.
When dependencies are explicit:
- testing gets easier
- environment changes hurt less
- plugins stay portable
- the architecture is readable from the function signature instead of from tribal knowledge
You can see the difference immediately between a plugin that receives emit, on, and config directly, and a plugin that reaches into window, touches internal state, or assumes a runtime it was never promised.
The first one is infrastructure. The second one is a future debugging session.
event-driven coordination
Plugins coordinating through events stay loose. A storage plugin can emit that something changed. Another plugin can react without either one knowing much about the other. That makes the system easier to extend, easier to replace in parts, and much easier to debug because behavior surfaces as signals instead of hiding inside call chains.
I felt that immediately in SDK Kit too. When behavior moved through events, debugging stayed local. I wasn't spelunking through tangled plugin-to-plugin method calls trying to find who secretly owned what.
what I did not want to copy
JSTag as a product is opinionated and batteries-included. It should be. Its job is to collect data for a specific platform with specific needs. Auto-loading essential plugins makes sense in that world. Strong product defaults make sense in that world.
But if I had copied that shape wholesale into SDK Kit, I would have copied the wrong thing.
I wasn't trying to make "JSTag, but generic." I wanted the infrastructure without the product assumptions.
The distinction I ended up with was simple:
- JSTag demonstrates the patterns at scale
- SDK Kit turns those patterns into a reusable composition layer
So SDK Kit keeps the minimal core, the functional plugin signature, the capability injection, and the event-driven coordination. It doesn't auto-load a worldview.
Users compose only what they need.
That was the part worth carrying forward.
building is what made the extraction honest
Production systems can make every design decision look inevitable once they have survived long enough. The only way I know to test whether I actually understand a pattern is to build with it.
SDK Kit was that test.
Auth HI! was the harsher test.
Chrome extensions are good at exposing architectural weakness. Service worker constraints, request interception, message-passing between UI and background logic, storage quirks, rule limits. The surface looks small until you build on it and realize the coordination problem is the product.
That was useful because the architecture had to hold or fall apart quickly.
What held up:
- plugins stayed isolated enough to test without drama
- events made behavior visible
- low coupling kept changes local
- the core stayed boring, which is exactly what you want from the core
Then the same infrastructure made sense again for Experience SDK, which is a completely different product shape. That was enough evidence for me. If the same composition layer works for a Chrome extension and a personalization runtime, you are probably looking at infrastructure.
what I actually learned
Production systems teach composition better than textbooks do.
Production code has already been pushed by real constraints. Scale, integrations, time, migration pressure, all the unglamorous things that reveal whether a pattern is structural or just clever.
Reading JSTag closely helped me separate two categories of thought that had been blurred together in my head:
- how a product chooses to use an architecture
- what the architecture itself is good at
I think it is one of the reasons building from production references can accelerate learning so much. You are not starting from greenfield purity. You are starting from something that has already survived.
what survived because it had to?
the edges I still see
The extraction is not finished. Building SDK Kit surfaced a few open edges clearly enough that I would not pretend otherwise.
Plugin dependencies are still implicit. Order matters in some cases, and while documentation can carry that for a while, I don't think documentation is the final answer.
Plugin-to-plugin extension is still unresolved. JSTag's hold() model is powerful, but it also introduces more coupling. I have not fully decided where I want that tradeoff to land in SDK Kit.
And lifecycle integration testing still has room to grow. Isolated plugin tests are not the same thing as proving that a whole coordination chain behaves in the right order when the system is actually live.
Those are real edges. I would rather see them clearly than write past them.
what I'm keeping
What I'm keeping is the method:
- notice the pattern that keeps repeating
- go to the production source that has already survived
- separate the infrastructure from the product opinion
- validate the extraction by building
That loop gave me SDK Kit. It also gave me a cleaner way to study systems I want to learn from without copying the wrong layer of the thing.
FAQ
What is plugin-based SDK architecture?
It is an SDK design where a minimal core provides registration and basic coordination, while features arrive through plugins rather than a large inheritance tree or a fixed monolith.
What is capability injection in SDK design?
Capability injection means plugins receive what they need explicitly as parameters: things like namespace registration, configuration access, event emission, and event subscription. That keeps dependencies visible and makes testing and portability much easier.
Why does event-driven coordination work well in SDKs?
Because it keeps plugins loosely coupled. One part of the system can emit behavior-relevant signals without hard-coding knowledge of every other part that might react to them.
What is the difference between JSTag and SDK Kit?
JSTag is a production product with strong opinions and auto-loaded behavior because it serves a specific data collection job. SDK Kit keeps the reusable infrastructure underneath that shape and makes composition explicit instead of automatic.
How do you extract reusable patterns from a production system?
Study the code closely enough to tell which parts exist because the product needs them and which parts exist because the architecture keeps surviving pressure. Then build something new with only the structural layer and see what still holds.