Skip to main content

What shipping actually looks like

· 6 min read
Laurynas Karvelis
Founder of Zero Ad Network

When starting what seemed like a little and easy research if the idea of Zero Ad Network could technically work, it seemed only a few moving parts will be necessary to get the project up and running in no time. Even with 20 years of web development experience I didn't anticipate the project will be This involved.

So I've spent the past 3 years building Zero Ad Network from scratch - a zero ad platform that gives site owners ability to monetize their websites while serving no advertisements or annoying cookie consent screens and more! Now - two browser extensions, a WordPress plugin, a TypeScript npm module, PHP Composer package, and a Stripe payout system later, it's finally live!

Getting Chrome extension into their Web Store wasn't easy!

Google's Chrome extension review process is, honestly, quite involved and not so easy it would look like. First of all, you need to pay a one time $5 fee to prove you are who you claim you are. Every permission your browser extension requests - also requires a written justification. Every feature that touches the network, storage, or the DOM has to be explained in detail to the reviewer, they take the review process rigorously.

It took roughly 3 weeks from the first submission to approval. Receiving the email "Your extension is now live in the Chrome Web Store" was a mixture of feelings, on one hand I felt a massive influx of dopamine and excitement, but right after that I was greeted by a feeling of responsibility, a sense that things are getting real now!

Submitting Firefox extension was significantly more straight forward by comparison. The codebase is pretty much identical, apart of slightly different manifest.json structure and, most importantly, the API interface on how zeroad.network website can communicate with the extension so both are aware of each other's existence - this is crucial - as zeroad.network needs to provide the extension with extension token payload once user's subscription is activated.

After relatively smooth extension development experience on Chrome and Firefox I've decided to give it a shot and attempt to get my extension working on Safari too, but almost immediately hit a hard wall. Apple has decided that custom header injection into request payloads via declarativeNetRequest is a hard "No no..", without this - the extension is unable to inject the X-Better-Web-Hello header and ultimately a handshake between partnering site and Safari extension cannot be achieved. I kind of understand their stance on that from a more privacy focused ideology, but on the other hand - extension capabilities on Safari are very limited. Their extension debugging tools are very confusing to figure out where's what. So much so that Chrome's extension development process feels like a walk in the park by comparison. I'll keep an eye on whether they'll loosen that grip at some point. For now: two extensions, not three.

npm and Composer are a joy

Publishing TypeScript npm, and PHP Composer packages were genuinely easy. I've set up accounts on npmjs.com and packagist.org, connected the GitHub repos, a few simple Github Actions workflow files and they're published.

WordPress - not so much

You can't freely inject JS and CSS into your plugin-specific pages. You have to use WordPress's own PHP functions for that, which makes sense. But I've had to learn that during the plugin review stage. I must say, they really polished the review feedback process - if you pay attention to what's listed in their personalized email back to you, and I had to go through it at least 3 times 😅, you'll be good. In the end of the day - WordPress plugin submission was the most time consuming and effort requiring process at least for me.

But! Eventually the plugin got approved and is now officially installable from the WordPress plugin directory. That felt like a genuine win 🎉. WordPress dominating the internet, by almost 40% of active sites using it, this was a hard requirement to get the plugin available.

The ups and downs of ORM

I started the platform by plugging in TypeORM. As I used TypeORM in the past with relatively great success, it was a no-brainer to start with it.

What's not so great about TypeORM: treeshaking is weak, the project seems stagnated for quite a while now, even though they're attempting to launch a v1 now. Once you push past simple CRUD, the QueryBuilder starts showing its teeth. Queries you'd write in 3 lines of SQL become 15-line method chains that genuinely take quite some to construct. So you have some seemingly beautiful wins for quick findOneOrFail(), save() and upserts, but then you lose out fiddling with queryBuilder().

After a few evening looking for alternatives to TypeORM I've dabbed my eyes into Drizzle. It's 1/6th of the bundle size, it just works with Bun's built in SQL driver, writing schemas (entities in TypeORM world) is such a joy and feels so natural, no nonsense decorators, no silly reflect-metadata as the first import. Even though TypeORM provides really robust migrations generation via its CLI tool, so is Drizzle, there's no Up and Down script - just 'Up' part. And when you think about it - this makes more sense, you just need to be more careful about it. The migrations are just a list of plain SQL statements, no TS migration transpilation needed. It's that easy.

For those who value pragmatism, power of SQL language and simplicity that opens up much more power and control as a developer - Drizzle is really worth it. They're launching Drizzle v1 too, since v1, the findMany() now looks almost 1:1 as in TypeORM, so there's that. Honestly, it took a couple of days, and the entire database stack was fully ported to Drizzle.

Payouts were deceptively involved

I was expecting the per site payout and URP (Unaddressed Revenue Pool) calculations won't be very easy to write, but with some help from Claude, some dynamic CTEs, timing windows and edge cases, still makes me pause for a bit while reading the queries a few days later. This is the kind of work that's completely invisible to users and took a week to get it right.

Getting UI and UX right for partner Stripe Connected account, and partner onboarding in general did take another 3 days to polish out.

The part I'm actually not ready for

Here's what I've realised after all of this - every technical challenge, the extension reviews, ORM migration, Stripe integration. All given enough time and enough iterations are relatively easily doable - you can just simply punch through.

Unrelated image ;)

Marketing, on the other hand, is another level beast to me. I've come to realisation that there's no PR to merge that will convince site owners to integrate, no SQL query that will get everyday web user to switch their habits for a better web experience. This will require a completely different set of skills, and for the first time in this whole project I'm genuinely scared 😅. I think I'll apply the same approach - simply punch through it!

So that's where Zero Ad Network is going now. Fingers crossed 🤞

Stay tuned!