How to Create Ctools Content Types in Drupal 7

Ctools content types, not to be confused with general content types, are Panels version of blocks. Another name for them are panel panes, but just think of them as more powerful blocks. Because they are Ctools plugins, the code to implement them is easy to write and maintain. Each plugin has their own file, whereas with blocks, you have to cram everything into hook_block_view().

They also integrate with Ctools contexts, which means you get a context object passed directly in the content type and most of the time this is an entity object. No longer will you have to write code which grabs the entity ID from the URL and load the entity.

In this post, we'll create a Ctools content type which displays a "Read more" link, with the link text being configurable. You'll learn how to create a plugin and a settings form for it. Basic PHP knowledge is required if you want to follow along.

Step 1: Integrate your Module with Ctools

Go into your custom module and add the following hook:

 * Implements hook_ctools_plugin_directory().
function morpht_ctools_content_type_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools' && !empty($plugin)) {
    return "plugins/$plugin";

This hook is used to tell Ctools where plugins are stored. It'll look for the content type plugin in "plugins/content_types".

Step 2: Create Plugin File

Create a file called "" and place it in "plugins/content_types", create these folders if required. Once created, the path to the file should be "plugins/content_types/".

Step 3: Describe Plugin

Open up "" and add the following bit of code at the top:

$plugin = array(
  'title' => t('Read more link'),
  'description' => t('Displays a read more link.'),
  'single' => TRUE,
  'content_types' => array('read_more'),
  'render callback' => 'read_more_content_type_render',
  'required context' => new ctools_context_required(t('Node'), 'node'),
  'edit form' => 'read_more_content_type_edit_form',
  'category' => array(t('Morpht Examples'), -9),

The $plugin array is used to describe the plugin. Let's break down this array:

  • 'title': This is the title of the plugin. It'll be used in the Panels administration area.
  • 'description': This is used as the description of the plugin, it's only used in the Panels administration area.
  • 'single': Content types have a concept called subtype, which is an advanced feature and beyond the scope of this tutorial. For basic content types leave this as TRUE. If you want to learn about subtypes check this Drupal Answers post.
  • 'content_types': This is the machine name of the plugin.
  • 'render callback': This is a callback to a function which will be used to render the content type.
  • 'required context': This tells Ctools which context is required for this content type. By using ctools_context_required(t('Node'), 'node'), this content type will only be available for node entities.
  • 'edit form': This is a callback for the edit form. Please note, this edit form must be implemented if you define a 'required context' key.
  • 'category': This allows you to add a content type into a Panels category.

Step 4: Settings Form

The "Read more" link for the content type needs to be configurable. Creating a configuration form for it is pretty easy. In the $plugin array we added "read_more_content_type_edit_form" as our form.

Go ahead and add the following bit of code after the $plugin:

 * Ctools edit form.
 * @param $form
 * @param $form_state
 * @return mixed
function read_more_content_type_edit_form($form, &$form_state) {
  $conf = $form_state['conf'];
  $form['read_more_label'] = array(
    '#type' => 'textfield',
    '#title' => t('Label'),
    '#description' => t('The read more label.'),
    '#default_value' => !empty($conf['read_more_label']) ? $conf['read_more_label'] : 'Read more',
  return $form;

 * Ctools edit form submit handler.
 * @param $form
 * @param $form_state
function read_more_content_type_edit_form_submit($form, &$form_state) {
  foreach (array('read_more_label') as $key) {
    $form_state['conf'][$key] = $form_state['values'][$key];

I won't go into too much detail about this because it's a basic Drupal form. The read_more_content_type_edit_form() function is the form and read_more_content_type_edit_form_submit() is the submit handler.

Important: If you want to use the 'required context' key, then you must implement an edit form. Thanks Sam152 for the heads up.

Step 5: Render Function

The last piece of the puzzle is the render function. This is the main function which will be used to render the content type. It is the equivalent of using hook_block_view() if you're creating a block.

Add the following function into the plugin:

 * Render callback function.
 * @param $subtype
 * @param $conf
 * @param $args
 * @param $context
 * @return stdClass
function read_more_content_type_render($subtype, $conf, $args, $context) {
  $node = $context->data;
  $block = new stdClass();

  $block->content = l(t("@read_more", array('@read_more' => $conf['read_more_label'])), 'node/' . $node->nid);

  return $block;

The HTML that you want to render needs to be added to the "content" property on the $block object, e.g, $block->content.

The "Read more" label from the settings form can be accessed via the $conf variable. And finally, you can get the node entity from the $context via $context->data.


Now that you know how powerful Ctools content types are I hope this opens your eyes to new possibilities. Best of all, content types can also be used with Display Suite. This means that you can write your content type once and use it with Panels or Display Suite.