Shortcodes let you add dynamic content to your Markdown without writing raw HTML. They're simple tags in square brackets that get replaced when the page renders.
How They Work
Shortcodes come in two forms:
<!-- Self-closing -->
Copyright © [year] [site_name]
<!-- Paired (with content between) -->
[email][email protected][/email]
When rendered, [year] becomes the current year, [site_name] becomes your site name, and [email]...[/email] creates a spam-protected mailto link.
Built-in Shortcodes
| Shortcode | Output |
|---|---|
[year] |
Current year |
[date format="Y-m-d"] |
Current date (optionally formatted) |
[site_name] |
Site name from config |
[site_url] |
Site URL from config |
[email][email protected][/email] |
Obfuscated mailto link |
[snippet name="file"] |
Renders app/snippets/file.php |
Creating Custom Shortcodes
Register shortcodes in your theme.php:
<?php
// app/themes/yourtheme/theme.php
use Ava\Application;
return function (Application $app): void {
$shortcodes = $app->shortcodes();
// Self-closing shortcode
$shortcodes->register('greeting', function (array $attrs, ?string $content, string $tag) {
$name = $attrs['name'] ?? 'friend';
return "Hello, " . htmlspecialchars($name) . "!";
});
// Paired shortcode (receives content)
$shortcodes->register('highlight', function (array $attrs, ?string $content, string $tag) {
$color = $attrs['color'] ?? 'yellow';
$inner = $content ?? '';
return '<mark style="background:' . htmlspecialchars($color) . '">' . $inner . '</mark>';
});
};
Usage:
[greeting name="Alice"]
[highlight color="#ffeeba"]This text is highlighted.[/highlight]
[ in examples so they display correctly without executing.
Shortcode Callback Parameters
| Parameter | Type | Description |
|---|---|---|
$attrs |
array |
All attributes passed to the shortcode |
$content |
?string |
Content between opening/closing tags (null when not provided) |
$tag |
string |
Normalized shortcode tag name (lowercase) |
If your callback only needs $attrs, you can still declare fewer parameters — PHP will ignore the extra arguments Ava CMS passes.
Attributes
Attribute parsing supports:
key="value"key='value'key=value- Boolean flags:
key(sets$attrs['key'] = true)
Best Practices
- Escape output — Always use
htmlspecialchars()for user-provided values to prevent XSS - Return strings — Return a string (or
null, which becomes an empty string) - Keep it simple — Complex shortcodes are better as snippets (see below)
- Name carefully — Shortcode names are case-insensitive, use underscores for multi-word names
Snippets: Reusable PHP Components
For more complex components, use snippets. A snippet is a PHP file in your app/snippets/ folder that you invoke with the [snippet] shortcode.
When to use snippets vs shortcodes:
| Use Shortcodes | Use Snippets |
|---|---|
| Simple text replacements | Complex HTML structures |
| No external files needed | Reusable across sites |
| 1-5 lines of code | Need full PHP file with logic |
| Site-specific utilities | Component libraries |
Creating a Snippet
<?php // app/snippets/cta.php ?>
<?php
$heading = $params['heading'] ?? 'Ready to get started?';
$button = $params['button'] ?? 'Learn More';
$url = $params['url'] ?? '/contact';
?>
<div class="cta-box">
<h3><?= htmlspecialchars($heading) ?></h3>
<p><?= $content ?></p>
<a href="<?= htmlspecialchars($url) ?>" class="button">
<?= htmlspecialchars($button) ?>
</a>
</div>
Using a Snippet
[snippet name="cta" heading="Join Our Newsletter" button="Subscribe" url="/subscribe"]
Get weekly tips delivered to your inbox.
[/snippet]
Variables in Snippets
| Variable | Description |
|---|---|
$content |
Text between opening/closing tags |
$params |
Array of all attributes (e.g., $params['heading']) |
$ava |
Rendering engine instance (Ava\Rendering\Engine) |
$app |
Application instance |
Example Snippets
YouTube Embed:
<?php // app/snippets/youtube.php ?>
<?php $id = $params['id'] ?? ''; ?>
<div class="video-embed" style="aspect-ratio: 16/9;">
<iframe src="https://www.youtube.com/embed/<?= htmlspecialchars($id) ?>"
frameborder="0" allowfullscreen style="width:100%;height:100%;"></iframe>
</div>
Usage: [snippet name="youtube" id="dQw4w9WgXcQ"]
Notice Box:
<?php // app/snippets/notice.php ?>
<?php
$type = $params['type'] ?? 'info';
$icons = ['info' => '💡', 'warning' => '⚠️', 'success' => '✅', 'error' => '❌'];
$icon = $icons[$type] ?? '💡';
?>
<div class="notice notice-<?= htmlspecialchars($type) ?>">
<span><?= $icon ?></span>
<div><?= $content ?></div>
</div>
Usage:
[snippet name="notice" type="warning"]
This feature is experimental.
[/snippet]
Pricing Card:
<?php // app/snippets/pricing.php ?>
<?php
$plan = $params['plan'] ?? 'Plan';
$price = $params['price'] ?? '$0';
$features = $params['features'] ?? '';
$url = $params['url'] ?? '#';
?>
<div class="pricing-card">
<h3><?= htmlspecialchars($plan) ?></h3>
<div class="price"><?= htmlspecialchars($price) ?><span>/month</span></div>
<ul>
<?php foreach (explode(',', $features) as $feature): ?>
<li><?= htmlspecialchars(trim($feature)) ?></li>
<?php endforeach; ?>
</ul>
<a href="<?= htmlspecialchars($url) ?>" class="button">Get Started</a>
</div>
Usage: [snippet name="pricing" plan="Pro" price="$29" features="Unlimited projects, Priority support, API access"]
How Processing Works
- Markdown is converted to HTML
- Shortcodes are processed in the HTML output
- Path aliases are expanded
- Result is sent to the browser
Since shortcodes run after Markdown processing, they can safely output raw HTML.
Limitations
- No nested shortcodes — shortcodes are not processed inside other shortcodes.
- Paired content cannot contain
[— the current parser stops paired shortcode content at the next[character. If you need rich/nested markup, prefer a snippet.
Security
- Path safety: Snippet names can't contain
..or/— no directory traversal - Disable snippets: Set
security.shortcodes.allow_php_snippetstofalse - Unknown shortcodes: Left as-is in output (no errors)
- Errors: Exceptions are logged and replaced with an HTML comment
- Escaping: Always use
htmlspecialchars()for user values