How to programmatically create nodes, comments and taxonomies in Drupal 7

I've been busy with a migration work for my client recently. There is an established website built with some proprietary CMS with a decent amount of articles / pages, different categories and user comments. My team was hired to make a complete migration from this money-sucking old-fashioned proprietary CMS to a modern shiny open-source Drupal 7. I like this kind of tasks. It’s always a pleasure to help people get rid of an old and proprietary technologies. To accomplish this task, I needed to create nodes, comments and taxonomies programmatically in Drupal 7, which is a pretty trivial task for Drupal 6. But in D7 with the introduction of entities and fields in core (aka CCK in core) things have changed a bit.

In today's post I'll show you how to programmatically create nodes, comments and taxonomies in Drupal 7. In detail you’ll find out how to:

1. Programmatically create a node:

  • Initialize a node object
  • Add body field
  • Add custom fields
  • Add file / image field
  • Add a term to a node
  • Save a node

2. Programmatically create a comment
3. Programmatically create a term

1. How to programmatically create a node

1.1 Initialize a node object

$node = new stdClass(); // We create a new node object
$node->type = "page"; // Or any other content type you want
$node->title = "Your title goes jere";
$node->language = LANGUAGE_NONE; // Or any language code if Locale module is enabled. More on this below *
$node->path = array('alias' => 'your node path'); // Setting a node path
node_object_prepare($node); // Set some default values.
$node->uid = 1; // Or any id you wish

*We set LANGUAGE_NONE for $node->language, if you don’t have the locale module enabled, the node will not be assigned any particular language. So that's why we put here the constant LANGUAGE_NONE. In Drupal, nodes and fields can exist in more that one language, so if your site is multilingual you should specify the language code for your field. You can configure languages and get language codes by going this path in Drupal administration: Configuration -> Regional and language -> Languages.

1.2 Add a body field

// Let's add standard body field
$node->body[$node->language][0]['value'] = 'This is a body text';
$node->body[$node->language][0]['summary'] = 'Here goes a summary';
$node->body[$node->language][0]['format'] = 'filtered_html'; // If field has a format, you need to define it. Here we define a default filtered_html format for a body field

1.3 Add custom fields

// Let's add some CCK/Fields API field. This is pretty similar to the body example 
$node->field_custom_name[$node->language][0]['value'] = 'This is a custom field value';
// If your custom field has a format, don't forget to define it here
$node->field_custom_name[$node->language][0]['format'] = 'This is a custom field value';
// And etc. you can add as much fields here as your content type has. The sky is the limit... and the server specs, of course ;)

1.4 Add file / image fields

// Some file on our system
$file_path = drupal_realpath('somefile.png'); // Create a File object
$file = (object) array(
  'uid' => 1,
  'uri' => $file_path,
  'filemime' => file_get_mimetype($file_path),
  'status' => 1,
); 
$file = file_copy($file, 'public://'); // Save the file to the root of the files directory. You can specify a subdirectory, for example, 'public://images' 
$node->field_image[LANGUAGE_NONE][0] = (array)$file; //associate the file object with the image field:

1.5 Add a term to a node

$node->field_tags[$node->language][]['tid'] = 1;

field_tags here is the name of a term reference field attached to your content type, 1 is a term id you wish to assign to a node. Simple!

1.6 Save a node

$node = node_submit($node); // Prepare node for a submit
node_save($node); // After this call we'll get a nid

2. How to programmatically create a comment

// Let's create a managed object $comment = new stdClass(); // We create a new comment object
$comment->nid = $node->nid; // nid of a node you want to attach a comment to
$comment->cid = 0; // leave it as is
$comment->pid = 0; // parent comment id, 0 if none 
$comment->uid = 1; // user's id, who left the comment
$comment->mail = 'email@example.com'; // user's email
$comment->name = 'User name'; // If user is authenticated you can omit this field, it will be auto-populated, if the user is anonymous and you want to name him somehow, input his name here
$comment->thread = '01/'; // OPTIONAL. If you need comments to be threaded you can fill this value. Otherwise omit it.
$comment->hostname = '127.0.01' // OPTIONAL. You can log poster's ip here
$comment->created = time(); // OPTIONAL. You can set any time you want here. Useful for backdated comments creation.
$comment->is_anonymous = 0; // leave it as is
$comment->homepage = ''; // you can add homepage URL here
$comment->status = COMMENT_PUBLISHED; // We auto-publish this comment
$comment->language = LANGUAGE_NONE; // The same as for a node
$comment->subject = 'Comment subject'; 
$comment->comment_body[$comment->language][0]['value'] = 'Comment body text'; // Everything here is pretty much like with a node
$comment->comment_body[$comment->language][0]['format'] = 'filtered_html'; 
$comment->field_custom_field_name[LANGUAGE_NONE][0]['value'] = ‘Some value’; // OPTIONAL. If your comment has a custom field attached it can added as simple as this // preparing a comment for a save
comment_submit($comment); // saving a comment
comment_save($comment);

3. How to programmatically create a taxonomy term

This one is the most easiest part of the tutorial. To create a term you just need to do the following:

$term = new stdClass();
$term->name = ‘Term Name’;
$term->vid = 1; // ‘1’ is a vocabulary id you wish this term to assign to
$term->field_custom_field_name[LANGUAGE_NONE][0]['value'] = ‘Some value’; // OPTIONAL. If your term has a custom field attached it can added as simple as this
taxonomy_term_save($term); // Finally, save our term

Well, that’s all for today my friends, hope this article helps you. Please leave your comments and additions and if you liked the post, share it on twitter and facebook, spread the knowledge!

Comments

Submitted by Rafael Silva on Mon, 2011-05-23 17:30

That's very nice, but I was wondering if you have considered using Migrate module. I recently used it to migrate some data to a D6 website and it was pretty slick!

Submitted by Tim on Mon, 2011-05-23 18:39

Yes, I know I could use this module, but I wanted to get an experience with D7's programmatic node / comments creation for my future projects, so I decided to choose my own custom way.

Submitted by Florian Loretan on Wed, 2011-05-25 16:06

You use $node->language as the language key in field structures:

$node->body[$node->language][0]['value'] = 'This is a body text';

However, the language of the field is not always the language of the node. The language code to be used should be retrieved using field_language() instead.

Submitted by Tim on Wed, 2011-05-25 19:58

Useful note, can be used when you have multiple languages enabled, but it's no point to make things more complex when you know that you have no language defined. Thanks for the addition, Florian.

Submitted by Deep on Thu, 2011-05-26 15:09

But where this file should be stored? in module folder?

Submitted by Tim on Thu, 2011-05-26 17:48

Deep, this code can be used as a function in your custom Drupal module.

Submitted by mamoun.othman on Sat, 2011-07-09 16:02

Thanks for this article really helped me, I am using drupal 6 for creating a node programmaticlly, and in drupal 6 the function to save a term is

taxonomy_save_term($term);

maybe this will help someone like me found this article and working on drupal 6 ;)

Again thanks for sharing.

Submitted by penguin89 on Thu, 2011-12-08 09:35

I have a error when i using a query to read a node in a database to write in my database is: PDOException: in drupal_write_record() (line 6868 of C:\xampp\htdocs\emerald\includes\common.inc). my query can read data in db but it only read 7line in db, and it have a problem like this, so it's not work. Can you show me how to resolve my problem. Thanks

Submitted by Marc Isaacson on Fri, 2012-01-20 06:20

Thanks so much for this write up. It helped me to do exactly what I needed to do!

Submitted by C0d3Alchemist on Tue, 2012-02-07 21:47

I am pretty new with D7. Could you point out which libraries need to be included in the script in order to have access to all D7 functions?

Regards

Submitted by Robert Weinstein on Sat, 2012-02-11 05:59

Thanks for the very useful how-to,

I have a question on one of the steps:
$node->uid = 1; // Or any id you wish

Forgive me if this is obvious as I am new to Drupal. Would we not want to get whatever the next node uid is after the last node was created assuming we are beyond the first node creation?

I also see:
node_save($node); // After this call we'll get a nid

Does this then mean that in the first piece of code uid=1 is just a placeholder id for the content UNTIL the node is created and the system will automatically "do the rest" and give it the next node id in the series (along with pathauto etc..)?

Thanks in advance

Submitted by Jonathan on Wed, 2012-12-05 12:47

$node->uid is the user ID of the node author or the user you want the node to be authored by.

Submitted by Alex on Thu, 2012-03-01 17:37

Thanks for this article, very useful.

However I had a bug regarding the author of my new node.

node_object_prepare($node) seems to reset $node->uid.
Therefore I needed to this line
$node->uid = '1';
AFTER this one:
node_object_prepare($node);

This is for Drupal 7.12, while creating nodes in a cron job.
I don't know if this is true for earlier versions of Drupal 7.

Submitted by Josh on Sat, 2012-04-14 04:07

Thanks for this note, saved me time!

Submitted by Bill Szkotnicki on Mon, 2012-03-05 17:55

Hi,
I am trying to make this work but I get
------> Undefined class constant 'MYSQL_ATTR_USE_BUFFERED_QUERY'
even with just this:
define('DRUPAL_ROOT', '/var/www/html/drupaldev/');
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
exit;

My drupal installation is working perfectly otherwise and pdo_mysql is enabled.

Any ideas?

Thanks, Bill

Submitted by Nirmal on Wed, 2012-03-07 07:42

Hi, This is the type of code i want. I also know that i need to create a custom module to use this code. But in drupal, a module is called using a hook. So please tell me which hook i need to use to call this function.
My basic requirement is ..... I have a csv file and i need to import data from that.

Submitted by Jonathan on Wed, 2012-12-05 12:49

You would need to setup a menu callback to your function using hook_menu (http://api.drupal.org/api/drupal/modules!system!system.api.php/function/...) or you could use it in a form submission function or something like that.

Submitted by gollyg on Wed, 2012-12-19 00:30

If your requirement is importing from an external data source, I would seriously consider the migrate module, or if this will be a regular thing, perhaps the feeds module.

Both modules take care of much of the hard work of importing files, setting up batch operations to avoid timeouts, and a load of other things. All you really need to create are the 'mappings' (defining which data gets put in which field). Feeds allows you to do this via the UI, whereas some coding is required for the Migrate module.

The solution on this page would appear to be for much more specific use cases where you needed full control in code (for example populating a bit of content on module install).

Submitted by joris on Sat, 2012-05-12 20:22

Hi,

Thanks for the great clear tutorial!

I want to import a book with approx 200 pages from an XML, the book is structured in different chapters. In the XML I have the parent id (the chapter they belong to, chapter is just a page as well) of each page. How can I set which book a page belongs to and what is the parent of each book (I tried $node->pid but this didn't work)

Thanks!

Joris

Submitted by Jonathan on Wed, 2012-12-05 12:50

You could setup content type called book and then another content type called page. The page content type could then have a reference field to the book content type or you could just have a simple text field that holds the node id of a book.

Submitted by Sergio Nunes on Mon, 2012-05-21 02:03

In 1.5 case how would you attach "yellow" term into the tags field?

Many Thanks,
Sergio

Submitted by subhojit777 on Thu, 2012-05-24 15:02

Thank you :)

Submitted by Cath on Tue, 2012-06-12 15:04

How about sanitation? Do I need to use check_plain() or does node_save take care of that?

Submitted by Giovanni on Wed, 2012-07-11 02:02

Thanks a lot. You saved my life. I have spent too much hours searching around, before discovering your tutorial.

Submitted by Alex on Mon, 2012-07-30 02:00

Just another typo error.
in 1.4 (file/image saving) you wrote:

  'filemime' => file_get_mimetype($filepath),

but it should be $file_path not $filepath:
  'filemime' => file_get_mimetype($file_path),

Thanks again for your article ;)

Submitted by Denis on Sun, 2012-08-05 21:04

THANK YOU!
Man, you are great! Your article did my day. Thank you
From Russia with Love,
Denis

Submitted by bobju on Fri, 2012-10-05 18:17

Useful!! It helps me very much!

Submitted by Ahmar Ali on Tue, 2013-01-22 15:20

Thanks for great article. I am new to Drupal and come from WP background. I am developing a small system for my client who is using Drupal to add nodes into the website externally. I see I can pull the products from API and put them as nodes . I will have to use create node functionality. But where exactly I have to put this code ? I mean in which file I do it extract products from API and then add them as nodes. Please help thanks.

Submitted by Capy on Tue, 2013-04-09 18:22

And you can add as many as you like! (if field is multivalue XD)

node->field_tags[$node->language][]['tid'] = 1;
node->field_tags[$node->language][]['tid'] = 2;
node->field_tags[$node->language][]['tid'] = 3;

Thanks for the tip!

Submitted by Manuel on Thu, 2013-04-18 16:51

It was pretty cool. It has helped me a lot.
Thanks.

Submitted by may on Tue, 2013-07-02 05:28

How about programmatically create custom block? Can you please help and advise?

Thanks for the share btw. :)

Submitted by Tim on Tue, 2013-07-02 15:03

Actually it is easy, you need to implement hook_block_info() (https://api.drupal.org/api/drupal/modules!block!block.api.php/function/h...) and hook_block_view() (https://api.drupal.org/api/drupal/modules!block!block.api.php/function/h...). See links I attached.

Submitted by Christopher Stevens on Tue, 2013-09-24 21:29

Hey Tim,
Thanks for the great Drupal 7 migration tips. Today I'm helping a friend move his blog from a blogspot account he can't remember how to login to using scrapy, a Python based web crawler. It's converting pages into easy-to-digest JSON data (as soon as I can finish getting that all figured out). All this is kind of sad as there is a helpful export option in blogger and some helpful modules to import those exports... but this is good fun practice in the grand scheme of things. You post is SUPER helpful in getting these comments to transfer with the nodes.

Have you tried posting user comments without an email? I don't have access to that info, but perhaps can assign them all to a single generic email or something if needed.

Thanks for the great post.

Chris

Submitted by Tim on Wed, 2013-10-02 10:59

You're welcome, Chris. As far as I remember, if you import comments, email address isn't required, but if it fails, assign a generic email to them and that's it.

Submitted by 2218 on Tue, 2013-10-08 20:33

Just to add something....... can i add a user programatically by following code??

$account = new stdClass;
$account->is_new = TRUE;
$account->name = 'foo';
$account->pass = user_hash_password('bar');
$account->mail = 'foo@example.com';
$account->init = 'foo@example.com';
$account->status = TRUE;
$account->roles = array(DRUPAL_AUTHENTICATED_RID => TRUE);
$account->timezone = variable_get('date_default_timezone', '');
user_save($account);

Submitted by Mambley on Wed, 2013-10-09 19:14

Hi,

Thanks for your article. Any idea/suggestion on how to get the new nid before saving? I need to get the nid of the new node before saving so i can use as reference.

Thanks a lot.

Submitted by Busla on Sat, 2013-12-07 20:41

Thanks for sharing!

Doesn´t the file field on the note need a "display" value?

Busla.

Submitted by Busla on Sun, 2013-12-08 12:58

I played around with this some more and the file object was created without a problem but it just wouldn´t attach to the node (erro display cannot be NULL).

Setting the following array item to the file object fixed it.
 'display' => 1

Add new comment

You are here