OPEN-SOURCE POLICY-AS-CODE

The First Policy Framework for Busy Founders and Developers

March 13, 2026

Policy Pages in SvelteKit That Actually Stay Up to Date

SvelteKit apps need legal pages before launch. The usual approach: find a template, paste it into a +page.svelte, and hope nobody notices when it still references “Your Company Name” six months later.

OpenPolicy treats your policies like code. You define them as TypeScript objects in openpolicy.ts, and the Vite plugin compiles them to HTML at build time — automatically, on every deploy, in sync with the rest of your app.

Install

bun add @openpolicy/sdk
bun add -D @openpolicy/vite

Define your policies

Create a single openpolicy.ts at the root of your project. The unified defineConfig() lets you define all policies in one file with a shared company block:

// openpolicy.ts
import { defineConfig } from "@openpolicy/sdk";

export default defineConfig({
  company: {
    name: "Acme",
    legalName: "Acme, Inc.",
    address: "123 Main St, San Francisco, CA 94105",
    contact: "privacy@acme.com",
  },
  privacy: {
    effectiveDate: "2026-03-13",
    dataCollected: {
      "Account information": ["Email address", "Display name"],
      "Usage data": ["Pages visited", "Session duration"],
    },
    legalBasis: "Legitimate interests and user consent",
    retention: {
      "Account data": "Until account deletion",
      "Analytics data": "13 months",
    },
    cookies: {
      essential: true,
      analytics: true,
      marketing: false,
    },
    thirdParties: [
      { name: "Vercel", purpose: "Hosting and edge delivery" },
      { name: "Plausible", purpose: "Privacy-friendly analytics" },
    ],
    userRights: ["access", "erasure", "portability", "objection"],
    jurisdictions: ["us", "eu"],
  },
  terms: {
    effectiveDate: "2026-03-13",
    acceptance: {
      methods: ["using the service", "creating an account"],
    },
    eligibility: {
      minimumAge: 13,
    },
    accounts: {
      registrationRequired: true,
      userResponsibleForCredentials: true,
      companyCanTerminate: true,
    },
    prohibitedUses: [
      "Violating any applicable laws or regulations",
      "Attempting to gain unauthorized access to any part of the service",
      "Transmitting malware or malicious code",
    ],
    intellectualProperty: {
      companyOwnsService: true,
      usersMayNotCopy: true,
    },
    disclaimers: {
      serviceProvidedAsIs: true,
      noWarranties: true,
    },
    limitationOfLiability: {
      excludesIndirectDamages: true,
      liabilityCap: "the amount paid by the user in the past 12 months",
    },
    governingLaw: {
      jurisdiction: "Delaware, USA",
    },
    changesPolicy: {
      noticeMethod: "email or prominent notice on the website",
      noticePeriodDays: 30,
    },
  },
  cookie: {
    effectiveDate: "2026-03-13",
    cookies: {
      essential: true,
      analytics: true,
      functional: false,
      marketing: false,
    },
    thirdParties: [
      {
        name: "Plausible",
        purpose: "Privacy-friendly analytics",
        policyUrl: "https://plausible.io/privacy",
      },
    ],
    consentMechanism: {
      hasBanner: true,
      hasPreferencePanel: false,
      canWithdraw: true,
    },
    jurisdictions: ["us", "eu"],
  },
});

Add the plugin to your Vite config

SvelteKit is built on Vite, so the plugin slots in naturally alongside sveltekit():

// vite.config.ts
import { openPolicy } from "@openpolicy/vite";
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    sveltekit(),
    openPolicy({
      formats: ["html"],
      outDir: "src/lib/policies",
    }),
  ],
});

Outputting to src/lib/policies puts the files inside SvelteKit’s $lib alias so you can import them directly in any route.

What gets generated

src/lib/policies/
  privacy-policy.html
  terms-of-service.html
  cookie-policy.html

A single defineConfig() with privacy, terms, and cookie sections compiles all three in one pass.

Render on dedicated routes

Import each HTML file as a raw string using Vite’s ?raw suffix, then render it with Svelte’s {@html} tag:

<!-- src/routes/privacy/+page.svelte -->
<svelte:head>
  <title>Privacy Policy</title>
</svelte:head>

<script lang="ts">
  import policy from "$lib/policies/privacy-policy.html?raw";
</script>

<main>
  {@html policy}
</main>
<!-- src/routes/terms/+page.svelte -->
<svelte:head>
  <title>Terms of Service</title>
</svelte:head>

<script lang="ts">
  import policy from "$lib/policies/terms-of-service.html?raw";
</script>

<main>
  {@html policy}
</main>
<!-- src/routes/cookie/+page.svelte -->
<svelte:head>
  <title>Cookie Policy</title>
</svelte:head>

<script lang="ts">
  import policy from "$lib/policies/cookie-policy.html?raw";
</script>

<main>
  {@html policy}
</main>

Hot-reload in dev

During vite dev, the plugin watches openpolicy.ts for changes and regenerates the affected policy files on every save — no restart needed. The same hot-reload loop you rely on for Svelte components works for your policy documents too.

Why this is better than a static page

  • Type-safe. Every field is checked by TypeScript. You can’t ship a policy with a missing contact email.
  • Structured. Each section is generated from your actual config — no stale placeholder text.
  • Version-controlled. The config lives in your repo. git blame shows you when and why anything changed.
  • Jurisdiction-aware. Set jurisdictions: ["eu"] and GDPR-required sections (right to erasure, data transfers, DPA contact) are included automatically.

The generated HTML includes all required sections for the jurisdictions you specify. You own the config; OpenPolicy handles the legal structure.