Config keys

7 min read

platformkit.config.js in one table, with defaults.

Key Type Default Notes
name string — required
dev.projectId string pk_<site>_local complete, stable local Supabase project ID; defaults from the site folder/subdomain with hyphens replaced by underscores. Lowercase letters, digits, underscores and hyphens only; runtime configs use it without rewriting the committed config
dev.projectName string site directory name optional display name for the workspace's local development directory card; blank or unset falls back to the site folder name and never changes SEO metadata
theme package name — required; its manifest is <package>/theme.js
build.steps string[] [] the site's own build steps, each a module exporting one record ({ key, title, phase, after, module, when, inputs, needs, run }), run in their phase among the kit's — see the build steps
deployment.backend boolean true is there a Supabase at all. false is the static shape: the deploy asks for no database, no relay and no push keys, and builds the site and nothing else. Declared, never inferred from an absent key, so switching a backend-needing module on fails at that commit — see The three deployment shapes
deployment.content 'files' \ | 'rows' 'files' where content lives. CONTENT_STORE=cloud overrides it for one run; backend sites with admin.models must use rows for the production CMS
deployment.repo boolean true is git in the publish cycle — does publishing commit and rebuild. An administrator may set it at runtime, since a cloud-only deployment has no checkout to edit
modules.<m> boolean true the kinds articles, books, wiki, docs; the features store, print, memberships, sessions, coaching, support, social-publishing, funnels, emails, forms, spaces, space-encryption, network, events, bots, copilot, models, instrument, recommendations, translation, tts, enrichment, push, api
store.suppliers path — a module exporting this site’s own suppliers, registered beside the kit’s
store.kinds path — a module exporting this site’s own kinds of thing to sell
looks.<name>.css path — one per look; every look's css is imported
looks.<name>.skins path — a module mapping section names to skin components
defaultLook string the first look the look when site.md names none
languages string[] ['en'] the catalogs under content/strings/<lang>/
overrides.<Component> path — a .vue file replacing the theme's component of that name
nav.links[] { key \ | label, href, icon?, prefix?, see?, cta? } the theme's the header's links as code — the middle of three layers (the theme's own by rule, this, then a nav in content/site.md an administrator edits); the most specific that exists is the whole list, never a merge. key names a strings key under nav; label is a site's own words. see is who sees it — see the site record
nav.cta { href, key, hrefIn?, keyIn? } the quiz when the instrument is on, else none the header's button; In for a signed-in person
routes[] { path, component, prefix?, prerender? } [] the site's own pages
app.tabs path — the /app shell's tabs and answered()
landing.sections, landing.schemas, landing.stages paths — the site's section components, their Node-safe schemas (SECTION_SCHEMAS, and STAGE_SCHEMAS for the hero's stages), and its stages for the hero's visual (a module exporting STAGES)
analytics.panels[] { key, component } [] dashboard panels
analytics.events[] { key, label, goal?, value? } [] the site's own events, by label; a goal on the dashboard; a value property
api.space string the site's slug the space id of the content API
api.public boolean true Delivery readable with no key
network.federate boolean false whether the social network joins the wider atproto network: a member may then join it by their own act, and the deploy asks for NETWORK_PLC_ROTATION_KEY. Off, the network is closed to the site's own people
network.apps boolean false whether other apps — Bluesky's, any atproto client — may sign in with an app password to read and post. Off (the default), the atproto surface refuses every sign-in and every read an app would make, answering only the site's own description and, where it federates, what relays need; the account page offers no app passwords. The site owner turns it on deliberately
network.access { read, write, join, plans } none who takes part in the network, where the site sells it or limits it. read and write are two grants, each written exactly as a space's fill (a mapping or a list of alternatives: tier, bought, entitlement, role, booking), checked at assembly and written into app_settings by the data script; whoever may write may read; the staff always take part. join is the offer the join card sells (a sku); plans, an address on the site (/pricing), adds View more plans under its button. Absent, everyone signed in reads and writes
community.layout chat \ | social chat how the community is laid out — the owner's choice, never the features'. chat is the spaces area, the network's Feed and Notifications in its column (with search, Who to follow and pins beside it) where both modules are on; social is the social-network layout, every community feature in its middle column. What anybody may read or do is the same in both
community.forYou boolean false Explore as For You — an Instagram-style grid of the photo and video posts the reader may read, ranked by what people did with them and who the reader is close to, never more than two from one person in any six; with it off there is no Explore place
community.path path /feed the community's base address — one segment, not one the site already answers on (for example, /community). Every community address sits under it, and an old /feed address forwards to the same place under it
community.pinning boolean false members choose which of their spaces reach Home (Pin to Home in a space's header). Off, every space a member is in reaches their Home and there is no pin
community.places list [] the site's own places in the social layout's menu, after Messages and above Notifications: key (a slug the community does not already use), word (a key of ## rail in the spaces strings), icon, component (the page), wide (the column and the sidebar's room), shows (signed-in · everyone), access (a fill rule: who enters it, as community.access), claims (old addresses that forward to /feed/<key>, keeping what follows them; !/x keeps one out; the longest claim wins). A clashing key or a claim another place holds refuses the build. A place claiming /app moves the app's pages there (APP_BASE); /app/call and /app/voice stay unless claimed
community.access { events, messages, notifications } none who enters each of those places, each a rule written exactly as a space's fill, checked at assembly and written into app_settings (community_access) by the data script; the database refuses what a rule does not admit, the staff always enter, and a reader outside one sees the place locked in the menu and the join card in it. A place with no rule is open to anyone signed in. Home is network.access; a registered place (community.places) takes access on its own entry. Any other key refuses the build
community.hide list [] what the site does not use, taken out of view only — nothing stored or allowed changes: following (one Home feed; no tabs, Who to follow, follow buttons or counts; follows kept), saved, drafts, tickets (their rows leave the navigation). A name it does not know refuses the build
network.appview url https://api.bsky.app the app view a joined member reads the world outside through
network.relay url https://bsky.network the relay asked to crawl the site
analytics.sources[] { key, name, hosts } [] other sites reporting in through /analytics.js: the key (12 characters at least), the name (lower-case, hyphens), the allowed hosts
cloud.repo owner/name · false — cloud mode only: where a publish pushes. false declares that the repository is out of the cycle — nothing is pushed, the export only writes rows out to files, and Publishing asks for no repository. Absent means undecided, and the admin asks for one
cloud.branch string main the branch a publish commits to; refused beside repo: false
instrument.module path the contract's defaults the component /app mounts and the vocabulary
recommendations.module path the contract's defaults signals — what the site knows of this person ({ has, text, themeTags, prescribed }), merged with what the person's own history says rather than replacing it; readerSignals and topicByKey for the ad picker; and invites — whether the site can earn recommendations for somebody who has none, which decides whether Recommended For You is offered while empty
spaces.member path no panels a module exporting panels — what this site puts on the private half of a member's card in a space, each { key, title, component }, the title a word or a function of { mine }
spaces.deskSources path no sources a module exporting sources, the site-registered staff Desk adapters. Social conversations appear only when a site opts in here and both spaces and social-publishing are on; otherwise Social Admin remains the fallback
avatars.style string patchwork which of the kit's three DiceBear styles (patchwork, big-smile, personas) somebody with no picture of their own is drawn from. The default is a pattern and not a face, on purpose: see Spaces
avatars.options object scale 130, DiceBear's six pastels passed straight to the collection; setting it replaces the kit's framing outright
coaching.where app \ | spaces app where coaching lives — the chat and the booking together: the results sheet's Chat and Coaching tabs, or one place in the community with Chat and Sessions. The same conversation, the same booker and the same policies either way; with spaces the header's chat button, /app/chat and a member's Chat and Coaching tabs are not drawn (somebody who gives sessions keeps Coaching, and has the coaching place too). coaching.chat is its former name and is read the same
spaces.nav icon \ | word decided how the community shows in the site's header — the two-people glyph among the other icons, or its name among the links. Unset, it follows who the link is for: a word while it is a way in (there is a room a visitor may discover), the icon once it is only offered to people already inside, where it is a member's affordance and carries the badge. The name itself is nav.spaces in the strings ("Community", "Forum", "The Guild"), never here; either way the button carries one number, everything waiting
spaces.link always \ | members always who is offered the way into the community. always shows it to everyone when the site declares a space anyone may discover — the surface's own join flow does the rest; members keeps it to somebody already in a room. A site whose rooms are all private behaves as members whatever this says, because there is nothing for a visitor to walk into
admin.sections path — a module exporting sections
admin.models path — a module exporting models: the site's own records in the admin's registry — a grammar, an editor, checks, actions, a hook — see The admin as a host
collections path — a module exporting velite collections (and prepare)
enrichment.briefs path the kit's the six stage prompts
data.schemas path ./db/schemas the site's declared schema files
data.rows path ./db/data.sql the site's idempotent rows
data.functions path ./db/functions the site's edge functions
data.seed path ./db/seed.sql the site's seed rows
data.ports number 54320 the local stack's port base (+1 the API, +2 the database, +3 Studio…)
data.migrations — — refused: there are no migrations
build.tests[] paths [] run by platformkit test after the kit's