Packages Basics
Package Format
Concrete CMS packages bundle functionality for easy distribution, installation, and uninstallation, usable with or without connecting to concretecms.org.
Package Structure
Packages include:
- An outer directory
- Optional
icon.png
controller.php
file defining:- Package version
- Installation and uninstallation routines
- Custom startup code for Concrete
Filesystem structure in a package mirrors the concrete/
directory for core-installed components. For example, a package with a theme, two custom block types, and a dashboard page might be organized as shown:
Development and Deployment
Initially, develop and install functionality (like block types or themes) in the application/
folder using the Concrete dashboard. Then, move it to a package folder and add code to controller.php
for package-based installation and uninstallation. This approach ensures automatic installation and uninstallation of components with the package, and enables running custom code when the package is installed on a Concrete site. This makes packages suitable for delivering both simple elements and complex event-based functionality.
Package Creation
Directory Name
For package creation in Concrete CMS:
- Use lowercase, underscore-separated names for the outer directory.
- Example: "Hello World" becomes "hello_world".
Icon
- Include an
icon.png
at the root of the package. - Any square size works, but typically 97px by 97px. Larger than 200px is unnecessary.
Controller
Every package must have a controller.php
in its base directory, handling package identification, installation, and uninstallation. Place additional logic elsewhere. Example structure:
<?php
namespace Concrete\Package\DocumentLibrary;
defined('C5_EXECUTE') or die('Access Denied.');
use Concrete\Core\Package\Package;
use Concrete\Core\Block\BlockType\BlockType;
class Controller extends Package
{
protected $pkgHandle = 'document_library';
protected $appVersionRequired = '5.7.4.3b1';
protected $pkgVersion = '1.0';
public function getPackageDescription()
{
return t('Adds a searchable file library to a page.');
}
public function getPackageName()
{
return t('Document Library');
}
public function install()
{
$pkg = parent::install();
BlockType::installBlockType('document_library', $pkg);
}
}
- Namespace should match the package directory name, camelCased (e.g.,
document_library
→Concrete\Package\DocumentLibrary
). - Add
defined('C5_EXECUTE') or die('Access Denied.');
for security. - Import classes outside the package namespace as needed.
- Controller class named
Controller
, extendingConcrete\Core\Package\Package
. - Include
$pkgHandle
,$appVersionRequired
, and$pkgVersion
. - Implement
getPackageDescription()
andgetPackageName()
for Dashboard display. - Define
install()
method for installation actions.
Important: Namespace and directory name must align; mismatches result in 'Unknown Package' and 'Broken Package' errors.
Modifying Concrete CMS Startup with Packages
Packages in Concrete CMS can add custom code to the startup routine. For example, to track page views, you can use the on_page_view
event:
\Events::addListener(
'on_page_view',
function ($e) {
$c = $e->getPageObject();
// Actions with the current page object
});
To integrate this into the startup, add it to the on_start()
method in controller.php
:
public function on_start()
{
\Events::addListener(
'on_page_view',
function ($e) {
$c = $e->getPageObject();
// Actions with the current page object
}
);
}
This code will execute whenever Concrete runs, provided the package is installed.
Handling Package Upgrades
Concrete CMS packages can handle ongoing code updates using version numbers. For example, consider a package installing a theme in version 1.0:
namespace Concrete\Package\CustomTheme;
defined('C5_EXECUTE') or die('Access Denied.');
use \Concrete\Core\Package\Package;
use \Concrete\Core\Page\Theme\Theme;
class Controller extends Package
{
protected $pkgHandle = 'custom_theme';
protected $appVersionRequired = '5.7.4';
protected $pkgVersion = '1.0';
// ... Additional methods
public function install()
{
$pkg = parent::install();
Theme::add('custom_theme', $pkg);
}
}
To add a block type in a later version:
Import
BlockType
:use \Concrete\Core\Block\BlockType\BlockType;
Modify
install()
to include the block type:public function install() { $pkg = parent::install(); Theme::add('custom_theme', $pkg); BlockType::installBlockTypeFromPackage('custom_block_type', $pkg); }
Update
$pkgVersion
for the new version (e.g.,1.2
).Implement an
upgrade()
method:public function upgrade() { parent::upgrade(); $pkg = Package::getByHandle('custom_theme'); $bt = BlockType::getByHandle('custom_block_type'); if (!is_object($bt)) { BlockType::installBlockTypeFromPackage('custom_block_type', $pkg); } }
This approach allows for seamless upgrades without data loss. Concrete CMS will notify administrators of available updates, and the upgrade()
method handles adding new features while checking for existing components to avoid duplicates.
Custom Code in Concrete CMS Packages
Packages in Concrete CMS can include custom PHP code. For instance, instead of directly adding code to the on_start()
method, it's more efficient to use a separate script. Here's an example with a Bootstrapper
class in the FooCorp\WidgetManager
namespace:
public function on_start()
{
$startup = new \FooCorp\WidgetManager\Bootstrapper();
$startup->boot();
}
To ensure Concrete finds this class, use PHP autoloading in the package controller:
protected $pkgAutoloaderRegistries = array(
'src/FooCorp/WidgetManager' => '\FooCorp\WidgetManager'
);
This registers the FooCorp\WidgetManager
namespace, directing it to the src/FooCorp/WidgetManager
directory in your package. Classes in this directory will be autoloaded based on their namespace and class name matching the filesystem structure.
Legacy Namespace Support
Before Concrete version 5.7.5.10, a namespace like Concrete\Package\WidgetManager\Src
was automatically created. This legacy support still exists in version 8 but is disabled if you use $pkgAutoloaderRegistries
or your package requires Concrete 8.0 or higher.
Extending Core Classes
Core Concrete objects can be extended in packages. For example, a package with the handle 'spam_stopper' that installs an 'akismet' anti-spam plugin would have a class:
Concrete\Package\SpamStopper\Antispam\AkismetController
This class would automatically be sourced from:
packages/spam_stopper/src/Concrete/Antispam/AkismetController
To know which items can be extended this way, refer to the Developer Documentation or look for overrideable_core_class()
in the Concrete source code.