@openpolicy/sdk 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/auto-collected.d.ts +27 -1
- package/dist/auto-collected.d.ts.map +1 -1
- package/dist/auto-collected.js +23 -1
- package/dist/auto-collected.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/skills/annotate-data-collection/SKILL.md +240 -0
- package/skills/annotate-third-parties/SKILL.md +237 -0
- package/skills/annotate-third-parties/references/known-packages.md +37 -0
- package/skills/define-config/SKILL.md +286 -0
- package/skills/define-config/references/privacy-config.md +153 -0
- package/skills/define-config/references/terms-config.md +130 -0
- package/skills/getting-started/SKILL.md +251 -0
- package/skills/migrate/SKILL.md +360 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# PrivacyPolicyConfig Field Reference
|
|
2
|
+
|
|
3
|
+
When used inside `defineConfig()`, `company` is omitted — it lives at the top level of `OpenPolicyConfig`. All other fields below are on `privacy: Omit<PrivacyPolicyConfig, "company">`.
|
|
4
|
+
|
|
5
|
+
## PrivacyPolicyConfig
|
|
6
|
+
|
|
7
|
+
| Field | Type | Required | Notes |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `effectiveDate` | `string` | Yes | ISO date string (e.g. `"2026-01-01"`). Validation fails if empty. |
|
|
10
|
+
| `dataCollected` | `Record<string, string[]>` | Yes | At least one entry required. Keys are category labels; values are arrays of data point labels. Use `DataCategories.*` presets or spread `dataCollected` sentinel. |
|
|
11
|
+
| `legalBasis` | `LegalBasis \| LegalBasis[]` | Yes (GDPR) | Required when `"eu"` is in `jurisdictions`. Accepts a single value or array. |
|
|
12
|
+
| `retention` | `Record<string, string>` | Yes | Keys should match `dataCollected` categories. Use `Retention.*` preset strings. |
|
|
13
|
+
| `cookies` | `{ essential: boolean; analytics: boolean; marketing: boolean }` | Yes | All three booleans required. Drives cookie policy sections. |
|
|
14
|
+
| `thirdParties` | `{ name: string; purpose: string; policyUrl?: string }[]` | Yes | Can be empty array. Use `Providers.*` presets or spread `thirdParties` sentinel. |
|
|
15
|
+
| `userRights` | `UserRight[]` | Yes | Empty array triggers a warning. GDPR recommends 6 rights; CCPA recommends 4. |
|
|
16
|
+
| `jurisdictions` | `Jurisdiction[]` | Yes | Controls which jurisdiction-specific sections appear. See values below. |
|
|
17
|
+
| `children` | `{ underAge: number; noticeUrl?: string }` | No | Include when service is directed at children. `underAge` must be a positive integer. |
|
|
18
|
+
|
|
19
|
+
### LegalBasis values
|
|
20
|
+
|
|
21
|
+
| Constant | String value |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `LegalBases.Consent` | `"consent"` |
|
|
24
|
+
| `LegalBases.Contract` | `"contract"` |
|
|
25
|
+
| `LegalBases.LegalObligation` | `"legal_obligation"` |
|
|
26
|
+
| `LegalBases.VitalInterests` | `"vital_interests"` |
|
|
27
|
+
| `LegalBases.PublicTask` | `"public_task"` |
|
|
28
|
+
| `LegalBases.LegitimateInterests` | `"legitimate_interests"` |
|
|
29
|
+
|
|
30
|
+
### Jurisdiction values
|
|
31
|
+
|
|
32
|
+
| Value | Region |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `"us"` | United States |
|
|
35
|
+
| `"eu"` | European Union (triggers GDPR sections) |
|
|
36
|
+
| `"ca"` | California, USA (triggers CCPA sections) |
|
|
37
|
+
| `"au"` | Australia |
|
|
38
|
+
| `"nz"` | New Zealand |
|
|
39
|
+
| `"other"` | Other / unspecified |
|
|
40
|
+
|
|
41
|
+
### UserRight values
|
|
42
|
+
|
|
43
|
+
| Constant | String value | Required by |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `Rights.Access` | `"access"` | GDPR, CCPA |
|
|
46
|
+
| `Rights.Rectification` | `"rectification"` | GDPR |
|
|
47
|
+
| `Rights.Erasure` | `"erasure"` | GDPR, CCPA |
|
|
48
|
+
| `Rights.Portability` | `"portability"` | GDPR |
|
|
49
|
+
| `Rights.Restriction` | `"restriction"` | GDPR |
|
|
50
|
+
| `Rights.Objection` | `"objection"` | GDPR |
|
|
51
|
+
| `Rights.OptOutSale` | `"opt_out_sale"` | CCPA |
|
|
52
|
+
| `Rights.NonDiscrimination` | `"non_discrimination"` | CCPA |
|
|
53
|
+
|
|
54
|
+
### DataCategories presets
|
|
55
|
+
|
|
56
|
+
Each entry is a `Record<string, string[]>` safe to spread into `dataCollected`:
|
|
57
|
+
|
|
58
|
+
| Constant | Category label | Data points |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `DataCategories.AccountInfo` | `"Account Information"` | Name, Email address |
|
|
61
|
+
| `DataCategories.SessionData` | `"Session Data"` | IP address, User agent, Browser type |
|
|
62
|
+
| `DataCategories.PaymentInfo` | `"Payment Information"` | Card last 4 digits, Billing name, Billing address |
|
|
63
|
+
| `DataCategories.UsageData` | `"Usage Data"` | Pages visited, Features used, Time spent |
|
|
64
|
+
| `DataCategories.DeviceInfo` | `"Device Information"` | Device type, Operating system, Browser version |
|
|
65
|
+
| `DataCategories.LocationData` | `"Location Data"` | Country, City, Timezone |
|
|
66
|
+
| `DataCategories.Communications` | `"Communications"` | Email content, Support tickets |
|
|
67
|
+
|
|
68
|
+
### Retention presets
|
|
69
|
+
|
|
70
|
+
| Constant | String value |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `Retention.UntilAccountDeletion` | `"Until account deletion"` |
|
|
73
|
+
| `Retention.UntilSessionExpiry` | `"Until session expiry"` |
|
|
74
|
+
| `Retention.ThirtyDays` | `"30 days"` |
|
|
75
|
+
| `Retention.NinetyDays` | `"90 days"` |
|
|
76
|
+
| `Retention.OneYear` | `"1 year"` |
|
|
77
|
+
| `Retention.ThreeYears` | `"3 years"` |
|
|
78
|
+
| `Retention.AsRequiredByLaw` | `"As required by applicable law"` |
|
|
79
|
+
|
|
80
|
+
### Providers presets
|
|
81
|
+
|
|
82
|
+
Each entry is a `{ name: string; purpose: string; policyUrl: string }` safe to use in `thirdParties`:
|
|
83
|
+
|
|
84
|
+
**Payments:** `Providers.Stripe`, `Providers.Paddle`, `Providers.LemonSqueezy`, `Providers.PayPal`
|
|
85
|
+
|
|
86
|
+
**Analytics:** `Providers.GoogleAnalytics`, `Providers.PostHog`, `Providers.Plausible`, `Providers.Mixpanel`
|
|
87
|
+
|
|
88
|
+
**Infrastructure:** `Providers.Vercel`, `Providers.Cloudflare`, `Providers.AWS`
|
|
89
|
+
|
|
90
|
+
**Auth:** `Providers.Auth0`, `Providers.Clerk`
|
|
91
|
+
|
|
92
|
+
**Email:** `Providers.Resend`, `Providers.Postmark`, `Providers.SendGrid`, `Providers.Loops`
|
|
93
|
+
|
|
94
|
+
**Monitoring:** `Providers.Sentry`, `Providers.Datadog`
|
|
95
|
+
|
|
96
|
+
### Compliance presets
|
|
97
|
+
|
|
98
|
+
| Preset | Expands to |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `Compliance.GDPR` | `{ jurisdictions: ["eu"], legalBasis: ["legitimate_interests"], userRights: ["access", "rectification", "erasure", "portability", "restriction", "objection"] }` |
|
|
101
|
+
| `Compliance.CCPA` | `{ jurisdictions: ["ca"], userRights: ["access", "erasure", "opt_out_sale", "non_discrimination"] }` |
|
|
102
|
+
|
|
103
|
+
### Validation behavior
|
|
104
|
+
|
|
105
|
+
- `effectiveDate` empty → fatal error
|
|
106
|
+
- `company.*` fields empty → fatal error per field
|
|
107
|
+
- `dataCollected` has zero keys → fatal error
|
|
108
|
+
- `userRights` empty → warning only
|
|
109
|
+
- `"eu"` in jurisdictions + no `legalBasis` → fatal error
|
|
110
|
+
- `"eu"` in jurisdictions + missing GDPR right → warning per right
|
|
111
|
+
- `"ca"` in jurisdictions + missing CCPA right → warning per right
|
|
112
|
+
- `children.underAge` ≤ 0 → fatal error
|
|
113
|
+
|
|
114
|
+
Source: `packages/core/src/validate.ts`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## CookiePolicyConfig
|
|
119
|
+
|
|
120
|
+
When used inside `defineConfig()`, supply as `cookie: Omit<CookiePolicyConfig, "company">`.
|
|
121
|
+
|
|
122
|
+
| Field | Type | Required | Notes |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| `effectiveDate` | `string` | Yes | ISO date string. |
|
|
125
|
+
| `cookies` | `CookiePolicyCookies` | Yes | `essential: boolean` is required; all other keys are `boolean` and treated as additional cookie categories. |
|
|
126
|
+
| `jurisdictions` | `Jurisdiction[]` | Yes | Same values as `PrivacyPolicyConfig.jurisdictions`. |
|
|
127
|
+
| `thirdParties` | `{ name: string; purpose: string; policyUrl?: string }[]` | No | Use `Providers.*` presets. |
|
|
128
|
+
| `trackingTechnologies` | `string[]` | No | e.g. `["cookies", "localStorage", "sessionStorage", "pixel"]` |
|
|
129
|
+
| `consentMechanism` | `{ hasBanner: boolean; hasPreferencePanel: boolean; canWithdraw: boolean }` | No | Required when `"eu"` in jurisdictions for GDPR compliance. |
|
|
130
|
+
|
|
131
|
+
### CookiePolicyCookies shape
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
type CookiePolicyCookies = {
|
|
135
|
+
essential: boolean; // required
|
|
136
|
+
[key: string]: boolean; // additional categories
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Example with custom categories:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
cookie: {
|
|
144
|
+
effectiveDate: "2026-01-01",
|
|
145
|
+
cookies: {
|
|
146
|
+
essential: true,
|
|
147
|
+
analytics: true,
|
|
148
|
+
marketing: false,
|
|
149
|
+
preferences: true,
|
|
150
|
+
},
|
|
151
|
+
jurisdictions: ["eu", "us"],
|
|
152
|
+
}
|
|
153
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# TermsOfServiceConfig Field Reference
|
|
2
|
+
|
|
3
|
+
When used inside `defineConfig()`, supply as `terms: Omit<TermsOfServiceConfig, "company">`. `company` lives at the top level of `OpenPolicyConfig`.
|
|
4
|
+
|
|
5
|
+
## Required fields
|
|
6
|
+
|
|
7
|
+
| Field | Type | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `effectiveDate` | `string` | ISO date string. Validation fails if empty. |
|
|
10
|
+
| `acceptance` | `{ methods: string[] }` | How users accept — e.g. `["using the service", "creating an account"]`. Warning if `methods` is empty. |
|
|
11
|
+
| `governingLaw` | `{ jurisdiction: string }` | **Required.** Validation throws fatal error if `jurisdiction` is absent or empty. |
|
|
12
|
+
|
|
13
|
+
## Optional sections
|
|
14
|
+
|
|
15
|
+
All fields below are optional. Include only those that apply to the service. Omitting a field causes the corresponding policy section to be absent from the rendered document.
|
|
16
|
+
|
|
17
|
+
| Field | Type | Notes |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `eligibility` | `{ minimumAge: number; jurisdictionRestrictions?: string[] }` | Include when service has age or geographic restrictions. |
|
|
20
|
+
| `accounts` | `{ registrationRequired: boolean; userResponsibleForCredentials: boolean; companyCanTerminate: boolean }` | Include when the service has user accounts. |
|
|
21
|
+
| `prohibitedUses` | `string[]` | List of prohibited use descriptions. e.g. `["Scraping content without permission", "Impersonating other users"]`. |
|
|
22
|
+
| `userContent` | `{ usersOwnContent: boolean; licenseGrantedToCompany: boolean; licenseDescription?: string; companyCanRemoveContent: boolean }` | Include when users upload or create content. |
|
|
23
|
+
| `intellectualProperty` | `{ companyOwnsService: boolean; usersMayNotCopy: boolean }` | Standard IP clause. |
|
|
24
|
+
| `payments` | `{ hasPaidFeatures: boolean; refundPolicy?: string; priceChangesNotice?: string }` | Include when service has paid tiers or subscriptions. |
|
|
25
|
+
| `availability` | `{ noUptimeGuarantee: boolean; maintenanceWindows?: string }` | SaaS services typically include this. |
|
|
26
|
+
| `termination` | `{ companyCanTerminate: boolean; userCanTerminate: boolean; effectOfTermination?: string }` | Include to describe account closure behavior. |
|
|
27
|
+
| `disclaimers` | `{ serviceProvidedAsIs: boolean; noWarranties: boolean }` | Strongly recommended — validation emits a warning if absent. |
|
|
28
|
+
| `limitationOfLiability` | `{ excludesIndirectDamages: boolean; liabilityCap?: string }` | Strongly recommended — validation emits a warning if absent. |
|
|
29
|
+
| `indemnification` | `{ userIndemnifiesCompany: boolean; scope?: string }` | Include when the service exposes the company to user-generated liability. |
|
|
30
|
+
| `thirdPartyServices` | `{ name: string; purpose: string }[]` | Third-party services embedded in the product (note: no `policyUrl` field here, unlike privacy's `thirdParties`). |
|
|
31
|
+
| `disputeResolution` | `{ method: DisputeResolutionMethod; venue?: string; classActionWaiver?: boolean }` | Include to specify arbitration or litigation preference. |
|
|
32
|
+
| `changesPolicy` | `{ noticeMethod: string; noticePeriodDays?: number }` | Describe how users are notified of ToS changes. |
|
|
33
|
+
| `privacyPolicyUrl` | `string` | URL of the privacy policy. Cross-references the privacy document. |
|
|
34
|
+
|
|
35
|
+
## DisputeResolutionMethod values
|
|
36
|
+
|
|
37
|
+
| Value | Meaning |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `"arbitration"` | Binding arbitration |
|
|
40
|
+
| `"litigation"` | Court litigation |
|
|
41
|
+
| `"mediation"` | Non-binding mediation |
|
|
42
|
+
|
|
43
|
+
## Validation behavior
|
|
44
|
+
|
|
45
|
+
- `effectiveDate` empty → fatal error
|
|
46
|
+
- `company.*` fields empty → fatal error per field
|
|
47
|
+
- `governingLaw.jurisdiction` empty or missing → fatal error
|
|
48
|
+
- `acceptance.methods` empty → warning only
|
|
49
|
+
- `disclaimers` absent → warning only
|
|
50
|
+
- `limitationOfLiability` absent → warning only
|
|
51
|
+
|
|
52
|
+
Source: `packages/core/src/validate-terms.ts`
|
|
53
|
+
|
|
54
|
+
## Complete example
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { defineConfig } from "@openpolicy/sdk";
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
company: {
|
|
61
|
+
name: "Acme",
|
|
62
|
+
legalName: "Acme, Inc.",
|
|
63
|
+
address: "123 Main St, San Francisco, CA 94105",
|
|
64
|
+
contact: "legal@acme.com",
|
|
65
|
+
},
|
|
66
|
+
terms: {
|
|
67
|
+
effectiveDate: "2026-01-01",
|
|
68
|
+
acceptance: { methods: ["using the service", "creating an account"] },
|
|
69
|
+
governingLaw: { jurisdiction: "Delaware, USA" },
|
|
70
|
+
eligibility: { minimumAge: 18 },
|
|
71
|
+
accounts: {
|
|
72
|
+
registrationRequired: true,
|
|
73
|
+
userResponsibleForCredentials: true,
|
|
74
|
+
companyCanTerminate: true,
|
|
75
|
+
},
|
|
76
|
+
prohibitedUses: [
|
|
77
|
+
"Scraping content without written permission",
|
|
78
|
+
"Using the service to distribute malware",
|
|
79
|
+
"Impersonating other users or Acme employees",
|
|
80
|
+
],
|
|
81
|
+
userContent: {
|
|
82
|
+
usersOwnContent: true,
|
|
83
|
+
licenseGrantedToCompany: true,
|
|
84
|
+
licenseDescription: "A worldwide, royalty-free license to host and display your content.",
|
|
85
|
+
companyCanRemoveContent: true,
|
|
86
|
+
},
|
|
87
|
+
intellectualProperty: {
|
|
88
|
+
companyOwnsService: true,
|
|
89
|
+
usersMayNotCopy: true,
|
|
90
|
+
},
|
|
91
|
+
payments: {
|
|
92
|
+
hasPaidFeatures: true,
|
|
93
|
+
refundPolicy: "30-day money-back guarantee on annual plans.",
|
|
94
|
+
priceChangesNotice: "30 days written notice before price changes take effect.",
|
|
95
|
+
},
|
|
96
|
+
availability: {
|
|
97
|
+
noUptimeGuarantee: true,
|
|
98
|
+
maintenanceWindows: "Scheduled maintenance occurs Sundays 02:00–04:00 UTC.",
|
|
99
|
+
},
|
|
100
|
+
termination: {
|
|
101
|
+
companyCanTerminate: true,
|
|
102
|
+
userCanTerminate: true,
|
|
103
|
+
effectOfTermination: "All licenses granted to the user terminate immediately upon account closure.",
|
|
104
|
+
},
|
|
105
|
+
disclaimers: { serviceProvidedAsIs: true, noWarranties: true },
|
|
106
|
+
limitationOfLiability: {
|
|
107
|
+
excludesIndirectDamages: true,
|
|
108
|
+
liabilityCap: "fees paid by the user in the twelve months preceding the claim",
|
|
109
|
+
},
|
|
110
|
+
indemnification: {
|
|
111
|
+
userIndemnifiesCompany: true,
|
|
112
|
+
scope: "Claims arising from user content or user violations of these Terms.",
|
|
113
|
+
},
|
|
114
|
+
thirdPartyServices: [
|
|
115
|
+
{ name: "Stripe", purpose: "Payment processing" },
|
|
116
|
+
{ name: "AWS", purpose: "Cloud infrastructure" },
|
|
117
|
+
],
|
|
118
|
+
disputeResolution: {
|
|
119
|
+
method: "arbitration",
|
|
120
|
+
venue: "San Francisco, CA",
|
|
121
|
+
classActionWaiver: true,
|
|
122
|
+
},
|
|
123
|
+
changesPolicy: {
|
|
124
|
+
noticeMethod: "Email notification to the address on your account",
|
|
125
|
+
noticePeriodDays: 30,
|
|
126
|
+
},
|
|
127
|
+
privacyPolicyUrl: "https://acme.com/privacy",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: getting-started
|
|
3
|
+
description: >
|
|
4
|
+
End-to-end setup for OpenPolicy: install @openpolicy/sdk, @openpolicy/react, and
|
|
5
|
+
@openpolicy/vite-auto-collect; create openpolicy.ts with defineConfig(); wire autoCollect()
|
|
6
|
+
into vite.config.ts; wrap the React app with <OpenPolicy>; render <PrivacyPolicy>.
|
|
7
|
+
type: lifecycle
|
|
8
|
+
library: openpolicy
|
|
9
|
+
library_version: "0.0.19"
|
|
10
|
+
sources:
|
|
11
|
+
- jamiedavenport/openpolicy:packages/sdk/README.md
|
|
12
|
+
- jamiedavenport/openpolicy:packages/react/src/context.tsx
|
|
13
|
+
- jamiedavenport/openpolicy:packages/vite-auto-collect/src/index.ts
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
Install packages:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
bun add @openpolicy/sdk @openpolicy/react @openpolicy/vite-auto-collect
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Create `openpolicy.ts` at the project root:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
company: {
|
|
31
|
+
name: "Acme",
|
|
32
|
+
legalName: "Acme, Inc.",
|
|
33
|
+
address: "123 Main St, San Francisco, CA 94105",
|
|
34
|
+
contact: "privacy@acme.com",
|
|
35
|
+
},
|
|
36
|
+
privacy: {
|
|
37
|
+
effectiveDate: "2026-01-01",
|
|
38
|
+
dataCollected: {
|
|
39
|
+
...dataCollected,
|
|
40
|
+
"Account Information": ["Email address", "Display name"],
|
|
41
|
+
},
|
|
42
|
+
thirdParties: [...thirdParties],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Add `autoCollect()` to `vite.config.ts` — it must appear before any React plugin:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { defineConfig } from "vite";
|
|
51
|
+
import react from "@vitejs/plugin-react";
|
|
52
|
+
import { autoCollect } from "@openpolicy/vite-auto-collect";
|
|
53
|
+
|
|
54
|
+
export default defineConfig({
|
|
55
|
+
plugins: [
|
|
56
|
+
autoCollect({ thirdParties: { usePackageJson: true } }),
|
|
57
|
+
react(),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Wrap the application root with `<OpenPolicy>`:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// main.tsx or _app.tsx or layout.tsx
|
|
66
|
+
import { OpenPolicy } from "@openpolicy/react";
|
|
67
|
+
import config from "./openpolicy";
|
|
68
|
+
|
|
69
|
+
export function App({ children }: { children: React.ReactNode }) {
|
|
70
|
+
return <OpenPolicy config={config}>{children}</OpenPolicy>;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Render a policy page:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { PrivacyPolicy } from "@openpolicy/react";
|
|
78
|
+
import "@openpolicy/react/styles.css";
|
|
79
|
+
|
|
80
|
+
export default function PrivacyPage() {
|
|
81
|
+
return <PrivacyPolicy />;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Core Patterns
|
|
86
|
+
|
|
87
|
+
### Mark data collection inline with `collecting()`
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { collecting } from "@openpolicy/sdk";
|
|
91
|
+
|
|
92
|
+
// Call next to the point of collection; autoCollect() scans for these at build time
|
|
93
|
+
export async function createUser(name: string, email: string) {
|
|
94
|
+
const user = collecting(
|
|
95
|
+
"Account Information",
|
|
96
|
+
{ name, email },
|
|
97
|
+
{ name: "Display name", email: "Email address" },
|
|
98
|
+
);
|
|
99
|
+
return db.users.create(user);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The category and label arguments must be string literals — dynamic variables are silently skipped by the static scanner.
|
|
104
|
+
|
|
105
|
+
### Mark third-party integrations with `thirdParty()`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { thirdParty } from "@openpolicy/sdk";
|
|
109
|
+
|
|
110
|
+
// Place next to the integration's initialisation
|
|
111
|
+
thirdParty("Stripe", "Payment processing", "https://stripe.com/privacy");
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `autoCollect({ thirdParties: { usePackageJson: true } })` option also auto-detects ~30 known packages (Stripe, Sentry, PostHog, etc.) from `package.json`.
|
|
115
|
+
|
|
116
|
+
### Spread both sentinels in `openpolicy.ts`
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
|
|
120
|
+
|
|
121
|
+
export default defineConfig({
|
|
122
|
+
company: {
|
|
123
|
+
name: "Acme",
|
|
124
|
+
legalName: "Acme, Inc.",
|
|
125
|
+
address: "123 Main St, San Francisco, CA 94105",
|
|
126
|
+
contact: "privacy@acme.com",
|
|
127
|
+
},
|
|
128
|
+
privacy: {
|
|
129
|
+
effectiveDate: "2026-01-01",
|
|
130
|
+
dataCollected: {
|
|
131
|
+
...dataCollected, // populated by autoCollect() at build time
|
|
132
|
+
"Manual Category": ["Manually added field"], // additional hand-declared entries
|
|
133
|
+
},
|
|
134
|
+
thirdParties: [...thirdParties], // populated by autoCollect() at build time
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Both `dataCollected` and `thirdParties` are placeholder objects in `@openpolicy/sdk`; `autoCollect()` replaces them via virtual module injection during the Vite build.
|
|
140
|
+
|
|
141
|
+
## Common Mistakes
|
|
142
|
+
|
|
143
|
+
### CRITICAL: Using `openPolicy()` from `@openpolicy/vite` instead of `autoCollect()`
|
|
144
|
+
|
|
145
|
+
Wrong:
|
|
146
|
+
```ts
|
|
147
|
+
// vite.config.ts
|
|
148
|
+
import { openPolicy } from "@openpolicy/vite";
|
|
149
|
+
|
|
150
|
+
export default defineConfig({
|
|
151
|
+
plugins: [openPolicy({ formats: ["markdown"], outDir: "public/policies" })],
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Correct:
|
|
156
|
+
```ts
|
|
157
|
+
// vite.config.ts
|
|
158
|
+
import { autoCollect } from "@openpolicy/vite-auto-collect";
|
|
159
|
+
|
|
160
|
+
export default defineConfig({
|
|
161
|
+
plugins: [autoCollect()],
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`openPolicy()` generates static files at build time that React components never read; `autoCollect()` populates the `dataCollected` and `thirdParties` sentinels that feed the React runtime rendering path.
|
|
166
|
+
|
|
167
|
+
Source: maintainer interview
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### HIGH: Rendering policy components without `<OpenPolicy>` provider
|
|
172
|
+
|
|
173
|
+
Wrong:
|
|
174
|
+
```tsx
|
|
175
|
+
// privacy-page.tsx
|
|
176
|
+
import { PrivacyPolicy } from "@openpolicy/react";
|
|
177
|
+
|
|
178
|
+
export default function PrivacyPage() {
|
|
179
|
+
return <PrivacyPolicy />;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Correct:
|
|
184
|
+
```tsx
|
|
185
|
+
// layout.tsx — wrap at the root
|
|
186
|
+
import { OpenPolicy } from "@openpolicy/react";
|
|
187
|
+
import config from "./openpolicy";
|
|
188
|
+
|
|
189
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
190
|
+
return <OpenPolicy config={config}>{children}</OpenPolicy>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// privacy-page.tsx — component reads from context
|
|
194
|
+
import { PrivacyPolicy } from "@openpolicy/react";
|
|
195
|
+
|
|
196
|
+
export default function PrivacyPage() {
|
|
197
|
+
return <PrivacyPolicy />;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`PrivacyPolicy`, `TermsOfService`, and `CookiePolicy` read config from React context; without the provider they silently render `null` with no visible error.
|
|
202
|
+
|
|
203
|
+
Source: packages/react/src/context.tsx
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### HIGH: Not spreading `dataCollected` and `thirdParties` sentinels into config
|
|
208
|
+
|
|
209
|
+
Wrong:
|
|
210
|
+
```ts
|
|
211
|
+
// openpolicy.ts
|
|
212
|
+
import { defineConfig } from "@openpolicy/sdk";
|
|
213
|
+
|
|
214
|
+
export default defineConfig({
|
|
215
|
+
company: {
|
|
216
|
+
name: "Acme",
|
|
217
|
+
legalName: "Acme, Inc.",
|
|
218
|
+
address: "123 Main St, San Francisco, CA 94105",
|
|
219
|
+
contact: "privacy@acme.com",
|
|
220
|
+
},
|
|
221
|
+
privacy: {
|
|
222
|
+
effectiveDate: "2026-01-01",
|
|
223
|
+
dataCollected: { "Account Information": ["Email address"] },
|
|
224
|
+
thirdParties: [],
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Correct:
|
|
230
|
+
```ts
|
|
231
|
+
// openpolicy.ts
|
|
232
|
+
import { defineConfig, dataCollected, thirdParties } from "@openpolicy/sdk";
|
|
233
|
+
|
|
234
|
+
export default defineConfig({
|
|
235
|
+
company: {
|
|
236
|
+
name: "Acme",
|
|
237
|
+
legalName: "Acme, Inc.",
|
|
238
|
+
address: "123 Main St, San Francisco, CA 94105",
|
|
239
|
+
contact: "privacy@acme.com",
|
|
240
|
+
},
|
|
241
|
+
privacy: {
|
|
242
|
+
effectiveDate: "2026-01-01",
|
|
243
|
+
dataCollected: { ...dataCollected, "Account Information": ["Email address"] },
|
|
244
|
+
thirdParties: [...thirdParties],
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Without spreading the sentinels, `autoCollect()` plugin output is discarded and the policy compiles with only the hand-declared entries — all `collecting()` and `thirdParty()` call annotations are silently ignored.
|
|
250
|
+
|
|
251
|
+
Source: packages/sdk/src/auto-collected.ts
|