our insights on web development and the Drupal CMS

Moving a Drupal Content Type to a Module

One of the most powerful features of Drupal is the ability to create custom content types. The default Drupal installation comes with two content types, "page," and "story." However, you can go on to create as many content types as you want, such as news posts, events, bios, etc. This feature of Drupal is most compelling because both Drupal core and especially contributed modules allow you to configure the behavior and features of each content type separately.

Drupal makes it very easy to create a new content type in the admin UI. If you add CCK, you can now add all sorts of different field types to enhance your node. With the location module, you can associate location information with nodes of a content type and display nifty google maps. These examples go on and on... with one exception: There is no easy way to control access to a content type you've created in the UI with custom code.

Now, to be fair, Drupal provides an answer to this challenge. Drupal uses a "node_access" system that is basically a form of ACL, on top of a simple permission system. By default, Drupal doesn't restrict anything with its node_access system, but instead leaves that up to contributed modules. Once you install a contributed module that implements the node_access system, the module makes node_access "grants" (read: ACL entries) that Drupal can query when it's trying to determine if you have permission to edit a blogroll item node created by another user in a certain group on tuesdays.

Here's the problem with node_access: it's complicated. Now, I like complicated as much as the next code monkey, but when you're trying to meet a deadline and you need some simple access rules that none of the contributed node_access modules quite cover, it's just overkill and too expensive. My answer? Provide your custom content types via a module!

Since the beginning of Drupal content types, contributed modules have been able to define content types too. The added benefit to doing it this way, is there are all sorts of hooks that you can put into your code that Drupal will call during specific events related to your content type. Of interest to me in the above scenario is hook_access(). Hook_access() is called every time there is a question about whether someone has permission to do something in relation to a node of your content type. If your hook_access() has something to say, it just has to return true or false -- if it doesn't care, just return NULL and the node_access system picks up as usual from there. So Zed Shaw would be happy as we can implement non-ACL, code-based rules for controlling access right there in our implementation of hook_access().

Now, if you pre-plan this approach, everything is great. However, reality and experience tells us that there will be times when you get almost all the way finished on a project and discover that you need to be able to implement custom, code-based access rules for your custom content type that you created through the Drupal UI. What now? It's time to convert that Drupal UI content type into a module-provided custom content type.

But wait, that's insane! Yes, yes it is. Listen carefully.

After you've written the module that will provide a content type with the same machine-readable name as the content type you created with the Drupal UI but want to convert, follow these steps:

  1. Backup your database. This is important. I warned you.
  2. Delete the content type in the UI. This will NOT delete your nodes (probably? But you backed up your DB, right?)
  3. Enable your nifty content-type module.
  4. Disable then Enable a trivial module (like the Help module). This refreshes all sorts of cache and is generally the easiest way to hit everything.
  5. If you use CCK: Restore the node_field table from your backup.
  6. If you use CCK: Restore the content_type_ from your backup.
  7. Clear the cache.
  8. Reassociate any vocabularies with your content type as necessary.

Voilla! You now have a module-provided custom content type, in which you can implement hook_access() or any other node hook to your heart's (and spec's) content!