Consent
Every contribution starts with an explicit, informed yes. Relay builds the consent screen from the schema you selected, so it always matches what is actually sent.
The built-in dialog
presentConsent() renders an accessible dialog from your app's active consent version and resolves with the user's choice.
const choice = await relay.presentConsent(); // "accepted" | "declined" | "dismissed"The dialog:
- Names your app, your organisation and Relay.
- Lists the exact events and fields that would be shared, and what is never collected.
- States the purposes, for example AI model training or evaluation.
- Explains that approved buyers may receive licensed contributions under separate agreements.
- Explains withdrawal and deletion, including their limits.
- Gives accept and decline the same size and style. Escape dismisses without deciding.
Pass { container } to mount the dialog inside a specific element.
Your own UI
To match your app's design, fetch the presentation and render it yourself. You must show the copy and field list as provided.
const presentation = await relay.getConsent();
// Render presentation.copy and presentation.fields in your own UI.
await relay.accept(presentation); // records the opt-in and starts collection
relay.decline(presentation); // remembers the decision, sends nothingAccept and decline must be equally prominent. Don't pre-select, nag, or make your app work differently for people who decline.
Consent versions and renewal
Each consent version is hashed over the schema and purposes. If you publish a version with a different hash, that is a material change: Relay stops accepting events from existing contributors and the SDK moves to renewal_required until they opt in again. Wording-only changes keep the same hash and need no renewal.
States
| State | Meaning |
|---|---|
| not_asked | The user hasn't decided yet. Nothing is collected. |
| declined | The user said no. Nothing is collected. |
| resuming | Restoring an earlier opt-in on page load. |
| active | Opted in. Approved events are collected. |
| renewal_required | The consent scope changed. Ask again before collecting. |
| withdrawn | The user withdrew. Collection stopped and the queue was cleared. |
| unavailable | Collection isn't currently permitted for this app. |
Use onStateChange to keep your UI in sync, or read relay.getState() at any time.
What is remembered
The SDK remembers only the decision, in localStorage by default, never an identifier. Pass storage: null to keep it in memory only. All requests use credentials: "omit", so no cookies are sent or set.