Exporting and creating field definitions in Drupal 7

I frequently export field definitions so I can programmatically create them in an update.php function. In Drupal 6, the CCK module came bundled with a Content Copy, but (so far) I have found no similar tool in Drupal 7. Here are the tricks I use to do this in a quick an relatively painless way.

Understanding the export functions

Drupal 7 comes with a welcome Field API, which standardizes all the content now stored in nodes, comments, users, and taxonomy terms. The major benefit (in my opinion) is that when you are working with a field—no matter what it is attached to—you can do just about anything you want. But that's beside the point.

The important thing to note is that a field is first it's one thing, and second is attached to a bundle. So, if you've got an image field on your Article content type, you actually have an image field (by itself), and an instance of that field which is attached to the "node" bundle of the type "article". Got it? Good.

If you want export a field definition, you can use the handy field_info_field and field_info_instance functions. Taking our example above, here's how you would call these two functions:

$field_data = field_info_field('field_article_image');
$instance_data = field_info_instance('node', 'field_article_image', 'article');

Understanding the create functions

Now that you have your field and instance definitions, you are ready to create them in your script. Drupal 7 brings two functions which look a lot like their export counterparts, field_create_field and field_create_instance. Just pass in your field definitions, and you're done.

field_create_field($field_data);
field_create_instance($instance_data);

Incorporating this into your development workflow

Now that you understand how it works, let me show you some easy tricks to incorporate this into your development workflow. After you've created your fields using the Field UI, navigate to /devel/php (this requires the must-have Devel module). Then, execute the following, populating the first three variables:

$entity_type = '';
$field_name = 'field_';
$bundle_name = '';

$info_config = field_info_field($field_name);
$info_instance = field_info_instance($entity_type, $field_name, $bundle_name);
unset($info_config['id']);
unset($info_instance['id'], $info_instance['field_id']);
include_once DRUPAL_ROOT . '/includes/utility.inc';
$output = "field_create_field(" . drupal_var_export($info_config) . ");\n";
$output .= "field_create_instance(" . drupal_var_export($info_instance) . ");";
drupal_set_message("<textarea rows=30 style=\"width: 100%;\">" . $output . '</textarea>');

This will do several things. First, it exports your field and instance definitions. Then, it removes the field IDs, so you don't have any ID conflicts when importing. Lastly, it formats the "create" functions for you, and display it inside a textarea which you can copy and paste it into your script.

Making it really, really easy

If you are like me, you'll do this over and over. Instead of bookmarking this article (or trying to memorize it), wouldn't it be nice if you could just punch a keystroke on your computer and have that all populate in the Devel textarea?

Wait, you can! If you use Google Chrome, you can install the Popchrom extension. Create yourself a trigger called "field_export", and put the code above in. Then, when you click Ctrl + Space (or whatever keystroke you've got Popchrom configured to recognize, this little snippet will replace "field_export". Easy, peasy, rice and cheesy.

Final thoughts

I know, I know... we could make a module out of it. Maybe somebody already has. But in the meantime, this gets the job done.

Comments

Hi Joel,

Correct me if I'm wrong, but D7 won't allow the same field to be instantiated more than once in a bundle, which can suck if you want to use the same type of field multiple times within a given Content Type.

For example, if I create a Content Type called "Item" and a field called "icon". I can only use this icon field once for "Items". If I want each "Item" to have a color_icon field instance, a greyscale_icon field instance, and a black_and_white icon field instance, I cannot reuse that dang "icon" field. I've looked at the database structures and the Field API code, and it looks like there's no getting around this unless someone wrote a module to modify the Field UI for managing a Content Type to add a "duplicate" button for each field that would take the field instance definition with all its accompanying settings and to copy and populate a new instance as your code above suggests.

Similarly, if I wanted to do this with the field_collection field type, under development, I suppose this might require a bit more complexity unless the definitions of the embedded fields of the field_collection come along for free. I created a field_collection of 6 fields and tried to reuse it in the same content type, but was flatly rejected due to the same reason for any field.

Even though I've programmed for many years, I'm new to Drupal. If my basic assumptions are off, please let me know. I'm not so sure I want to create my first module to do something like this, but it could be fun... perhaps.

It looks like you code above creates a snippet that can pasted into one of your custom modules, but I'd prefer a more seamless method with a UI if possible.

Thoughts?

You are correct. It is a rather complex issue, creating more than one instance of a field in the same bundle (content type). It wouldn't be as easy as adding a "duplicate" button to the field UI, because each field in a bundle has a unique name, and everything (core and contrib) works off that assumption. At the core (literally), it's impossible to pull off.

Options would include 1) creating new fields (field_color_icon, field_color_greyscale, etc), and 2) creating a new field type which allows you to select different options in a field that stores more than 1 value.

I think #2 is your best bet. The Field API is very strong in Drupal 7, and is well documented.

FIRST, ON SOURCE SITE:

$export_data = array();
$export_data['entity_name'] = 'user';
$export_data['bundle_name'] = 'user';
$instances = field_info_instances($export_data['entity_name'], $export_data['bundle_name']);

foreach ($instances as $field_name => $field) {
$export_data['data'][] = array(
'field' => field_info_field($field_name),
'instance' => field_info_instance($export_data['entity_name'], $field_name, $export_data['bundle_name']),
);
}
var_export($export_data);

--------------------------------
THEN ON DESTINATION SITE:

//First, ensure $import_data = pasted output

foreach ($export_data['data'] as $item) {
field_create_field($item['field']);
field_create_instance($item['instance']);
}

Hope it helps!

Thanks this worked well for me, but only once.
can you add a line or two that deletes the existing field and field instance so the import continues to work for developers who have to repeat the process?

Hi,
Is it possible with this technique to copy a node reference field to a taxonomy term name?
I would like to have the same names as terms, as used on node reference to use also the drupal menu system. Having nodes with references:
- Continent node
- Country node with reference Continent
- Area node with reference Country, Continent
- Village node with reference Area, Country, Continent
- Accommodation node with reference Village, Area, Country, Continent

I would like to make taxonomy terms automated from the references to use as menu items with taxonomy menu and other taxonomy based modules.

Thanks a lot in advance for your reply!
greetings, Martijn

Thanks, that was helpful. I needed to copy a couple of field instances from one bundle to several other bundles within the same site. This is the script I ran on /devel/php:

$origin = 'article';
$bundles = array('blog', 'news');
$fields = array('field_quote', 'field_image');

foreach ($fields as $field) {
$instance_data = field_info_instance('node', $field, $origin);
foreach ($bundles as $bundle) {
$instance_data['bundle'] = $bundle;
field_create_instance($instance_data);
}
}

Brilliant!

This is great, thanks.
How about if it issues an extra line before creating the instance and the field, to check if it already exists. I keep getting conflicts when I use this code in my install hook and re-install. Field API doesn't fully delete a field's data when its module is uninstalled. See this from drupal core
function _comment_body_field_create($info) {
// Create the field if needed.
if (!field_read_field('comment_body', array('include_inactive' => TRUE))) {
$field = array(
'field_name' => 'comment_body',
'type' => 'text_long',
'entity_types' => array('comment'),
);
field_create_field($field);
}

Just wanted to let you know that this was infinitely helpful!

The function field_purge_batch($number_of_fields_to_purge) can be used to delete fields completely. One might want to call this with a high number as its argument in order to remove any remaining field data from the database before recreating the same fields, e. g. during a module re-install.

Add new comment