Concrete CMS Custom Pages
August 7, 2025 · AI Conversations
—
### ✅ Step-by-step to Create List / Add / Edit Pages in a Custom Package
#### 1. **Create Dashboard Single Pages**
Inside your package’s `controller.php` file:
public function install()
{
$pkg = parent::install();
// Create single pages
ConcreteCorePageSingle::add('/dashboard/your_section/list', $pkg);
ConcreteCorePageSingle::add('/dashboard/your_section/add', $pkg);
ConcreteCorePageSingle::add('/dashboard/your_section/edit', $pkg);
}
—
#### 2. **Create Page Controllers**
Create a directory inside your package:
packages/your_package/controllers/single_page/dashboard/your_section/
Then create:
– `list.php`
– `add.php`
– `edit.php`
Example for `list.php`:
namespace ConcretePackageYourPackageControllerSinglePageDashboardYourSection;
use ConcreteCorePageControllerDashboardPageController;
use ConcreteCoreDatabaseConnectionConnection;
class List extends DashboardPageController
{
public function view()
{
$db = $this->app->make(Connection::class);
$items = $db->fetchAll("SELECT * FROM your_table ORDER BY id DESC");
$this->set('items', $items);
}
}
—
#### 3. **Create the View Templates**
Under:
packages/your_package/single_pages/dashboard/your_section/
Create:
– `list.php`
– `add.php`
– `edit.php`
Example for `list.php`:
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<h2>Item List</h2>
<a class="btn btn-primary" href="<?= URL::to('/dashboard/your_section/add') ?>">Add New</a>
<table class="table">
<thead>
<tr><th>ID</th><th>Title</th><th>Actions</th></tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= $item['id'] ?></td>
<td><?= h($item['title']) ?></td>
<td>
<a href="<?= URL::to('/dashboard/your_section/edit', $item['id']) ?>" class="btn btn-sm btn-secondary">Edit</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
—
#### 4. **Handle Form Submission (Add/Edit)**
In `add.php` and `edit.php` controller files, add logic like:
public function submit()
{
$token = $this->app->make('token');
if (!$token->validate('add_item')) {
$this->error->add(t("Invalid CSRF token."));
return;
}
$title = $this->post('title');
if (!$title) {
$this->error->add(t("Title is required."));
}
if (!$this->error->has()) {
$db = $this->app->make(Connection::class);
$db->insert('your_table', ['title' => $title]);
$this->flash('success', 'Item added successfully.');
return $this->redirect('/dashboard/your_section/list');
}
}
And in `add.php` view:
<form method="post" action="<?= $view->action('submit') ?>">
<?= $token->output('add_item') ?>
<div class="form-group">
<label>Title</label>
<input class="form-control" name="title" />
</div>
<button class="btn btn-success">Save</button>
</form>
—
### 🧠 Optional: Reuse Core Components
You can also extend existing Concrete CMS form helpers and dashboard page layout elements to keep your UI consistent.
—
If you share the exact model or table name you're using, I can give you full working code with real values (e.g., `Feed`, `Product`, etc.).
Here’s how to do it **the Concrete CMS way**, so your pages look and behave like native dashboard pages:
—
## ✅ 1. Use Dashboard Layout Structure
Wrap your views in Concrete CMS's dashboard panel styles.
### Example: `list.php` View
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<?php
use ConcreteCoreViewView;
use ConcreteCoreSupportFacadeUrl;
use ConcreteCoreApplicationServiceUserInterface;
$app = ConcreteCoreSupportFacadeApplication::getFacadeApplication();
/** @var UserInterface $ui */
$ui = $app->make(UserInterface::class);
?>
<div class="ccm-ui">
<div class="dashboard-header-buttons">
<a href="<?= Url::to('/dashboard/your_section/add') ?>" class="btn btn-primary"><?= t('Add New') ?></a>
</div>
<table class="table table-striped">
<thead>
<tr>
<th><?= t('ID') ?></th>
<th><?= t('Title') ?></th>
<th><?= t('Actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= $item['id'] ?></td>
<td><?= h($item['title']) ?></td>
<td>
<a href="<?= Url::to('/dashboard/your_section/edit', $item['id']) ?>" class="btn btn-sm btn-secondary">
<?= t('Edit') ?>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
—
## ✅ 2. Use Concrete CMS Form Helpers for Consistency
### Example: `add.php` View
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<?php
use ConcreteCoreFormServiceForm;
use ConcreteCoreSupportFacadeUrl;
$form = app(Form::class);
?>
<div class="ccm-ui">
<form method="post" action="<?= $view->action('submit') ?>">
<?= $token->output('add_item') ?>
<fieldset>
<legend><?= t('Add Item') ?></legend>
<div class="form-group">
<?= $form->label('title', t('Title')) ?>
<?= $form->text('title') ?>
</div>
<div class="form-group">
<button class="btn btn-success"><?= t('Save') ?></button>
<a href="<?= Url::to('/dashboard/your_section/list') ?>" class="btn btn-secondary"><?= t('Cancel') ?></a>
</div>
</fieldset>
</form>
</div>
—
## ✅ 3. Add Flash Messages & Error Handling
Concrete CMS uses the `message()` function or `$error`, `$message` in controller to pass alerts.
### In your controller:
public function view()
{
$this->set('message', $this->flash('success'));
}
### In your view:
<?php if (isset($message)) { ?>
<div class="alert alert-success"><?= h($message) ?></div>
<?php } ?>
<?php if (isset($error) && $error->has()) {
echo $error->output();
} ?>
—
## ✅ 4. Use Core Assets
Concrete CMS uses Bootstrap, jQuery, and its own styles. Make sure your layout is wrapped with:
<div class="ccm-ui">
<!-- Your dashboard page content -->
</div>
This ensures everything inside matches the admin panel’s design system.
—
## ✅ 5. Optional: Extend Core Controllers
If you want to adopt native logic (e.g. pagination, search filters), you can extend:
use ConcreteCorePageControllerDashboardPageController;
class List extends DashboardPageController
{
// your logic
}
—
Would you like a **full working list/add/edit page set** with styling and data fetching based on a specific table or model like `Feeds`? I can scaffold that for you right away.
—
## ✅ Use `$this->set('headerMenuButton')` in Your Controller
Concrete CMS dashboard pages support a **header menu area**. You can inject buttons into it using a controller variable named `headerMenuButton`.
### 🧩 Step-by-Step:
### 1. **In your page controller** (`list.php`, `edit.php`, etc.):
use ConcreteCorePageControllerDashboardPageController;
use ConcreteCoreSupportFacadeUrl;
class List extends DashboardPageController
{
public function view()
{
$url = Url::to('/dashboard/your_section/add');
$this->set('headerMenuButton', [
[
'url' => $url,
'label' => t('Add Item'),
'icon' => 'plus',
'class' => 'btn-primary',
]
]);
}
}
—
### 2. **In your `list.php` view**, **you don’t need to add the button manually**
Concrete automatically renders the `headerMenuButton` in the page’s header **above the title**, just like core pages.
But if you are overriding the dashboard layout, and want to render it manually (not recommended unless you’ve customized the layout), you can do:
<?php if (isset($headerMenuButton)) {
foreach ($headerMenuButton as $button) { ?>
<div class="float-end">
<a class="btn <?= $button['class'] ?>" href="<?= $button['url'] ?>">
<i class="fas fa-<?= $button['icon'] ?>"></i> <?= $button['label'] ?>
</a>
</div>
<?php }
} ?>
—
## 🧪 Example: Full Controller with Header Button
namespace ConcretePackageYourPackageControllerSinglePageDashboardYourSection;
use ConcreteCorePageControllerDashboardPageController;
use ConcreteCoreSupportFacadeUrl;
use ConcreteCoreDatabaseConnectionConnection;
class List extends DashboardPageController
{
public function view()
{
$db = $this->app->make(Connection::class);
$items = $db->fetchAll("SELECT * FROM your_table");
$this->set('items', $items);
$this->set('headerMenuButton', [
[
'url' => Url::to('/dashboard/your_section/add'),
'label' => t('Add Item'),
'icon' => 'plus',
'class' => 'btn-primary',
]
]);
}
}
—
## 🎯 Result
Your button will appear **next to the page title** in the standard Concrete CMS dashboard header section — styled and placed just like core pages (e.g., “Add Page” in Pages, “Add User” in Members).
—
Let me know if you want to add **multiple buttons**, a **dropdown**, or customize the layout further — I can give you full code for that too.
You're referring to the **default Concrete CMS 9 dashboard page header**, where the **title appears with a bookmark icon** and **no visible buttons**, and you want to **add a button next to that heading** (in the top-right corner of the header bar — just like “Add User” or “Add Page” buttons on core pages).
—
## ✅ The Correct Concrete CMS 9 Way
Concrete CMS 9 uses a view variable called `headerMenuButton` (or `headerMenuButtons`) which you **set in the controller** — and Concrete will **automatically render the buttons in that header bar** (next to "Shopping Feed" in your case).
—
## 🧩 Implementation
### ✅ 1. Set the Button in Your Controller
use ConcreteCorePageControllerDashboardPageController;
use ConcreteCoreSupportFacadeUrl;
class ShoppingFeed extends DashboardPageController
{
public function view()
{
$this->set('headerMenuButton', [
[
'url' => Url::to('/dashboard/shopping_feed/add'),
'label' => t('Add Feed'),
'icon' => 'plus',
'class' => 'btn-primary',
]
]);
}
}
> ✅ Make sure your controller extends `DashboardPageController`.
—
### ✅ 2. That’s it! **You don’t need to add the button in the view**
Concrete CMS will automatically place the button in the top-right of the default dashboard header (next to “Shopping Feed”).
—
## ✅ Multiple Buttons?
If you want multiple buttons:
$this->set('headerMenuButton', [
[
'url' => Url::to('/dashboard/shopping_feed/add'),
'label' => t('Add Feed'),
'icon' => 'plus',
'class' => 'btn-primary',
],
[
'url' => Url::to('/dashboard/shopping_feed/settings'),
'label' => t('Settings'),
'icon' => 'cog',
'class' => 'btn-secondary',
]
]);
—
## ✅ Bonus: Ensure You’re Using Concrete’s Default Header
Make sure:
– You are **not overriding the layout** (`single_pages/dashboard/…/yourpage.php`)
– You are **not using a custom view template** that omits the Concrete CMS “ structure
The layout should start like:
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<div class="ccm-ui">
<!-- Concrete CMS will inject the page title and headerMenuButton here -->
—
Let me know if your button still doesn’t show up — I can help you verify if it’s a controller or view override issue.