Skip to main content

Creating content types and fields using a custom module in Drupal 7

Too Long?
See the abridged version

I was writing a custom module recently which used a custom content type or two. I wanted to make the module as reusable as possible but I also wanted to avoid including a feature inside of the module to add these content types.

I figured that if the module itself created the content types when installed, then someone could then customise them if need be, adding more fields and so on. If the content types came from a feature inside the module however, then customising them would probably override the feature. Stupid huh?

Yeah, well anyway, working this out took more googling than I anticipated, so I felt it warranted a good old blog post.

First off, I'll just run through a quick overview of what we are doing and the hooks we need to use:

Overview

Below is the file structure for our module and what each file is doing:

module_name/              
|
|-- module_name.info                 # Standard .info file stuff.
|-- module_name.module               # hook_node_info() declares content types.
|-- module_name.install              # hook_install() creates fields and field instances.
|-- module_name.field_base.inc       # Returns all the data for the fields.
|-- module_name.field_instance.inc   # Returns all the date for the field instances.

So there are a few files needed for this - and the .inc files could potentially contain a lot of data, depending on how many fields you are adding and how complex they are. Rather than writing this all out by hand however, like some kind of peasant, we can sneakily make the Features module generate a lot of this for us.

Luckily we can use use the the Features module to generate a lot of the code for this.

Making it easy

So the trick is to just go ahead and create the content type/s as you normally would, using the UI - adding any fields you might need and making sure the Display Settings are to your liking etc.

Once you are happy with all of this, make sure Features is enabled and create a new feature at admin/structure/features/create. Call it whatever you want, we are going to delete it anyway, and add the content type/s you just created then download or generate your feature.

Create the content type/s as you normally would, using the UI, and add them to a new feature - call it whatever you want, we are going to delete it anyway.

Don't enable the feature, just set it aside for now, we will grab some code from it in a bit.

Now we can start creating our custom module. Let's go through the files one by one:

module_name.info

So the first thing features can help us with is the dependencies - if you aren't sure which ones your content type/s need, you can grab them from the .info file of your feature - remembering to remove the dependency on features itself. Otherwise this is fairly stock standard.

name = Module Name
description = A description of our custom module.
core = 7.x
dependencies[] = entityreference
dependencies[] = image
dependencies[] = link
dependencies[] = text

module_name.module

Next up we need to add hook_node_info() to our .module file. Conveniently, we can also get this from our feature - in the feature_name.features.inc file. Copy all of that and pop it in your .module file, replacing feature_name with module_name of course.

/**
 * Implements hook_node_info().
 */
function module_name_node_info() {
  $items = array(
    'content_type_name' => array(
      'name' => t('Content type name'),
      'base' => 'node_content',
      'description' => t('Description of my content type.'),
      'has_title' => '1',
      'title_label' => t('Title'),
      'help' => '',
    ),
  );
  return $items;
}

module_name.install

In this file we need a hook_install(). In here we first call node_types_rebuild() to make sure our content types have been created. We then go through and adds all of our fields and field instances using field_create_field() and field_create_instance() respectively.

include_once 'module_name.field_base.inc';
include_once 'module_name.field_instance.inc';

/**
 * Implements hook_install().
 */
function module_name_install() {
  node_types_rebuild();
  foreach (_module_name_installed_fields() as $field) {
    field_create_field($field);
  }
  foreach (_module_name_installed_instances() as $fieldinstance) {
    field_create_instance($fieldinstance);
  }
}

We get the data for these fields by calling two custom functions we are about to create in the next two files - so they should be included at the top of the file.

module_name.field_base.inc

What you wanna do for this file is copy the contents of feature_name.features.field_base.inc into it, and change the function name from feature_name_field_default_field_bases to _module_name_installed_fields(). You should update the function comments as well.

You should wind up with something that looks a bit like this, but there will be more of it:

/**
 * Custom function generates field bases.
 */
function _module_name_installed_fields() {
  $field_bases = array();

  // Exported field_base: 'field_my_custom_field'
  $field_bases['field_my_custom_field'] = array(
    'active' => 1,
    'cardinality' => 1,
    'deleted' => 0,

.....

module_name.field_instance.inc

This file gets the same treatment as the last one really: copy the contents of feature_name.features.field_instance.inc into it, and change the function name from feature_name_field_default_field_instances to _module_name_installed_instances() updating the comments where necessary. It will look pretty similar to the last file.

Result!

That's it! Now that we have drained that feature of all of it's delicious code, it can now be discarded and our new module is complete!

Thanks to Features we made that up to one thousand times easier than it could have been had we typed all of that out manually. Way to go, Features!