Ava CMS's configuration is simple and transparent. All settings live in app/config/ as plain PHP files.
Quick Start: The settings most people change first
Open app/config/ava.php and update these:
'site' => [
'name' => 'My Awesome Site',
'base_url' => 'https://example.com', // Full URL, no trailing slash
'og_image' => '/media/social.jpg', // Default social sharing image
'timezone' => 'Europe/London', // php.net/timezones
'locale' => 'en_GB', // php.net/setlocale
'date_format' => 'F j, Y', // php.net/datetime.format
],
See Site Identity below for details on each option, or the PHP date format reference for date formatting.
Why PHP Configs?
We use PHP arrays instead of YAML or JSON because:
- It's Readable: You can add comments to explain why you changed a setting.
- It's Powerful: You can use constants, logic, or helper functions right in your config.
- It's Standard: No special parsers or hidden
.envfiles to debug.
The Config Files
| File | What it controls |
|---|---|
| ava.php | Main site settings (name, URL, cache, themes, plugins, security). |
| content_types.php | Defines your content types (Pages, Posts, etc.). See also Writing Content. |
| taxonomies.php | Defines how you group content (Categories, Tags). See also Taxonomy Fields. |
Main Settings (ava.php)
This is where you set up your site's identity and behavior.
Site Identity
return [
'site' => [
'name' => 'My Awesome Site',
'base_url' => 'https://example.com', // Full URL, no trailing slash
'og_image' => '/media/social.jpg', // Default social sharing image
'timezone' => 'Europe/London', // php.net/timezones
'locale' => 'en_GB', // php.net/setlocale
'date_format' => 'F j, Y', // php.net/datetime.format
'title_format' => '{title} · {site}', // null disables formatting
],
// ...
];
| Option | Type | Default | Description |
|---|---|---|---|
site.name |
string | 'My Ava Site' |
Your site's display name. Used in templates, RSS feeds, and sitemaps. |
site.base_url |
string | required | Full URL (no trailing slash). Used for sitemaps, canonical URLs, absolute links, and to define the Host header the webpage cache stores pages for. If you serve the site on more than one hostname, set the primary one here and list the rest under webpage_cache.hosts; otherwise, pages requested via the other hostnames will not be cached. |
site.og_image |
string|null | null |
Default social sharing image used when an item has no og_image or featured_image. Relative paths and aliases such as /media/ are expanded and converted to absolute URLs. |
site.timezone |
string | 'UTC' |
Timezone for dates. Use a PHP timezone identifier. |
site.locale |
string | 'en_GB' |
Locale for date/number formatting. See PHP locale codes. Also sets the page language exposed to themes ($site['lang'], e.g. en-GB) and the RSS feed's <language> (e.g. en-gb). |
site.date_format |
string | 'F j, Y' |
Default format for $ava->date(). Uses PHP date() format codes. |
site.title_format |
string|null | '{title} · {site}' |
Page title format when content has no meta_title. {title} is the content title and {site} is site.name. Set to null to use the content title without formatting. An explicit meta_title is always used verbatim. |
In templates: Access site info via $site['name'], $site['url'], $site['timezone'], $site['locale'] (e.g. en_GB, for og:locale) and $site['lang'] (e.g. en-GB, for <html lang>). See Theming - Template Variables.
Paths
Directory locations for your content, themes, plugins, and other assets.
'paths' => [
'content' => 'content', // Where your Markdown files live
'themes' => 'app/themes', // Where theme folders live
'plugins' => 'app/plugins', // Where plugin folders live
'snippets' => 'app/snippets', // Snippets for [snippet] shortcode
'storage' => 'storage', // Cache, logs, and temporary files
'aliases' => [
'@media:' => '/media/',
],
],
| Option | Type | Default | Description |
|---|---|---|---|
paths.content |
string | 'content' |
Directory containing your Markdown content files. |
paths.themes |
string | 'app/themes' |
Directory containing theme folders. |
paths.plugins |
string | 'app/plugins' |
Directory containing plugin folders. |
paths.snippets |
string | 'app/snippets' |
Directory for PHP snippets. See Shortcodes - Snippets. |
paths.storage |
string | 'storage' |
Directory for cache files, logs, and temporary data. |
paths.aliases |
array | ['@media:' => '/media/'] |
Path aliases for use in content. See Writing Content - Images and Media. |
All paths are relative to your project root unless they start with /.
Theme
'theme' => 'default',
| Option | Type | Default | Description |
|---|---|---|---|
theme |
string | 'default' |
The active theme folder name inside app/themes/. |
See: Theming for theme structure, template variables, and development documentation.
Content Index
The content index is a binary snapshot of your content metadata—used to avoid parsing Markdown files on every request.
'content_index' => [
'mode' => 'auto', // When to rebuild: auto, never, always
'backend' => 'array', // Storage: array or sqlite
'use_igbinary' => true, // Faster serialization if available
'prerender_html' => true, // Pre-render Markdown during rebuild
'background_rebuild' => false, // auto mode: rebuild after the response is sent
],
| Option | Type | Default | Description |
|---|---|---|---|
mode |
string | 'auto' |
auto = rebuild when files change, never = rebuild via CLI only (production), always = every request (debug only). |
check_interval |
int | 1 |
auto mode only. Minimum seconds between checks for changed files. Only one request runs each check; other requests keep serving the current index. |
background_rebuild |
bool | false |
auto mode only. false = a detected change is rebuilt inside the request that noticed it, so edits show on the next refresh. true = the rebuild runs after that request's response is sent, so no visitor waits for it, but that one request still gets the old page. Recommended for large sites. Needs PHP-FPM or LiteSpeed to fully detach from the request; see Performance - Automatic Rebuilds. |
backend |
string | 'array' |
array = binary PHP arrays (default), sqlite = SQLite database (for 10k+ items). |
use_igbinary |
bool | true |
Use igbinary extension for faster serialization if installed. |
prerender_html |
bool | true |
Pre-render Markdown → HTML during rebuild to speed up uncached requests. |
See: Performance - Content Indexing for detailed explanations of modes, backends, tiered caching, and benchmarks.
Webpage Cache
The webpage cache stores fully-rendered HTML for near-instant serving. Only requests whose Host header matches site.base_url (or an entry in hosts) are cached.
'webpage_cache' => [
'enabled' => true,
'ttl' => null, // Seconds, or null = until rebuild
'exclude' => [
'/api/*',
'/preview/*',
],
'hosts' => [], // Extra hostnames allowed to use the cache
],
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable HTML webpage caching. Recommended true for production. |
ttl |
int|null | null |
Cache lifetime in seconds. null = cached until next rebuild. |
exclude |
array | ['/api/*', '/preview/*'] |
URL patterns to never cache. Supports glob wildcards (*). |
hosts |
array of strings | [] |
Extra hostnames, besides the host in site.base_url, that may create webpage cache entries. Use this for aliases that serve the canonical site, for example 'hosts' => ['www.example.com']. Include the port for a non-default port ('example.com:8080'). Values are matched case-insensitively against the request Host header. Other hosts bypass the cache entirely: the page is still rendered and served, just not cached. |
URLs with query strings other than ?paged=N and utm_* campaign parameters (for example search, filters and sorting) are never cached. To stop them overloading your server, rate limit them at the web server; see Hosting - Rate Limiting.
See: Performance - Webpage Caching for how it works, fast-path optimization, and cache management.
Generator Comment
'generator_comment' => false,
| Option | Type | Default | Description |
|---|---|---|---|
generator_comment |
bool | false |
Append an HTML generator comment and X-Render-Time response header to every rendered page. |
When enabled, Ava appends a comment to each page's HTML output. When the webpage cache is active, this is replaced with a cached variant and a <!-- Page cached: ... --> header comment is also added.
Set to true when you need these diagnostics. It defaults to false to minimize version information disclosed in production and keep HTML output clean.
Routing
'routing' => [
'trailing_slash' => false,
],
| Option | Type | Default | Description |
|---|---|---|---|
trailing_slash |
bool | false |
URL style: false = /about, true = /about/. Mismatches trigger 301 redirects. |
See: Routing for URL styles, custom routes, and taxonomy URLs.
Content Parsing
Controls how Ava CMS processes your Markdown content files.
'content' => [
'frontmatter' => [
'format' => 'yaml',
],
'markdown' => [
'allow_html' => true,
'heading_ids' => true,
'disallowed_tags' => ['script', 'noscript'],
],
'id' => [
'type' => 'ulid',
],
],
| Option | Type | Default | Description |
|---|---|---|---|
frontmatter.format |
string | 'yaml' |
Frontmatter parser format. Currently only YAML is supported. |
markdown.allow_html |
bool | true |
Allow raw HTML tags in Markdown content. Safe for trusted authors; set to false when accepting content from untrusted authors, including via pull requests. <script> and <noscript> are stripped, but attribute-based HTML event handlers are not. |
markdown.heading_ids |
bool | true |
Add id attributes to headings for deep linking. |
markdown.disallowed_tags |
array | ['script', 'noscript'] |
HTML tags to strip even when allow_html is true. |
id.type |
string | 'ulid' |
ID format for new content: 'ulid' (recommended) or 'uuid7'. |
See: Writing Content for frontmatter fields and Markdown syntax.
Security
Ava includes security settings for shortcodes, preview tokens, and HTTP security headers.
'security' => [
'shortcodes' => [
'allow_php_snippets' => true,
],
'headers' => [
'content_security_policy' => [
"default-src 'self'",
"base-uri 'none'",
"object-src 'none'",
"frame-ancestors 'none'",
"frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com https://player.vimeo.com",
"form-action 'self'",
"connect-src 'self' https:", // External APIs, analytics
"img-src 'self' data: https:", // External images
"font-src 'self' data: https:", // Google Fonts, CDNs
"style-src 'self' 'unsafe-inline' https:",
"script-src 'self'", // Add hosts here for analytics etc.
],
'permissions_policy' => [
'camera=()',
'microphone=()',
'geolocation=()',
'payment=()',
'usb=()',
'interest-cohort=()',
],
],
'preview_token' => null,
],
| Option | Type | Default | Description |
|---|---|---|---|
shortcodes.allow_php_snippets |
bool | true |
Enable the [snippet] shortcode for including PHP files from app/snippets/. |
headers.content_security_policy |
array | See above | CSP directives applied to public responses. See CSP explanation below. |
headers.permissions_policy |
array | See above | Browser permissions to disable (camera, mic, etc.). |
preview_token |
string|null | null |
Secret token for previewing draft content without logging in. This is one shared, non-expiring secret for every draft. |
Preview token usage: Access drafts via https://example.com/path?preview=1&token=your-token
Understanding Content Security Policy
Content Security Policy (CSP) is a security layer that helps protect your site from attacks like cross-site scripting (XSS) and data injection. It works by telling browsers which sources of content are allowed to load.
How it works: When a browser loads your page, it reads the CSP header and blocks any resources (scripts, styles, images, etc.) that don't match the allowed sources. This stops attackers from injecting malicious scripts, even if they find a vulnerability in your site.
Ava's default CSP explained:
| Directive | Value | What it does |
|---|---|---|
default-src 'self' |
Only your domain | Fallback rule: only load resources from your own site. |
script-src 'self' |
Only your domain | JavaScript from your own files only. Inline <script> blocks and third-party scripts (analytics, widgets) are blocked until you allow them; see below. |
style-src 'self' 'unsafe-inline' https: |
Your domain + inline + HTTPS | CSS from your files, inline style="" attributes, and any HTTPS stylesheet URLs. |
img-src 'self' data: https: |
Your domain + data URIs + HTTPS | Images from your site, embedded base64 data URIs, or any HTTPS image URL (shields.io badges, external photos, etc.). |
font-src 'self' data: https: |
Your domain + data URIs + HTTPS | Fonts from your site, embedded data URIs, or any HTTPS font URL (Google Fonts, etc.). |
connect-src 'self' https: |
Your domain + HTTPS | AJAX/fetch requests to your site and any HTTPS API (analytics, external services). |
frame-src 'self' youtube vimeo |
Your domain + video platforms | Allows embedding YouTube and Vimeo videos by default. |
form-action 'self' |
Only your domain | Forms can only submit to your own site. |
frame-ancestors 'none' |
Nobody | Your site cannot be embedded in iframes (prevents clickjacking). |
object-src 'none' |
Blocked | No Flash, Java applets, or other plugins. |
base-uri 'none' |
Blocked | Prevents <base> tag injection attacks. |
Why these defaults? Ava's CSP is designed to balance security with flexibility for a content management system. The defaults are permissive enough for common use cases:
- Embedding YouTube and Vimeo videos
- Loading images from external sources
- Loading fonts from CDNs like Google Fonts
- Connecting to external APIs
Scripts are the exception: only your own script files run by default, because scripts are what an XSS attack injects. For high-security sites, also restrict the remaining https: sources to specific domains.
When you might need to customize:
- Adding analytics or other third-party scripts? Add their hosts to
script-src. See Allowing Analytics and Third-Party Scripts below. - Embedding other video platforms? Add their domains to
frame-src. - Need your site to be embeddable? Change
frame-ancestors 'none'to list allowed parent domains. - Need stricter security? Replace
https:with specific domains (e.g.,https://cdn.example.com).
Example: Stricter CSP with specific domains only
'headers' => [
'content_security_policy' => [
"default-src 'self'",
"base-uri 'none'",
"object-src 'none'",
"frame-ancestors 'none'",
"frame-src 'self' https://www.youtube.com",
"form-action 'self'",
"connect-src 'self' https://www.google-analytics.com",
"img-src 'self' data:",
"font-src 'self' data: https://fonts.gstatic.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"script-src 'self' https://www.googletagmanager.com",
],
// ... other headers
],
Testing your CSP: Open your browser's developer tools (F12) and check the Console tab. Any blocked resources will show as errors, telling you exactly which directive blocked them and what source was attempted.
Learn more: MDN Content Security Policy Guide
Allowing Analytics and Third-Party Scripts
The default script-src 'self' only runs script files served from your own site. To load a third-party script, add its host to script-src. For example, for Plausible:
"script-src 'self' https://plausible.io",
This also applies to external URLs in a page's assets.js frontmatter: add their host to script-src, or the browser will block them.
If a service gives you an inline snippet (a <script> block with code inside it), either move the code into a .js file in your theme and load it with $ava->asset(), or add 'unsafe-inline':
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com",
'unsafe-inline' lets any inline script run, including one injected by an attacker, so it weakens CSP's protection against XSS. Prefer an external file where you can.
script-src 'self' 'unsafe-inline' https:. Updates never modify app/config/ava.php, so those sites keep their current policy until you change it.
Permissions Policy disables browser features you don't need. The defaults block camera, microphone, geolocation, payment APIs, USB access, and FLoC tracking. Remove items from the array if your site legitimately needs these features.
HSTS (Strict Transport Security) should be configured at your web server level (Apache, Nginx, Caddy), not in Ava's config.
See: Shortcodes - Snippets and Routing - Preview Mode.
Debug Mode
Control error visibility and logging for development and troubleshooting.
'debug' => [
'enabled' => false,
'display_errors' => false,
'log_errors' => true,
'level' => 'errors',
],
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Master switch for debug features. |
display_errors |
bool | false |
Show PHP errors in browser output. Never enable in production! |
log_errors |
bool | true |
Write errors to storage/logs/error.log. |
level |
string | 'errors' |
Error reporting: all (dev), errors (prod), or none. |
display_errors in production—it can expose sensitive information.
Logs
Control log file size and automatic rotation.
'logs' => [
'max_size' => 10 * 1024 * 1024, // 10 MB
'max_files' => 3,
],
| Option | Type | Default | Description |
|---|---|---|---|
max_size |
int | 10485760 |
Maximum log file size in bytes before rotation (10 MB). |
max_files |
int | 3 |
Number of rotated log files to keep. |
See: CLI - Logs for log viewing and management commands.
CLI Appearance
'cli' => [
'theme' => 'cyan',
],
| Theme | Description |
|---|---|
cyan |
Cool cyan/aqua (default) |
pink, purple, green, blue, amber |
Alternative colors |
disabled |
No colors (for CI/CD or non-ANSI terminals) |
Plugins
'plugins' => [
'sitemap',
'feed',
'redirects',
],
Array of plugin folder names to activate. Plugins load in the order listed.
See: Bundled Plugins for sitemap, RSS feed, and redirects configuration.
Custom Settings
Add your own site-specific configuration. Access values in templates with $ava->config():
// In ava.php
'analytics' => [
'tracking_id' => 'G-XXXXXXXXXX',
'enabled' => true,
],
// In templates
<?php if ($ava->config('analytics.enabled')): ?>
<!-- Analytics code for <?= $ava->config('analytics.tracking_id') ?> -->
<?php endif; ?>
Content Types: content_types.php
Define what kinds of content your site has. Each content type specifies where files live, how URLs are generated, and which templates to use.
<?php
return [
'page' => [
'label' => 'Pages',
'icon' => 'description',
'content_dir' => 'pages',
'url' => [
'type' => 'hierarchical',
'base' => '/',
],
'templates' => [
'single' => 'page.php',
],
'taxonomies' => [],
'sorting' => 'manual',
'search' => [
'enabled' => true,
'fields' => ['title', 'body'],
],
],
'post' => [
'label' => 'Posts',
'icon' => 'article',
'content_dir' => 'posts',
'url' => [
'type' => 'pattern',
'pattern' => '/blog/{slug}',
'archive' => '/blog',
],
'templates' => [
'single' => 'post.php',
'archive' => 'archive.php',
],
'taxonomies' => ['category', 'tag'],
'sorting' => 'date_desc',
'search' => [
'enabled' => true,
'fields' => ['title', 'excerpt', 'body'],
],
],
];
Content Type Options
| Option | Type | Required | Description |
|---|---|---|---|
label |
string | Yes | Human-readable display name for this content type. |
icon |
string | No | Material icon name. See Material Icons. |
content_dir |
string | Yes | Folder inside content/ where files for this type live. |
url |
array | Yes | URL generation settings. See Routing - URL Styles. |
templates |
array | Yes | Template file mappings (single, archive). |
taxonomies |
array | No | Which taxonomies apply to this type. Default: [] |
sorting |
string | No | Default sort order for the archive listing page (e.g., /blog). date_desc = newest first (default); date_asc = oldest first; title = title A–Z; manual = by order frontmatter field, ascending. See Hierarchy & Ordering Fields. |
cache_fields |
array | No | Extra frontmatter fields to include in archive cache for fast access. |
search |
array | No | Search configuration (enabled, fields, weights). See Search. |
Taxonomies: taxonomies.php
Taxonomies organize content into groups (categories, tags, authors, etc.).
<?php
return [
'category' => [
'label' => 'Categories',
'icon' => 'folder',
'hierarchical' => true,
'public' => true,
'rewrite' => [
'base' => '/category',
'separator' => '/',
],
'behaviour' => [
'allow_unknown_terms' => true,
'hierarchy_rollup' => true,
],
'ui' => [
'show_counts' => true,
'sort_terms' => 'name_asc',
],
],
'tag' => [
'label' => 'Tags',
'icon' => 'tag',
'hierarchical' => false,
'public' => true,
'rewrite' => [
'base' => '/tag',
],
'behaviour' => [
'allow_unknown_terms' => true,
],
'ui' => [
'show_counts' => true,
'sort_terms' => 'count_desc',
],
],
];
Taxonomy Options
| Option | Type | Default | Description |
|---|---|---|---|
label |
string | Required | Human-readable name for the taxonomy. |
icon |
string | null |
Material icon name. |
hierarchical |
bool | false |
Support parent/child term relationships. |
public |
bool | true |
Create public archive pages for terms. |
rewrite.base |
string | '/{taxonomy}' |
URL prefix for term archives. |
rewrite.separator |
string | '/' |
Separator for hierarchical term paths. |
behaviour.allow_unknown_terms |
bool | true |
Auto-create terms when used in content. |
behaviour.hierarchy_rollup |
bool | true |
Include child terms when filtering by parent. |
ui.show_counts |
bool | true |
Display content count next to terms. |
ui.sort_terms |
string | 'name_asc' |
Default sort: name_asc, name_desc, count_asc, count_desc. |
See: Taxonomies for assigning terms to content, term storage, hierarchical terms, and template helpers.
Environment-Specific Config
Use PHP logic to override settings per environment:
// app/config/ava.php
$config = [
'site' => [
'name' => 'My Site',
'base_url' => 'https://example.com',
],
'content_index' => ['mode' => 'never'],
'debug' => ['enabled' => false],
];
// Development overrides
if (getenv('APP_ENV') === 'development') {
$config['site']['base_url'] = 'http://localhost:8000';
$config['content_index']['mode'] = 'auto';
$config['debug'] = [
'enabled' => true,
'display_errors' => true,
'level' => 'all',
];
}
return $config;
Set the environment variable in your server config:
export APP_ENV=development