building-singlepages
npx machina-cli add skill MacareuxDigital/concretecms-skills/building-singlepages --openclawBuilding Single Pages in Packages
Single pages are unique pages within a Concrete CMS site that are typically used for administrative dashboards, custom application logic, or specific landing pages that don't follow the standard page type/template system.
Creating a Single Page in a Package
1. File Structure
- Controller:
packages/your_package/controllers/single_page/your_page.php - View:
packages/your_package/single_pages/your_page.php
The path to the single page is relative to these directories. For example, for a page at /dashboard/my_module/settings:
- Controller:
packages/your_package/controllers/single_page/dashboard/my_module/settings.php - View:
packages/your_package/single_pages/dashboard/my_module/settings.php
2. Implementation
Controller
The controller should extend Concrete\Core\Page\Controller\DashboardPageController for dashboard pages, or Concrete\Core\Page\Controller\PageController for frontend pages.
<?php
namespace Concrete\Package\YourPackage\Controller\SinglePage\Dashboard\MyModule;
use Concrete\Core\Page\Controller\DashboardPageController;
use Concrete\Core\Package\PackageService;
use Concrete\Core\Config\Repository\Liaison;
class Settings extends DashboardPageController
{
/**
* Get package-specific configuration repository.
*/
protected function getConfig(): ?Liaison
{
/** @var PackageService $packageService */
$packageService = $this->app->make(PackageService::class);
$package = $packageService->getClass('your_package_handle');
if ($package) {
return $package->getFileConfig();
}
return null;
}
public function view()
{
$config = $this->getConfig();
$settingValue = $config->get('my_module.setting_key', 'default');
$this->set('settingValue', $settingValue);
}
public function save()
{
if (!$this->token->validate('save_settings')) {
$this->error->add($this->token->getErrorMessage());
}
if (!$this->error->has()) {
$config = $this->getConfig();
$config->save('my_module.setting_key', $this->post('settingValue'));
$this->flash('success', t('Settings saved successfully.'));
return $this->buildRedirect($this->action('view'));
}
$this->view();
}
}
View
The view is a standard PHP file. For dashboard pages, it's typically wrapped in the dashboard theme.
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<form method="post" action="<?= $view->action('save') ?>">
<?= $token->output('save_settings') ?>
<div class="form-group">
<?= $form->label('settingValue', t('My Setting')) ?>
<?= $form->text('settingValue', $settingValue) ?>
</div>
<div class="ccm-dashboard-form-actions-wrapper">
<div class="ccm-dashboard-form-actions">
<button class="float-end btn btn-primary" type="submit"><?= t('Save') ?></button>
</div>
</div>
</form>
3. Installation
In your package controller's install() or upgrade() method:
use Concrete\Core\Page\Single as SinglePage;
public function install()
{
$pkg = parent::install();
$page = SinglePage::add('/dashboard/my_module/settings', $pkg);
if ($page) {
$page->update(['cName' => 'My Settings']);
}
return $pkg;
}
Best Practices
- Configuration: Use
$package->getFileConfig()to get a configuration repository specific to your package. This avoids prefixing all keys withpackage.your_package_handle.. - Security: Always use CSRF tokens (
$token->output(),$token->validate()) in forms. - Redirection: For parent dashboard pages that should just redirect to a child, use
$this->buildRedirectToFirstAccessibleChildPage()in theview()method of the parent controller and provide an empty view file. This approach ensures that the user is redirected only to a page they have permission to view.
Source
git clone https://github.com/MacareuxDigital/concretecms-skills/blob/main/building-singlepages/SKILL.mdView on GitHub Overview
Single pages are unique pages used for admin dashboards, custom application logic, or landing pages that don’t follow the standard page type/template system. This skill covers the file structure, controller/view patterns, and installation steps to add such pages from a package.
How This Skill Works
Place a controller under packages/your_package/controllers/single_page/your_page.php that extends DashboardPageController for dashboards or PageController for frontend pages, and a corresponding view at packages/your_package/single_pages/your_page.php. Use a package-specific config repository via getFileConfig to read/write settings, implement view() to load data, and save() with token validation to persist changes. Register the page during install with SinglePage::add('/dashboard/your_module/settings', $pkg) and optionally rename it to reflect its purpose.
When to Use It
- Add a dashboard settings page for a Concrete CMS package
- Create a frontend page that handles custom application logic outside standard templates
- Provide package-scoped configuration accessible from a single page
- Automatically install a single page during package install or upgrade
- Create admin-facing pages that don’t map to standard page types
Quick Start
- Step 1: Create the controller at packages/your_package/controllers/single_page/your_page.php and the view at packages/your_package/single_pages/your_page.php
- Step 2: Extend DashboardPageController for dashboard pages or PageController for frontend pages; implement view() and save() with proper config access and token checks
- Step 3: Install the page by adding it in the package install() with SinglePage::add('/dashboard/your_module/settings', $pkg) and rename it if needed
Best Practices
- Configuration: Use the package's file config repository via getFileConfig to avoid key prefixing
- Controller choice: Extend DashboardPageController for dashboard pages or PageController for frontend pages
- Directory structure: Place controllers in controllers/single_page and views in single_pages following the naming convention
- Security: Implement token validation in post handlers (e.g., save) to protect actions
- Installation: Use SinglePage::add('/dashboard/your_module/settings', $pkg) and set a clear cName
Example Use Cases
- Dashboard page at /dashboard/my_module/settings implemented by a Settings controller extending DashboardPageController
- getConfig() retrieves package-specific configuration using getFileConfig and stores it into a local variable
- View form saves settings via a save() action with token validation and a success flash message
- During install, the page is created with SinglePage::add('/dashboard/my_module/settings', $pkg) and labeled My Settings
- cName is updated to 'My Settings' to provide a friendly label in the dashboard