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 its 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 its 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:

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:

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:

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:

function node_example_form(&$node, $form_state) {
  $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, $form_state) {
  (all the code stays the same)
}
-----

In our example, it becomes:
-----
function press_release_form(&$node, $form_state) {
  (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

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

/**
* 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, $form_state) {
  $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;
  }
} 

Comments

ahaapaka’s picture

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.

stevecowie’s picture

If you use node_body_field you also get the facility to bypass the body field, which you might want to do if you are creating a node that then has its own custom cck fields. The use case for me incidentally was that I wanted to use hook_access to ensure that other modules - specifically OG - would not be able to give access using the node_access table and the grants and access hooks.

acolade’s picture

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

ahaapaka’s picture

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:

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;
}
20th’s picture

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

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

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.

acolade’s picture

This is great, thank you both!

codycraven’s picture

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

Xavier’s picture

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.

Xavier’s picture

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.

$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.

20th’s picture

I think you should use drupal_get_path() function.

Xavier’s picture

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.

lionic’s picture

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

rantebi’s picture

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

daph2001’s picture

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.

rantebi’s picture

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.

harizon9’s picture

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

Summit’s picture

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

drupeo’s picture

You should not have to implement the press_release_menu() method, there is probably something wrong in the code you've used. If you've called your node type "example_node" in press_release_node_info() then try accessing the form directly at http://example.com/node/edit/example-node

If that doesn't work try the node.example module, grab the source from the link below. It doesn't have the form hook defined. Delete all but the 4 methods defined above and it will work without error. Of course if you do not do this then you will get a DB error as the node_example table won't exist.

http://api.drupal.org/api/drupal/developer--examples--node_example.modul...

chadhester’s picture

So, I have this almost working for my own content types, which is great. Now, some general questions:

Where are the fields (aside from body and title) stored for each content type? Or rather, in what database table(s)?

Are my own fields like CCK fields, and if not, can I make them behave as-if they were made using CCK? I'm thinking about extended functionality, field permissions, and all the CCK goodness, but from a pre-configured/pre-programmed solution point-of-view.

Great job on this article, by the way. :)

__________
Regards,
Chad Hester

chadhester’s picture

So, looking at Chapter 7 in the book Pro Drupal Development really helps in this area.

The tables used in a programmed content type appear to be:
node
node_revisions
node_comment_statistics
-AND-
mycontent_name_here (replace with whatever you set as TECHNICAL-NAME mentioned in the article above)

They also mention in the section Creating a Node Type with CCK:

"Because CCK is under heavy development at the time of this writing, I won’t go into more detail. However, it seems clear that in the future, writing a module to create a new node type will become rarer, while the CCK approach of assembling content types through the web will become more common."

This makes me VERY torn about creating content types programmatically vs. just using CCK. CCK is very straightforward and provides pretty much all the functionality I would want from a content type. But for completeness, I'd like my module, which handles and creates the data for these content types, to handle the creation of those types on install, and destruction on uninstall.

Right now my module uses a custom table to store the content, which isn't helpful for easy edits and using the content in views. This is why I'm converting it all to nodes.

__________
Regards,
Chad Hester

phecht’s picture

redndahead’s picture

I have fixed the link. Thanks for reporting it.

wildpeaks’s picture

http://api.drupal.org/api/6/file/developer/examples/node_example.module seems still wrong: it says "No search results found".

redndahead’s picture

I meant the link in the document. That link is still wrong of course. Look at the link in the content of this page.

anjjriit’s picture

derridaDD’s picture

hi

i am very new to drupal, and i have CCK enabled and it was quite easy to create a content type with it.
but since i am a control freak i wanted to write a content type by my self.

after reading this i have this situation:

the content type does not appear in the manage-> content type and not in create->content type.
the permissions does appear.
in the help section there is a link and in it a link to the permissions.

what am i missing?

please give full details since i am a begginer.

best regards

skomorokh’s picture

I just ran into this myself. Eventually found it though! It seems like even if you have caching turned off you may need to clear the page cache.

I ran Administer->Performance->Clear cached data and then it showed up.

vsalrhal’s picture

You know I've done this about 50 times now. I've copied that exact code, match-all to 'TECHNICAL-NAME', replaced it with 'somemodulename' (literally that's what I used). Modified the 'USER-FRIENDLY-NAME' to appear as 'Some Module Name', then modified the content description.

In the info file, I've used exactly the same.
I can enable it, however, I cannot see it under create content and I cannot access it directly.

I've flushed all my caches several times, enabled, uninstalled and re-enabled at least 30 times.

I just cannot for the life of me see it.

If I use the node_example files that they provide, it works fine.

But I don't want all the extra markup that comes with it. As this module describes, in order for a 'Content Creation Type' to appear in the list, it must have 4 attributes; hook_node_info, hook_perm(), hook_access() and hook_form()...all of these I have tested over and over - and just having those 4 doesn't generate anything for me at all. It's quite frustrating when every tutorial I read purports the exact same walk-through, however, in Drupal 6.19, none of them seem to be resolving correctly.

More over, many of the links provided aren't where the items actually exist in the physical realm of Drupal (that is, at least, anymore)..you also assume that people run with clean URLs....when some don't, for many reasons.

If anyone could perhaps point me in the direction of why I'm blocking myself from being able to move forward, I would greatly appreciate it.

(P.S. - There is nothing under my 'permissions' page about creating this content type, even though I've explicitly defined that need for permissions in my code.)

Here's the code I'm using -

somemodulename.module ->

/**
* Implementation of hook_node_info().
*/
function somemodulename_node_info() {
  return array(
    'somemodulename' => array(
      'name' => t('Some Module Name'),
      'module' => 'somemodulename',
      'description' => "Nothing to see here, move on.",
    )
  );
}

/**
* Implementation of hook_perm().
*/
function somemodulename_perm() {
  return array('create somemodulename', 'edit own somemodulename');
}

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

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

  // 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 somemodulename') && ($user->uid == $node->uid)) {
      return TRUE;
    }
  }
}

/**
* Implementation of hook_form().
*/
function somemodulename_form(&$node, $form_state) {
  $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 somemodulename_help($path, $arg) {
  switch ($path) {
    case 'admin/help#somemodulename':
      return '<p>' . t('This is Administrative help text, It would be useful if I could access the admin panel for this module at all, but I cant, so nevertheless it is deemed useless.') . '</p>';
      break;
  }
} 

And here's the .info file ->

; $Id$
name = Audio Release
description = "MODULE-DESCRIPTION"
core = 6.x

of course there is no closing ?> tag in my module file, and NO PHP markup in my .info file.

Thanks in advance.

michal.k’s picture

Hi,
My problem is that I have module which defines new content-type "unit" and so on hooks: info, access, perm, form. There is "edit own unit" perm and it doesn't work. I grant permision for "manager" role and manager doesn't see the "Edit" link, even if I type node/500/edit into my url textfield, system says: permission denaied.

Thanks in advance, Mike.

jayco33’s picture

IMPORTANT note for D7

I also copied the exact same code from above, and my content type did not show up. It did, however, after I changed function node_example_node_info() into

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

See the 'base' => ''node_content' ? Adding or removing that to the code (i.e DB) makes the content type appear/disappear in the Home » Administration » Structure » Content types listing.

I leave it to the more experience to explain, why this is the case???

@ozyrys
Please use http://pastebin.com/ to post such (more than) lengthy code!