Tutorial: Building Your First Site
To understand the core philosophy of the system, we will build a simple "Project" section.
In Pragma CMS, data, routing, and presentation are strictly decoupled. Building a page always follows this logical workflow:
- Model: Define the structure (Content Types).
- Data: Write the content and upload files (Entries & Media).
- Route: Assign a URL (Pages).
- View: Display the data (Controllers & Templates).
Step 1: Defining the Model (Content Types)
Before writing articles, we need to define their structure.
Go to Administration > Content Types and create a new type called "Project".
Content Types act as the blueprint for your data.
Since every project is different, we want structured data. Add these fields:
Text: Client NameMedia: Featured ImageBuilder: Case Study Content
By defining explicit fields, you ensure your content editors always output perfectly structured data.
Step 2: Adding Data (Entries & Media)
Now that your blueprint exists, go to Content > Articles and create your first Entry.
Entries are the actual pieces of content. Fill in your title, text, and upload an image. Pragma CMS handles revisions natively, allowing you to draft, preview, and restore previous versions of your content effortlessly.
Managing Files (Media)
When a file is uploaded, its original version is stored in the media/source folder to preserve an unaltered copy.
For images, the CMS then automatically generates optimized variants (compressed jpg, webp, and avif) in various dimensions (thumbnail, medium, large, xlarge and social).
Public versions of media files are then made available in their respective folders (media/images, media/documents, or media/videos). For images, these are generated variants, while for documents and videos, this corresponds to a direct copy of the original file.
Step 3: Setting up the Route (Pages)
Now, we need to make these projects visible at /portfolio.
- Go to
Pages > Create New Page. - Type: Choose
Dynamic Page. (Refer to the Page Creation documentation for details on Static vs Dynamic vs Builder). - Route: Set it to
/portfolio. - Content Type: Select
Project.
Step 4: Rendering the View (Controllers & Templates)
The final step is to display the data on the front-end. To do this, we use a Page Controller and a Template.
htmlspecialchars()).Preparing Data (Controller Layer)
The global $page object is automatically populated by the router (router.php). You just need to prepare the variables for your view.PHP Method Example (Blog List Route):
// Retrieve SEO and Page data
$page->viewData["title"] = $pageData["title"];
$page->metaTitle = $pageData["meta_title"];
$page->metaDescription = $pageData["meta_description"];
// Inject the Entry data (Fields)
$page->entry = $entry;
// Build layout shell
$page->header = renderHeader($page);
$page->footer = renderFooter($page);
// Render the specific view into the main layout
$page->main = render_template("blog/list.php", $page);
// Output the final base layout
echo render_template("base.php", $page);
The Template Layer (HTML Output)
Templates are strictly used for display. Use the render_template() function to locate and load them.
<section id="intro">
<div class="container">
<h1><?= htmlspecialchars($page->entry["fields"]["hero-title"]); ?></h1>
<p><?= htmlspecialchars($page->entry["fields"]["hero-subtitle"]); ?></p>
</div>
<div id="hero-cover">
<!-- The render_image() helper generates a full <picture> tag with WebP/AVIF support -->
<?php render_image($page->entry["fields"]["hero-image"]["id"], 'large'); ?>
</div>
</section>
Using Twig (blog/list.twig):
If using Twig, the controller logic remains the same (render_template('blog/list.twig', ['page' => $page])), but the view is rendered like this:
{{ BreadcrumbManager.renderBreadcrumbs() }}
<h1>{{ page.viewData.title|e }}</h1>
<div class="hero-cover">
{{ renderImage(page.entry.fields['hero-image'].id, 'large') }}
</div>
<ul>
{% for entry in page.entries %}
<li><a href="{{ entry.url|e }}">{{ entry.title|e }}</a></li>
{% endfor %}
</ul>
(Note: In Twig, the |e or |escape filter is highly recommended to secure HTML output).
Templates Location
To render a template, use render_template().
If you need to render a template from an extension, prefix it with the extension name (e.g., my-extension/blog/list.php).
The find_template_file() core function will automatically locate the file. The global HTML structure is defined in base.php and should wrap all page renders.
Images Rendering
To render a fully optimized responsive image, use:
render_image(int $mediaId, string $sizeSuffix = 'large', array $attributes = [])
This core function outputs an HTML <picture> tag, automatically serving modern formats (AVIF, WebP) with the correct srcset when available.