Skip to content

Configuration

All Glint configuration lives in Cloudflare KV and is managed through the web UI or environment variables. There is no .env file — the init wizard writes values directly into KV on first run, and subsequent changes go through the Settings page.


App Config

Configured during the initialization wizard or in Settings → App Config (owner only after setup).

KeyDescription
prism_base_urlBase URL of your Prism instance, e.g. https://id.example.com. No trailing slash.
prism_client_idOAuth Client ID from your Prism app registration.
prism_client_secretOAuth client secret. Only required for confidential clients. Leave empty when using PKCE.
prism_redirect_uriThe callback URL registered in your Prism app, e.g. https://glint.example.com/callback. Must match exactly.
use_pkcetrue for public (PKCE) clients; false for confidential (secret-based) clients.
allowed_team_idRestrict sign-in to members of a specific Prism team. Leave empty to allow any authenticated Prism user.
owner_team_idRestrict who can view/change the app config and register permissions to owners of this Prism team. Leave empty to fall back to allowed_team_id.
session_ttlSession lifetime in seconds. 0 lets Glint derive the lifetime from the Prism access-token expiry.
welcome_messageMessage shown once to every user right after they sign in (in a dialog). Leave empty to disable.
action_bar_defaultsArray of action keys shown in the per-todo quick-action bar for all users by default. See Action Bar below.
user_profile_cache_ttlHow long (in seconds) resolved user names/avatars are cached in KV. 0 disables caching. Default: 86400 (1 day).

All values are stored under the KV key config:app as a JSON object.

WARNING

prism_client_secret is stored in KV, not as an environment variable. Make sure your KV namespace is not publicly readable. In Cloudflare's security model, KV bindings are only accessible to your Worker — they are never exposed via any public API.

PKCE vs Confidential Client

PKCE (Public)Confidential
use_pkcetruefalse
prism_client_secretemptyrequired
Token exchangeCode + PKCE verifierCode + client secret
Security modelVerifier stays in WorkerSecret stays in Worker, never in browser
Best forMost deploymentsHigh-security private deployments

Both modes are equally secure when deployed on Cloudflare Workers — the PKCE verifier and the client secret are both handled server-side and never exposed to the browser.

allowed_team_id

When set, Glint enforces team membership at login time only. Users not in the specified team(s) cannot sign in and see a "Not Authorized" page.

⚠️ Important: This is an authentication boundary, not a visibility filter. Once a user successfully signs in, they can access all teams they are a member of using the workspace switcher in the sidebar — not just the allowed team. allowed_team_id only controls who can enter the system.

Multiple team IDs can be specified, separated by commas, semicolons, or spaces:

team_a, team_b, team_c

TIP

allowed_team_id set via the ALLOWED_TEAM_ID environment variable (in wrangler.jsonc or the Cloudflare dashboard) takes precedence over the KV-stored value and cannot be modified from the UI. The Settings page shows a "locked" indicator when the env var is active.

owner_team_id

allowed_team_id controls who can log in, but by itself it also decides who administers the instance: any owner of an allowed team can view and change the app config and register permissions. When you share a single Glint instance across several teams, that is unsafe — an owner of any allowed team could reconfigure the whole instance.

owner_team_id separates these two concerns. When set, only owners of the specified team(s) can:

  • view the app config (GET /api/init/config)
  • change the app config (PUT /api/init/config)
  • register cross-app permissions (POST /api/init/register-permissions)

When owner_team_id is empty, Glint falls back to allowed_team_id for the admin gate (the previous behaviour), so existing deployments are unaffected.

Like allowed_team_id, multiple team IDs can be specified, separated by commas, semicolons, or spaces. This value is stored in KV and edited from Settings → App Config; there is no environment-variable override.

WARNING

owner_team_id is an administration boundary, not a login boundary. It does not affect who can sign in — use allowed_team_id for that.


Team Settings

Per-team branding and behaviour, configurable in Settings → Branding (owner or users with manage_settings).

KeyDescription
site_nameDisplay name shown in the sidebar header, browser tab title, and login page.
site_logo_urlURL to a logo image. When set, replaces the text title in the sidebar. Must be publicly accessible.
accent_colorCSS color value (hex, rgb(), etc.) applied as the primary theme color. Leave empty for default.
default_set_nameName given to the auto-created set when a team is first accessed. Default: "Not Grouped".
allow_member_create_setsWhen true, members can create sets without the manage_sets permission. Default: false.
default_timezoneIANA timezone (e.g. UTC, Asia/Shanghai) used by set auto-renew when a set has no timezone of its own. Default: "UTC".

Team settings are stored under the KV key team_settings:{teamId}.


Action Bar Defaults

The action_bar_defaults field in App Config sets which quick-action buttons appear on each todo row for all users by default. It is configurable in Settings → App Config (owner only).

Valid action keys:

KeyAction
add_beforeInsert a new todo above this one
add_afterInsert a new todo below this one
add_subtodoAdd a nested sub-todo
editEdit the todo title
completeToggle completion
assign_selfAssign / unassign the todo to yourself
assignOpen the assignee picker (assign to team members)
commentOpen the comments panel
moveMove the todo to another list
deleteDelete the todo

Built-in site default (used when not configured): ["add_after", "edit", "complete", "delete"].

Users can override the site default with their own preference (stored in localStorage). Workspace owners can set a workspace-level default that applies to all team members. The priority chain is: user preference → workspace default → site default.


Cloudflare Bindings

Your wrangler.jsonc must declare three bindings:

D1 Database (DB)

Stores all persistent data: todos, sets, comments, and permission overrides.

jsonc
{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "glint-db",
      "database_id": "YOUR_DATABASE_ID"
    }
  ]
}

The schema is managed via numbered migration files in migrations/. Run them with:

bash
# Local
wrangler d1 migrations apply glint-db --local

# Production
wrangler d1 migrations apply glint-db

Migrations use CREATE TABLE IF NOT EXISTS and are idempotent — re-running is safe.

KV Namespace (KV)

Used for sessions, configuration, team settings, and user-teams caching.

jsonc
{
  "kv_namespaces": [
    {
      "binding": "KV",
      "id": "YOUR_KV_ID",
      "preview_id": "YOUR_KV_PREVIEW_ID"
    }
  ]
}

preview_id is required for wrangler dev (local development). Create one with:

bash
wrangler kv namespace create KV --preview

Durable Object (TODO_SYNC)

Powers realtime WebSocket sync. No manual creation is needed — Cloudflare provisions the namespace automatically on first deploy.

jsonc
{
  "durable_objects": {
    "bindings": [
      { "name": "TODO_SYNC", "class_name": "TodoSync" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_classes": ["TodoSync"] }
  ]
}

The migrations array registers the class with Cloudflare's migration system. This only needs to be present once; subsequent deploys detect that the class is already registered and skip re-provisioning.

TIP

Durable Objects require a Workers Paid plan. On the free tier the binding will fail to resolve and realtime sync will be unavailable. All other functionality is unaffected.

See Realtime Sync for full details on how the WebSocket layer works.


Scheduled Jobs (Cron)

Glint registers a Cloudflare Cron Trigger that runs every 15 minutes:

jsonc
{
  "triggers": {
    "crons": ["*/15 * * * *"]
  }
}

On each run the worker:

  1. Processes set auto-renew — resets completed todos to incomplete for any set whose renew time has passed for the current local day. See Auto-Renew.
  2. Refreshes the site service token — keeps the long-lived Prism service token used for cross-app profile resolution fresh.

No additional setup is required; Cloudflare invokes the schedule automatically once the worker is deployed.


KV Key Reference

Key patternContentsTTL
init:configured"1" once setup is completeNone (permanent)
config:appJSON object with all app config fieldsNone (permanent)
team_settings:{teamId}JSON object with branding/settingsNone (permanent)
session:{sessionId}JSON session data including access tokenSet to token expiry
user-teams:{userId}JSON array of TeamInfo objects10 minutes
user_profile:{userId}Cached display name / username / avatar from Prismuser_profile_cache_ttl (default 1 day)
user_settings:{userId}JSON of personal preferencesNone (permanent)
site:service_tokenLong-lived Prism service token for profile resolutionRefreshed by cron

The user-teams cache is populated at login and used by cross-app middleware to resolve team membership without a live Prism API call. It expires after 10 minutes; if absent, the middleware falls back to a live lookup.


Environment Variables

Only one optional environment variable exists:

VariablePurpose
ALLOWED_TEAM_IDOverrides config:app.allowed_team_id. Cannot be changed from the UI when set.

Set it in wrangler.jsonc:

jsonc
{
  "vars": {
    "ALLOWED_TEAM_ID": "your-prism-team-uuid"
  }
}

Or in the Cloudflare dashboard under Workers → Your Worker → Settings → Variables.


Resetting Configuration

To wipe the init state and re-run the wizard (local development):

bash
wrangler kv key delete --local --binding KV "init:configured"

This lets you re-run POST /api/init/setup. Tables are recreated with IF NOT EXISTS, so existing data is preserved.

To reset on production, delete init:configured from your production KV namespace:

bash
wrangler kv key delete --binding KV "init:configured"

WARNING

Re-initializing in production does not wipe your database. It only allows the config to be overwritten. Todos, sets, and comments remain intact.