Config keys
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 |