The Context System

Every widget needs a storage location. Instead of passing file paths to each widget call, Mini CMS uses a context stack that tracks where content should be read from and written to.

How it works

When a page renders, Mini CMS sets a page context based on the URL:

/              → _content/home/
/about         → _content/about/
/team/erik     → _content/team/erik/

All cms_text(), cms_html(), and cms_image() calls within that page read and write to the page’s context directory. The slug parameter becomes the key in widgets.json or the filename for .html content.

cms_partial() — reusable sub-contexts

The most common way to create a nested context. It renders a view template inside its own content scope:

<?= cms_partial('partials/card.php', 'Feature 1') ?>
<?= cms_partial('partials/card.php', 'Feature 2') ?>
<?= cms_partial('partials/card.php', 'Feature 3') ?>

Each call pushes a sub-context named after the label. The label is slugified and prefixed with _ to avoid collision with URL-derived paths:

'Feature 1' → _content/home/_feature-1/
'Feature 2' → _content/home/_feature-2/
'Feature 3' → _content/home/_feature-3/

Inside partials/card.php, widget slugs like 'heading' and 'body' are scoped to that sub-context. No conflicts even though the same template runs three times with identical slug names.

Manual context control

For cases where cms_partial() doesn’t fit, use the explicit push/pop functions:

<?php cms_context_start('sidebar', 'Sidebar'); ?>
    <?= cms_text('cta', 'Call to Action', 1, 'Contact us', 'h3') ?>
    <?= cms_html('cta-body', 'CTA Body', 2, '<p>Get in touch.</p>') ?>
<?php cms_context_end(); ?>

This creates _content/home/_sidebar/. Every cms_context_start() must have a matching cms_context_end() — Mini CMS throws a LogicException if the stack is unbalanced after rendering.

Why the underscore prefix?

Page URLs map to directory names directly: /about stores content in _content/about/. If a sub-context were also named about, its directory would collide with the page directory. The _ prefix on sub-context directories (_feature-1, _sidebar) prevents this — URL paths never start with an underscore.

Absolute context paths

Context paths starting with / resolve from the content root instead of nesting under the current context. This is useful for shared content that appears on multiple pages:

<?php cms_context_start('/shared/footer-cta', 'Footer CTA'); ?>
    <?= cms_text('heading', 'Heading', 1, 'Ready to start?', 'h2') ?>
<?php cms_context_end(); ?>

This reads from _content/_shared/footer-cta/ regardless of which page is rendering.

Admin UI grouping

In the admin panel, widgets are grouped by their context label. The home page example above shows three groups: “Page” (the base context), “Feature 1”, “Feature 2”, and “Feature 3” — each containing its own “heading” and “body” fields.