Altamar Labs
Back to Blog
architecture· 3 min read

Why browser APIs are moving from imperative to declarative

The shift from intercepting network requests line by line to declaring rules upfront isn't a syntax trend. It's a direct response to a security and performance problem.

For years, intercepting and modifying network traffic from a browser extension meant writing imperative code: a function that ran on every request, inspected its data, and decided in real time whether to block it, redirect it, or let it through. That API is flexible, powerful, and, seen from the perspective of whoever designs the browser platform, a security and performance risk that's hard to contain.

The problem with the imperative model

A model where arbitrary third-party code runs on every network request has two costs that grow with usage. The first is performance: every request pays the cost of executing that code, no matter how simple or complex the logic, which limits how much the browser can optimize the network pipeline overall. The second is security: code with that level of inspection capability can, in principle, read and modify any traffic, turning every installed extension into a potential attack surface over the user's sensitive data.

The declarative alternative

The declarative model inverts the relationship: instead of running code on every request, the extension registers a set of rules upfront ("if the URL matches this pattern, block it" or "if it matches this other one, redirect it") and it's the browser, not the extension's code, that evaluates those rules natively and in an optimized way. The extension no longer needs to run on the critical path of every network request, which removes most of the performance cost, and the registered rules are auditable as data, not as arbitrary code, which drastically reduces the security surface.

What's gained and what's lost

The declarative model isn't strictly superior, it's a different trade-off. You gain predictable performance and a much smaller, more auditable security surface. You lose the ability to make truly dynamic decisions based on complex business logic evaluated at the exact moment of each request, like analyzing a full response's content before deciding what to do with the next one.

This forces a real change in how you design on top of this API: instead of writing decision logic in code, you have to anticipate the possible cases and express them as rules declared ahead of time, also managing those rules' lifecycle (unique identifiers, updates, limits on the number of active rules) as if they were configuration data, not side effects of running a function.

Why this pattern repeats in other domains

This same shift (from imperative code running on the critical path, to declarative rules evaluated by an optimized platform layer) shows up in other contexts with similar motivations: firewall rules declared instead of hand-written packet filters, authorization policies declared instead of permission checks scattered through the code, infrastructure configuration declared instead of imperative provisioning scripts. In every case, the underlying pattern is the same: when a platform needs to reason about, optimize, or audit a behavior at scale, it prefers that behavior to be expressed as declared data, not as arbitrary code that can only be understood by running it.

The lesson

Choosing between an imperative and a declarative design isn't an aesthetic preference. It's a decision about who needs to reason about the system's behavior, and with what guarantees. When that "who" is a platform that needs to optimize, audit, or provide security guarantees at scale, declarative almost always wins, even if it costs more expressiveness in the short term.