Saturday, February 4, 2017

Ctools: custom content type plugin

Ctools content types are an alternative to standard Drupal blocks. They are more comfortable and powerfull than blocks. Ctools content type plugins also known as panel panes. In this post you will learn how to create a configurable ctools content pane plugin.

For example we will create a custom content type plugin that will render a user name and user email. Let's start.

1. Create a module which will contain all defined plugins. File example_module/
name = Example module
description = Provides ctools content type plugin
core = 7.x

; Since we're using ctools api for creating
; ctools plugins we have to define this
; dependency.
dependencies[] = ctools
File example_module/example_module.module:

 * Implements hook_ctools_plugin_directory().
 * Integrate our module with Ctools. Tell where
 * Ctools have to search plugins. Usually a place
 * where developers store all defined plugins is
 * "module_name/plugins" directory. Variable
 * $plugin contains a name of a plugin type. It can be
 * content_types, context, argument, cache, access. So all
 * of listed plugin types should be located in corresponding
 * subdirectories (plugins/content_types if you create a content
 * type plugin).
function example_module_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools' && !empty($plugin)) {
    return "plugins/$plugin";
2. Create a file which will contain plugin definition. File example_module/plugins/content_types/

 * @file
 * File with plugin definition.

$plugin = [
  // Pane title.
  'title' => t('Example pane'),
  // Pane description. 
  'description' => t('Example pane'),
  // Tell ctools that it's not a child of another pane.
  'single' => TRUE,
  // You can categorize all defined panes by
  // defining a category name for a pane.
  'category' => [t('Example panes')],
  // A machine name of your pane.
  'content_types' => ['example_content_type_plugin'],
  // Function that will render markup of this pane.
  'render callback' => 'example_content_type_plugin_render',
  // A required context for this pane. If there is no required
  // context on a panel page than you will not be able to add
  // this pane to the panel page.
  'required context' => new ctools_context_required(t('User'), 'user'),
  // Edit form constructor callback.
  'edit form' => 'example_content_type_plugin_edit_form',
  // Default values for edit form inputs.
  'defaults' => [
    'example_option' => 'example_value',
A file name is just a combination of a module name and plugin name joined by a dot. But actually you can name it as you want. Please note that you can omit 'required context' property if you want to be able to add this pane to all existing panel pages. If you want to add your pane only to pages that have a context just use required context like described above. If you want to add your pane to all pages (but in some cases you need a context) just define it as optional:
'required context' => new ctools_context_optional(t('User'), 'user'),
Let's define plugin configuration form:
 * Configuration form for a pane.
function example_content_type_plugin_edit_form($form, $form_state) {
  $form['example_option'] = [
    '#type' => 'textfield',
    '#title' => t('Example text option'),
    '#default_value' => $form_state['conf']['example_option'],
    '#required' => TRUE,

  return $form;

 * Configuration form submit.
function example_content_type_plugin_edit_form_submit($form, &$form_state) {
  foreach ($form_state['plugin']['defaults'] as $key => $default) {
    $form_state['conf'][$key] = $form_state['values'][$key];
The last thing we shoud define is render callback:
 * Render callback.
function example_content_type_plugin_render($subtype, $conf, $args, $context) {
  $block = new stdClass();

  // Variable $context contains a context grabbed by a panel page.
  // In this case we will put this pane on user/%uid page which
  // has a user context.
  if (!empty($context->data)) {
    $user_from_context = $context->data;

    // Just output user's name and email.
    $block->content = 'User name: ' . $user_from_context->name . '</br>User mail: ' . $user_from_context->mail . '</br>Example option: ' . $conf['example_option'];

  return $block;
3. Clear cache. Edit user view panel page and put this pane into some region:

Example ctools content type pane

Enter settings for a pane:

Configuration form

Open user/1 page and here's a result:

Rendered example ctools content type pane

The above is true for:

  • Ctools: 7.x-1.12
  • Panels: 7.x-3.8

Key notes:


  1. Very useful article. I always wanted to learn this.
    A slightly off topic question. Which theme you are using in the screenshot(for user/1 page) you provided above? Or is that a module giving such a clean defined interface? It looks like drupal 8 but you have specifically labled it as drupal 7 so curious.