Appearance
All settings & options
The single reference for every configurable option in the product.
How settings work
Settings live in one Supabase table, workspace_settings, keyed (workspace_id, module_id) with a settings jsonb column. The engine is core/settings/index.js (settingsManager):
- Schemas are in-memory — at boot,
registerModuleSettingsfolds each manifest'ssettingsSchemainto the manager. - Resolution: schema defaults ← stored overrides.
jsonfields deep-merge (default ← stored) so partial objects inherit default siblings; arrays replace;mergeStrategy: 'replace'opts out. - Unknown keys are rejected on write.
Registered module_id rows: global, 1a-blog-publisher, orbit-pixel, pages, backlinks, health, ai-visibility, agent-autopilot, social, sales, market. No row: gsc (namespaces into 1a-blog-publisher), lead-audit, web, mcp (account-level mcp_tokens table, no workspace state).
Namespaces (core/settings/namespaces.js): profile, publishing, agent, gsc are projections over the ONE frozen '1a-blog-publisher' row — a named subset of its keys, not separate rows. Invariant (boot + npm test): the four namespaces form a total, disjoint cover of the schema.
No reserved enabled toggle anymore: the per-workspace module switch (an enabled boolean injected per schema at registration) was removed 2026-08-14 — every module is available to every workspace, schemas register exactly as declared, and enabled keys still stored in old rows are inert (nothing reads them).
global defaults (core/settings/defaults.js)
| Key | Type | Default | Controls |
|---|---|---|---|
workspaceName | string | 'Default Workspace' | Workspace display name |
timezone | string | 'UTC' | Scheduling + date formatting (cron stagger, cost-cap day boundary) |
language | string | 'en' | Primary UI language |
locale | string | 'en-US' | Date/number formatting |
targetCountries | string | null | Comma-separated target country codes |
The 1a-blog-publisher row (Collections engine + workspace identity)
Set-via legend: O = onboarding wizard, S = Settings UI, R = runtime/system, I = internal/operator.
Namespace profile — identity, audience, lifecycle
| Key | Type | Default | Controls | Via |
|---|---|---|---|---|
companyName | string | null | Brand name (required; used in all prompts + UI) | O/S |
companyDescription | string | null | Operator "what we do" line; fallback when brandIdentity missing | O/S |
industry | string | null | Industry/niche label | O/S |
products | string | null | Product/service list (prompt input) | O/S |
languages | json | null | Ordered ISO 639-1 codes; first = primary. Required at runtime | O/S |
serviceAreas | json | [] | ISO country codes; [0] drives the search market keywords are validated in (the vendor location code) | O/S |
idealClient | string | null | Long-form ICP — canonical audience anchor in every AI prompt; never AI-overwritten | O/S |
visibilityTargets | json | null | Searches the workspace wants visibility for | O/S |
competitors | json | null | Competitor hostnames — filtered from research + rendered HTML | S |
brandVoiceNotesByLocale | json | null | Per-locale free-text voice notes | S |
avgLeadValueCurrency | string | null | ISO 4217 workspace display currency — every money amount renders in it (onboarding Localization slide + Settings → Workspace). avgLeadValue was retired 2026-08-20: value is per conversion goal (pixel_conversion_goals.value_amount, required at setup); the legacy key self-strips from stored rows on read | O/S |
brandIdentity | string | null | AI-generated company identity paragraph (primary locale) | O(AI)/S |
brandIdentityByLocale | json | null | Localized identity paragraphs | O(AI)/S |
brandVoiceByLocale | json | null | Dense per-locale voice reference pasted into the writer prompt; re-extractable | O(AI)/S |
derivedBrandTerms | json | null | { hash, terms[] } — brand/product NAMES the offer judge extracted from the person's own profile text (signal plan step 4), pinned by the territory hash; merged into gsc/brand.js's branded split. Derived, never edited | O(AI) |
lastSiteRefreshAt | string | null | ISO timestamp of last site refresh | R |
experience | string | 'agency' | Which SURFACE this workspace gets: agency = the Signal board (today's product) · content = board hidden, banked ideas as a plan, ops alarms only. Operator-set (internal dashboard → workspace popup → Experience) because it maps to what they pay for. Unknown/absent fails OPEN to agency. Not a cost lever — the pipeline is identical in both modes; the agent level ladder is the cost control. Resolution is mode × viewer in workspace/experience.js (resolveSurfaces), so agency STAFF keep the board inside a content workspace | O |
onboardingStatus/StartedAt/CompletedAt/Error | string | null | /setup orchestration state (running/complete/failed); settings PATCH refused while running | R |
Namespace publishing — production config + CMS credentials
| Key | Type | Default | Controls | Via |
|---|---|---|---|---|
imageDimensions | json | null | Cover-image resize { width?, height?, fit? } | S |
cmsType | string | null | payload/wordpress/webflow/drupal/shopify/wix/custom (required) | O/S |
payloadApiUrl, payloadApiKey | string | null | Payload credentials | O/S |
wordpressApiUrl, wordpressUsername, wordpressAppPassword | string | null | WordPress credentials | O/S |
webflowApiToken, webflowSiteId | string | null | Webflow credentials | O/S |
drupalSiteUrl, drupalClientId, drupalClientSecret, drupalOauthScope | string | null | Drupal OAuth credentials | O/S |
shopifyStore, shopifyClientId, shopifyClientSecret, shopifyAuthorName | string | null | Shopify credentials + author | O/S |
wixApiKey, wixSiteId, wixMemberId | string | null | Wix credentials + author member | O/S |
sanityProjectId, sanityDataset, sanityApiToken | string | null | Sanity credentials (Editor token; dataset usually production) | O/S |
customEndpointBaseUrl, customEndpointAuthHeader ('Authorization'), customEndpointAuthSecret, customEndpointBodyFormat ('markdown'), customEndpointRoutes | string/json | null | Custom-endpoint config; {id} substituted in routes | O/S |
The publishing target (base URLs, collection, field/taxonomy mapping) lives on workspace_collections, not here.
Namespace gsc — Search Console
| Key | Type | Default | Controls | Via |
|---|---|---|---|---|
gscSiteUrl | string | null | GSC property URL (required) | O/S |
gscServiceAccountJson | string | null | Legacy per-workspace service account (new workspaces use the shared account) | S |
gscVerified | boolean | false | Connection tested OK | R |
cronGscSync | string | '0 5 * * *' | Daily GSC sync schedule | S/I |
Namespace agent
| Key | Type | Default | Controls | Via |
|---|---|---|---|---|
agent | json | { paused: false } | agent.paused = the workspace's ONE off-switch: every scheduled job for the workspace — agent lanes AND module crons (syncs, audits, refresh, rollups) — is skipped by the scheduler chokepoint (workspace/readiness.js#automationGate, read fresh at every fire), so no AI spend; chat always works. Written by the app's "Pause agent" toggle (POST /api/agent/:ws/status) and the internal dashboard's level ladder (off). agent.dailyCostCapUsd (default $15/workspace/day via core/spend; enforced inside every paid provider client, so it covers LLM, search-data, research and embedding spend on every path — chat, crons, routes and MCP alike, not just agent dispatch); agent.thresholds overrides classification cuts. Deep-merged on partial write | S (pause) / I (cap) |
Empty-schema module rows
Rows that exist by the shell-module convention (a module that owns a row declares settingsSchema: {}); nothing is configurable on them. The per-module paused "operator hatch" these rows were said to carry was never in any schema — settingsManager.set rejected the key — and was removed 2026-08-16; the ONE per-workspace pause is agent.paused above (the injected enabled switch was removed 2026-08-14 — every module runs everywhere):
| module_id | Notes |
|---|---|
orbit-pixel | Frozen id |
pages, backlinks, health, ai-visibility | Weekly crons, fixed schedules |
agent-autopilot | The agent's cron seam; the agent's own knobs stay in the agent namespace |
social, sales, market | Shells (501) |