Create the Empty Package Code

Concrete CMS packages are placed in the packages/ directory in the web root. Here’s our Dreamrs web root:

See that packages/ directory? Let’s create our package there. Since we want our package to have the handle dreamrs, we’ll make that our directory name. The handle and directory name for packages must match.

cd public/packages/  
mkdir dreamrs

Icon

Next, we need to give the package an icon, with a PNG file named icon.png. Historically, we’ve advocated for files that 97x97 in dimensions, because these dimensions work well with our marketplace UI. But really, any square icon will do (although ideally a little larger than 97x97 is best, because it will look better on high-DPI displays.)

I’m going to use an icon derived from the logo included with the Dreamrs theme. It’s a little small, so I’m going to add some padding within the image.

Place this file within the new dreamrs folder, and name it icon.png.

Controller

Next, we need to create the controller for the package. This file contains the information needed for Concrete to identify the package, install it, uninstall it, and optional custom code that this package adds to the Concrete startup routine. All other logic relating to what your package actually does should be located elsewhere.

First, create a file named controller.php at the root of the package.

cd dreamrs  
touch controller.php

Edit this file, and first give it the required namespace.

<?php  
namespace Concrete\Package\Dreamrs;

How does this namespace work? It’s derived from the handle of your package. Whatever your package is named, the third segment of this namespace is the PascalCase version of whatever the handle is. So in our case, dreamrs becomes Concrete\Package\Dreamrs. Is your package directory “document_library”? Then the namespace would be Concrete\Package\DocumentLibrary. Is it ecommerce? Concrete\Package\Ecommerce. If it’s e_commerce, then it’d be Concrete\Package\ECommerce. In this case, capitalization matters.

Next, let’s add this line, which we should be adding to the top of any PHP file run by Concrete:

defined('C5_EXECUTE') or die('Access Denied.');

This line makes it impossible to browse directly to the PHP file in question, which is important, since packages are within the web root.

Now let’s import the base package class that our package controller must extend.

use \Concrete\Core\Package\Package;

We’ll be extending this class. Now we will create the first version of our controller:

class Controller extends Package
{
    protected $pkgHandle = 'dreamrs';
    protected $appVersionRequired = '8.5.1';
    protected $pkgVersion = '0.6';

    public function getPackageDescription()
    {
        return t('Adds the Dreamrs theme to your website.');
    }

    public function getPackageName()
    {
        return t('Dreamrs');
    }

    public function install()
    {
        $pkg = parent::install();
        // Our custom package installation code will go here.
    }

    public function upgrade()
    {
        parent::upgrade();
        // Our custom package upgrade code will go here.
    }
}

Let’s walk through this. Controller is the required name for this file. It’s upper-cased -- in fact, all Concrete classes are upper-cased, even if the files that they are stored in are not always upper-cased. It extends the Package class mentioned earlier, which all package controllers must do.

Next we define three required protected variables. $pkgHandle here is easy: it must match the directory name of the package exactly. $appVersionRequired refers to the minimum version of Concrete that this package supports. This doesn’t have to conform to a specific released version of Concrete -- it’s compared against the installed version using PHP’s version_compare(). Don’t want your package running on any version of Concrete before version 8? Don’t worry about making your $appVersionRequired equal to 5.7.5.11 or something similar; just make it equal to 7.9.9. For our package I’m going to make Dreamrs require Concrete 8.5.1, the latest stable version of Concrete as of this writing.

Finally, we have $pkgVersion, which denotes the version number of this package. This can be anything. Since we’re starting development work on this package I’ve picked a pre-release version number, but this isn’t required. You can start on version 1.0, 2.0, 3.5 -- whatever you’d like. Just note that once you start development, you’ll be incrementing version numbers from there.

Next, we have two methods that provide more data about the package to Concrete. getPackageDescription() and getPackageName() are both used to display information about the package in the Dashboard. (Note: make sure to encase these in the t() function if you want this text to be translatable.)

Finally, in the install() method defines what gets installed when this package is installed, and the upgrade() method defines what happens when the package is upgraded. These replace their parent methods on the base Package class, and must still run the parent methods when they run.

With these icon and controller files in place, we can install the package! Login to Concrete and proceed to Dashboard > Extend Concrete > Add Functionality.

To install it, just click the Install button. But if you do that right now you’ll install a package that doesn’t actually contain anything! Let’s add some functionality to our package first, before we install it.