Create new content-type for Drupal 6.x

Last modified: May 21, 2009 - 00:26

Introduction

Okay, you've read the tutorial on creating modules and know the basics of hooks, blocks and possibly forms. You'd now like to use more of the various Drupal APIs, and create a new node type.

Before creating a new node type, first decide if you really need to create a new one: if you can use CCK and Views to gather and manipulate your data, you may not need this tutorial. However, if you need to manipulate your data in a different way, or have multiple data dependencies that can't be done with CCK, or if you just want to learn more about Drupal internals, then this tutorial may just be for you.

Note that you can probably still use CCK+Views if the custom parts of your node just includes variations of the built-in commenting system, paths, publishing information and other built-in Drupal goodies.

A simple node-type (or content type) such as "page" or "story" is the most elemental aspect of a Drupal site. In it's most basic form it is nothing more than a Title, Body, and an optional Teaser. You can easily build your own node-type programmatically by following these step-by-step directions. Learn this, and you will begin to understand how flexible Drupal can be with it's content.

Why would you need to know this?

Drupal 6.x allows you to easily create these "simple" node-types using the admin interface, doesn't it? The answer is a resounding YES, and everyone is absolutely thrilled with that. Thanks, Drupal developers!!! Then CCK came along, and made our lives even easier! However, it does not teach you the basics of module programming and that's why this tutorial was written.

There is a great tutorial at Creating Modules 6.x Tutorial. There is also a great example of creating your own node-type in the API at node_example. We will be using this as the code reference point (and you should definitely read that).

There will be no database changes or hacking of code, the result will be a new module that you will need to upload into its own module directory.

What you need

  • means to download/upload files to your modules directory
  • a text editor
  • a little php knowledge is helpful, but not required
  • some information about your node type
    • a TECHNICAL-NAME for your node type - don't use the name of an existing module - no spaces, numbers or punctuation unless you know what you're doing
    • a USER-FRIENDLY-NAME for your node type (and a plural version of this name) (this is how your node type will appear in most places on your site) - spaces and numbers are ok but again no punctuation unless you know what you're doing, note: you can use the same name as your TECHNICAL-NAME above
    • a MODULE-DESCRIPTION - a short description of your node type that will appear on the admin->modules page - don't use quotes or apostrophes unless you know what you are doing
    • a CREATE-CONTENT-DESCRIPTION - a short description of your node type that will appear on the create content page - don't use quotes or apostrophes unless you know what you are doing
    • a HELP-DESCRIPTION - a short description of your node type that will appear on the help page - don't use quotes or apostrophes unless you know what you are doing

As an example... say you want a simple press release node type.

  • TECHNICAL-NAME = "press_release" (note the underscore)
  • USER-FRIENDLY-NAME = "Press Release"
  • USER-FRIENDLY-PLURAL = "Press Releases"
  • MODULE-DESCRIPTION = "Enables the creation of press releases."
  • CREATE-CONTENT-DESCRIPTION = "Create a press release."
  • ADMIN-HELP-TEXT = "This module was created by [your name here]."

I specifically use an example that has two words: "Press Release" - this provides a slightly different TECHNICAL-NAME: "press_release" (note the underscore) and USER-FRIENDLY-NAME: "Press Release" (no underscore) so that it is clear in the code changes where the two are used, but many node-types that exist (or that you may want to create) use the same value.

In the following, bolded text is intended to show where the code changes (so PHP formatting is not used). Quotes and apostrophes in the code are important, please pay close attention. Where you see ----- this is intended to show where the code starts and ends and is not needed in your code.

There are two files that are required to create a module. The first file is a .info file (say it out loud: "dot info file"). You can read more about .info files at Writing .info files (Drupal 6.x) or in the Creating Modules 6.x Tutorial series in section 02. Telling Drupal about your module.

This is not a PHP file so you don't start with the <?php

The general format is:

; $Id$
name = USER-FRIENDLY-NAME
description = "MODULE-DESCRIPTION"
core = 6.x

In our example:

; $Id$
name = Press Release
description = "Enables the creation of press releases."
core = 6.x

Yes, you only need those 4 lines (minimally).

Save this file as "TECHNICAL-NAME.info" (in our example: "press_release.info").

The second file needed is the .module file (say it out loud "dot module file"). This is the file that does all the heavy lifting.

In the node_example there are a number of hooks listed and exampled. The node_example is one where the node-type has its own additional fields. This how-to does not address that need, but is a only a primer to get you started. If you need additional fields, you will want to learn about those hooks and node_example is a great place to start that.

The .module file is where all the "hooks" appear. Hooks are pretty easy to understand. All functions in your module that will be used by Drupal are named {modulename}_{suffix}, where "suffix" is a pre-defined function name suffix for the hook. So when we learn that we need to implement "hook_foo()", for our example module, we would write a function called "press_release_foo()". Drupal will call these functions to get specific data, so having these well-defined names means Drupal knows where to look.

Each of the following hooks are outlined in node_example in the API

- these hooks should be implemented in every node-type module. This example will implement these 4 hooks.

  • hook_node_info()
  • hook_perm()
  • hook_access()
  • hook_form()

- these hooks are needed if you have additional fields that your node-type creates (note you can use CCK with your node-type to add additional fields). This example will not implement these hooks.

  • hook_insert()
  • hook_update()
  • hook_delete()
  • hook_validate()
  • hook_nodeapi()
  • hook_view()
  • hook_load()

- this hook does not appear in node_example but is a good idea to use. This example will implement this hook.

  • hook_help()

Note: it is considered a good practice to include the following comment before each hook

/**
* Implementation of hook_{hook name here}().
*/

but it is not included in the following.

So let's get started. This is a PHP file so the very first line should be <?php

Implementation of hook_node_info()

This is a required hook and can define a lot of things about the node-type, minimally the following is required.

From node_example:

<?php
function node_example_node_info() {
  return array(
   
'node_example' => array(
     
'name' => t('example node'),
     
'module' => 'node_example',
     
'description' => "This is an example node type with a few fields.",
    )
  );
}
?>

This breaks down to:
-----
function TECHNICAL-NAME_node_info() {
  return array(
    'TECHNICAL-NAME' => array(
      'name' => t('USER-FRIENDLY-NAME'),
      'module' => 'TECHNICAL-NAME',
      'description' => "CREATE-CONTENT-DESCRIPTION",
    )
  );
}
-----

In our example:
-----
function press_release_node_info() {
  return array(
    'press_release' => array(
      'name' => t('Press Release'),
      'module' => 'press_release',
      'description' => "Create a press release.",
    )
  );
}
-----

Implementation of hook_perm()

Since we are limiting the ability to create new nodes to certain users, we need to define what those permissions are here. We also define a permission to allow users to edit the nodes they created.

The permissions you define will be referenced by the next hook (hook_access) in the example.

from node_example:

<?php
function node_example_perm() {
  return array(
'create example node', 'edit own example nodes');
}
?>

node_example uses USER-FRIENDLY-PLURAL to define its permissions, but it is recommended that your permission strings must be unique within your module. If they are not, the permissions page will list the same permission multiple times. They should also contain your module name, to avoid name space conflicts with other modules. The current naming convention is an action verb + modulename. This example will follow that advice (although using USER-FRIENDLY-PLURAL is probably fine.

This hook then breaks down to:
-----
function TECHNICAL-NAME_perm() {
  return array('create TECHNICAL-NAME', 'edit own TECHNICAL-NAME');
}
-----

In our example, this becomes:
-----
function press_release_perm() {
  return array('create press_release', 'edit own press_release');
}
-----

Implementation of hook_access()

Node modules may implement node_access() to determine the operations users may perform on nodes. This example uses a very common access pattern. It is important to note that you should use the same permissions you defined in the hook above.

from node_example:

<?php
function node_example_access($op, $node, $account) {

  if (
$op == 'create') {
   
// Only users with permission to do so may create this node type.
   
return user_access('create nameofnodetype', $account);
  }

 
// Users who create a node may edit or delete it later, assuming they have the
  // necessary permissions.
 
if ($op == 'update' || $op == 'delete') {
    if (
user_access('edit own nameofnodetype', $account) && ($account->uid == $node->uid)) {
      return
TRUE;
    }
  }
}
?>

Again, we are following the advice to use the TECHNICAL-NAME and not the USER-FRIENDLY-PLURAL

This breaks down to:
-----
function TECHNICAL-NAME_access($op, $node, $account) {

  if ($op == 'create') {
    // Only users with permission to do so may create this node type.
    return user_access('create TECHNICAL-NAME', $account);
  }

  // Users who create a node may edit or delete it later, assuming they have the
  // necessary permissions.
  if ($op == 'update' || $op == 'delete') {
    if (user_access('edit own TECHNICAL-NAME',$account) && ($account->uid == $node->uid)) {
      return TRUE;
    }
  }
}
-----

In our example:
-----
function press_release_access($op, $node, $account) {

  if ($op == 'create') {
    // Only users with permission to do so may create this node type.
    return user_access('create press_release', $account);
  }

  // Users who create a node may edit or delete it later, assuming they have the
  // necessary permissions.
  if ($op == 'update' || $op == 'delete') {
    if (user_access('edit own press_release',$account) && ($account->uid == $node->uid)) {
      return TRUE;
    }
  }
}
-----

Implementation of hook_form()

Now it's time to describe the form for collecting the information specific to this node type. This hook requires us to return an array with a sub array containing information for each element in the form. Forms are in my mind one of the most difficult aspects of Drupal to learn. But because of the amazing way Drupal implements forms, in this example we have very little code to change.

from node_example:

<?php
function node_example_form(&$node) {
 
$type = node_get_types('type', $node);

 
// We need to define form elements for the node's title and body.
 
$form['title'] = array(
   
'#type' => 'textfield',
   
'#title' => check_plain($type->title_label),
   
'#required' => TRUE,
   
'#default_value' => $node->title,
   
'#weight' => -5
 
);
 
// We want the body and filter elements to be adjacent. We could try doing
  // this by setting their weights, but another module might add elements to the
  // form with the same weights and end up between ours. By putting them into a
  // sub-array together, we're able force them to be rendered together.
 
$form['body_filter']['body'] = array(
   
'#type' => 'textarea',
   
'#title' => check_plain($type->body_label),
   
'#default_value' => $node->body,
   
'#required' => FALSE
 
);
 
$form['body_filter']['filter'] = filter_form($node->format);

 
// NOTE in node_example there is some addition code here not needed for this simple node-type

 
return $form;
}
?>

The only change needed for this function is the function name itself - all the code stays the same

This breaks down to:
-----
function TECHNICAL-NAME_form(&$node) {
  (all the code stays the same)
}
-----

In our example, it becomes:
-----
function press_release_form(&$node) {
  (all the code stays the same)
}
-----

Implementation of hook_help()

This hook is not included in the node_example - not sure why

from the hook_help() example in the api

<?php
function hook_help($path, $arg) {
  switch (
$path) {
    case
'admin/help#block':
      return
'<p>'. t('Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. Blocks are usually generated automatically by modules (e.g., Recent Forum Topics), but administrators can also define custom blocks.') .'</p>';

    case
'admin/build/block':
      return
t('<p>Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. They are usually generated automatically by modules, but administrators can create blocks manually.</p>
<p>If you want certain blocks to disable themselves temporarily during high server loads, check the "Throttle" box. You can configure the auto-throttle on the <a href="@throttle">throttle configuration page</a> after having enabled the throttle module.</p>
<p>You can configure the behaviour of each block (for example, specifying on which pages and for what users it will appear) by clicking the "configure" link for each block.</p>'
, array('@throttle' => url('admin/settings/throttle')));
  }
}
?>

This breaks down to:
-----
function TECHNICAL-NAME_help($path, $arg) {
  switch ($path) {
    case 'admin/help#TECHNICAL-NAME':
      return '<p>' . t('ADMIN-HELP-TEXT') . '</p>';
      break;
  }
}
-----

This breaks down to:
-----
function press_release_help($path, $arg) {
  switch ($path) {
    case 'admin/help#press_release':
      return '<p>' . t('This module was created by [your name here].') . '</p>';
      break;
  }
}
-----

That's it for the code. Hopefully not too difficult.

Save the file as "TECHNICAL-NAME.module", (in our example: "press_release.module") you're now ready to upload.

Testing Your Module

Create a directory in the modules directory with your TECHNICAL-NAME (in our example: "press_release")
Upload both files (the .module file and the .info file) to your new directory

Go to your site, administer -> modules, (admin/build/modules) you should see your new module listed, enable it as with any other module.
Note: if after you enable the module, you get a white screen, it means you have an error in the php code of your new module, delete the files from your new directory and everything should be fine.
You need to fix your new module, but it is probably easier to start over, re-read the directions and follow them carefully, if it still doesn't work, then consider these directions a bunch of bunk and try something else.
Your new node type should work the same as with page and story nodes, you will need to enable access to them (administer-> users -> access control), configure them (administer->content->content types), allow for categorization (administer->content->categories), etc.

Code that you can copy and paste, then find and replace:

for the .info file

; $Id$
name = USER-FRIENDLY-NAME
description = "MODULE-DESCRIPTION"
core = 6.x

for the .module file

<?php
/**
* Implementation of hook_node_info().
*/
function TECHNICAL-NAME_node_info() {
  return array(
   
'TECHNICAL-NAME' => array(
     
'name' => t('USER-FRIENDLY-NAME'),
     
'module' => 'TECHNICAL-NAME',
     
'description' => "CREATE-CONTENT-DESCRIPTION",
    )
  );
}

/**
* Implementation of hook_perm().
*/
function TECHNICAL-NAME_perm() {
  return array(
'create TECHNICAL-NAME', 'edit own TECHNICAL-NAME');
}

/**
* Implementation of hook_access().
*/
function TECHNICAL-NAME_access($op, $node) {
  global
$user;

  if (
$op == 'create') {
   
// Only users with permission to do so may create this node type.
   
return user_access('create TECHNICAL-NAME');
  }

 
// Users who create a node may edit or delete it later, assuming they have the
  // necessary permissions.
 
if ($op == 'update' || $op == 'delete') {
    if (
user_access('edit own TECHNICAL-NAME') && ($user->uid == $node->uid)) {
      return
TRUE;
    }
  }
}

/**
* Implementation of hook_form().
*/
function TECHNICAL-NAME_form(&$node) {
 
$type = node_get_types('type', $node);

 
// We need to define form elements for the node's title and body.
 
$form['title'] = array(
   
'#type' => 'textfield',
   
'#title' => check_plain($type->title_label),
   
'#required' => TRUE,
   
'#default_value' => $node->title,
   
'#weight' => -5
 
);
 
// We want the body and filter elements to be adjacent. We could try doing
  // this by setting their weights, but another module might add elements to the
  // form with the same weights and end up between ours. By putting them into a
  // sub-array together, we're able force them to be rendered together.
 
$form['body_filter']['body'] = array(
   
'#type' => 'textarea',
   
'#title' => check_plain($type->body_label),
   
'#default_value' => $node->body,
   
'#required' => FALSE
 
);
 
$form['body_filter']['filter'] = filter_form($node->format);

 
// NOTE in node_example there is some addition code here not needed for this simple node-type

 
return $form;
}

/**
* Implementation of hook_help().
*/
function TECHNICAL-NAME_help($path, $arg) {
  switch (
$path) {
    case
'admin/help#TECHNICAL-NAME':
      return
'<p>' . t('ADMIN-HELP-TEXT') . '</p>';
      break;
  }
}
?>

Why not use node_body_field()?

ahaapaka - April 13, 2009 - 07:07

In http://api.drupal.org/api/function/node_example_form/6 it says that:

In Drupal 6, we can use node_body_field() to get the body and filter elements. This replaces the old textarea + filter_form() method of setting this up. It will also ensure the teaser splitter gets set up properly.

If using node_body_field() is the best practise, then TECHNICAL-NAME_form() should be updated.

More node types in one module

acolade - April 22, 2009 - 09:37

It is possible to create more them one node type in one module?

Sure

ahaapaka - April 22, 2009 - 16:27

An implementation of hook_node_info() returns an array of information on the module's node types. In the above example the returned array contains only one element ('node_example') but there can be several elements.

Note that hooks are still per module (not per node type). So most of your hook implementations probably need to check what type of node is being processed:

<?php
function mymodule_form(&$node, $form_state) {
 
$type = node_get_types('type', $node);
  switch (
$type) {
    case
'mytype_example':
     
$form['foo'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Foo'),
      );
      break;
    case
'mytype_xyzzy':
     
$form['bar'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Bar'),
      );
      break;
  }
  return
$form;
}
?>

It is not necessary to check

v_20q - April 22, 2009 - 17:14

It is not necessary to check node type in every hook. You can use different "module" attributes in hook_node_info().
Like this:

<?php
function node_example_node_info() {
  return array(
   
'type1' => array(
     
'module' => 'node_example_type1'
   
),
   
'type2' => array(
     
'module' => 'node_example_type2'
   
)
  );
}
?>

Now you have two groups of hooks in your module
<?php

function node_example_type1_form();
function
node_example_type1_access();

function
node_example_type2_form();
function
node_example_type2_access();
?>

Each is called only when you create that particular type of node.

This is great, thank you

acolade - April 28, 2009 - 08:06

This is great, thank you both!

Invaluable

codycraven - December 22, 2009 - 05:10

Setting module keys within hook_node_info() is an invaluable way to structure large or complex modules.

About the menu ?

Xavier - July 15, 2009 - 11:27

Thank you this was very instructive :)

But I don't find how to add a menu pointing to the TECHNICAL-NAME_form so that I can get my add node form from my own submenu tree. I think this would be the right place to add that information (and a couple others, such as the edit/delete links too to be complete).

Meanwhile, with 'page callback' => 'drupal_get_form' and 'page arguments' => array(TN_form,1), I get my custom fields, but nothing else is displaying. I don't get how this work.. and I didn't manage to find that information yet.

Solution for add node

Xavier - July 15, 2009 - 12:40

I finally managed to get it, mainly through trying to duplicate what was in menu_router table. So I arrived to this , where 'tn' stands for technical-name.
My module is stored in sites/all/modules/module_name, hence the long /../ part.

<?php
$items
['menu_path'] = array(
     
'title' => 'Menu Name',
     
'page callback' => 'node_add',
     
'page arguments' => array('type_tn'),
     
'access callback' => 'node_access',
     
'access arguments' => array('create','type_tn'),
     
'file' => '../../../../modules/node/node.pages.inc',
     
'type' => 6,
    );
?>

Hoping this helps.

File path

v_20q - July 15, 2009 - 14:23

I think you should use drupal_get_path() function.

Yes, it should be

Xavier - July 15, 2009 - 14:47

Yes, it should be include_once(drupal_get_path('module', 'node') .'/nodes.pages.inc')
I don't know yet what the literal type should be.

Word of Advice

Abbas Gadhia - September 5, 2009 - 11:09

This tutorial will not work if you have CCK enabled.
I was trying to edit the 'content type' found in
Home › Administer › Content management
and when i would click on the 'edit' button, i would simply be redirected to the
Home › Administer
page

I think I know why this article is needed.... (?)

rantebi - November 26, 2009 - 17:56

Hi,

I've seen creation of content-types using the content creation kit.. It's quite easy to master.
So the question is why go thru all the trouble of creating content types as modules...?

I think one of the considerations for creating a content-type this way is so that I won't be database dependent.
I mean... Suppose someday I'd like to move my content-types to a different server/Contribute them online,
I'd be able to do it very easily - Just move the module's directory...

Am I seeing the complete picture?
Is there any other way of being DB independent?

And one more thing... Is there any way to export content-types I have already created using the "Administer"?

Thanks, (It's my first comment in Drupal... :) )

Ranteb

Ran, it is possible to import/export content types and fields

daph2001 - December 14, 2009 - 13:48

Using the Content Copy module that comes with CCK it is possible to export content types and fields you created in one installation and import them into another one.

Thanks

rantebi - December 23, 2009 - 16:10

Your answer has been helpful.
I have also found a way of Importing thru code, by imitating form submit to the CCK import form.
and also found a way of importing using a module called Features which does a lot of the work for you...!
Thanks.

Cannot see the item in Create content?

harizon9 - January 3, 2010 - 10:58

Hi.

I am new to module development. I cannot see the 'press release' link in 'create content' menu. Do we need to imprement press_release_menu()?

Thanks in advance

-: Harry :-

I do not see my link also in

Summit - January 8, 2010 - 01:17

I do not see my link also in 'create content' menu..and got a Missing argument 2 for node_access() using these hooks..what could be wrong please?
Greetings, Martijn

Greetings,
Martijn www.trekking-world.com

 
 

Drupal is a registered trademark of Dries Buytaert.