I've finally started looking at Drupal 8 seriously and I'm particularly interested in the configuration management. I've come across something that could be a little bit problematic and that's regarding custom block content.

I can see that the configuration management system is able to export block configuration - region, theme, weight, visibility etc. however the actual block content does not come across in the config export, which is reasonable and understandable.

On importing that block config to a production site, what appears to happen is the block config is created and a holding message is put in place, reporting the block is broken or missing. Obviously the block content doesn't exist on the production server.

How can custom blocks be migrated from a dev/staging server to a production server? I realise that blocks in Drupal 8 are fieldable entities like nodes and so will need to be migrated in the same way and I understand there is a Migrate API in Drupal 8 but this appears to be built for migrating content from Drupal 6 and 7 sites to Drupal 8 as opposed to Drupal 8 to Drupal 8 sites.

This issue is specifically regarding custom blocks as blocks generated by other modules such as Views will obviously migrate across as configuration.

  • 1
    There are several content staging solutions in the works including deploy module and entitypilot.com (disclaimer, that's my product)
    – larowlan
    May 21, 2015 at 20:09
  • 2
    Similar: Block error on CMI
    – kenorb
    May 21, 2015 at 20:48

8 Answers 8


Another answer I haven't seen mentioned here is to use the Simple Block module, which is pretty much identical to core's 'Custom Block' setup, but instead of having a weird hybrid of content + config, you have all the Block settings and content stored in configuration, which can be cleanly exported and imported.

See, for further discussion in Drupal 8 core: Custom blocks cannot be properly exported and imported.


I have just publish a contributed module that solves this. Essentially, the module provides a type of block based on configuration (the fixed block) that wraps a custom block (the content block). If the content block does not exist, it is created with a default content or empty if no default content was set. Everything is done through UI, no special files or custom module are needed.

I named it Fixed block content and it is published at:



Please have hands on the Structure Sync module.

Structure sync provides Drush commands and admin interface screens for synchronizing content that could also be considered configuration. Including menu items, custom blocks and taxonomy terms.


  1. Go to structure sync.
  2. Go to Blocks tab.
  3. Export.
  4. Your configurations and content will be exported in configuration folder.
  5. Take the configurations to other sites & Import.
  6. Go to structure sync & click on import.
  7. Done

Another approach for keeping content that is added as part of development also pushed to live is to use the Default Content module to export the content. It is built for the content to be exported to an installation profile's 'content' folder, and then the module, if enabled, automatically brings the content in when the site is installed, but it's also possible to import the content one item at a time, such as in an update hook, with the below code in your example.install or example.profile:

* Import a piece of content exported by default content module.
function example_import_default_content($path_to_content_json) {
  list($entity_type_id, $filename) = explode('/', $path_to_content_json);
  $p = drupal_get_path('profile', 'guts');
  $encoded_content = file_get_contents($p . '/content/' . $path_to_content_json);
  $serializer = \Drupal::service('serializer');
  $content = $serializer->decode($encoded_content, 'hal_json');
  global $base_url;
  $url = $base_url . base_path();
  $content['_links']['type']['href'] = str_replace('http://drupal.org/', $url, $content['_links']['type']['href']);
  $contents = $serializer->encode($content, 'hal_json');
  $class = 'Drupal\\' . $entity_type_id . '\Entity\\' . str_replace(' ', '', ucwords(str_replace('_', ' ', $entity_type_id)));
  $entity = $serializer->deserialize($contents, $class, 'hal_json', array('request_method' => 'POST'));

Export a custom block with an ID of 8:

drush dcer block_content 8

(If you don't set your profile path in Drush settings you'll have to specify it above.)

And use the resultant export in your example.install file like this:

* Add the footer block content.
* Implements hook_update_N().
function example_update_8001() {



Having the same issue and not really a solution, only additions: In collaborative development we're using a staging server which pulls from the repository and resets all configuration. This means that block config is being reset automaticalls, you simply cannot place blocks you consider as "content" directly on that server.

It's easy to use drush config-export sync while knowing exactly what you've done and being sure that any configurations changes are meant for deploying. But Drupal decides for us that blocks are configuration (while obviously block content is handled as content). So this seems to be broken by design.

For the time given I reckon the most practical solution would be to add the block-related yml files to .gitignore.


I'm not sure I see any strong advantages of synching block configurations amongst multiple environments as blocks are so intertwined with content.

The reason for this is that there's a new block being created from the yml files that has no title/body (content) and is therefore giving the 'broken/missing' message.

You could try to make the UUID (if you wanted to make the block in both places - make sure the machine name matches...) in your development block_content table match what uuid you have in production (the other relationships seem to use the entity id). Then, when you do a config sync you could see the 'View differences' in the yml files and possibly see what else you need to change on dev to get it to match production uuids, etc. I got this to work, but still figured it's easiest to ignore all your block configurations in code unless you go through this process or create some kind of database block sync for yourself using block_content, block_content__body, and block_content_field_data.

It's not very elegant but could allow you to keep your block configs in code. Otherwise, if you keep deploying blocks with config, they will always be 'broken or missing'.

Another blog post suggests creating a custom block in live environment but not placing it. After syncing the database to dev, custom block could be configured, config exported and since it already exists in live import of placement is possible.


I am not sure too, however, if you didn't find any solution you may look this module https://www.drupal.org/project/deploy. Frankly speaking, I don't remember can deploy push blocks from DEV to PROD or not.


I think the best way to handle this would be to:

This is what I usually see people using and I personally use. But it syncs the entire database as compared to only the block content.

  • 1
    That can work if there is no issue with a database overwrite. Now, if the only desire is to move a new custom block into an existing database, this method would be difficult to implement.
    – karolus
    Oct 12, 2018 at 17:52
  • This answer has its place, in theory. But in practice, this isn't a good solution, especially if the project is using configuration split, or has any different configuration between environments (which is very likely).
    – komlenic
    Nov 2, 2018 at 23:30