Guide to programmatic node creation in Drupal 7

Revised 2013-07-18

See also Updating nodes programmatically in Drupal 7.

Please note that most of this was written in 2011 and while everything is still perfectly usable, there’s more ground that should be covered, such as working with i18n, media, etc. Also, you’ll probably want to use the entity metadata wrappers provided by the entity module. Consider ignoring this post and instead reading Programmatically creating nodes using Entity Wrapper and Death to Field Arrays.

I had to import a few thousand items from a legacy database to a Drupal 7 site and found that it was quite easy to do so programmatically. Here I’ll first show you the basic code for adding nodes and then I’ll talk about different field types, including how to add images and term references (taxonomy). If you have any questions, just ask in the comments and I’ll be happy to help!

Basic node creation - example

Put the code below in e.g. foo_create.php. Make sure it’s not accessible through the web (unless you don’t have shell access and must execute it through the browser and really know what you are doing). In other words, put it outside your Drupal directory, and then just run php foo_create.php. Note that you have to make sure that the input is valid! Test and test again, always make backups and get used to examining nodes with e.g. drush php-eval 'print_r(node_load($nid))' (see below for more about that).

If you use drush, you can skip the bootstrap part and, from your Drupal root directory, run drush scr ../foo_create.php (assuming foo_create.php is one directory above).

<?php
# Bootstrap start
define('DRUPAL_ROOT', '/path/to/drupal/root/directory');
$_SERVER['REMOTE_ADDR'] = "localhost"; // Necessary if running from command line
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
# Bootstrap end

$bodytext = "Foo m bar fnord?";

// If you update an existing node instead of creating a new one,
// comment out the three lines below and uncomment the following:
// $node = node_load($nid); // ...where $nid is the node id
$node = new stdClass(); // Create a new node object
$node->type = "article"; // Or page, or whatever content type you like
node_object_prepare($node); // Set some default values

$node->title    = "A new node sees the light of day";
$node->language = LANGUAGE_NONE; // Or e.g. 'en' if locale is enabled

$node->uid = 1; // UID of the author of the node; or use $node->name

$node->body[$node->language][0]['value']   = $bodytext;
$node->body[$node->language][0]['summary'] = text_summary($bodytext);
$node->body[$node->language][0]['format']  = 'filtered_html';

// I prefer to use pathauto, which would override the below path
$path = 'node_created_on' . date('YmdHis');
$node->path = array('alias' => $path);

if($node = node_submit($node)) { // Prepare node for saving
    node_save($node);
    echo "Node with nid " . $node->nid . " saved!\n";
}
?>

$node->language is important! If you don’t have the locale module enabled, the node will not be assigned any particular language. Or rather, the language code used then is LANGUAGE_NONE, which is a constant with the value und (undefined) in Drupal. If you have locale enabled nodes can exist in more than one language, and you should specify the language code. Go to Configuration -> Regional and language -> Languages to configure languages and see what code you should use. For English, it would be en; for Swedish, it would be sv; etc.

Other things you might want to set:

  • $node->status (1 or 0): published or not
  • $node->promote (1 or 0): promoted to front page
  • $node->sticky (1 or 0): sticky at top of lists
  • $node->comment: 2 = comments open, 1 = comments closed, 0 = comments hidden

This all you need for basic node creation. I will continue with different field types. The code below should be placed before the node_submit line. All fields can be accessed with $node->field_fieldname, where fieldname is the name you find in Structure -> Content types -> Manage fields.

The best way to see how Drupal nodes are made up, and how to work with the various fields, is to simply look at the structure of an existing node. Just create a new PHP file with the first bunch of lines above (including drupal_bootstrap()) and add print_r(node_load($nid)), where $nid is the node id. Or use drush: drush php-eval 'print_r(node_load($nid))'.

Setting various field types

Text or integer field

Nothing special:

$node->field_fnordtext[$node->language][0]['value'] = "Fnord fnord fnord";

Multiple values (make sure the field is configured to allow this):

$node->field_author[$node->language][]['value'] = "Ford, Tom";
$node->field_author[$node->language][]['value'] = "Fnord, Dom";

Creation time

To set a node’s creation time, you’d think you could just set $node->created. And you can - if you don’t use node_submit() (or if you set it after node_submit()). Looking at the node_submit() function in node.module, we find this line:

$node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;

Which means that node_submit() will set $node->created to the current time if $node->date doesn’t exist. So, if you want to set the creation time, you have to do something like this:

$node->date = "2009-05-27";

This also means that if you are updating a node programmatically, don’t forget to set $node->date; otherwise node_submit() will change the creation time. Or, when updating a node, simply don’t use node_submit(), since all it does is populate author and creation time. See Updating nodes programmatically in Drupal 7 for more about updating.

Date field (datetime, date, datestamp)

If you use the date module, you get three new field types - date, datetime and datestamp. (See this page to read about the differences; also check out this discussion.)

If your field is named datetest, you could do:

// For datetime
$node->field_datetest[$node->language][0][value] = "2011-05-25 10:35:58";

// For date
$node->field_datetest[$node->language][0][value] = "2011-05-25T10:35:58";

// For datestamp
$node->field_datetest[$node->language][0][value] = strtotime("2011-05-25 10:35:58");

Note that you don’t need to specify a complete date; for datetime and date you can just pad with zeros, e.g. 2011-05-00 00:00:00 (datetime), 2011-00-00T00:00:00 (date), etc. For datestamp you could just do e.g. strtotime("2011-05-25").

Important: Also note that while the exact value you specify will be stored in the database, the actual time displayed on the site might be different depending on timezone settings. When you create a new datetime/date/datestamp field, you get to choose between five different timezone handling methods. The default one is “site’s time zone”:

When entering data into the field, the data entered is assumed to be in the site’s time zone. When the data is saved to the database, it is converted to UTC.

However, if you set a date field programmatically like in the above example then no conversion takes place, so make sure you account for the field’s timezone settings. Or in other words, if you use “site’s time zone”, make sure the time is in UTC.

Boolean field

Single on/off checkbox:

$node->field_bork[$node->language][0]['value'] = 1;

Term reference (taxonomy) field

Set the term reference field tags to taxonomy term id 25 (for more than one term, just repeat the line; and note that it doesn’t matter whether the widget type is select list, check boxes/radio buttons or autocomplete):

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

Note: If you are trying this with the tags field in the default article content type, and have locale enabled, and have set $node->language to e.g. ‘en’, and it doesn’t seem to work: I also encountered this oddity (bug?). For some reason tags are saved to $node->field_tags[und] instead of $node->field_tags[en], while e.g. body is saved to $node->body[en] as expected. If I create a new term reference field, it works as it should. So either do that, or change the above to $node->field_tags[und][]['tid'].

As you can see, you need the know the taxonomy term’s id. Fortunately, there’s a Drupal function to help us with this: taxonomy_get_term_by_name(). You supply the name (“Italy”) and it returns an array of matching term objects, so you can do something like this:

if ($foo = taxonomy_get_term_by_name('Italy')) {
    $foo_keys = array_keys($foo);
    $node->field_tags[$node->language][]['tid'] = $foo_keys[0];
}

Unfortunately, you can’t specify a certain vocabulary with this function. This means that if you have the same term name in more than one vocabulary, the above code will just use whatever happens to come first. If you want to specify a certain vocabulary, say the one with id 9, you could do something like this:

$foo = taxonomy_get_term_by_name('Italy');
foreach ($foo as $term) {
    if ($term->vid == 9) {
        $node->field_tags[$node->language][]['tid'] = $term->tid;
    }
}

(One way of figuring out the vocabulary id is to run print_r(taxonomy_get_vocabularies());)

But what if you want to create new vocabulary terms for those that aren’t already in the database? You can use taxonomy_term_save(), like this:

$new_term = array(
    'vid' => 1,
    'name' => 'Fugazi',
    // You can optionally also set id of parent term, e.g. 'parent' => 25
);
$new_term = (object) $new_term;
taxonomy_term_save($new_term);

$new_term is very conveniently updated with the tid of our newly created term, which you can get with $new_term->tid.

Node references

If you use the references module for node/user references you can set a reference like this:

// 453 is the id of the referenced node
$node->field_author[$node->language][]['nid'] = 453;

This way you can also add multiple references in the same field - just repeat.

Entity field references

I haven’t had time to update this document properly (yet!) but Mark Losey wrote the following in a comment (thanks Mark!):

$node->field_my_reference[$node->language][0]['target_id'] = $entity_id;

“If you are using the product reference field in drupal commerce it’s a variation on that:“”

$node->field_product_reference[$node->language][0]['product_id'] = $product_id;

Image field

Attaching an image to any given image field is easy. Create a file object, copy the file and associate the file object with the image field:

$file_path = drupal_realpath('foo.jpg');
$file = (object) array(
    'uid' => 1,
    'uri' => $file_path,
    'filemime' => file_get_mimetype($file_path),
    'status' => 1,
);
// You can specify a subdirectory, e.g. public://foo/
$file = file_copy($file, 'public://');
$node->field_image[$node->language][0] = (array) $file;

Pathauto URL aliases

Joakim Hedlund comments (thanks!) that you can turn off the automatic path aliases generated by pathauto with the following:

$node->path['pathauto'] = FALSE;

Note that you have to turn pathauto off like this if you don’t want it to overwrite custom aliases. See How does Pathauto determine if the ‘Automatic URL alias’ checkbox should be checked or not? for more information.