2 min read
Declarative Configuration
Understanding secretspec.toml and its declarative approach
SecretSpec uses secretspec.toml to declare what secrets your application needs, separating requirements from storage mechanisms for portability across environments.
Basic Structure
[project]
name = "my-app"
revision = "1.0"
extends = ["../shared/common"] # Optional: inherit from other configs
[profiles.default]
DATABASE_URL = { description = "PostgreSQL connection string", required = true }
API_KEY = { description = "External API key", required = true }
SESSION_SECRET = { description = "Session signing secret", required = true, type = "password", generate = true }Secret Declarations
Each secret is declared with configuration options:
SECRET_NAME = {
description = "Human-readable explanation", # Required: shown in prompts
required = true, # Optional: defaults to true
default = "value" # Optional: fallback if not set
}
Options:
description: Explains the secret’s purpose (required in thedefaultprofile; profile overrides inherit it when omitted)required: Whether the secret must be provided (default:true)default: Fallback value for optional secretscomposed(0.16+): Derive a read-only value from other declared secrets (see Composed Secrets for the strict template and dependency semantics)type: Secret type for auto-generation (password,hex,base64,uuid,command,rsa_private_key,openpgp_private_key(0.21+), andssh_private_key(0.21+))generate: Enable auto-generation when the secret is missing (trueor a table with options)prompt(0.19+): Securely ask for a missing value duringsecretspec runand let the selected provider decide whether to save the answer
Related Concepts
- Configuration Inheritance lets projects share common secret definitions via the
extendsfield - Secret Generation auto-creates passwords, tokens, and keys when secrets are missing
- Run prompts (0.19+) provision stored secrets on first use, or remain invocation-only with
null - Composed Secrets (0.16+) derive values from other declared secrets without dotenv or shell expansion
Best Practices
- Descriptive names: Use
STRIPE_API_KEYinstead of genericAPI_KEY - Clear descriptions: Help developers understand each secret’s purpose
- Sensible defaults: Provide development defaults, require production values
- Modular inheritance: Create reusable base configurations for common patterns
Complete Example
[project]
name = "web-api"
revision = "1.0"
extends = ["../shared/base", "../shared/auth"]
[profiles.default]
# Inherits DATABASE_URL, INTERNAL_API_KEY from base
# Inherits JWT_SECRET, SESSION_SECRET from auth
# Service-specific additions:
STRIPE_API_KEY = { description = "Stripe payment API", required = true }
REDIS_URL = { description = "Redis cache connection", required = true }
PORT = { description = "Server port", required = false, default = "3000" }
Last updated Oct 11, 2026