Skip to content

Getting started

Louise is a library, not a scaffold — you add it to a Cloudflare Workers app (Astro, Hono, or a bare Worker) and wire the pieces you need.

Terminal window
npm install louise-toolkit

Louise’s heavier dependencies are optional peers, so a route that only uses louise-toolkit/errors pulls in nothing extra. Install the peers for the exports you actually use:

If you import… Also install
louise-toolkit/db, /content drizzle-orm
louise-toolkit/client solid-js prosekit @prosekit/pm
/email, /queues, /errors, /commerce (no peers)
Terminal window
npm install drizzle-orm # for /db and /content
npm install solid-js prosekit @prosekit/pm # for the /client editor

Every Louise primitive is dependency-injected: it takes your Cloudflare binding as an argument and returns a plain result. Louise never reaches for cloudflare:workers, so the same functions run in astro dev, in production, and in a unit test with a fake binding.

// A bare Cloudflare Worker endpoint.
import { db } from "louise-toolkit/db";
import { sendEmail } from "louise-toolkit/email";
export default {
async fetch(_req: Request, env: Env) {
const orm = db(env.DB); // Drizzle over your D1 binding — your schema, not Louise's
await sendEmail(env.EMAIL, {
from: "studio@example.com",
to: "you@example.com",
subject: "Hello from the edge",
html: "<p>Sent V8-natively.</p>",
});
return new Response("ok");
},
};

Bindings arrive on env, but plain env vars and secrets (an API token, a provider mode) are easy to fat-finger. On Astro, declare a schema and let astro:env validate it and generate a typed accessor — the values still come from wrangler.jsonc vars / wrangler secret at runtime, this is just the schema on top:

astro.config.mjs
import { defineConfig, envField } from "astro/config";
export default defineConfig({
env: {
schema: {
SQUARE_ENV: envField.enum({
context: "server",
access: "public",
values: ["sandbox", "production"],
default: "sandbox",
}),
SQUARE_TOKEN: envField.string({ context: "server", access: "secret", optional: true }),
},
},
});

Import the validated values from astro:env/server and pass them into the Louise primitive — same dependency-injection model, just type-checked first:

import { SQUARE_ENV, SQUARE_TOKEN } from "astro:env/server";
// …hand SQUARE_ENV / SQUARE_TOKEN to the Square client — see the Commerce guide.

astro:env covers env vars and secrets; D1/R2/Queues bindings still arrive on env. The live sandbox wires its Square checkout exactly this way.

Inline editing is progressive enhancement. Server-render the page normally; in edit mode, mark the editable regions and mount the client.

1. Mark the region. The marker is "<collection>:<key>:<field>".

<h1 data-louise-field="settings:1:heroHeadline">Your Studio</h1>

2. Mount the client. It self-gates — if the page has no markers (i.e. it wasn’t rendered in edit mode) mountLouise does nothing, so you can lazy-import it safely.

import { mountLouise } from "louise-toolkit/client";
mountLouise();

3. Accept the save. The client sends one PATCH per changed field to your save endpoint. Allowlist every writable field — a forged request must not be able to touch an unlisted column.

// POST /api/louise/save (your route — you own auth + the allowlist)
const EDITABLE = new Set(["heroHeadline", "heroIntro"]);

See Inline editing for the full field lifecycle, and Rich text for prose fields.

Louise is developed in the louise workspace with Vite+:

Terminal window
curl -fsSL https://vite.plus | bash # once
vp install
vp pack # build the library → dist/
vp test # Vitest
vp check # Oxlint + Oxfmt + type-check