Configuration philosophy
Piko configures itself almost entirely through Go code in your func main.At runtime the framework reads no application configuration files. Thebuild-time pipeline is the one exception. When generating the manifest, thegenerator looks for a config.json in the project root and folds itsWebsiteConfig (theme, fonts, locales, i18n strategy) into the embeddedmanifest. Passing WithWebsiteConfig in piko.New overrides that fileentirely. If neither is present, the framework starts with an emptywebsite config.
The framework also consults a small set of environment variables whentheir associated options are not set in code. piko.New readsPIKO_LOG_LEVEL once so you can raise the bootstrap logger to debugwithout recompiling. PIKO_PORT, PIKO_I18N_SOURCE_DIR,PIKO_ACTION_SERVE_PATH, and others provide defaults forthe matching With* options. An explicit option in your main alwayswins over the environment.
This page explains why and how to configure the framework. It also coverswhere to put deploy-time secrets. Finally, it points to the utility Pikoships for file-based configuration in your application (which Pikoitself does not consume).
Configure the framework
Every framework knob is a With* functional option passed to piko.New.The same options work in dev and prod, and deploy-time values come from yourown func main.
ssr := piko.New(
piko.WithPort(8080),
piko.WithAutoNextPort(true),
piko.WithCSRFSecret(secretKey),
piko.WithLogLevel("info"),
piko.WithRateLimit(piko.RateLimitConfig{
Enabled: new(true),
HeadersEnabled: new(true),
}),
piko.WithCSSReset(piko.WithCSSResetComplete()),
piko.WithDevWidget(),
)
Group With* options exist for tightly coupled fields:
WithSecurityHeaders(piko.SecurityHeadersConfig{...})WithCookieSecurity(piko.CookieSecurityConfig{...})WithRateLimit(piko.RateLimitConfig{...})WithSandbox(piko.SandboxConfig{...})WithReporting(piko.ReportingConfig{...})WithCaptcha(piko.CaptchaOptions{...})WithAWSKMS(piko.AWSKMSConfig{...})WithGCPKMS(piko.GCPKMSConfig{...})WithStoragePresign(piko.StoragePresignConfig{...})WithOTLP(piko.OtlpConfig{...})WithLogger(logger_dto.Config{...})
Per-field options exist for the rest. See bootstrapoptions for the full surface.
Why code, not files
Piko is a framework you compile a binary against. The compiler bakes routes,generators, asset pipelines, build modes, and all framework wiring in atgo build time. Treating those values as runtime YAML created a second mental modelthat competed with functional options for the same surface and silentlydrifted from it. Removing the file-loaded ServerConfig collapsesconfiguration to one shape, the one the type system already enforces.
This matches the SSR-framework convention: Nuxt, Next.js, Astro, SvelteKit,Remix, Vite, and Tailwind all configure themselves through executableTypeScript or JavaScript modules, not through YAML. Next.js explicitlydeprecated andremoved its filebased runtime config in favour of env-var access from application code.
The PIKO_LOG_LEVEL exception
piko.New reads PIKO_LOG_LEVEL once, before any options apply, so youcan raise log verbosity in production without rebuilding the binary. Anexplicit WithLogLevel(...) option in your main overrides it.
PIKO_LOG_LEVEL=debug ./my-app prod
Other PIKO_* variables (PIKO_PORT, PIKO_I18N_SOURCE_DIR,PIKO_ACTION_SERVE_PATH, and similar) provide defaults for the matchingWith* options when those options are not set in code. They aredeliberately limited. Anything secret, environment-specific, or composedfrom multiple sources is your func main's problem.
Deploy-time values: bring your own loader
Piko has no opinion on how you load secrets and per-environment values. Passthem to the relevant With* option from any source you trust.
ssr := piko.New(
piko.WithCSRFSecret([]byte(os.Getenv("CSRF_SECRET"))),
piko.WithPostgresURL(os.Getenv("DATABASE_URL")),
piko.WithStoragePresignSecret(os.Getenv("STORAGE_PRESIGN_SECRET")),
)
For more structure, use a small loader of your choice(koanf, viper,envconfig, or your own).Piko ships a generic loader and resolver kit atpiko.sh/piko/wdk/config that you may use:it has no dependency on Piko's internals and you can keep it or swap it.
Secret resolvers
To expand placeholder strings such as "aws-secret:my/key" in your ownconfiguration, use the wdk/config package. It provides resolvers for AWSSecrets Manager, GCP Secret Manager, Azure Key Vault, Kubernetes Secrets,and HashiCorp Vault. They satisfy the generic Resolverinterface. You can register them with the global registry or compose theminto a custom Loader. See secrets andresolvers.
Theme, fonts, favicons, locales
Pass the website-level metadata (theme colours, favicons, fonts, supportedlocales, i18n strategy) explicitly via WithWebsiteConfig:
ssr := piko.New(
piko.WithWebsiteConfig(piko.WebsiteConfig{
Name: "My Site",
I18n: piko.I18nConfig{
DefaultLocale: "en",
Locales: []string{"en", "fr", "de"},
},
// theme, fonts, favicons, ...
}),
)
If you prefer keeping these as JSON, drop a config.json next to theproject root with the same shape as WebsiteConfig. The build-timemanifest generator reads it and embeds the result. The runtime thenserves the embedded data with no further file access. Designers can editconfig.json and rebuild without touching Go code. SettingWithWebsiteConfig in func main overrides the file entirely. If youwant both editing paths, unmarshal your own JSON in main and pass theresult to WithWebsiteConfig.