This project is under very early, active development and may contain bugs or security issues. It is likely not ready for production websites.

You are responsible for reviewing, testing, and securing any deployment. Ava CMS is provided as free, open-source software without warranty (GNU General Public License), see LICENSE.

Configuration

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
    '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:

  1. It's Readable: You can add comments to explain why you changed a setting.
  2. It's Powerful: You can use constants, logic, or helper functions right in your config.
  3. It's Standard: No special parsers or hidden .env files 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.
users.php Admin users (managed automatically by CLI). See User Management.

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
        'timezone'    => 'Europe/London',          // php.net/timezones
        'locale'      => 'en_GB',                  // php.net/setlocale
        'date_format' => 'F j, Y',                 // php.net/datetime.format
    ],
    // ...
];
Option Type Default Description
site.name string 'My Ava Site' Your site's display name. Used in templates, RSS feeds, sitemaps, and admin.
site.base_url string required Full URL (no trailing slash). Used for sitemaps, canonical URLs, and absolute links.
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.
site.date_format string 'F j, Y' Default format for $ava->date(). Uses PHP date() format codes.

In templates: Access site info via $site['name'], $site['url'], and $site['timezone']. See Theming - Template Variables.

Paths

Directory locations for your content, themes, plugins, and other assets.

Important: Most people should not change these paths from the defaults. Only change them if you have a specific reason. Aliases are safe to customize.
'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
],
Option Type Default Description
mode string 'auto' auto = rebuild when files change, never = rebuild via CLI/admin dashboard only (production), always = every request (debug only).
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.

'webpage_cache' => [
    'enabled' => true,
    'ttl'     => null,       // Seconds, or null = until rebuild
    'exclude' => ['/api/*', '/preview/*'], // URL patterns to never 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 (*).

See: Performance - Webpage Caching for how it works, fast-path optimization, and cache management.

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.
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' 'unsafe-inline' https:",
        ],
        'permissions_policy' => [
            'camera=()',
            'microphone=()',
            'geolocation=()',
            'payment=()',
            'usb=()',
            'interest-cohort=()',
        ],
    ],

    'preview_token' => null,
],
Option Type Default Description
shortcodes.allow_php_snippets bool true Enable the 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.

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' 'unsafe-inline' https: Your domain + inline + HTTPS JavaScript from your files, inline scripts, and any HTTPS source. Allows analytics, widgets, etc.
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
  • Adding analytics scripts (Google Analytics, Plausible, Matomo, etc.)
  • Connecting to external APIs

This is more permissive than a locked-down web app, but appropriate for a CMS where content flexibility is expected. For high-security sites, restrict https: to specific domains.

When you might need to customize:

  • 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

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.

Note: Security headers are only applied to public (non-admin) responses. The admin dashboard has its own separate Content Security Policy.

See: Shortcodes - Snippets and Routing - Preview Mode.

Admin Dashboard

The admin dashboard provides a web-based interface for managing your site.

'admin' => [
    'enabled' => false,
    'path'    => '/ava-admin',
    'theme'   => 'cyan',

    'session' => [
        'timeout'    => 1800,  // 30 minutes
        'ip_binding' => false,
    ],

    'media' => [
        'enabled'          => true,
        'path'             => 'public/media',
        'organize_by_date' => true,
        'max_file_size'    => 10 * 1024 * 1024,   // 10 MB
        'allowed_types'    => [
            'image/jpeg', 'image/png', 'image/gif',
            'image/webp', 'image/svg+xml', 'image/avif',
        ],
    ],
],
Option Type Default Description
enabled bool false Enable the admin dashboard.
path string '/ava-admin' URL path for the admin area. Change to obscure admin location.
theme string 'cyan' Color theme: cyan, pink, purple, green, blue, or amber.
session.timeout int|null 1800 Session lifetime in seconds. null disables timeout.
session.ip_binding bool false Lock session to login IP. See Admin - IP Binding for warnings.
media.enabled bool true Enable the media upload feature.
media.path string 'public/media' Upload directory (relative to project root).
media.organize_by_date bool true Create /year/month/ subfolders for uploads.
media.max_file_size int 10485760 Maximum file size in bytes (10 MB default).
media.allowed_types array See code Array of allowed MIME types for uploads.
Important: Create admin users with ./ava user:add before enabling the admin dashboard.

See: Admin Dashboard for features, security, and user management.

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.
Security Warning: Never enable 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' => [],
        'fields'     => [],
        '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'],
        'fields'     => [],
        'sorting'    => 'date_desc',
        'search' => [
            'enabled' => true,
            'fields'  => ['title', 'excerpt', 'body'],
        ],
    ],
];

Content Type Options

Option Type Required Description
label string Yes Human-readable name shown in admin UI.
icon string No Material icon name for admin UI.
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: []
fields array No Custom field definitions for validation and admin UI. See Fields.
sorting string No Default sort: date_desc, date_asc, title, or manual.
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.

Example with Custom Fields

Here's a more complete example showing custom fields for a blog post type:

'post' => [
    'label'       => 'Blog Posts',
    'content_dir' => 'posts',
    'url' => [
        'type'    => 'pattern',
        'pattern' => '/blog/{slug}',
        'archive' => '/blog',
    ],
    'templates' => [
        'single'  => 'post.php',
        'archive' => 'archive.php',
    ],
    'taxonomies' => ['category', 'tag'],
    'sorting'    => 'date_desc',
    
    // Custom fields for validation and admin UI
    'fields' => [
        'author' => [
            'type'     => 'text',
            'label'    => 'Author Name',
            'required' => true,
        ],
        'featured_image' => [
            'type'  => 'text',
            'label' => 'Featured Image URL',
        ],
        'reading_time' => [
            'type'    => 'number',
            'label'   => 'Reading Time (minutes)',
            'min'     => 1,
            'max'     => 60,
            'default' => 5,
        ],
        'featured' => [
            'type'    => 'checkbox',
            'label'   => 'Featured Post',
            'default' => false,
        ],
    ],
    
    // Include these fields in archive cache for fast listing access
    'cache_fields' => ['author', 'featured_image', 'reading_time', 'featured'],
],

See: Fields for all available field types (text, number, checkbox, select, date, etc.) and validation options.

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 for admin UI.
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['admin']['enabled'] = true;
    $config['debug'] = [
        'enabled'        => true,
        'display_errors' => true,
        'level'          => 'all',
    ];
}

return $config;

Set the environment variable in your server config:

export APP_ENV=development

Ava CMS is provided as free, open-source software without warranty. You are responsible for reviewing, testing, and securing any deployment.