KATLA FOR TYPESCRIPT
A consent API, not a script tag.
One typed client for the cookie inventory, the generated policies and the visitor's decision. No framework, no interface, and no undocumented global to guess at — the categories, the consent state and the policy locales are types your editor already knows.
@katla.app/sdk5 KB headlessAny bundler, any framework
src/consent.tsNo framework
import { createKatlaClient, type ConsentState } from '@katla.app/sdk'
const katla = createKatlaClient({ siteId: 'your-site-id' })
// Refuse non-consented cookies before anything can set one
await katla.injectGuard()
// The scanned inventory, grouped and typed by category
const { cookies } = await katla.getCookies()
cookies.marketing.forEach((c) => console.log(c.name, c.platform))
// Every decision, for as long as you listen
const off = katla.onConsentChange((consent: ConsentState) => {
if (consent.analytics) loadAnalytics()
})onConsentChange hands back its own unsubscribeFull guide →
WHY TYPESCRIPT SITES STRUGGLE WITH CONSENT
A banner is a UI problem. Consent is a data problem.
THE PROBLEMThe platform ships a widget, and everything behind it — which cookies exist, what they do, what the visitor chose — is locked inside someone else's iframe.
WITH KATLAThe SDK is that data. getCookies() returns the inventory, getPolicy() returns the generated document as markdown, onConsentChange() returns the decision. The interface is the optional part.
THE PROBLEMThe consent global is undocumented and different on every platform, so each integration is a guess that fails quietly in production.
WITH KATLAwindow.KatlaConsent is typed as KatlaConsentAPI, and ConsentState, CookieCategory, CookieData and the thirteen policy locales are exported alongside it. A wrong category name is a compile error, not a support ticket.
THE PROBLEMBlocking trackers means auditing every script by hand, and the audit is out of date by the next deploy.
WITH KATLAinjectGuard() redefines document.cookie and holds anything whose category has not been granted. The scan keeps the inventory current, so the list is not one you maintain.
SETUP
Install, create a client, gate your tags.
- Installnpm install @katla.app/sdk. About 5 KB over the wire, types included.
- Create a clientcreateKatlaClient({ siteId }) is the whole setup. Pass baseUrl as well only if you serve the CDN from your own domain.
- Inject the guardawait client.injectGuard(). From that point a cookie whose category has not been granted is never written.
- Gate your tagsSubscribe with onConsentChange() and load analytics or ad scripts when their category turns true.
BUILT FOR TYPESCRIPT
What you get for one dependency.
The inventory, typedgetCookies() returns every scanned cookie and pixel grouped by category, with its vendor, data controller and description, in any of thirteen languages.
The cookie guardinjectGuard() redefines document.cookie, so a non-consented cookie is refused as it is written rather than deleted some time after it was set.
Policies as datagetPolicy({ format }) returns the cookie policy, the privacy policy or the cookie table as markdown, with the date it was generated. Render it however your stack renders markdown.
Consent Mode v2 and GPCsetupGoogleConsentMode() pushes all four Google signals, denied by default and updated on the decision. isGPCEnabled() reads the browser's Global Privacy Control.
Builds with no runtime fetchkatla pull writes the inventory, the policies and the guard into .katla/, so a build can ship them as static assets and fetch nothing at page view.
Framework-freeThe client is a plain object over fetch and window events. The React and Next.js entry points sit on top of it; Vue, Svelte and vanilla call it directly.
CORE WEB VITALS
That's how the cookie crumbles. Except your SEO shouldn't.
Headless means what it says: no interface, no stylesheet, no webfont, and no second request to fetch any of them. 5 KB over the wire for the guard and the consent API — and pulled into the build with katla pull, it is not a request at all.
How headless mode works →5 kBOver the wireGuard and consent API, brotli
0 msAdded LCPAgainst the same page without it
0Layout shiftThe banner reserves no space in flow
1RequestOne script, cached at the edge
Our own measurements, taken — how they were taken.
Questions TypeScript teams ask
- Do I have to build my own banner?
- Only if you want to. The React entry point ships CookieBanner and CookieCatalog unstyled, and Katla's hosted widget is one script tag. The SDK itself deliberately renders nothing.
- Does it run outside the browser?
- getCookies() and getPolicy() are plain fetches and run anywhere fetch does, including Node and edge runtimes. injectGuard() and onConsentChange() need a DOM, because the decision itself is made in the browser.
- What about Vue, Svelte or no framework at all?
- The client has no framework dependency, so it works in all of them. Only the React and Next.js entry points import React, and nothing obliges you to import those.
- Where does the cookie data come from?
- From your own site's scan, served from the CDN and cached at the edge — the same responses the widget reads. It is purged when a scan or a reclassification changes something, so it is never a copy you have to keep in sync.
One dependency, and consent is typed.
The free plan scans your TypeScript site and generates a cookie policy, without a card.