Content in Ava CMS is just text. You write in Markdown, which is a simple way to format text, and save it as a file. There's no database to manageโyour files are your content.
Ava CMS can handle any combination of Markdown and standard HTML, even within the same file. You can also embed safe and reusable PHP snippets using Shortcodes for absolute flexibility.
What is Markdown?
What is Markdown?
Markdown is a lightweight way to format text using plain characters.
- You write readable text.
- You sprinkle in simple symbols for headings, links, lists, and code.
- Mix in your own custom HTML if required for advanced styling.
- Ava CMS (and your theme) turns it into HTML.
A tiny Markdown cheat-sheet
# Heading 1
## Heading 2
**bold** and *italic*
- bullet item
1. numbered item
[a link](https://example.com)
`inline code`
```php
// a code block
echo 'Hello';
```โ
View full Markdown reference โ
Markdown Editors (use what you like)
You can write Ava CMS content in almost anything:
- Code editors: VS Code, Sublime Text, PhpStorm
- Markdown-focused apps: Obsidian, Typora, MarkText, iA Writer, Zettlr
- In the browser: StackEdit, your web host's file manager, or GitHub's editor if you're using Git
There's no "correct" editor. If you like writing in a notes app and uploading later, that works. If you like editing on the server over SSH, that works too.
Frontmatter vs Markdown (two different things)
Each content file has:
- Frontmatter (YAML) between
---lines: structured metadata - Body (Markdown): the actual writing
Note: YAML is sensitive to indentation. If something breaks, it's often a missing space or an unclosed quote in frontmatter. Running ./ava lint is the fastest way to get a clear error message.
The Basics
Every piece of content is a .md file with two parts:
- ๐ Frontmatter โ Metadata about the content (like title, date, status) at the top. Think of it like the address on an envelope.
- ๐ Body โ The actual content, written in Markdown.
---
title: My First Post
slug: my-first-post
status: published
date: 2024-12-28
---
# Hello World
This is my first post. I can use **bold**, *italics*, and [links](https://example.com).
Set status: draft while writing, then switch to published when youโre happy.
Creating Content
Manually
Create a .md file in the appropriate content directory:
content/
โโโ pages/
โโโ my-new-page.md โ create your file here
Add frontmatter and content, then save. If cache mode is auto, the site updates immediately.
Via the Admin Dashboard
If you have the admin dashboard enabled, you can create, edit, and delete content files directly in the browser. Ava CMS writes changes back to your Markdown files (files remain the source of truth).
See Admin Dashboard for setup and usage.
Via CLI
Use the make command:
./ava make <type> "Title"
Examples:
./ava make page "About Us"
./ava make post "Hello World"
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ โ Created new post! โ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ File: content/posts/hello-world.md ID: 01JGHK8M3Q4R5S6T7U8V9WXYZ Slug: hello-world Status: draft ๐ก Tip: Edit your content, then set status: published when ready
Run without arguments to see available types:
./ava make
โ Usage: ./ava make <type> "Title" Available types: โธ page โ Pages โธ post โ Posts Example: ./ava make post "My New Post"
This creates a properly formatted file with:
- Generated ULID
- Slugified filename
- Date (for dated types)
- Draft status
Organising Your Files
Content lives in the content/ folder. You can organise it however you like, but typically it looks like this:
content/
โโโ pages/ # Standard pages like About or Contact
โ โโโ index.md # Your homepage (or _index.md, if you prefer)
โ โโโ about.md # /about
โ โโโ services/
โ โโโ index.md # /services
โ โโโ web.md # /services/web
โโโ posts/ # Example of a pattern-based type (only if you define it)
โ โโโ hello.md
โโโ _taxonomies/ # Optional term registries (only if you use taxonomies)
โโโ category.yml
โโโ tag.yml
For hierarchical types (like the default pages), folder structure maps cleanly to URLs. For example content/pages/services/web.md becomes /services/web.
Frontmatter Reference
Frontmatter is metadata about your content, written in YAML between two --- lines at the top of your file. Think of it as the "settings" for each page.
Full Example
Here's a complete example showing all the common fields (but you can keep it much simpler if you prefer):
---
id: 01JGMK0000POST0000000000001
title: My Blog Post
slug: my-blog-post
status: published
date: 2024-12-28
updated: 2024-12-30
excerpt: A short summary for listings and search results.
template: custom-post.php
order: 10
category:
- tutorials
- php
tag:
- beginner
meta_title: SEO-Optimised Title
meta_description: Description for search engines.
canonical: https://example.com/blog/my-blog-post
og_image: "@media:2024/social-card.jpg"
noindex: false
cache: true
redirect_from:
- /old-url
- /another-old-url
assets:
css:
- "@media:css/custom-post.css"
js:
- "@media:js/interactive.js"
---
Your Markdown content goes here...
Core Fields
These fields are used by Ava CMS to manage and display your content.
| Field | Required | Description |
|---|---|---|
title |
No* | Display title. If omitted, Ava CMS derives one from slug (e.g. my-post โ My Post). |
slug |
No* | A URL-safe identifier. If omitted, Ava CMS sets it from the filename (not the title). Must match: lowercase letters/numbers + hyphens only (^[a-z0-9-]+$). |
status |
No | draft, published, or unlisted. Defaults to draft. |
id |
No | Optional unique identifier. Useful for stable references and ID-based URLs (see below). |
date |
No | Optional date. Ava CMS accepts common date strings (and some other types); invalid values become null. |
updated |
No | Optional updated timestamp. If omitted (or invalid), Ava CMS falls back to date. |
excerpt |
No | Optional short summary for listings/search/etc. |
template |
No | Optional template override (e.g., landing.php). |
*Ava CMSโs linter validates that the computed title and slug are non-empty, but Ava CMS also supplies defaults:
slugdefaults to the filename (e.g.hello-world.mdโhello-world)titledefaults to a title-cased version ofslug
In other words: you can omit title/slug in frontmatter, but your filename still needs to be a valid slug or ./ava lint will fail.
Slugs and URLs (pattern vs hierarchical)
Ava CMS supports two URL styles per content type (configured in app/config/content_types.php):
- Pattern URLs (
url.type = pattern): the itemโsslugis used in the URL pattern (e.g./blog/{slug}), and as the lookup key. - Hierarchical URLs (
url.type = hierarchical): the URL is derived from the file path, not the itemโsslug.
For hierarchical types:
content/pages/about/team.mdbecomes/about/team.index.mdor_index.mdrepresent the folder URL (e.g.content/pages/docs/index.mdโ/docs).- The internal lookup key is the path (e.g.
about/team). If you fetch items manually in templates, use that key:
$item = $ava->get('page', 'about/team');
Setting slug: in frontmatter does not change the URL for hierarchical content (the filesystem path wins).
IDs: optional, but powerful
The id field is optional.
Benefits of setting/keeping IDs:
- Ava CMS can fetch an item by ID via the repository index.
- You can create stable, rename-proof permalinks by using
{id}in a pattern URL (e.g.'/p/{id}'). - Ava CMS detects duplicate IDs during indexing (helpful when merging content).
If you donโt need any of that, you can omit id: entirely.
Taxonomy Fields
Assign content to categories, tags, or any taxonomy defined in taxonomies.php.
category:
- tutorials
- php
tag:
- getting-started
- beginner
You can use either a single value or a list. Ava CMS normalizes both into an array when reading.
How taxonomy indexing works:
- Only taxonomies defined in
app/config/taxonomies.phpare indexed and routed (see Configuration - Taxonomies) - Taxonomy indexes only include published content (not drafts or unlisted items)
- Terms do not need to be "pre-created" โ if a published item references a term slug, the term appears automatically
- Term display names are auto-generated from slugs (e.g.,
php-tutorialsโPhp Tutorials) unless defined in a registry file - Term slugs should be lowercase alphanumeric with hyphens
Single vs Multiple Terms
# Single term (string)
category: tutorials
# Multiple terms (array)
category:
- tutorials
- php
- cms
Both formats work โ Ava CMS normalizes single values into arrays internally.
Alternative format: tax: map
If you prefer to group all taxonomies under a single key:
tax:
category: [tutorials, php]
tag: beginner
This keeps frontmatter tidy when you have many taxonomies.
Accessing Terms in Templates
For detailed template examples, see Theming - Working with Taxonomies.
// Get terms for a specific taxonomy on an item
<?php foreach ($content->terms('category') as $term): ?>
<a href="<?= $ava->termUrl('category', $term) ?>">
<?= $ava->termName('category', $term) ?>
</a>
<?php endforeach; ?>
// Get all terms for a taxonomy across the site
<?php foreach ($ava->terms('category') as $slug => $info): ?>
<li>
<a href="<?= $ava->termUrl('category', $slug) ?>">
<?= $ava->e($info['name']) ?>
</a>
<span class="count">(<?= $info['count'] ?>)</span>
</li>
<?php endforeach; ?>
Term registries (content/_taxonomies/*.yml)
You can optionally define a "term registry" file per taxonomy to add metadata:
content/_taxonomies/category.ymlcontent/_taxonomies/tag.yml
Registry files let you:
- Define term display names (instead of auto-generated)
- Add descriptions, images, or custom fields to terms
- Pre-create terms before any content uses them
Registry file format:
# content/_taxonomies/category.yml
- slug: tutorials
name: Tutorials
description: Step-by-step guides and how-tos
icon: book
- slug: php
name: PHP
description: PHP-specific content
- slug: reference
name: Reference
description: API and syntax reference
How registry merging works:
- Terms used in published content get their
countanditemsfrom indexing, plus any extra fields from the registry - Terms that exist only in the registry appear with
count: 0 - Registry fields are available in templates via
$ava->terms('category')[$slug]['description']etc.
SEO Fields
Control how your content appears in search engines and social media.
| Field | Description |
|---|---|
meta_title |
Custom title for search engines. Defaults to title. |
meta_description |
Description shown in search results. |
canonical |
Explicit canonical URL. Use when content exists at multiple URLs or to point to the original source. |
noindex |
Set to true to hide from search engines (adds <meta name="robots" content="noindex">). |
og_image |
Image URL for social media sharing (Open Graph). Supports path aliases like /media/. |
Your theme must output SEO meta tags in the page <head> (for example by including <?= $ava->metaTags($content) ?>).
Hierarchy & Ordering Fields
Control content structure and manual sorting.
| Field | Description |
|---|---|
parent |
Parent page slug (for building navigation trees or breadcrumbs). Not used for URL generation in hierarchical types โ URLs come from the filesystem. |
order |
Integer for manual sorting (e.g., order: 10). Lower values appear first. Default is 0. Use with sorting: 'manual' in content type config. |
Example using order for manual sorting:
---
title: Getting Started
order: 1
---
---
title: Advanced Usage
order: 2
---
In templates, sort by order:
$items = $ava->query()
->type('page')
->published()
->orderBy('order', 'asc')
->get();
Behaviour Fields
Fine-tune how Ava CMS handles this specific piece of content.
| Field | Description |
|---|---|
cache |
Set to false to disable page caching for this URL. Set to true to force caching. |
redirect_from |
Array of old URLs that should 301 redirect here. See Redirects. |
template |
Override the default template for this content type (e.g., landing.php). See Theming. |
raw_html |
Set to true to skip Markdown parsing. Shortcodes and path aliases are still processed. Useful for pages with custom HTML layouts. |
Per-Item Assets
Load CSS or JS only on specific pages (for "art-directed" posts). See Per-Item Assets for details.
Custom Fields
You can add any custom fields you likeโthey're just YAML keys in your frontmatter:
---
title: Team Member
slug: jane-doe
status: published
role: Lead Developer
website: "https://janedoe.com"
featured: true
---
Access them in templates with $item->get('field_name'):
<p>Role: <?= $ava->e($item->get('role')) ?></p>
<?php if ($item->get('featured')): ?>
<span class="badge">Featured</span>
<?php endif; ?>
Want validation and admin UI for your custom fields? Define them in your content type configuration to get proper form inputs, type validation, and linting. See Fields for the complete guide to typed fields.
Redirects
When you move or rename content, set up redirects in the new file:
redirect_from:
- /old-url
- /another-old-url
Requests to the old URLs will 301 redirect to the new location.
Per-Item Assets (Art-Directed Posts)
For art-directed blogging, you can load custom CSS or JS on specific pages. Put your files in public/media/ and reference them:
assets:
css:
- "@media:css/my-styled-post.css"
js:
- "@media:js/interactive-chart.js"
Your theme must include <?= $ava->itemAssets($item) ?> in the <head> for these to load (the default theme already does this).
See also: Theming - Per-Item Assets for how to implement this in your theme.
Images and Media
Store images, PDFs, and other files in public/media/. Reference them using the @media: path alias:

[Download PDF](@media:docs/guide.pdf)
The @media: alias expands to /media/ at render time. You can add custom aliases (like @cdn:) in ava.phpโthis makes it easy to change asset locations later without updating every content file.
Uploading files:
- Manually โ Drop files into
public/media/via SFTP or your file manager - Admin Dashboard โ Use the built-in media uploader (see Admin Dashboard)
public/media/. The media folder is for content-related files like images, downloads, and per-post assets.
Shortcodes
Embed dynamic content using shortcodes:
Current year: [year]
Site name: [site_name]
Include snippet: [snippet name="cta" heading="Join Us"]
See Shortcodes for the full reference.
Content Status
| Status | Visibility |
|---|---|
draft |
Not routed publicly. Viewable via preview token (if configured). |
published |
Publicly routed. Included in listings/archives and taxonomy indexes. |
unlisted |
Publicly routed (accessible via direct URL, no preview token required). Excluded from published-only listings (e.g. $ava->recent()), archives, and taxonomy indexes. |
Previewing Your Site
Local Development
Run PHP's built-in server to preview locally:
php -S localhost:8000 -t public
Then open http://localhost:8000 in your browser.
Live Editing
Editing files directly on your server works great too! Ava CMS's auto-rebuild mode (the default) means changes appear immediatelyโjust save and refresh.
Workflow Options
There's no single "right" way to work with Ava CMS:
- Edit on server โ Use SFTP, your host's file manager, or SSH. Changes are live immediately.
- Work locally โ Edit with your favourite tools, preview with PHP's dev server, upload when ready.
- Use Git โ Track changes with version control, sync via GitHub/GitLab, automate deployments.
Many people combine approaches: quick fixes directly on the server, bigger changes locally with Git. Do what works for you!
Validation
Run the linter to check all content:
./ava lint
This catches:
- Invalid YAML syntax
- Missing required fields
- Invalid status values
- Malformed slugs
- Duplicate content keys (slug for pattern types; path key for hierarchical types)
- Duplicate IDs (when IDs are present)
See CLI Reference - Lint for more details.