fromSeptember 2013
Article:

Drupal 7 Content Types from Code

Building More and Clicking Less
2

One key feature of Drupal 7 that makes it one of the most flexible content management frameworks available, is the ability for administrators to build new types of content – beyond the two built into a standard installation of Drupal 7, “Article” and “Basic page”. Content types are typically created in the administration section of the website, clicking through a series of pages, and manually entering information such as the names of new fields. This is sufficient for most situations, but can become tedious and error-prone. There are several advantages to automating this process: You could define new content types for your own use without having to step through all the screens and mouse clicks. You could define them for use by other people and on other websites without having to document the steps. It would expand your module-writing capabilities, since oftentimes module functionality calls for one or more custom content types. On a related note, this can be valuable in setting up testing harnesses (e.g., in test/modules/node/node.test, the class NodeWebTestCase).

Fortunately, Drupal allows for the programmatic building of content types, using its Fields application programming interface (API). We have already noted two useful examples, "Article" and "Basic page", which we can build upon.

Consult the Core

During a standard installation, Drupal runs the PHP code in the file profiles/standard/standard.install, which consists of a single function, standard_install(). It includes the code to create the two aforesaid content types:

  $types = array(
    array(
      'type' => 'page',
      'name' => st('Basic page'),
      'base' => 'node_content',
      'description' => st("Use basic pages for your static content, such as an 'About us' page."),
      'custom' => 1,
      'modified' => 1,
      'locked' => 0,
    ),
    array(
      'type' => 'article',
      'name' => st('Article'),
      'base' => 'node_content',
      'description' => st('Use articles for time-sensitive content like news, press releases or blog posts.'),
      'custom' => 1,
      'modified' => 1,
      'locked' => 0,
    ),
  );

  foreach ($types as $type) {
    $type = node_type_set_defaults($type);
    node_type_save($type);
    node_add_body_field($type);
  }

Each content type is defined by an associative array, containing fields that define the content type's machine name (the "type" element), name, description, and other details.

For the "Basic page" content type, the above code is sufficient for its purposes, because by default every new content type has the minimum two fields, "Title" (whose field type is "Node module element") and "Body" (whose field type is "Long text and summary", i.e., a textarea with optional teaser text). These settings can be verified by checking the "Manage fields" tab (at admin/structure/types/manage/page/fields). The code that adds these fields can be found in the file modules/node/node.module, in the functions node_field_extra_fields() and node_add_body_field(), respectively. That code is not listed here, due to space considerations, but examining it can prove instructive.

The "Article" type naturally contains these two default fields, but two others as well. "Tags" and "Image" are added explicitly in code further down in the standard_install() function. It too is worth studying.

Custom Fields

To illustrate how one can add new fields to existing content types, consider a simple example, in which we add a text field to the "Article" content type:

//  The machine name of the field can contain only lowercase alphanumeric characters and underscores.
$field_name = 'new_field';

//  Verify the field does not already exist.
if ( field_info_field( $field_name ) ) {
    return;
}

//  Create the field definition array.
$field = array(
    'field_name' => $field_name,
    'type' => 'text',
);
//  Create the field.
$field = field_create_field( $field );

//  Create the field instance definition array.
$instance = array(
    'field_name' => $field[ 'field_name' ],
    'entity_type' => 'node',
    'bundle' => 'article',
    'description' => 'A field for testing the programmatic creation of new fields.',
    'label' => 'New Field',
    'widget' => array(
        'type' => 'textfield',
    ),
);

//  Create an instance of the field and bind it to the bundle.
field_create_instance($instance);

There are at least two possible approaches to implementing code like that of the above: If it is a one-off instance of adding a field, then you could preface the code with the minimal statements needed to bootstrap Drupal (the details of which we will not get into here), and simply run that PHP script. But if you wish to share your field or content type with others, or use it again in the future, your handiwork should be encapsulated in a custom module – specifically, in an 'install' or 'enable' function in the 'install' file of the module's package (again, the details are beyond the scope of this article).

As a result of running this code, the "Article" content type will have associated with it our new field.

Article type fields

Custom Content Types

Now that you have an idea as to what is involved in adding new fields programmatically to existing content types, then in order to create a new custom content type with fields, you only need to create a skeleton type, and fill it with all the fields you want, using the above technique. Here is the code to generate a new content type with a "Body" field, in addition to the mandatory "Title" field:

//  The machine name of the content type can contain only lowercase alphanumeric
//  characters and underscores.
$type_name = 'new_type';

//  Verify the content type does not already exist. To instead get the node types
//  as objects, use node_type_get_types().
if ( in_array( $type_name, node_type_get_names() ) ) {
    return;
}

//  Create the type definition array.
$type = array(
    'type' => $type_name,
    'name' => st( 'New content type' ),
    'base' => 'node_content',
    'description' => st( 'For testing the programmatic creation of new content types and their fields.' ),
    'custom' => 1,
    'modified' => 1,
    'locked' => 0,
);
$type = node_type_set_defaults( $type );
node_type_save( $type );
//  Add a body field.
node_add_body_field( $type );

This example code generates the content type, without your having to wade through all the admin dialogs. If you try the above code, be sure to use the correct type name in the field code (specifically, in the 'bundle' element).

New content type fields

To learn more about fields, be sure to check the documentation of the Field API at http://wdog.it/3/2/field.

Comments

this line does not work as node_type_get_names() responds with a mulit-dimentional array, and in_array does not work with that.

if ( in_array( $type_name, node_type_get_names() ) ) {

You should use

if ( in_array( $type_name, array_keys(node_type_get_names() ) ) ) {

or use
$array = node_type_get_names();
if(isset($array[ $type_name])){

}

That's correct. Thanks for pointing it out. I probably misinterpreted the vague Drupal.org phrase "node type names" (https://api.drupal.org/api/drupal/modules!node!node.module/function/node...) to mean the node type machine names and not the descriptive names, in which case the original code would've worked.