[{"data":1,"prerenderedAt":1504},["ShallowReactive",2],{"navigation:en":3,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables:en":311},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Nuxfire Docs","\u002Fdocs","docs\u002F0.index",[9,11,152],{"title":5,"path":6,"stem":7,"icon":10},"i-lucide-layout-grid",{"title":12,"path":13,"stem":14,"children":15,"icon":151},"SaaS Starter Kit","\u002Fdocs\u002Fsaas-starter-kit","docs\u002F1.saas-starter-kit\u002F0.index",[16,19,63,129],{"title":17,"path":13,"stem":14,"icon":18},"What is the SaaS Starter Kit?","i-lucide-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-lucide-play",true,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started","docs\u002F1.saas-starter-kit\u002F1.getting-started",[26,30,34,38,42,46,50,54,58],{"title":27,"path":28,"stem":29},"Quick Start","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fquick-start","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F0.quick-start",{"title":31,"path":32,"stem":33},"Installation","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Finstallation","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F1.installation",{"title":35,"path":36,"stem":37},"Cloudflare Access","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fcloudflare-access","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F2.cloudflare-access",{"title":39,"path":40,"stem":41},"Configuration","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fconfiguration","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F3.configuration",{"title":43,"path":44,"stem":45},"Secrets","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fsecrets","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F4.secrets",{"title":47,"path":48,"stem":49},"Environment Variables","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables",{"title":51,"path":52,"stem":53},"Deploy","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fdeploy","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F6.deploy",{"title":55,"path":56,"stem":57},"Local Development","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Flocal-development","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F7.local-development",{"title":59,"path":60,"stem":61},"Project Structure","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fproject-structure","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F8.project-structure",false,{"title":64,"icon":65,"hide":22,"path":66,"stem":67,"children":68,"page":62},"Features","i-lucide-box","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures","docs\u002F1.saas-starter-kit\u002F2.features",[69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":70,"path":71,"stem":72},"Authentication","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fauth","docs\u002F1.saas-starter-kit\u002F2.features\u002F1.auth",{"title":74,"path":75,"stem":76},"Rate Limiting","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frate-limiting","docs\u002F1.saas-starter-kit\u002F2.features\u002F10.rate-limiting",{"title":78,"path":79,"stem":80},"WebSockets","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fwebsockets","docs\u002F1.saas-starter-kit\u002F2.features\u002F11.websockets",{"title":82,"path":83,"stem":84},"Blog","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fblog","docs\u002F1.saas-starter-kit\u002F2.features\u002F14.blog",{"title":86,"path":87,"stem":88},"Documentation","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdocumentation","docs\u002F1.saas-starter-kit\u002F2.features\u002F15.documentation",{"title":90,"path":91,"stem":92},"Analytics","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fanalytics","docs\u002F1.saas-starter-kit\u002F2.features\u002F16.analytics",{"title":94,"path":95,"stem":96},"Platform Admin Console","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fplatform-admin","docs\u002F1.saas-starter-kit\u002F2.features\u002F17.platform-admin",{"title":98,"path":99,"stem":100},"Database","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdatabase","docs\u002F1.saas-starter-kit\u002F2.features\u002F2.database",{"title":102,"path":103,"stem":104},"Payments & Billing","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments","docs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments",{"title":106,"path":107,"stem":108},"Emails","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Femails","docs\u002F1.saas-starter-kit\u002F2.features\u002F4.emails",{"title":110,"path":111,"stem":112},"Notifications","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications","docs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications",{"title":114,"path":115,"stem":116},"Role-Based Access Control","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frbac","docs\u002F1.saas-starter-kit\u002F2.features\u002F6.rbac",{"title":118,"path":119,"stem":120},"Async Jobs","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fasync-jobs","docs\u002F1.saas-starter-kit\u002F2.features\u002F7.async-jobs",{"title":122,"path":123,"stem":124},"Storage","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fstorage","docs\u002F1.saas-starter-kit\u002F2.features\u002F8.storage",{"title":126,"path":127,"stem":128},"Internationalization","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Finternationalization","docs\u002F1.saas-starter-kit\u002F2.features\u002F9.internationalization",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":62},"Concepts","i-lucide-graduation-cap","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts","docs\u002F1.saas-starter-kit\u002F3.concepts",[135,139,143,147],{"title":136,"path":137,"stem":138},"Technology Stack","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Ftech-stack","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F0.tech-stack",{"title":140,"path":141,"stem":142},"SST.dev","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fsst","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F1.sst",{"title":144,"path":145,"stem":146},"NuxtHub","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fnuxt-hub","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F2.nuxt-hub",{"title":148,"path":149,"stem":150},"Design System & Branding","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fdesign-system","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F3.design-system","i-lucide-zap",{"title":153,"path":154,"stem":155,"children":156,"icon":159},"Fireskills","\u002Fdocs\u002Ffireskills","docs\u002F2.fireskills\u002F0.index",[157,160,219,249,275,293],{"title":158,"path":154,"stem":155,"icon":159},"What is Fireskills?","i-lucide-flame",{"title":20,"icon":21,"hide":22,"path":161,"stem":162,"children":163,"page":62},"\u002Fdocs\u002Ffireskills\u002Fgetting-started","docs\u002F2.fireskills\u002F1.getting-started",[164,167,171,175,179,183,187,191,195,199,203,207,211,215],{"title":27,"path":165,"stem":166},"\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fquick-start","docs\u002F2.fireskills\u002F1.getting-started\u002F00.quick-start",{"title":168,"path":169,"stem":170},"Workflow & Governance","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fworkflow","docs\u002F2.fireskills\u002F1.getting-started\u002F01.workflow",{"title":172,"path":173,"stem":174},"Project Context","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fproject-context","docs\u002F2.fireskills\u002F1.getting-started\u002F02.project-context",{"title":176,"path":177,"stem":178},"Step 0: Setup","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-setup","docs\u002F2.fireskills\u002F1.getting-started\u002F03.step-setup",{"title":180,"path":181,"stem":182},"Step 1: Inbox","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-inbox","docs\u002F2.fireskills\u002F1.getting-started\u002F04.step-inbox",{"title":184,"path":185,"stem":186},"Step 2: Backlog","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-backlog","docs\u002F2.fireskills\u002F1.getting-started\u002F05.step-backlog",{"title":188,"path":189,"stem":190},"Step 3: Specify","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-specify","docs\u002F2.fireskills\u002F1.getting-started\u002F06.step-specify",{"title":192,"path":193,"stem":194},"Step 4: Validate","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-validate","docs\u002F2.fireskills\u002F1.getting-started\u002F07.step-validate",{"title":196,"path":197,"stem":198},"Step 5: Tasks","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tasks","docs\u002F2.fireskills\u002F1.getting-started\u002F08.step-tasks",{"title":200,"path":201,"stem":202},"Step 6: TDD\u002FBDD","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tdd-bdd","docs\u002F2.fireskills\u002F1.getting-started\u002F09.step-tdd-bdd",{"title":204,"path":205,"stem":206},"Step 7: Implement","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-implement","docs\u002F2.fireskills\u002F1.getting-started\u002F10.step-implement",{"title":208,"path":209,"stem":210},"Step 8: Documentator","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-documentator","docs\u002F2.fireskills\u002F1.getting-started\u002F11.step-documentator",{"title":212,"path":213,"stem":214},"Late Changes","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Flate-changes","docs\u002F2.fireskills\u002F1.getting-started\u002F12.late-changes",{"title":216,"path":217,"stem":218},"Other Entry Points","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fother-entry-points","docs\u002F2.fireskills\u002F1.getting-started\u002F13.other-entry-points",{"title":220,"icon":221,"hide":22,"path":222,"stem":223,"children":224,"page":62},"Methodology","i-lucide-git-branch","\u002Fdocs\u002Ffireskills\u002Fmethodology","docs\u002F2.fireskills\u002F2.methodology",[225,229,233,237,241,245],{"title":226,"path":227,"stem":228},"The Three Acts","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-acts","docs\u002F2.fireskills\u002F2.methodology\u002F0.three-acts",{"title":230,"path":231,"stem":232},"The Three Gates","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-gates","docs\u002F2.fireskills\u002F2.methodology\u002F1.three-gates",{"title":234,"path":235,"stem":236},"The Canonical Spec","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fcanonical-spec","docs\u002F2.fireskills\u002F2.methodology\u002F2.canonical-spec",{"title":238,"path":239,"stem":240},"Lifecycle States","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Flifecycle-states","docs\u002F2.fireskills\u002F2.methodology\u002F3.lifecycle-states",{"title":242,"path":243,"stem":244},"Effort Scale","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Feffort-scale","docs\u002F2.fireskills\u002F2.methodology\u002F4.effort-scale",{"title":246,"path":247,"stem":248},"Milestones & Governance","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fgovernance-and-milestones","docs\u002F2.fireskills\u002F2.methodology\u002F5.governance-and-milestones",{"title":250,"icon":251,"hide":22,"path":252,"stem":253,"children":254,"page":62},"Skills","i-lucide-layers","\u002Fdocs\u002Ffireskills\u002Fskills","docs\u002F2.fireskills\u002F3.skills",[255,259,263,267,271],{"title":256,"path":257,"stem":258},"Catalog","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fcatalog","docs\u002F2.fireskills\u002F3.skills\u002F0.catalog",{"title":260,"path":261,"stem":262},"Act I: Define","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-1-define","docs\u002F2.fireskills\u002F3.skills\u002F1.act-1-define",{"title":264,"path":265,"stem":266},"Act II: Design & Prove","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-2-design-prove","docs\u002F2.fireskills\u002F3.skills\u002F2.act-2-design-prove",{"title":268,"path":269,"stem":270},"Act III: Deliver","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-3-deliver","docs\u002F2.fireskills\u002F3.skills\u002F3.act-3-deliver",{"title":272,"path":273,"stem":274},"Governance & Decisions","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fgovernance-and-decisions","docs\u002F2.fireskills\u002F3.skills\u002F4.governance-and-decisions",{"title":276,"icon":277,"hide":22,"path":278,"stem":279,"children":280,"page":62},"Specialists","i-lucide-sparkles","\u002Fdocs\u002Ffireskills\u002Fspecialists","docs\u002F2.fireskills\u002F4.specialists",[281,285,289],{"title":282,"path":283,"stem":284},"Overview","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Foverview","docs\u002F2.fireskills\u002F4.specialists\u002F0.overview",{"title":286,"path":287,"stem":288},"Nuxfire Stack","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fnuxfire-stack","docs\u002F2.fireskills\u002F4.specialists\u002F1.nuxfire-stack",{"title":290,"path":291,"stem":292},"Engineering Specialists","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fengineering-specialists","docs\u002F2.fireskills\u002F4.specialists\u002F2.engineering-specialists",{"title":294,"icon":295,"hide":22,"path":296,"stem":297,"children":298,"page":62},"CLI & Tooling","i-lucide-terminal","\u002Fdocs\u002Ffireskills\u002Fcli","docs\u002F2.fireskills\u002F5.cli",[299,303,307],{"title":300,"path":301,"stem":302},"CLI Overview","\u002Fdocs\u002Ffireskills\u002Fcli\u002Foverview","docs\u002F2.fireskills\u002F5.cli\u002F0.overview",{"title":304,"path":305,"stem":306},"Commands","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fcommands","docs\u002F2.fireskills\u002F5.cli\u002F1.commands",{"title":308,"path":309,"stem":310},"Safety Scripts","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fgovernance-scripts","docs\u002F2.fireskills\u002F5.cli\u002F2.governance-scripts",{"id":312,"title":47,"body":313,"description":1498,"extension":1499,"meta":1500,"navigation":1501,"path":48,"seo":1502,"stem":49,"__hash__":1503},"docs\u002Fdocs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables.md",{"type":314,"value":315,"toc":1476},"minimark",[316,334,345,350,373,461,465,474,541,545,557,651,656,674,679,732,739,753,764,768,781,935,939,957,1027,1031,1041,1181,1199,1205,1212,1248,1255,1262,1268,1325,1329,1335,1378,1382,1417,1421,1449,1453],[317,318,319,320,324,325,328,329,333],"p",{},"Every variable below lives in ",[321,322,323],"code",{},".env.example"," (the template) and is loaded into SST's own secrets vault per stage — see ",[326,327,43],"a",{"href":44}," for the loading commands. This page is the reference for ",[330,331,332],"strong",{},"where each value comes from",", one platform at a time, for anyone setting this up for the first time.",[317,335,336,337,340,341,344],{},"You don't need every row — only fill in what a feature you're actually using requires. Everything not marked ",[330,338,339],{},"Always"," is gated by a flag in ",[321,342,343],{},"config.ts","; leave it blank and disable the flag if you don't want that integration.",[346,347,349],"h2",{"id":348},"_1-branding-your-identity","1. Branding — your identity",[317,351,352,353,356,357,360,361,364,365,368,369,372],{},"Set in the ",[330,354,355],{},"Branding"," section of your stage's env file — the single place your domain and sender email live; nothing in the code repeats them. Unlike everything below, these are ",[330,358,359],{},"not secrets",": the deploy reads them straight from the file (and stops with the variable's name if one is missing or malformed), and a real environment variable of the same name takes precedence. Each stage reads its own file — ",[321,362,363],{},".env.production"," for ",[321,366,367],{},"production",", ",[321,370,371],{},".env.stage"," for every other stage.",[374,375,376,392],"table",{},[377,378,379],"thead",{},[380,381,382,386,389],"tr",{},[383,384,385],"th",{},"Variable",[383,387,388],{},"Required",[383,390,391],{},"What it is",[393,394,395,408,435],"tbody",{},[380,396,397,403,405],{},[398,399,400],"td",{},[321,401,402],{},"BRAND_NAME",[398,404,339],{},[398,406,407],{},"Your product or company name: the emails' sender name, subject lines and logo alt text. Up to 80 characters.",[380,409,410,415,417],{},[398,411,412],{},[321,413,414],{},"BRAND_DOMAIN",[398,416,339],{},[398,418,419,420,423,424,427,428,430,431,434],{},"Your production domain, name only (",[321,421,422],{},"acme.io",") — no ",[321,425,426],{},"https:\u002F\u002F",", path or port. Must be an active zone in the same Cloudflare account that owns your API token. ",[321,429,367],{}," answers on it; every other stage answers on ",[321,432,433],{},"\u003Cstage>.dev.\u003Cdomain>",".",[380,436,437,442,444],{},[398,438,439],{},[321,440,441],{},"BRAND_EMAIL",[398,443,339],{},[398,445,446,447,450,451,454,455,457,458,434],{},"The ",[330,448,449],{},"complete"," sender address (",[321,452,453],{},"contact@acme.io","), address only — the display name comes from ",[321,456,402],{},". Its domain must be verified in your email provider; it can be a subdomain such as ",[321,459,460],{},"mail.acme.io",[346,462,464],{"id":463},"_2-cloudflare-deploy-authentication","2. Cloudflare — deploy authentication",[317,466,467,468,471,472,434],{},"Required before any ",[321,469,470],{},"sst"," command works at all, on every machine that deploys. Full walkthrough with screenshots (permissions, token scope) is in ",[326,473,35],{"href":36},[374,475,476,487],{},[377,477,478],{},[380,479,480,482,484],{},[383,481,385],{},[383,483,388],{},[383,485,486],{},"Get it at",[393,488,489,518],{},[380,490,491,496,498],{},[398,492,493],{},[321,494,495],{},"CLOUDFLARE_API_TOKEN",[398,497,339],{},[398,499,500,506,507,506,510,513,514,517],{},[326,501,505],{"href":502,"rel":503},"https:\u002F\u002Fdash.cloudflare.com",[504],"nofollow","dash.cloudflare.com"," → ",[330,508,509],{},"Manage Account → Account API Tokens",[330,511,512],{},"Create Token",". Not the personal ",[321,515,516],{},"profile\u002Fapi-tokens"," page — that lists a different kind of token.",[380,519,520,525,527],{},[398,521,522],{},[321,523,524],{},"CLOUDFLARE_ACCOUNT_ID",[398,526,339],{},[398,528,529,532,533,536,537,540],{},[326,530,505],{"href":502,"rel":531},[504]," → the ",[330,534,535],{},"⋮"," menu next to your account name → ",[330,538,539],{},"Copy Account ID"," (also shown in the API section of any domain's Overview page)",[346,542,544],{"id":543},"_3-database-postgresql","3. Database — PostgreSQL",[317,546,547,548,553,554,556],{},"Any standard Postgres works — the app speaks the native wire protocol, never a provider SDK. Using ",[326,549,552],{"href":550,"rel":551},"https:\u002F\u002Fsupabase.com",[504],"Supabase"," as the example (it's what this project's own ",[321,555,371],{}," points at):",[374,558,559,569],{},[377,560,561],{},[380,562,563,565,567],{},[383,564,385],{},[383,566,388],{},[383,568,486],{},[393,570,571,595,611,623,639],{},[380,572,573,578,580],{},[398,574,575],{},[321,576,577],{},"DATABASE_HOST",[398,579,339],{},[398,581,582,587,588,506,591,594],{},[326,583,586],{"href":584,"rel":585},"https:\u002F\u002Fsupabase.com\u002Fdashboard\u002Fproject\u002F_\u002Fsettings\u002Fdatabase",[504],"supabase.com\u002Fdashboard"," → your project → ",[330,589,590],{},"Settings → Database",[330,592,593],{},"Direct connection"," tab. See warning below — don't use the pooler tab.",[380,596,597,602,604],{},[398,598,599],{},[321,600,601],{},"DATABASE_PORT",[398,603,339],{},[398,605,606,607,610],{},"Same tab (",[321,608,609],{},"5432",")",[380,612,613,618,620],{},[398,614,615],{},[321,616,617],{},"DATABASE_NAME",[398,619,339],{},[398,621,622],{},"Same tab",[380,624,625,630,632],{},[398,626,627],{},[321,628,629],{},"DATABASE_USER",[398,631,339],{},[398,633,634,635,638],{},"Same tab — plain ",[321,636,637],{},"postgres",", no suffix",[380,640,641,646,648],{},[398,642,643],{},[321,644,645],{},"DATABASE_PASSWORD",[398,647,339],{},[398,649,650],{},"Same tab — set when the project was created; reset it there if you don't have it",[652,653,655],"h3",{"id":654},"use-the-direct-connection-not-the-session-pooler","Use the Direct connection, not the Session pooler",[317,657,658,659,662,663,668,669,673],{},"Supabase's database settings page shows two connection modes. ",[330,660,661],{},"Use Direct connection"," — ",[326,664,667],{"href":665,"rel":666},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fhyperdrive\u002Fexamples\u002Fconnect-to-postgres\u002Fpostgres-database-providers\u002Fsupabase\u002F",[504],"Cloudflare's own Hyperdrive docs are explicit about this",": ",[670,671,672],"em",{},"\"you should use the Direct connection connection string rather than the pooled connection strings, as Hyperdrive will perform pooling of connections to ensure optimal access from Workers.\""," Hyperdrive already pools connections at Cloudflare's edge — routing it through Supabase's own pooler (Supavisor) on top would be pooling behind a pooler, for no benefit.",[317,675,676],{},[330,677,678],{},"The two modes use different, non-interchangeable host and username formats — never mix one mode's host with the other mode's username:",[374,680,681,693],{},[377,682,683],{},[380,684,685,687,690],{},[383,686],{},[383,688,689],{},"Host",[383,691,692],{},"User",[393,694,695,712],{},[380,696,697,703,708],{},[398,698,699,702],{},[330,700,701],{},"Direct"," (use this)",[398,704,705],{},[321,706,707],{},"db.\u003Cproject-ref>.supabase.co",[398,709,710],{},[321,711,637],{},[380,713,714,722,727],{},[398,715,716,717,719,720,610],{},"Session pooler (don't use for ",[321,718,577],{},"\u002F",[321,721,629],{},[398,723,724],{},[321,725,726],{},"aws-0-\u003Cregion>.pooler.supabase.com",[398,728,729],{},[321,730,731],{},"postgres.\u003Cproject-ref>",[317,733,734,735,738],{},"Pairing a direct-mode host with a pooler-mode username (or vice versa) doesn't fail loudly with a clear message — it deploys, then Cloudflare rejects it with ",[321,736,737],{},"Hyperdrive → 400: \"Invalid database credentials\""," (error code 2013), which reads like a wrong password even when the password is correct.",[317,740,741,744,745,748,749,752],{},[330,742,743],{},"The one case where you might still need the pooler:"," every deploy runs database migrations automatically from ",[330,746,747],{},"your own machine"," (",[321,750,751],{},"infra\u002Futils\u002Fnuxt.ts",", a local command), using these same values — not just from Cloudflare's edge. Supabase's direct connection is IPv6-only. If your network has no real IPv6 route to the internet, that local migration step will fail to connect (a connection timeout, not the credentials error above) even though Hyperdrive itself would have worked fine from Cloudflare's own IPv6-capable network. If you hit that specific failure, switching to the Session pooler pair (table above) is the pragmatic fix — you're trading a small amount of double-pooling overhead for reachability from your own machine.",[317,754,755,756,759,760,763],{},"Using RDS, Neon, or self-hosted Postgres instead: the same five values come from that provider's own connection-info page — the app doesn't care which one, as long as the ",[321,757,758],{},"postgis"," extension is enabled (Supabase has it available by default; run ",[321,761,762],{},"CREATE EXTENSION IF NOT EXISTS postgis;"," on others if needed).",[346,765,767],{"id":766},"_4-email-pick-at-least-one-provider","4. Email — pick at least one provider",[317,769,770,771,506,773,776,777,780],{},"Enable\u002Fdisable each in ",[321,772,343],{},[321,774,775],{},"flags",". Only ",[321,778,779],{},"resend"," is on by default.",[374,782,783,797],{},[377,784,785],{},[380,786,787,789,792,795],{},[383,788,385],{},[383,790,791],{},"Provider",[383,793,794],{},"Required when",[383,796,486],{},[393,798,799,824,849,883,908],{},[380,800,801,806,809,814],{},[398,802,803],{},[321,804,805],{},"RESEND_API_KEY",[398,807,808],{},"Resend",[398,810,811],{},[321,812,813],{},"flags.resend",[398,815,816,506,821],{},[326,817,820],{"href":818,"rel":819},"https:\u002F\u002Fresend.com\u002Fapi-keys",[504],"resend.com\u002Fapi-keys",[330,822,823],{},"Create API Key",[380,825,826,831,834,839],{},[398,827,828],{},[321,829,830],{},"MAILGUN_API_KEY",[398,832,833],{},"Mailgun",[398,835,836],{},[321,837,838],{},"flags.mailgun",[398,840,841,506,846],{},[326,842,845],{"href":843,"rel":844},"https:\u002F\u002Fapp.mailgun.com\u002F",[504],"app.mailgun.com",[330,847,848],{},"Settings → API Keys",[380,850,851,859,862,867],{},[398,852,853,368,856],{},[321,854,855],{},"AWS_ACCESS_KEY_ID",[321,857,858],{},"AWS_SECRET_ACCESS_KEY",[398,860,861],{},"Amazon SES",[398,863,864],{},[321,865,866],{},"flags.ses",[398,868,869,874,875,878,879,882],{},[326,870,873],{"href":871,"rel":872},"https:\u002F\u002Fconsole.aws.amazon.com\u002Fiam\u002Fhome#\u002Fsecurity_credentials",[504],"console.aws.amazon.com\u002Fiam"," → create an IAM user with SES send permissions → ",[330,876,877],{},"Security credentials → Create access key",". Not automated as infrastructure yet (see comment in ",[321,880,881],{},"infra\u002Fsecret.ts",") — the IAM user itself has to be created by hand.",[380,884,885,890,893,898],{},[398,886,887],{},[321,888,889],{},"SPARKPOST_API_KEY",[398,891,892],{},"SparkPost",[398,894,895],{},[321,896,897],{},"flags.sparkPost",[398,899,900,506,905],{},[326,901,904],{"href":902,"rel":903},"https:\u002F\u002Fapp.sparkpost.com\u002F",[504],"app.sparkpost.com",[330,906,907],{},"Account → API Keys",[380,909,910,924,927,932],{},[398,911,912,368,915,368,918,368,921],{},[321,913,914],{},"SMTP_HOST",[321,916,917],{},"SMTP_PORT",[321,919,920],{},"SMTP_USERNAME",[321,922,923],{},"SMTP_PASSWORD",[398,925,926],{},"Generic SMTP",[398,928,929],{},[321,930,931],{},"flags.smtp",[398,933,934],{},"Your SMTP provider's own dashboard — there's no single link here since this path accepts any SMTP server",[346,936,938],{"id":937},"_5-social-login","5. Social login",[317,940,941,942,368,945,948,949,952,953,956],{},"Both are optional, independently toggled (",[321,943,944],{},"flags.githubAuth",[321,946,947],{},"flags.googleAuth","). Each callback URL below must match ",[330,950,951],{},"exactly",", including the ",[321,954,955],{},"\u002Fcallback"," suffix — the OAuth provider will reject the login with a redirect-URI-mismatch error otherwise.",[374,958,959,969],{},[377,960,961],{},[380,962,963,965,967],{},[383,964,385],{},[383,966,794],{},[383,968,486],{},[393,970,971,999],{},[380,972,973,981,985],{},[398,974,975,368,978],{},[321,976,977],{},"GITHUB_CLIENT_ID",[321,979,980],{},"GITHUB_CLIENT_SECRET",[398,982,983],{},[321,984,944],{},[398,986,987,506,992,995,996],{},[326,988,991],{"href":989,"rel":990},"https:\u002F\u002Fgithub.com\u002Fsettings\u002Fdevelopers",[504],"github.com\u002Fsettings\u002Fdevelopers",[330,993,994],{},"New OAuth App",". Authorization callback URL: ",[321,997,998],{},"https:\u002F\u002Fauth.\u003Cyour-domain>\u002Fgithub\u002Fcallback",[380,1000,1001,1009,1013],{},[398,1002,1003,368,1006],{},[321,1004,1005],{},"GOOGLE_CLIENT_ID",[321,1007,1008],{},"GOOGLE_CLIENT_SECRET",[398,1010,1011],{},[321,1012,947],{},[398,1014,1015,506,1020,1023,1024],{},[326,1016,1019],{"href":1017,"rel":1018},"https:\u002F\u002Fconsole.cloud.google.com\u002Fapis\u002Fcredentials",[504],"console.cloud.google.com\u002Fapis\u002Fcredentials",[330,1021,1022],{},"Create Credentials → OAuth client ID"," (type: Web application). Authorized redirect URI: ",[321,1025,1026],{},"https:\u002F\u002Fauth.\u003Cyour-domain>\u002Fgoogle\u002Fcallback",[346,1028,1030],{"id":1029},"_6-payments","6. Payments",[317,1032,1033,1034,1037,1038,1040],{},"Stripe and Paddle can both be active at once — a ",[321,1035,1036],{},"Plan"," just picks one provider per row (see ",[326,1039,102],{"href":103},").",[374,1042,1043,1053],{},[377,1044,1045],{},[380,1046,1047,1049,1051],{},[383,1048,385],{},[383,1050,794],{},[383,1052,486],{},[393,1054,1055,1074,1103,1132,1158],{},[380,1056,1057,1062,1067],{},[398,1058,1059],{},[321,1060,1061],{},"STRIPE_SECRET_KEY",[398,1063,1064],{},[321,1065,1066],{},"flags.stripe",[398,1068,1069],{},[326,1070,1073],{"href":1071,"rel":1072},"https:\u002F\u002Fdashboard.stripe.com\u002Fapikeys",[504],"dashboard.stripe.com\u002Fapikeys",[380,1075,1076,1081,1085],{},[398,1077,1078],{},[321,1079,1080],{},"STRIPE_WEBHOOK_SECRET_KEY",[398,1082,1083],{},[321,1084,1066],{},[398,1086,1087,506,1092,506,1095,1098,1099,1102],{},[326,1088,1091],{"href":1089,"rel":1090},"https:\u002F\u002Fdashboard.stripe.com\u002Fwebhooks",[504],"dashboard.stripe.com\u002Fwebhooks",[330,1093,1094],{},"Add endpoint",[321,1096,1097],{},"https:\u002F\u002Fapp.\u003Cyour-domain>\u002Fapi\u002Fwebhooks\u002Fstripe"," → copy the ",[330,1100,1101],{},"Signing secret"," shown after creating it",[380,1104,1105,1110,1115],{},[398,1106,1107],{},[321,1108,1109],{},"PADDLE_API_KEY",[398,1111,1112],{},[321,1113,1114],{},"flags.paddle",[398,1116,1117,506,1122,1125,1126,1131],{},[326,1118,1121],{"href":1119,"rel":1120},"https:\u002F\u002Fvendors.paddle.com\u002Fauthentication",[504],"vendors.paddle.com\u002Fauthentication",[330,1123,1124],{},"Developer Tools → Authentication → API keys"," tab (sandbox testing: ",[326,1127,1130],{"href":1128,"rel":1129},"https:\u002F\u002Fsandbox-vendors.paddle.com\u002Fauthentication",[504],"sandbox-vendors.paddle.com"," instead)",[380,1133,1134,1139,1143],{},[398,1135,1136],{},[321,1137,1138],{},"PADDLE_WEBHOOK_SECRET",[398,1140,1141],{},[321,1142,1114],{},[398,1144,1145,506,1150,1153,1154,1157],{},[326,1146,1149],{"href":1147,"rel":1148},"https:\u002F\u002Fvendors.paddle.com\u002Fnotifications",[504],"vendors.paddle.com\u002Fnotifications",[330,1151,1152],{},"New destination"," → Webhook → ",[321,1155,1156],{},"https:\u002F\u002Fapp.\u003Cyour-domain>\u002Fapi\u002Fwebhooks\u002Fpaddle"," → copy the secret key shown",[380,1159,1160,1165,1169],{},[398,1161,1162],{},[321,1163,1164],{},"PADDLE_CLIENT_TOKEN",[398,1166,1167],{},[321,1168,1114],{},[398,1170,1171,1172,1174,1175,662,1177,1180],{},"Same ",[330,1173,70],{}," page as ",[321,1176,1109],{},[330,1178,1179],{},"Client-side tokens"," tab. This one is public by design (it ends up in the browser bundle), but is still set per stage like the others.",[317,1182,1183,1184,1187,1188,1191,1192,1195,1196,1198],{},"There is no environment variable for which plan the landing page's Buy CTA sells — that's a database flag (",[321,1185,1186],{},"Plan.isPublicOffer","), toggled from ",[321,1189,1190],{},"\u002Fadmin\u002Fplans"," and read live by ",[321,1193,1194],{},"apps\u002Fweb"," over a Cloudflare service binding. See ",[326,1197,102],{"href":103}," for how it works.",[652,1200,1202,1204],{"id":1201},"paddle_api_key-permissions-dont-select-all",[321,1203,1109],{}," permissions — don't select \"All\"",[317,1206,1207,1208,1211],{},"When creating the API key, the app only ever calls two Paddle resources server-side (",[321,1209,1210],{},"grep -rn \"paddle\\.\\w*\\.\\w*(\" apps\u002Fapp\u002Fserver"," to re-verify against a newer checkout of this repo):",[1213,1214,1215,1233],"ul",{},[1216,1217,1218,662,1221,1224,1225,1228,1229,1232],"li",{},[330,1219,1220],{},"Customers",[330,1222,1223],{},"Read",". ",[321,1226,1227],{},"paddle.customers.get(customerId)"," in the webhook handler (",[321,1230,1231],{},"server\u002Fapi\u002Fwebhooks\u002Fpaddle.post.ts","), to read the customer's email.",[1216,1234,1235,662,1238,1224,1241,748,1244,1247],{},[330,1236,1237],{},"Customer portal sessions",[330,1239,1240],{},"Write",[321,1242,1243],{},"paddle.customerPortalSessions.create(...)",[321,1245,1246],{},"server\u002Ftrpc\u002Frouters\u002Fbilling.ts","), to generate the billing-portal link a customer uses to manage their subscription.",[317,1249,1250,1251,1254],{},"Nothing else — no Adjustments, Businesses, Addresses, Discounts, Products, Prices, Transactions, or Reports permission is used anywhere in the codebase. A broader key is unnecessary blast radius if it ever leaks; widen the scope later only if new code actually calls another Paddle resource. (",[321,1252,1253],{},"paddle.webhooks.unmarshal(...)",", the third Paddle SDK call in the same webhook handler, verifies the webhook signature locally — it's not a network call and needs no API permission.)",[346,1256,1258,1259,610],{"id":1257},"_7-platform-admin-console-admin","7. Platform Admin console (",[321,1260,1261],{},"\u002Fadmin",[317,1263,1264,1265,1267],{},"Not from any external platform — generate these yourself, locally. See ",[326,1266,94],{"href":95}," for what they protect.",[374,1269,1270,1281],{},[377,1271,1272],{},[380,1273,1274,1276,1278],{},[383,1275,385],{},[383,1277,388],{},[383,1279,1280],{},"How to generate",[393,1282,1283,1307],{},[380,1284,1285,1290,1297],{},[398,1286,1287],{},[321,1288,1289],{},"PLATFORM_MFA_SESSION_SECRET",[398,1291,1292,1293,1296],{},"Always, before ",[321,1294,1295],{},"\u002Fadmin\u002F*"," works",[398,1298,1299,1302,1303,1306],{},[321,1300,1301],{},"openssl rand -base64 32"," (Git Bash\u002FmacOS\u002FLinux) — or PowerShell: ",[321,1304,1305],{},"[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))",". Safe to rotate — worst case it signs out active elevated sessions.",[380,1308,1309,1314,1318],{},[398,1310,1311],{},[321,1312,1313],{},"PLATFORM_ADMIN_ENCRYPTION_KEY",[398,1315,1292,1316,1296],{},[321,1317,1295],{},[398,1319,1320,1321,1324],{},"Same command. ",[330,1322,1323],{},"Do not rotate casually"," — it encrypts every admin's TOTP secret at rest; rotating it forces every enrolled admin to set up MFA again. Back it up somewhere durable (a password manager), not only in this file.",[346,1326,1328],{"id":1327},"_8-two-factor-authentication-regular-users","8. Two-factor authentication (regular users)",[317,1330,1331,1332,1334],{},"Same generation command as the platform-admin pair above, same rules, but kept on separate secrets — see ",[326,1333,94],{"href":95}," for why. Only required if a user actually turns on 2FA from Account Settings → Security; nothing else in the app depends on these.",[374,1336,1337,1347],{},[377,1338,1339],{},[380,1340,1341,1343,1345],{},[383,1342,385],{},[383,1344,388],{},[383,1346,1280],{},[393,1348,1349,1364],{},[380,1350,1351,1356,1359],{},[398,1352,1353],{},[321,1354,1355],{},"USER_MFA_SESSION_SECRET",[398,1357,1358],{},"Before any user can enable 2FA",[398,1360,1361,1363],{},[321,1362,1301],{},". Safe to rotate — worst case it re-prompts already-enrolled users for a code on their next request.",[380,1365,1366,1371,1373],{},[398,1367,1368],{},[321,1369,1370],{},"USER_MFA_ENCRYPTION_KEY",[398,1372,1358],{},[398,1374,1320,1375,1377],{},[330,1376,1323],{}," — it encrypts every user's TOTP secret at rest; rotating it forces everyone who enrolled to set up 2FA again.",[346,1379,1381],{"id":1380},"_9-optional-ai-system-one-models-typesafe","9. Optional — AI & System One models (TypeSafe)",[374,1383,1384,1394],{},[377,1385,1386],{},[380,1387,1388,1390,1392],{},[383,1389,385],{},[383,1391,388],{},[383,1393,486],{},[393,1395,1396],{},[380,1397,1398,1403,1406],{},[398,1399,1400],{},[321,1401,1402],{},"TYPESAFE_API_KEY",[398,1404,1405],{},"Optional",[398,1407,1408,1413,1414,434],{},[326,1409,1412],{"href":1410,"rel":1411},"https:\u002F\u002Fdocs.typesafe.ai\u002F",[504],"docs.typesafe.ai"," — API key for System One models returning typed judgments and probabilities. Server-only: never expose it as ",[321,1415,1416],{},"NUXT_PUBLIC_*",[346,1418,1420],{"id":1419},"_10-optional-build-tuning","10. Optional — build tuning",[374,1422,1423,1434],{},[377,1424,1425],{},[380,1426,1427,1429,1431],{},[383,1428,385],{},[383,1430,388],{},[383,1432,1433],{},"Notes",[393,1435,1436],{},[380,1437,1438,1443,1446],{},[398,1439,1440],{},[321,1441,1442],{},"SST_BUILD_CONCURRENCY_SITE",[398,1444,1445],{},"No",[398,1447,1448],{},"Caps how many packages SST builds in parallel. Useful on a CPU-constrained CI runner; leave unset otherwise.",[346,1450,1452],{"id":1451},"what-never-goes-in-this-file","What never goes in this file",[317,1454,1455,1457,1458,1461,1462,1465,1466,1469,1470,1473,1474,434],{},[321,1456,1416],{}," variables, worker URLs, client IDs exposed to the browser, and anything else derived at deploy time are computed automatically by ",[321,1459,1460],{},"infra\u002F*.ts"," — never set those by hand here. The one exception: running ",[321,1463,1464],{},"seed\u002Fdemo-accounts.ts"," manually needs ",[321,1467,1468],{},"AUTH_URL"," and ",[321,1471,1472],{},"AUTH_ADMIN_SECRET"," in your terminal's environment at the moment you run it (not in this file, not in the vault) — see ",[326,1475,94],{"href":95},{"title":1477,"searchDepth":1478,"depth":1478,"links":1479},"",2,[1480,1481,1482,1486,1487,1488,1492,1494,1495,1496,1497],{"id":348,"depth":1478,"text":349},{"id":463,"depth":1478,"text":464},{"id":543,"depth":1478,"text":544,"children":1483},[1484],{"id":654,"depth":1485,"text":655},3,{"id":766,"depth":1478,"text":767},{"id":937,"depth":1478,"text":938},{"id":1029,"depth":1478,"text":1030,"children":1489},[1490],{"id":1201,"depth":1485,"text":1491},"PADDLE_API_KEY permissions — don't select \"All\"",{"id":1257,"depth":1478,"text":1493},"7. Platform Admin console (\u002Fadmin)",{"id":1327,"depth":1478,"text":1328},{"id":1380,"depth":1478,"text":1381},{"id":1419,"depth":1478,"text":1420},{"id":1451,"depth":1478,"text":1452},"Every variable Nuxfire reads, what it's for, and a direct link to create it on each platform.","md",{},{"title":47},{"title":47,"description":1498},"E8u03kPANHfy21SPp8RPH6JzE9jdKgFq4HSeETtNl4I",1790707485287]