Primate LogoPrimate

Configuration

Primate works out of the box with zero configuration. In some cases, you may wish to change the defaults. The most common use case is activating additional modules.

Configuration files are located in config. Anything you configure is merged into the defaults.

app.ts

TypeScript JavaScriptconfig/app.tsconfig/app.js
import config from "primate/config";

export default config({
  /* options */
});

config/app.ts exports the application facade for your app. For using that value at runtime — including app.config(), app.env(), app.view(), and app.root — see the Application page.

App options

Option Default Description
build.external [] packages excluded from the server bundle
client.partitions undefined isolated client bundle partitions
client.routes undefined route-prefix to bundle-partition mapping
db.migrations undefined database migration configuration
entrypoints {} named client entrypoints
http.csp {} content security policy
http.headers {} default HTTP response headers
http.host "localhost" server host
http.port 6161 server port
http.timeout undefined in-flight HTTP idle timeout
http.ssl.cert undefined path to SSL certificate
http.ssl.key undefined path to SSL private key
http.static.root "/" web path of static assets
loaders {} esbuild file loaders by extension
env.schema undefined schema for typed environment variables
modules [] extension modules
i18n.defaultLocale undefined default locale
i18n.locales [] list of locales
i18n.currency "USD" active currency
i18n.persist "cookie" locale persistence mode

build.external

Packages and import paths to exclude from the production server bundle. At runtime they are loaded from node_modules instead. Use this for dependencies that load adjacent WebAssembly modules, native addons, or data files that would otherwise be separated from the package by bundling.

import config from "primate/config";

export default config({
  build: {
    external: ["satori", "@resvg/resvg-js"],
  },
});

External dependencies must be installed wherever the production server runs. Development already keeps packages external by default.

db.migrations

Configuration for Primate's opt-in migration system.

import config from "primate/config";
import db from "@/config/db";

export default config({
  db: {
    migrations: {
      table: "migration",
      db,
    },
  },
});

Apply migrations explicitly with primate migrate:apply during development or build/migrate.js apply before starting a production server. Automatic startup application is not supported. See the Stores page for the complete migration workflow and 0.41 upgrade guidance.

client.partitions

Client partitions isolate browser code and assets by URL route prefix. Each partition is an independent build branch, compiled into its own output file tree. Shared dependencies are duplicated rather than emitted as chunks shared across an access boundary.

import config from "primate/config";

export default config({
  client: {
    partitions: {
      public: "/",
      admin: "/admin/app/",
    },
    routes: {
      "/": "public",
      "/admin": "admin",
    },
  },
});

client.partitions maps partition names to asset URL prefixes. client.routes maps URL path prefixes to those names; values that are not keys in client.partitions are rejected. The longest matching route prefix wins, and prefixes match whole path segments: /admin includes /admin/users but not /administrator. A / assignment is required as the default and is checked during startup. Mappings to unknown partitions, duplicate normalized mappings, and duplicate asset prefixes are also rejected during startup.

In 0.42, client.partitions replaces the 0.41 client.groups key. Rename the key when upgrading; the old key is rejected with a migration diagnostic rather than treated as a virtual route group. client.routes is unchanged.

Each partition receives its own bootstrap, route-page registry, CSS, dynamic imports, and file-loader assets beneath its configured asset prefix. Top-level files in views are shared deliberately and are duplicated into every partition; put partition-private pages and layouts beside their handlers in routes.

Imported fonts, images, and other file-loader assets are likewise emitted under each partition's prefix, even when their contents are identical. Distinct URLs are part of the isolation guarantee. Assets that are deliberately public and shared across every authorization boundary can instead live in static.

Navigation within a partition keeps normal client routing. Navigation across partitions stays in the same document: Primate first loads the destination assets and inactive styles and waits for its frontend to become ready. It then disposes the active frontend instance, replaces its partition styles, and mounts the destination. Links, form redirects, and browser history all use this transition. If a destination asset cannot be loaded or is rejected by the proxy, the active app, URL, and styles remain in place. Static module code may remain in the browser's module cache after disposal.

This allows a reverse proxy to apply the same authentication rule to both the HTML route and its complete client file tree. For example, protect /admin and /admin/app/, with the more specific asset rule evaluated before a catch-all public rule. Production filenames are content-hashed and can be cached as immutable assets; HTML should not be cached with the same policy.

Client partitions provide asset isolation, not application authorization. Continue to authorize every protected request in route hooks or handlers. Source maps are not emitted by Primate's client production build.

entrypoints

Named client entrypoints bundled from the client directory and injected into templates/app.html as placeholders.

import config from "primate/config";

export default config({
  entrypoints: {
    css: "master.css",
    colorscheme: "colorscheme.ts",
  },
});

Each key becomes a placeholder in your HTML template. For a key css, add %css% wherever you want the asset injected:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    %css% %head%
  </head>
  <body>
    %body%
  </body>
</html>

Entrypoint keys follow the placeholder naming rules: they start with a letter or underscore and may contain letters, numbers, underscores, and hyphens.

Any file type supported by your active frontend modules works as an entrypoint — if you have @primate/svelte configured, you can point an entrypoint at a .svelte file and it will be bundled and injected like any other asset.

The names app, head, and body are reserved and cannot be used as entrypoint keys.

http.csp

The Content Security Policy (CSP) to use.

Example of a restrictive policy.

{
// all content must come from own origin, excluding subdomains
"default-src": ["'self'"],
// styles must come from own origin, excluding subdomains
"style-src": ["'self'"],
// disallow <object>, <embed> and <applet> elements
"object-src": ["'none'"],
// disallow embedding
"frame-ancestors": ["'none'"],
// all form submissions must be to own origin
"form-action": ["'self'"],
// allow only own origin in <base>
"base-uri": ["'self'"],
}

http.headers

HTTP headers to use when generating requests using the view handler.

http.host

The HTTP host to use. This value is directly passed to the runtime.

http.port

The HTTP port to use. This value is directly passed to the runtime.

http.timeout

The maximum time, in whole milliseconds, that an in-flight request or response may remain inactive in production. Accepted values range from 0 through 255000; 0 disables the timeout. If omitted, the HTTP runtime's default is preserved. This is an inactivity timeout, not an overall request deadline or a completed connection's keep-alive timeout.

Development always disables this timeout so debugging and live reload are not interrupted, regardless of the configured value. Bun rounds positive values up to whole seconds. Deno does not expose an HTTP idle-timeout setting, so the option is validated but Deno's native default is preserved.

For server-sent events and other long-lived responses, either disable the timeout or send heartbeats more frequently than it. Reverse proxies and load balancers have independent idle timeouts; configure heartbeat cadence below the shortest timeout in the complete request path.

http.ssl.cert

Path to SSL certificate. If this property and http.ssl.key are set and point to a valid key/certificate pair, Primate uses https instead of http.

http.ssl.key

Path to SSL key. If http.ssl.cert and this property are set and point to a valid key/certificate pair, Primate uses https instead of http.

http.static.root

The path at which to serve static assets (those located in the static directory). Static assets take precedence over routes. This option allows you to have all static assets served at a subpath, like /public.

loaders

A map of file extensions to esbuild loaders. Currently only "file" is supported, which copies the asset into the build output and replaces the import with its URL — useful for fonts and other binary assets referenced from CSS.

import config from "primate/config";

export default config({
  loaders: {
    ".woff2": "file",
  },
});

env.schema

A Pema object schema used to validate and type environment variables exposed through the application facade.

import config from "primate/config";
import p from "pema";

export default config({
  env: {
    schema: p({
      API_TOKEN: p.string,
      PORT: p.u16,
    }),
  },
});

With env.schema configured, app.env(key) validates values when the app starts serving and becomes type-aware. See the Application page for usage.

modules

Additional modules to load at runtime.

i18n.defaultLocale

The default locale to use, must be one from the locales list.

i18n.locales

List of locales to use, must have at least one locale.

i18n.currency

Currency to use in localization.

i18n.persist

Locale persistence mode.

Reference

import type { Module } from "@primate/core";
import type { FileRef } from "@rcompat/fs";

interface Config {
  http?: {
    csp?: Record<string, string>;
    headers?: Record<string, string>;
    host?: string; // "localhost"
    port?: number; // 6161
    ssl?: {
      cert?: FileRef | string;
      key?: FileRef | string;
    };
    static?: {
      root?: string; // "/"
    };
  };
  modules?: Module[];
}

session.ts

TypeScript JavaScriptconfig/session.tsconfig/session.js
import session from "primate/session";

export default session/*<SessionShape>*/({
  /* options */
});

Session options

Option Default Description
cookie.httpOnly true mark cookie as HttpOnly
cookie.maxAge none persist cookie for this many seconds
cookie.name "session_id" name of the session cookie
cookie.path "/" path for which the cookie is valid
cookie.sameSite "Lax" SameSite cookie policy
cookie.secure app TLS setting mark cookie as Secure
store default in-memory store store managing sessions

Whether the session cookie should be marked HttpOnly and hidden from client-side JavaScript.

The number of seconds for which the browser should retain the session cookie. When omitted, it is removed when the browser session ends.

The name of the session cookie.

The session cookie path (paths on which the cookie is loaded).

The level of security to use in sending the session cookies when browsing between websites.

Whether the session cookie should be marked Secure. By default, this follows the app's TLS setting. Set it explicitly when TLS terminates at a trusted reverse proxy.

store

The store used to validate and persist sessions. By default, Primate uses an in-memory store that accepts JSON-compatible object data and resets when the app restarts. A custom store validates data against its declared fields.

db/*.ts

TypeScript JavaScriptconfig/db.tsconfig/db.js
import sqlite from "@primate/sqlite";

export default sqlite();