In c15t 3.2, we deprecated offline mode. We renamed offline() to local(), and it's now for development and tests only. We didn't remove it.
Offline mode saved each visitor's consent choice in their browser and nowhere else. You didn't need a backend. So it was the quickest way to get a banner on your site.
But it never kept a record of consent. It was the wrong thing to offer for production.
Here's what changed, why we did it and what to use instead.
What changed
- We deprecated
offline(). It still works as an alias until c15t v4. local()replaces it. It does the same thing. Policy rules resolve in the browser, and choices stay in browser storage.- In development builds, local mode logs one warning per page load. It tells you that c15t keeps no server-side record of consent. You won't see it in production builds. And it never blocks the banner or a save.
- If you decide a browser-only record is enough, like in tests, set
acknowledgeNoRecords: true. That hides the warning. - The script tag file
c15t.offline.jsis nowc15t.local.js. We still ship the old file as a deprecated alias. c15t setupnow labels local mode "Development and tests only, no consent records".- In c15t v4, the
offline()name goes away. AndacknowledgeNoRecords: truebecomes required in thelocal()type.
On an older major? c15t 1.9.0 and 2.4.0 add the same development warning to offline mode. They keep the offline name.
Why we made this change
A consent banner has two jobs. First, it asks your visitor for a choice. Second, it keeps proof of that choice.
Offline mode only did the first job. The choice lived in browser storage on your visitor's device. c15t didn't keep a copy.
So if your visitor cleared their browser, the record was gone. And if someone later asked what a visitor agreed to and when, you had nothing to show them.
Under the General Data Protection Regulation (GDPR), the proof is on you. If your business relies on consent, you have to show it. Here's Article 7(1):
Article 5(2) makes it a general rule. The GDPR calls it accountability:
The European Data Protection Board (EDPB) explains what this means in its Guidelines 05/2020 on consent. They say: "The burden of proof will be on the controller". They also give an online example:
What the GDPR does not say
To be clear, the GDPR doesn't say "use a database". The EDPB says so: "The GDPR does not prescribe exactly how this must be done."
And it isn't illegal to keep consent only in the browser. Some sites keep their own records another way.
But a record that only lives on your visitor's device is weak proof. Your visitor can delete it any time. You can't read it or audit it later.
With offline mode, it was hard or impossible to show consent.
Offline mode also had no geolocation. So every visitor got the same policy.
It looked like a complete setup. It wasn't. We think it was irresponsible for c15t to make that the easy path, so we changed it.
What a consent record holds
In c15t, a consent record is the backend's copy of a choice. It holds three things: which categories your visitor allowed or refused, when they chose and which policy and copy they saw.
That's close to what the EDPB asks for. And it's no more than they ask for. They say controllers "should have enough data to show a link to the processing (to show consent was obtained) but they shouldn't be collecting any more information than necessary."
Two ways to keep consent records
In production, send each choice to a c15t backend. You've got two options.
Inth hosted
Inth is the backend we recommend for c15t. You create a project and set your policy rules. Then you add your site's origin to its trusted origins and copy the backend URL.
Sign-up is free. Inth keeps a record of every choice, and you don't run a database.
Self-hosted
Or you can run @c15t/backend on your own server, with your own SQL database. Your app connects to it with the same modes it uses for Inth.
The catch is that you run it. You set up the database. You own migrations, backups, policy configuration, secrets, uptime and upgrades.
Start with the self-hosted quickstart.
Already have your own consent store? A custom() transport can send each choice to your own API.
How to move off offline mode
In a React app built with Vite, change the mode and set the backend URL:
// Before
import { offline } from 'c15t/react';
const mode = offline();
// After, for production
import { hosted } from 'c15t/react';
const mode = hosted();hosted() reads the backend URL from VITE_C15T_BACKEND_URL or VITE_INTH_PROJECT_URL. Other frameworks use their own variable. You'll find each one in the consent modes docs.
Do you use the script tag? Load c15t.js with data-backend-url instead of c15t.offline.js.
Keep local() for tests and demos:
import { local } from 'c15t/react';
const mode = local({ acknowledgeNoRecords: true });Thank you
Offline mode was our call. So is this change. If it breaks something for you, or you've got questions, open an issue on GitHub.
