@openpolicy/sdk
Version:
Public API for defining privacy policies with OpenPolicy
285 lines (241 loc) • 9.83 kB
Markdown
name: define-config
description: >
Writing the defineConfig() object for OpenPolicyConfig — privacy and cookie — including all field types, jurisdiction requirements, and preset constants from @openpolicy/sdk.
type: core
library: openpolicy
library_version: "0.0.19"
sources:
- jamiedavenport/openpolicy:packages/core/src/types.ts
- jamiedavenport/openpolicy:packages/sdk/src/constants.ts
# openpolicy/define-config
Write and maintain the `defineConfig()` call in `openpolicy.ts`. The function is an identity function used as a type marker — it accepts `OpenPolicyConfig` and returns it unchanged.
## Setup
Minimal config with privacy policy:
```ts
// openpolicy.ts
import {
ContractPrerequisite,
cookies,
dataCollected,
defineConfig,
LegalBases,
thirdParties,
} from "@openpolicy/sdk";
export default defineConfig({
company: {
name: "Acme",
legalName: "Acme, Inc.",
address: "123 Main St, San Francisco, CA 94105",
contact: { email: "privacy@acme.com" },
},
effectiveDate: "2026-01-01",
jurisdictions: ["us-ca"],
data: {
collected: { ...dataCollected },
context: {
"Account Information": {
purpose: "To create and manage user accounts",
lawfulBasis: LegalBases.Contract,
retention: "Until account deletion",
provision: ContractPrerequisite("We cannot create or operate your account."),
},
},
},
thirdParties: [...thirdParties],
cookies: {
used: cookies,
context: { essential: { lawfulBasis: LegalBases.LegalObligation } },
},
automatedDecisionMaking: [],
});
```
The `data` block keeps privacy fields together: `collected` lists field labels per category, and `context` carries the per-category metadata (`purpose`, `lawfulBasis`, `retention`, `provision`). Every key in `collected` must have a matching `context` entry — TS-enforced via `defineConfig`. The `cookies` block mirrors that pattern with `used` (categories) and `context` (Article 6 basis per category). OpenPolicy auto-detects which policies to generate from the fields you provide: a `data` block produces a privacy policy; a `cookies` block produces a cookie policy. `effectiveDate` and `jurisdictions` are shared across both.
User rights (access, erasure, portability, etc.) are **derived automatically** from `jurisdictions` — declare `eu` (GDPR) or `uk` (UK-GDPR) for the six GDPR-style rights, `us-ca` for the four CCPA rights, or any combination for the union. There is no `userRights` field on the public config. See [Supported jurisdictions](https://docs.openpolicy.sh/references/jurisdictions) for the full list of codes.
## Core Patterns
### 1. Privacy config with GDPR
Use `Compliance.GDPR` to spread the required `jurisdictions` (rights are derived automatically). Pair each collected category with a single context object holding its purpose, lawful basis, retention, and provision requirement:
```ts
import {
ContractPrerequisite,
cookies,
Compliance,
DataCategories,
dataCollected,
defineConfig,
LegalBases,
Providers,
Retention,
thirdParties,
Voluntary,
} from "@openpolicy/sdk";
export default defineConfig({
company: {
name: "Acme",
legalName: "Acme, Inc.",
address: "123 Main St, San Francisco, CA 94105",
contact: { email: "privacy@acme.com" },
},
effectiveDate: "2026-01-01",
...Compliance.GDPR,
data: {
collected: {
...dataCollected,
...DataCategories.AccountInfo,
...DataCategories.UsageData,
},
context: {
"Account Information": {
purpose: "To authenticate users and send service notifications",
lawfulBasis: LegalBases.Contract,
retention: Retention.UntilAccountDeletion,
provision: ContractPrerequisite("We cannot create or operate your account."),
},
"Usage Data": {
purpose: "To understand product usage and improve the service",
lawfulBasis: LegalBases.LegitimateInterests,
retention: Retention.NinetyDays,
provision: Voluntary("None — your service is unaffected."),
},
},
},
thirdParties: [...thirdParties, Providers.Stripe, Providers.PostHog],
children: { underAge: 13 },
cookies: {
used: cookies,
context: {
essential: { lawfulBasis: LegalBases.LegalObligation },
analytics: { lawfulBasis: LegalBases.Consent },
marketing: { lawfulBasis: LegalBases.Consent },
},
},
automatedDecisionMaking: [],
});
```
`Compliance.GDPR` expands to `{ jurisdictions: ["eu"] }`. The six GDPR user rights are derived automatically from `jurisdictions: ["eu"]`. `defineConfig` infers the category set from `data.collected` and requires every category to appear in `context` — omitting any is a TS error.
### 2. Using Compliance presets
`Compliance.GDPR`, `Compliance.UK_GDPR`, and `Compliance.CCPA` are objects safe to spread directly into `defineConfig()`:
```ts
import { Compliance, defineConfig } from "@openpolicy/sdk";
// GDPR only
defineConfig({ ...Compliance.GDPR /* ... */ });
// Multi-region — union the jurisdictions; user rights are derived automatically
defineConfig({
jurisdictions: [
...Compliance.GDPR.jurisdictions,
...Compliance.UK_GDPR.jurisdictions,
...Compliance.CCPA.jurisdictions,
],
// ...
});
```
Each preset provides only `jurisdictions`. The Article 6 basis per data category lives in `data.context[category].lawfulBasis` (you choose), not the preset.
Available preset groups from `@openpolicy/sdk`:
| Export | Content |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `DataCategories` | Named `dataCollected` entries (AccountInfo, SessionData, PaymentInfo, UsageData, DeviceInfo, LocationData, Communications) |
| `Retention` | Retention period strings (UntilAccountDeletion, ThirtyDays, NinetyDays, OneYear, ThreeYears, AsRequiredByLaw, …) |
| `LegalBases` | `LegalBasis` string constants (Consent, Contract, LegitimateInterests, …) |
| `Compliance` | Preset bundles: `GDPR`, `UK_GDPR`, `CCPA` |
| `Providers` | Named third-party descriptors: Stripe, PostHog, Vercel, Sentry, Clerk, Resend, … |
### 3. Cookie config
```ts
export default defineConfig({
company: { ... },
effectiveDate: "2026-01-01",
jurisdictions: ["eu", "us-ca"],
cookies: {
used: { essential: true, analytics: true, marketing: false },
context: {
essential: { lawfulBasis: LegalBases.LegalObligation },
analytics: { lawfulBasis: LegalBases.Consent },
marketing: { lawfulBasis: LegalBases.Consent },
},
},
consentMechanism: {
hasBanner: true,
hasPreferencePanel: true,
canWithdraw: true,
},
trackingTechnologies: ["localStorage", "sessionStorage", "cookies"],
thirdParties: [Providers.GoogleAnalytics, Providers.Cloudflare],
});
```
`cookies.used` requires `essential: true` — all other keys are `boolean` and are treated as additional cookie categories. Every enabled category in `cookies.used` must have a matching entry in `cookies.context` (TS-enforced via the `ScannedCookieKeys` interface). Presence of `cookies`, `consentMechanism`, or `trackingTechnologies` auto-detects a cookie policy.
## Common Mistakes
### HIGH — Using free-form keys in `data.context`
Wrong:
```ts
// WRONG: data.context keys must match data.collected keys (TS will reject this)
defineConfig({
data: {
collected: { "Account Information": ["Name", "Email"] },
context: {
"Providing the service": {
// ← unrelated string
purpose: "Account creation",
lawfulBasis: LegalBases.Contract,
retention: "Until account deletion",
provision: ContractPrerequisite("We cannot create your account."),
},
},
},
});
```
Correct:
```ts
defineConfig({
data: {
collected: { "Account Information": ["Name", "Email"] },
context: {
"Account Information": {
purpose: "Account creation",
lawfulBasis: LegalBases.Contract,
retention: "Until account deletion",
provision: ContractPrerequisite("We cannot create your account."),
},
},
},
});
```
`data.context` is keyed by the same set of strings as `data.collected`. The renderer joins each context entry into the GDPR Art. 13(1)(c) chain: `**Account Information** — used for Account creation — Performance of a contract (Article 6(1)(b))`.
Source: `packages/core/src/types.ts`, `packages/core/src/documents/privacy.ts`
### MEDIUM — Not specifying jurisdictions — GDPR/CCPA sections silently absent
Wrong:
```ts
// WRONG: jurisdictions missing — Legal Basis section and GDPR/CCPA content will not appear,
// and no user rights will be derived
defineConfig({
company: {
/* ... */
},
data: { collected: {}, context: {} },
// jurisdictions omitted
});
```
Correct:
```ts
defineConfig({
company: {
/* ... */
},
data: {
collected: { "Account Information": ["Name"] },
context: {
"Account Information": {
purpose: "Account creation",
lawfulBasis: LegalBases.Contract,
retention: "Until account deletion",
provision: ContractPrerequisite("We cannot create your account."),
},
},
},
jurisdictions: ["eu", "us-ca"],
});
```
Section builders for GDPR (`eu`), UK-GDPR (`uk`), and CCPA (`us-ca`) content check the top-level `jurisdictions` field before generating output, and the user rights list is derived from the same field. Omitting `jurisdictions` (or passing an empty array) causes those sections to be silently skipped and no rights to be listed.
Source: `packages/core/src/templates/privacy/`
## Reference
- [PrivacyPolicyConfig and CookiePolicyConfig field table](./references/privacy-config.md)