The Big Picture

Backdrop's codebase is similar to Drupal 7 and generally needs very few changes to convert. To make things even easier, Backdrop includes a Drupal compatibility layer which should enable Drupal functions (even those with the format 'drupal_XXX', such as drupal_set_message), to just work, for now. However, Backdrop still has significant advantages over Drupal 7, and it would make sense to fully port your module rather than depend on the compatibility layer.

This guide suggests a process for converting Drupal 7 modules to Backdrop, and the common issues which might arise during the conversion. (If your module is not Drupal version 7, we recommend you follow Drupal's module conversion guidelines to upgrade (or downgrade) to Drupal 7 before continuing.)

A useful video by @quicksketch demonstrates most of the main issues in this document.

Before You Begin

Before you start porting a module, please create the two recommended issues stated on the Communicating section of the Converting Drupal code page.

Coder Upgrade

The Coder Upgrade module can carry out many of the required changes automatically. You should still, however, read through all of the below even if using Coder Upgrade, because there will likely need to be some "touch-up" changes to the code after Coder Upgrade does its conversion.

Creating the Repository

The process typically starts by creating a new repository on GitHub for the Backdrop version from the Drupal 7 module codebase. This can be done on the command line as follows. This would be for a module that had a 2.x release:

# Checkout from drupal.org:
git clone --branch 7.x-2.x http://git.drupal.org/project/[project_name].git

cd [project_name]

# Rename the branch.
git branch -m 7.x-2.x 1.x-2.x

# Remove the old origin and point at Github instead.
git remote remove origin
git remote add origin git@github.com:backdrop-contrib/[project_name].git

# Push to the new repository.
git push origin 1.x-2.x

You can now see all of releases that are on the Drupal project page (e.g., https://www.drupal.org/project/[project_name]/releases) in your repository.

Note, however, if you are not yet a member of the backdrop contrib group you cannot push into the github.com:backdrop-contrib repository.

Organization of Files

The organization of files within the module directory is similar to that of Drupal, but there are some differences. For reference, see the developer documentation for Backdrop modules. You can also download a sample module template to use for reference.

Updating the README file

Most Drupal modules have a README.txt file. Backdrop modules have a README.md file, so you should convert the Drupal README to Markdown format. There is considerable variation in what gets put into a Drupal README file, but Backdrop has a recommended structure: you can use this sample README.md file for the overall structure.

Note that Drupal README files often contain detailed documentation. Backdrop README files are short summary documents. If you have extended documentation, you should include that in a Wiki page in your project's GitHub repository once it has been moved into the backdrop-ops/contrib project group.

Modifying Your Module's .info File

Here is a comparison of Drupal and Backdrop module info files:

name = Book
description = Allows users to create and organize related content in an outline.
package = Core
version = VERSION
core = 7.x
files[] = book.test
files[] = includes/whatever.inc
stylesheets[all][] = book.css
configure = admin/content/book/settings
dependencies[] = node

; Information added by Drupal.org packaging script on 2014-11-19
version = "7.34"
project = "drupal"
datestamp = "1416429488"

and here is Backdrop:

type = module
name = Book
description = Allows users to create and organize related content in an outline.
package = Core
version = VERSION
backdrop = 1.x
stylesheets[all][] = book.css
configure = admin/content/book/settings
dependencies[] = node

The first and most important change is that a new Backdrop-specific core version needs to be added: 'backdrop = 1.x'. For modules that would like to support both Drupal 7 and Backdrop at the same time, the old Drupal core version can also remain, but for Backdrop-only modules this line should be removed: core = 7.x

Backdrop also distinguishes between different types of projects (modules, themes, layouts) by using the line type = module and the packaging script on backdropcms.org will fail if the type line is excluded.

In Backdrop the class registry has also been removed and replaced with a static class map (see the related change record). This means that you'll need to remove any lines in your info file that define files containing PHP classes: files[] = whatever.inc. To have this class loaded you'll need to implement hook_autoload_info() in your module file instead.

The exception is test classes. The .test files are loaded with a .tests.info file and not listed in hook_autoload_info(). For example: files = menu_import.test. Read more in next section.

in the Drupal 7 .info file:

files[] = includes/whatever.inc

in the Backdrop .module file:

function hook_autoload_info() {
  return array(
    'ClassName' => 'includes/whatever.inc',
  );
}

In the .info file you should remove the version and datestamp as those will be generated automatically by Drupal.org as part of creating the release.

There is a new feature in Backdrop: tags in the .info file. More details here.

Example:

name = Menu Export/Import
description = Import and export menu hierarchy using indented, JSON-like text files.
package = Development
tags[] = Menus
tags[] = Site Architecture
tags[] = Structure
backdrop = 1.x
dependencies[] = menu
type = module
configure = admin/structure/menu/import

Organization of Module Files

There are now recommended locations for common module files. Javascript files are always kept in a js folder, CSS in a css folder, templates in a templates folder, and tests in a tests folder.

Backdrop recommends the following files be kept in the root of the module folder. Having these files immediately visible makes it more obvious that this module provides theme functions (and makes them easy for front-end developers to find), has a settings page, and provides user-facing pages. If a module provides too many additional .inc files it would also be reasonable to place those additional files into an includes folder also in the module root, or organize further as necessary.

  • modulename.info
  • modulename.module
  • modulename.admin.inc
  • modulename.pages.inc
  • modulename.theme.inc
  • modulename.api.php
  • config
  • css
  • js
  • templates
  • tests

If your module contains tests, those tests should live within the tests directory. Information about the tests should be moved out of the getInfo() method in the test class, and instead moved into a new .info file located at tests/modulename.tests.info. Check out the Book test info file for an example.

Another example:

Old:

class MenuImportTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Menu export/import',
      'description' => 'Perform various tests on menu_import module.',
      'group' => 'Menu',
    );
  }
...

In the tests/menu_import.tests.info:

[MenuImportTestCase]
name = Menu export/import
description = Perform various tests on menu_import module.
group = Menu
file = menu_import.test

If your module contains the jQuery core file, you should remove it. Backdrop currently ships with updated jQuery. If the Drupal 7 module uses third-party scripts, which are in DRUPAL_ROOT/sites/all/libraries, then you can create a new directory BACKDROP_ROOT/libraries, and place the script files inside this directory.

The preferred method, though, is to integrate these files into your module: place inside the MODULE_ROOT/js directory, and register it with hook_library_info() in modulename.module. If the library is likely to be used by others, you should create a separate module for the library, then include that module in your module's dependencies (and then others can do the same).

If your module contains .make file, you should consider that Drush make is no longer maintained. Composer is the supported solution.

Configuration Management

Drupal 7 stores most module configuration in the variables table in the database. A trio of handy functions were provided to manipulate these variables: variable_get(), variable_set(), and variable_del().

In Backdrop, configuration is stored in flat files. These files are often called configuration (or config) files, and end in a .json extension. By default, active config files are saved into BACKDROP_ROOT/files/config_xxxx/active (where xxxx is a long alphanumeric string generated during install and entered into settings.php). Backdrop provides the corresponding config_get() and config_set() functions to manage the retrieval and writing to disk.

For the purpose of backwards compatibility with Drupal 7, both the variables database table and the 'variable_' functions still exist in Backdrop. They have been deprecated and will be removed in Backdrop 2.0, so conversion to 'config_' is highly recommended.

In Drupal 7, the variables table was something of a catch-all for small bits of data; it held not only configuration data (information that was typically set once and then never changed), but also environment-specific data that was regularly updated (for example, the last time that a particular cron function was called). In Backdrop, information of this latter type is called state data, is stored in the database, and is accessed using functions state_set() and state_get().

In the discussion that follows, we will assume all variables are to be converted to config and will be accessed with config_set(), and config_get(). As you plan your conversion, though, some variables may be more appropriate for state_set()/state_get(). See this change record for a fuller description of the distinction.

Config Storage File(s)

To convert variables to config, first identify all the module's current variables: use your editor, search, or grep. The aim is to get an idea of the number or variables and type of configuration your module requires.

To store configuration, the module will need its own config file. Config files are commonly named 'modulename.settings.json'.

Sometimes it is appropriate to have multiple config files, separated by type. For example, the settings for node module are saved by node type: node.type.article.json, and node.type.page.json.

For a module with very complex configuration, two separate settings files such as modulename.first_set_of.settings.json, and modulename.second_set_of.settings.json may be a better fit.

Think about how you would like to deploy this configuration. Would you prefer to deploy all the module's settings at once, or separately? It makes sense to deploy one node type at a time, but you usually want all the settings at once.

Changing the Code

Once you have decided on a single config file or several, you will need to replace every instance of a 'variable_' function. The standard method is as follows:

Instead of

$variable = variable_get('foo', 'bar');

Use

$variable = config_get('modulename.settings', 'foo');

And instead of

variable_set('foo', $variable);

Use

config_set('modulename.settings', 'foo', $variable);

The above examples of config will read and write to a JSON file called modulename.settings.json in the active config directory.

An alternative method for saving config, usually when getting or setting more than one value at a time, is to create a config object and use the object's get and set methods.

// Get the full config object
$config = config('modulename.settings');

// Get individual settings
$value_foo = $config->get('foo');
$value_next = $config->get('next');

This is especially useful when creating settings form arrays where you may need a '#default_value' for many form elements. It's also the preferred method for saving values in a form submit handler, where config is being saved:

// Get the full config object
$config = config('modulename.settings');

// Set individual config settings
$config->set('setting_one', $form_state['values']['value_one']);
$config->set('setting_two', $form_state['values']['value_two']);

// Save
$config->save();

Create Config Defaults

The function variable_get() has the useful capacity to declare default values; writing $setting = variable_get('my_setting', 'red') allows the developer to initialize the value 'red' if 'my_setting' variable has not been set. Conversely, though, this required an initialization value to be provided every place variable_get() was called, leading to lots of code duplication (or worse, non-duplication).

In Backdrop, there's no such thing as a variable that has not been set, and therefore config defaults need to be explicitly set at module enabling.

The simplest way to set default values for your config variables is to provide a default config file. This file should be placed into a config directory within your module: config/modulename.settings.json. When the module is enabled for the first time, the config file will be copied from this location into the active config directory.

BACKDROP_ROOT/modules
    modulename
        modulename.info
        modulename.module
        config
            modulename.settings.json

Here is a sample config file to get started. Note the lack of a trailing comma after the last value; this is a JSON requirement.

{
  "_config_name": "module_name.settings",
  "setting_one": "default value 1",
  "setting_two": 2,
  "array_one": ["value_1", "value_2"],
  "empty_array": []
}

Declare the Module's Config

Modules need to declare config files in hook_config_info() in modulename.module. This is how Backdrop knows that you have a config file that needs to automatically copied when the module is enabled, and removed when uninstalled. It's also how Backdrop compares changes in config during deployment.

/**
* Implements hook_config_info().
*/
function contact_config_info() {
  $prefixes['modulename.first_set_of.settings'] = array(
    'label' => t('First set of settings'),
    'group' => t('Configuration'),
  );
  $prefixes['modulename.second_set_of.settings'] = array(
    'label' => t('Second set of settings'),
    'group' => t('Configuration'),
  );
  return $prefixes;
}

Create an Upgrade Path

At this stage the module should be nearly completely converted to CMI; new installs will use CMI exclusively for storing config. However, existing users with this module installed on a Drupal site and who wish to convert to Backdrop will still need a process to convert existing settings stored as variables to the config system.

Backdrop uses the hook_update_N() system for upgrades and updates in modulename.install. For initial porting to Backdrop 1.x, N should be in the 1000s; most commonly, modulename_update_1000() is the update that converts variables to config.

Note that while the standard docblock summary line for implementation of a hook is "Implements hook_NAME()", the docblock for implementations of hook_update_N() is special; it should describe what the update does, for example, "Migrate variables to config."

In this update, retrieve the values of existing variables and store them as config, then delete the variables. For example:

 /**
* Move book settings from variables to config.
*/
function book_update_1000() {
  // Migrate variables to config.
  $config = config('book.settings');
  $config->set('book_allowed_types', update_variable_get('book_allowed_types', array('book')));
  $config->set('book_child_type', update_variable_get('book_child_type', 'book'));
  $config->set('book_block_mode', update_variable_get('book_block_mode', 'all pages'));
  $config->save();

  // Delete variables.
  update_variable_del('book_allowed_types');
  update_variable_del('book_child_type');
  update_variable_del('book_block_mode');
}

If you have renamed a column in a database table, you have to use the db_change_field() function.

For example when 'drupal' string is replaced with 'backdrop' in the name of a column:

/**
* Rename a column in the 'book_records' table.
*/
function book_update_1001() {
  db_change_field('book_records', 'drupal_user', 'backdrop_user',
    array('type' => 'varchar', 'length' => 255, 'not null' => FALSE));
}

Note that when upgrading a module, all previous updates should be deleted. If a module has updates numbered 6xxx or 7xxx, be sure to remove all these updates.

When removing these updates, you should provide an implementation of hook_update_last_removed() in modulename.install to indicate which version of the module schema you're expecting when an upgrade is performed:

/**
* Implements hook_update_last_removed().
*/
function modulename_update_last_removed() {
  return 7012;
}

Using system_settings_form()

Many settings forms use the system_settings_form() function to finish off creating the form. From the API of system_settings_form():

"This function adds a submit handler and a submit button to a form array. The submit function saves all the data in the form, using variable_set(), to variables named the same as the keys in the form array."

Since variables are not used in Backdrop, system_settings_form() has been updated to save to a single config file. If your module saves all the settings in its form to a single file, you'll need to add a new #config key to the form array so system_settings_form() knows where to save the values in the form build function. Such settings form functions are commonly located in either the modulename.module or modulename.admin.inc files.

Old:

function modulename_settings_form($form, $form_state) {
  $form['modulename_my_setting'] = array(
    '#type' => 'textfield',
    '#title' => t('My setting'),
    '#default_value' => variable_get('modulename_my_setting', ''),
  );

  return system_settings_form($form);
}

New:

function modulename_settings_form($form, $form_state) {
  $form['#config'] = 'modulename.settings';
  $form['my_setting'] = array(
    '#type' => 'textfield',
    '#title' => t('My setting'),
    '#default_value' => config_get('modulename.settings', 'my_setting'),
  );

  return system_settings_form($form);
}

Note that config_get() does not take a default value.

If you need to save values into multiple config files, we recommend that you add your own submit button, and submit handler for the form instead, as follows.

function modulename_settings_form($form, $form_state) {
  $config = config('modulename.settings');

  $form['my_setting'] = array(
    '#type' => 'textfield',
    '#title' => t('My setting'),
    '#default_value' => $config->get('my_setting'),
  );

  // Add a submit button
  $form['actions']['#type'] = 'actions';
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );

  return $form;
}

/**
* Submit handler for module_settings_form().
*/
function modulename_settings_form_submit($form, &$form_state) {
  $config = config('modulename.settings');
  $config->set('my_setting', $form_state['values']['my_setting']);
  $config->save();

  $config = config('modulename.other-settings');
  $config->set('my_other_setting', $form_state['values']['my_other_setting']);
  $config->save();

  backdrop_set_message(t('The configuration options have been saved.'));
}

Notice how with the new approach, you can remove the module name from the name of your variables (if you like) since the variables are saved in individual config files that contain the module name.

Keep in mind, though, that it's common to keep the module name as part of the variable name when upgrading a Drupal 7 module to Backdrop. If other modules will be depending on your module and its settings, if you change the name of your variables in the move to config, modules that depend on yours will need to do so, too in their calls to your module's config settings.

Classed Entities

Several entities in Backdrop are now classed objects which extend the Entity Class. Users, nodes, comments, files, taxonomy terms and vocabularies have been converted to extend the new base class. Therefore programmatic creation of these objects has changed in Backdrop. For example:

Old

$node = new stdClass();
$node->type = 'blog';
$node->title = 'New Title';
...
node_save($node);

New

$node = entity_create('node', array ('type' => 'blog'));
$node->uid = $user->uid;
$node->title = 'New Title';
$node->save();

You should search for these functions in the code (further details):

Node entity has a langcode property instead of a language property. Replace it:

Old:

$node = new stdClass();
$node->language = LANGUAGE_NONE;

New:

$node = entity_create('node', array ('type' => 'blog'));
$node->langcode = LANGUAGE_NONE;

Default Views

If your module contains any Default Views in PHP code, you might want to try the Default Views Convert to Config module.

In Drupal 7 default Views were provided by invoking hook_views_default_views(), but this hook is absent in Backdrop. Default Views in Backdrop are provided as JSON configuration files instead. This module will allow you to drop in the PHP code for your Drupal 7 view, and it will generate a JSON file for the equivalent Backdrop view.

Core Modules Removed

There are several modules that were included in Drupal 7 that have been removed from Backdrop (see complete list). If your module implemented APIs for any of these modules, those hooks can be safely removed.

A commonly-used Drupal function was hook_help() in modulename.module. As the Help module was removed from Backdrop, this function no longer exists and so you should instead display any necessary help text on forms/pages directly, or in your module's GitHub Wiki.

Leaving these hooks in your code will not affect a Backdrop module in any way, since the code will simply not be called. You may leave them in place if you would like to be able run the same module on both Drupal and Backdrop sites.

Features Added to Core

The most commonly used Drupal 7 features are being added into Backdrop core. Check the list of modules those are added into Backdrop core or no longer necessary to include. If any of these is a dependency of your module, you should remove the dependencies[] line from your modulename.info file.

You should rewrite the dependencies[] if the module has been replaced or its functionality integrated into an another module. In the latter case, the integrated modules' functions may have changed, in which case you may need to fix the function call. For example, the functions of the Libraries API module have been changed.

Install and Uninstall Hooks

If the module implements hook_schema(), hook_install() will create the schema. So, backdrop_install_schema() is not needed to be called in hook_install() (nor is drupal_install_schema()). Similarly, backdrop_uninstall_schema() (and drupal_uninstall_schema) are unnecessary to call. Check the .install file to see if these hooks can be removed.

If your module provides separate configuration files, you should implement hook_config_info(). This will allow the config files to be automatically removed during the module uninstallation, without having to specifically do that "manually" in an uninstall hook.

Without Drupal Compatibility

Drupal compatibility is enabled by default in the variable backdrop_drupal_compatibility within BACKDROP_ROOT/settings.php. The last step to completely port the module to Backdrop CMS, is to check that it works well with the Drupal compatibility disabled. To do that, you should replace

  • drupal with backdrop,
  • DRUPAL with BACKDROP,
  • Drupal with Backdrop,

in all the files of the module and test its operation. Each is case sensitive. With this easy step we can avoid using deprecated functions (which could be the cause of warnings or errors). Note, however, these replacements should be done manually, not in bulk; there can be legitimate reasons for the continued existence of "Drupal" (for example, in comments explaining the difference between a Backdrop feature and its Drupal predecessor).

Testing

Some suggestions:

  • Set the "All messages" option on the admin/config/development/logging page.
  • Log page: admin/reports/dblog
  • If your module contains a .test file, then you should enable the Testing module, and run the module's test on the admin/config/development/testing page. If you encounter an error message, you may need TestCase's classes descriptions.

Summary

These are a few of the common module changes which will be required for porting. However, other less common API changes exist. See the change records for a full list. Use the search bar on this page if you encounter an error message. If you find an unlisted API change, please report this on the Backdrop Issue Queue.

Some examples:

  • Search result of "hook_page_build": New Layout module -> $page variable has been removed
  • If the module uses the language key object within the $message array parameter in hook_mail(), now has a langcode property instead of language property. Replace it.

You can learn from the code of previously converted modules. Select a module on the Backdrop Modules site, follow the "Project page" link to Github, and study the commits.

If your converted module is a contrib module, and you want to publish on Backdropcms.org, then read the Contributed Project Agreement for how to join the contrib group.

Tagging for Release

When tagging your Backdrop module for release, please use the same version number as the Drupal module being ported. For example, if you are porting the 7.x-4.15 version of webform module, that should become the 1.x-4.15.0 version of the Backdrop module. Any bug fixes would increment the last number to 4.15.1 or 4.15.2.

When a new version of the Drupal module comes out (7.x-4.16) the minor-version number of the backdrop version can also be updated to match (1.x-4.16.0) and that version can contain all/any commits from the Drupal project that you'd like to include.

If the Backdrop and Drupal version numbers do not match for any reason, the Backdrop release description should contain the Drupal version that is comparable.

Pre-release modules can have a pre-release suffix, e.g., 1.x-1.0.0-alpha1, 1.x-2.0.0-beta4, etc. Creating a prerelease-tagged version is a good way to get usage feedback from users who are interested in the module and willing to try it out. Be sure to mark prerelease versions as such in Github when you create the release tag.

Happy converting!