SPIN Framework uses a JSON-based configuration system that's loaded at runtime and provides easy access through helper functions.
Every Spin application must have src/app/Config/version.json. The framework loads it automatically at startup — before any config-{env}.json — and exposes the values through the app() helper:
{
"application": {
"code": "my-app",
"name": "My Application",
"version": "1.0.0"
}
}| Field | Purpose |
|---|---|
code |
Machine identifier — used as Monolog log channel name and shared-storage path suffix |
name |
Human-readable application label |
version |
Semver version string |
app()->getAppCode(); // "my-app"
app()->getAppName(); // "My Application"
app()->getAppVersion(); // "1.0.0"SPIN applications use JSON configuration files organized by environment (e.g., config-dev.json, config-prod.json). The configuration is structured hierarchically and supports environment variables.
Configuration files support ${env:<varName>} macros for environment variables in values
{
"application": {
"global": {
"maintenance": false,
"message": "We are in maintenance mode, back shortly",
"timezone": "Europe/Stockholm"
},
"secret": "${application-secret}"
},
"session": {
"cookie": "SID",
"timeout": 3600,
"refresh": 600,
"driver": "apcu",
"apcu": {
"option": "value"
}
},
"logger": {
"level": "notice",
"driver": "php",
"drivers": {
"php": {
"line_format": "[%channel%] [%level_name%] %message% %context%\n",
"line_datetime": "Y-m-d H:i:s.v e"
},
"file": {
"file_path": "storage/log",
"file_format": "Y-m-d",
"line_format": "[%datetime%] [%channel%] [%level_name%] %message% %context%\n",
"line_datetime": "Y-m-d H:i:s.v e"
}
}
},
"templates": {
"extension": "html",
"errors": "/Views/Errors",
"pages": "/Views/Pages"
},
"caches": {
"apcu": {
"adapter": "APCu",
"class": "\\Spin\\Cache\\Adapters\\Apcu",
"options": {}
},
"redis": {
"adapter": "Redis",
"class": "\\Spin\\Cache\\Adapters\\Redis",
"options": {
"host": "172.20.0.1",
"port": 6379
}
}
},
"connections": {
"example_mysql": {
"type": "Pdo",
"driver": "mysql",
"schema": "${env:DB_DATABASE}",
"host": "${env:DB_HOST}",
"port": "${env:DB_PORT}",
"username": "${env:DB_USER}",
"password": "${env:DB_PASS}",
"charset": "UTF8",
"options": {
"ATTR_PERSISTENT": false,
"ATTR_ERRMODE": "ERRMODE_EXCEPTION",
"ATTR_AUTOCOMMIT": false,
"ATTR_EMULATE_PREPARES": false
}
}
},
"factories": {
"http": {
"serverRequest": {
"class": "\\Spin\\Factories\\Http\\ServerRequestFactory",
"options": {}
},
"request": {
"class": "\\Spin\\Factories\\Http\\RequestFactory",
"options": {}
},
"response": {
"class": "\\Spin\\Factories\\Http\\ResponseFactory",
"options": {}
},
"stream": {
"class": "\\Spin\\Factories\\Http\\StreamFactory",
"options": {}
},
"uploadedFile": {
"class": "\\Spin\\Factories\\Http\\UploadedFileFactory",
"options": {}
},
"uri": {
"class": "\\Spin\\Factories\\Http\\UriFactory",
"options": {}
}
},
"container": {
"class": "\\Spin\\Factories\\ContainerFactory",
"options": {
"autowire": true
}
},
"event": {
"class": "\\Spin\\Factories\\EventFactory",
"options": {}
}
}
}SPIN provides a config() helper function to access configuration values using dot notation:
// Access nested configuration values
$maintenance = config('application.global.maintenance');
$timezone = config('application.global.timezone');
$sessionTimeout = config('session.timeout');
// Access with default values
$logLevel = config('logger.level', 'info');
$dbHost = config('connections.example_mysql.host', 'localhost');SPIN automatically loads a .env file from the project root at startup, before any
configuration is processed. Variables defined there become available to ${env:VAR}
macros and the env() helper function.
# .env (project root — never commit to version control)
DB_HOST=localhost
DB_USER=myuser
DB_PASS=s3cr3t
APP_ENV=dev
Priority: Real environment variables (OS, Docker, CI) always win. .env values are
only applied when the variable is not already set in the process environment. This means
container-injected or CI secrets are never overridden by a local .env file.
SPIN supports environment variable substitution in JSON config values using ${env:VARIABLE_NAME} syntax:
{
"application": {
"secret": "${env:APPLICATION_SECRET}",
"database": {
"password": "${env:DB_PASS}"
}
}
}Macros support a fallback value used when the variable is not set:
{
"database": {
"driver": "${env:DB_DRIVER:pdo}",
"host": "${env:DB_HOST:localhost}",
"port": "${env:DB_PORT:3306}"
}
}If DB_DRIVER is not in the environment (and not in .env), the value resolves to pdo.
Missing variables with no inline default resolve to an empty string.
{
"application": {
"global": {
"maintenance": false,
"message": "We are in maintenance mode, back shortly",
"timezone": "Europe/Stockholm"
},
"secret": "${application-secret}"
}
}{
"session": {
"cookie": "SID", // Cookie name
"timeout": 3600, // Timeout in seconds
"refresh": 600,
"driver": "apcu", // Driver name
"apcu": {
"option": "value"
}
}
}{
"logger": {
"level": "notice", // Log level
"driver": "php", // Driver name
"drivers": {
"php": {
"line_format": "[%channel%] [%level_name%] %message% %context%\n",
"line_datetime": "Y-m-d H:i:s.v e"
},
"file": {
"file_path": "storage/log",
"file_format": "Y-m-d",
"line_format": "[%datetime%] [%channel%] [%level_name%] %message% %context%\n",
"line_datetime": "Y-m-d H:i:s.v e"
}
}
}
}
line_formatand line endings: The\nat the end ofline_formatensures each log entry is terminated with a newline. On Linux, Docker, and Unix systems this is required for the file driver to produce readable log files. On Windows the newline is written automatically, but the\nis harmless.
{
"caches": {
"apcu": { // WebServer in-memory cache
"adapter": "APCu",
"class": "\\Spin\\Cache\\Adapters\\Apcu",
"options": {}
},
"redis": {
"adapter": "Redis",
"class": "\\Spin\\Cache\\Adapters\\Redis",
"options": {
"host": "172.20.0.1",
"port": 6379
}
}
}
}The 1st connection in the "connections" array is the default, and does not need to be named when using db() funtions.
{
"connections": {
"example_mysql": {
"type": "Pdo",
"driver": "mysql",
"schema": "${env:DB_DATABASE}",
"host": "${env:DB_HOST}",
"port": "${env:DB_PORT}",
"username": "${env:DB_USER}",
"password": "${env:DB_PASS}",
"charset": "UTF8",
"options": {
"ATTR_PERSISTENT": false,
"ATTR_ERRMODE": "ERRMODE_EXCEPTION",
"ATTR_AUTOCOMMIT": false,
"ATTR_EMULATE_PREPARES": false
}
},
"example_sqlite": {
"type": "Pdo",
"driver": "SqlLite",
"filename": "storage\\database\\db.sqlite"
}
}
}Controls the shared storage path used by app()->getSharedStoragePath().
{
"storage": {
"shared": "${env:SHARED_STORAGE_PATH}"
}
}| Key | Description |
|---|---|
shared |
Base path for shared storage. The framework appends /{environment}/{appCode} to form the final path. Leave empty or omit to use local {basePath}/storage instead. |
See Storage Folders for full details.
{
"factories": {
"http": {
"serverRequest": {
"class": "\\Spin\\Factories\\Http\\ServerRequestFactory",
"options": {}
},
"response": {
"class": "\\Spin\\Factories\\Http\\ResponseFactory",
"options": {}
}
},
"container": {
"class": "\\Spin\\Factories\\ContainerFactory",
"options": {
"autowire": true
}
}
}
}- Environment Separation: Use different configuration files for different environments (dev, staging, prod)
- Sensitive Data: Store sensitive information like passwords and secrets in environment variables
- Validation: Validate configuration values at startup
- Defaults: Provide sensible default values for optional configuration
- Documentation: Document all configuration options and their expected values
SPIN automatically validates configuration when the application starts. Missing required configuration will cause the application to fail to start.
While SPIN primarily uses static JSON configuration, you can also set configuration values programmatically:
// Set configuration values at runtime
config('application.global.maintenance', true);
config('session.timeout', 7200);