I'm trying to migrate nodes from Drupal 7 to Drupal 8 with paragraphs fields, but I can't seem to get the configuration right.

I believe I have a functioning custom source plugin for d7 paragraphs, because when I run it, it correctly (as far as I can tell) migrates paragraphs, but I'm having trouble with migrating the reference relationship when I migrate the nodes.

I see in comments on other issues relating to the migration of paragraphs fields that others are successfully migrating paragraphs fields, but I can't seem to figure out how to structure my node migration to establish the relationship.

This is my migrate_node_page migration. It migrates the nodes, and gets other relationship fields like taxonomy terms, but the paragraphs are just missing:

.
.
.
process:
  .
  .
  .
  field_paragraphs:
    plugin: iterator
    source: field_paragraphs
    process:
      target_id: 
        plugin: migration_lookup
        migration: migrate_paragraph_items
        source: target_id
.
.
.

Does anyone see what I'm doing wrong?

Issue fork paragraphs-2990527

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

    Comments

    safetypin created an issue. See original summary.

    safetypin’s picture

    Issue summary: View changes
    safetypin’s picture

    Issue summary: View changes
    havran’s picture

    I think you must use both target_id and target_revision_id like here:

      field_paragraphs:
        plugin: sub_process
        source: paragraphs
        process:
          target_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: pid
            -
              plugin: extract
              index:
                - 0
          target_revision_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: pid
            -
              plugin: extract
              index:
                - 1
    

    Extract is used because paragraph use compound ID (array of target_id and target_revision_id).

    safetypin’s picture

    @havran - thanks for getting back to me.

    Unfortunately that configuration also didn't work for me. The paragraphs are migrated, but the relationship between the paragraphs and the nodes is not.

    Why do you have "pid" in that migration? Do I need to change that?

    safetypin’s picture

    I was looking at the MigrationLookup.php class, and I see that on line 166 there is this block of code:

          if (isset($this->configuration['source_ids'][$migration_id])) {
            $configuration = ['source' => $this->configuration['source_ids'][$migration_id]];
            $value = $this->processPluginManager
              ->createInstance('get', $configuration, $this->migration)
              ->transform(NULL, $migrate_executable, $row, $destination_property);
          }
    

    When I inspect $this->configuration I see an array that appears to be created based on the migration you suggested, however, there is no "source_ids" element to this array, so as far as I can tell, this block of code would never be executed. The $migration_id matches the configuration that I've used (referencing the specific migration I've created to migrate my paragraph entities). Is $this->configuration['source_ids'] supposed to be configured in the migration? I don't see anyone else using anything like that in example migrations for paragraphs.

    anu_mathew’s picture

    @safetypin : have you got the solution for this ? Am also facing same issue with paragraph and content mapping.

    safetypin’s picture

    @anu_matthew I haven't found a migrations based solution. If you are willing to maintain IDs across the migration (make the id numbers the same in Drupal 8 as they are in Drupal 7) then exporting/importing the data isn't very difficult.

    The migration will create the paragraphs_item and paragraphs_item_revision data, and you should be able to migrate non-reference field data (numbers, text, emails, links, etc) with drush migrate commands.

    Using a migration like this will maintain the ID numbers (note "id:item_id" and "revision_id: revision_id" at the beginning of the process), so you can export/import the entity reference field tables and all you have to do is make some minor changes to the column names (like in D7 the column is "language" and in D8 the column is "langcode"). The data stored in the database is exactly the same:

    source:
      plugin: d7_paragraphs_item
      bundle: image
    
    destination:
      plugin: 'entity_reference_revisions:paragraph'
      default_bundle: image
    
    process:
      id: item_id
      revision_id: revision_id
      field_image:
        plugin: iterator
        source: field_image
        process:
          target_id: fid
          alt: alt
          title: title
          width: width
          height: height
    

    If you have paragraphs that also have entity reference revisions fields that contain other paragraphs then it gets a little more complicated. In this case, you have an extra step of export/import for each reference field.

    I was able to migrate successfully a "columns" type paragraph that contained paragraph reference field, that contained several different other paragraph types using a database GUI that allowed table export and import. I'm often logged into the Drupal IRC channels (my handle there is also safetypin).

    mirsoft’s picture

    The solution that works perfectly for me (based on answer #4, thanks @havran) in latest Drupal 8.6.2 is below.

    Please note that I didn't need to migrate past revisions of paragraphs. I just wanted to migrate the last (actual) paragraph revision from the source and attach it properly to the parent node. In reality, I think it's very common use case, because one might not need to fill in the target DB with mostly obsolete past revisions. But if you'd need to migrate *all* the revisions from the source paragraphs as well, you would probably need to extend the code below and migrate also proper revisions references.

      field_paragraphs:
        plugin: sub_process
        source: field_source_paragraphs
        process:
          target_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 0
          target_revision_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 0
    

    These items you will likely need to customize:

    • field_paragraphs: name of the target paragraph field (typically attached to the parent node etc.)
    • field_source_paragraphs: name of the source paragraph field (typically attached to the source node)
    • migration: array of *all* paragraph migrations the source paragraph field can use. It is important to list all migrations there and migrate paragraphs with all that migrations before. If you don't do thiat and the source ID will not be found between any of source migration, you will likely end with a not very explanatory error "input should be an array", resulting from failure on the following extract plugin that expects an array on input (and instead, it gets nothing)
    • source: is always a "value" as that's how the source paragraph reference field is named in Drupal 7. If "value" doesn't work for you, check the "field_data_[field_source_paragraphs]" table in source D7 databse, where [field_source_paragraphs] is the value you entered above. There is one field that holds the value of reference, in my case it was [field_source_paragraphs]_value
    • index: is always 0 as we are getting a first index from the returning [value, revision_id] pair both for target value and also for a target revision.

    I'm setting the same "value" and "index" values for both target_id and target_revision_id fields. That's possible, because without migrating all the past revisions, we are okay to assume that the current revision is the first one, thus with same id as target_id. This would be probably a place to extend if you would need to migrate all the revisions.

    Note: This pattern works perfectly for field collections as source as well. Just enter your field collection migration instead of paragraphs into the "migration:" field and follow the guide above for other fields.

    mirsoft’s picture

    Actually, the previous example can be even simplified like this:

      field_paragraphs:
        plugin: sub_process
        source: field_source_paragraphs
        process:
          target_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 0
          target_revision_id: '@target_id'
    
    joshua.roberson’s picture

    #10 works for me with no issues.

    hazong’s picture

    Hey Guy, I'a facing the same problem without solution:
    here is the structure of my json file:

    {
        "data": [
            {
                "uuid": {
                    "value": "0142f404-0289-42ec-b26b-ab1ce7798fd5",
                    "parent": false
                },
                "name": {
                    "value": "P1",
                    "parent": false
                },
                "supplierName": {
                    "value": "U1",
                    "parent": false
                },
                "description": {
                    "value": "",
                    "parent": true
                },
                "mandatoryInformation": {
                    "value": "",
                    "parent": true
                },
                "technicalInformation": {
                    "value": "",
                    "parent": false
                },
                "packageInformation": {
                    "value": "",
                    "parent": false
                },
                "productSpecifications": {
                    "data": [
                        {
                            "name": {
                                "value": "p11",
                                "parent": false
                            },
                            "npp": {
                                "value": "1385002",
                                "parent": true
                            },
                            "amount": {
                                "value": "28",
                                "parent": false
                            },
                            "packagingUnit": {
                                "value": "lo",
                                "parent": false
                            },
                            "packagingSize": {
                                "value": "NN",
                                "parent": false
                            },
                            "atcCode": {
                                "value": "0o",
                                "parent": false
                            },
                            "labelling": {
                                "value": "",
                                "parent": false
                            },
                            "dosageForm": {
                                "value": "Tabletten",
                                "parent": false
                            },
                            "activeSubstances": {
                                "value": "Pioglitazon hydrochlorid",
                                "parent": false
                            },
                            "activeSubstancesWithAmounts": {
                                "value": "plo",
                                "parent": false
                            },
                            "indications": {
                                "data": [
                                    {
                                        "code": {
                                            "value": "00.00",
                                            "parent": true
                                        },
                                        "codeDescription": {
                                            "value": "leo",
                                            "parent": true
                                        }
                                    }
                                ]
                            },
    
    

    the firts level i.e. uuid, name, descript... represents the field taht will be migrated to a entity:node with machine name 'produkt'

    the content for my paragrap is the array 'productSpecifications'

    Here is the yml file configuration for migrating the paragraphs:

    id: produkte_paragraph
    label: Migrate Paragraph content from json file into Drupal 8
    migration_group: produkt
    
    source:
      plugin: migrate_source_url
      track_changes: true
      data_fetcher_plugin: file
      data_parser_plugin: json
      urls: data/json/data.json
    
      item_selector: /data
      fields:
    
        -
          name: name
          label: 'Produkt name'
          selector: productSpecifications/data/name/value
    
        - name: npp
          label: 'Produkt Npp'
          selector: productSpecifications/data/npp/value
    
      ids:
        npp:
          type: string
    
    process:
    
      field_produkt_name: name
      field_produkt_npp: npp
    
    
    
      uid:
        plugin: default_value
        default_value: 1
    
      type:
        plugin: default_value
        default_value: productspecifications(the paragrap_type)
    
    
    
    
    destination:
      plugin: 'entity_reference_revisions:paragraph'
      default_bundle: productspecifications
    
    
    migration_dependencies:
      required: {}
      optional: {}
    

    Here is the yml configuration for my node (the node migration always works):

    id: migrate_json_file
    label: Migrate content from json file
    migration_group: produkt
    
    source:
      plugin: migrate_source_url
      track_changes: true
      data_fetcher_plugin: file
      data_parser_plugin: json
      urls: data/json/data.json
    
    
      item_selector: /data
      fields:
      - name: uuid
        label: 'uuid'
        selector: uuid/value
    
      - name: name
        label: 'Produkt Name'
        selector: name/value
    
      - name: supplierName
        label: 'Anbietername'
        selector: supplierName/value
    
      - name: description
        label: 'Beschreibung'
        selector: description/value
    
      - name: mandatoryInformation
        label: 'Pflichtangaben'
        selector: mandatoryInformation/value
    
      - name: technicalInformation
        label: 'Fachinformationen'
        selector: technicalInformation/value
    
      - name: packageInformation
        label: 'Gebrauchsinformationen'
        selector: packageInformation/value
    
    
    
      ids:
        uuid:
          type: string
    
    destination:
      plugin: entity:node
    
    process:
    
      langcode:
        plugin: default_value
        default_value: "und"
    
      type:
        plugin: default_value
        default_value: produkt
    
      title: name
      field_uuid: uuid
      field_suppliername: supplierName
      field_description:  description
      field_mandatory_info: mandatoryInformation
      field_technical_info: technicalInformation
      field_package_info: packageInformation
      field_productspecifications:(the paragraph field)
        plugin: iterator
        source: productSpecifications/data
        process:
          temporary_ids:
            plugin: migration_lookup
            migration: takeda_produkte_paragraph
            no_stub: true
            source: npp
          target_id:
            plugin: extract
            source: '@temporary_ids'
            index:
              - 0
          target_revision_id:
            plugin: extract
            source: '@temporary_ids'
            index:
              - 1
    
    
    
      uid:
        plugin: default_value
        default_value: 1
    
    migration_dependencies:
      required: {}
      optional:
        - produkte_paragraph
    
    dependencies:
      enforced:
        module:
          - migration_json_file
    

    I'm really hopeless with this, I'm working on that since one week without sucess, The migrate on import one paragraph.

    Don't know what to do.

    Help needed!

    rviner’s picture

    I can't get #10 to work. It would be useful if the whole yml is posted so I can see the source and destination. It would also be useful to see an example of the yml for one of the previous migrations e.g paragraph_box.

    The nearest I can get is #7 but I was hoping to not manually copy the mappings.

    amit.drupal’s picture

    Migrate Slideshow Drupal 6 to Drupal 8.

    Field by field mapping in migrate_plus.migration..yml file

    field_slide_caption: field_slide_caption
    field_slide_desc: field_slide_desc
    field_slideshow_image: field_slideshow_image
    

    Create custom_migrate.module file and Implement HOOK_migrate_prepare_row().

      use Drupal\paragraphs\Entity\Paragraph;
    
    /**
      * Implements hook_migrate_prepare_row().
      */
    
     function custom_migrate_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
     
    //SlideShow
    //GET data form drupal 6 database.
       $query_paragraphs_fid = $source->getDatabase()->select('content_field_slideshow_image', 'fsi');
       $query_paragraphs_fid->fields('fsi', array('nid','field_slideshow_image_fid','delta'));
       $query_paragraphs_fid->orderBy('fsi.field_slideshow_image_fid', 'ASC');
       $query_paragraphs_fid->condition('fsi.nid', $row->getSourceProperty('nid'));
       $mt_results_paragraphs_fid = $query_paragraphs_fid->execute()->fetchAll();
       foreach ($mt_results_paragraphs_fid as $key => $value) {
        if ($value->field_slideshow_image_fid) {
        //Caption
        $query_caption = $source->getDatabase()->select('content_field_slide_caption', 'c');
        $query_caption->fields('c', array('field_slide_caption_value'));
        $query_caption->condition('c.nid', $row->getSourceProperty('nid'));
        $query_caption->condition('c.delta', $value->delta);
        $mt_results_caption = $query_caption->execute()->fetchAssoc();
        //Description
        $query_description = $source->getDatabase()->select('content_field_slide_desc', 'd');
        $query_description->fields('d', array('field_slide_desc_value'));
        $query_description->condition('d.nid', $row->getSourceProperty('nid'));
        $query_description->condition('d.delta', $value->delta);
        $mt_results_description = $query_description->execute()->fetchAssoc();
        //Credit
        $query_credit = $source->getDatabase()->select('content_field_image_credit', 'ic');
        $query_credit->fields('ic', array('field_image_credit_value'));
        $query_credit->condition('ic.nid', $row->getSourceProperty('nid'));
        $query_credit->condition('ic.delta', $value->delta);
        $mt_results_credit = $query_credit->execute()->fetchAssoc();
        //end
       $file = File::load($value->field_slideshow_image_fid);
       $ppt_values = array(
           'id' => NULL,
           'type' => 'slideshow',
           'field_slideshow_description' => array('value' => $mt_results_description['field_slide_desc_value'], 'format' => 'full_html'),
           'field_image_credit' => $mt_results_credit['field_image_credit_value'],
           'field_slideshow_caption' => $mt_results_caption['field_slide_caption_value'],
           'field_image' => $file,
           );
        // Set Data in Drupal 8 database.   
        $ppt_paragraph = Paragraph::create($ppt_values);
        $ppt_paragraph->save();
        $target_id_dest = $ppt_paragraph->Id();
        $target_revision_id_dest = $ppt_paragraph->getRevisionId();
        $paragraphs[] = array('target_id' => $target_id_dest, 'target_revision_id' => $target_revision_id_dest);
       }
       }
      $row->setSourceProperty('field_image_slideshow', $paragraphs);
      //Slideshow End
     }
    
    amit.drupal’s picture

    Status: Active » Needs review
    joshua.roberson’s picture

    Here is my implementation for migrating from D7 field collections to D8 paragraphs.

    Migrating data from D7 field collection to D8 paragraph.

    migrate_plus.migration.he_fc_education.yml

    migration_group: he
    id: he_fc_education
    label: 'Field Collection (Education)'
    source:
      plugin: d7_field_collection_item
      field_name: field_education
    process:
      #id: skip, newest only
      #revision_id: skip, newest only
      #uid: n/a
      #created: n/a
      status:
        -
          plugin: static_map
          source: archived
          map:
            1: 0
            0: 1
        -
          plugin: skip_on_empty
          method: row
      field_education_degree:
        plugin: he_clean_string
        source: 'field_education_degree/0/value'
      field_education_institution:
        plugin: he_clean_string
        source: 'field_education_institution/0/value'
    destination:
      plugin: 'entity_reference_revisions:paragraph'
      default_bundle: education
    

    Migrating field references from D7 field collection to D8 paragraph.

    migrate_plus.migration.he_node_person.yml

    migration_group: he
    id: he_node_person
    label: 'Nodes (Person)'
    source:
      plugin: d7_node
      node_type: person
    process:
      uid: node_uid
      status: status
      created: created
      changed: changed
      title: title
      field_education:
        plugin: sub_process
        source: field_education
        process:
          target_id:
            -
              plugin: migration_lookup
              migration: he_fc_education
              source: value
              no_stub: TRUE
            -
              plugin: extract
              index:
                - 0
          target_revision_id: '@target_id'
    destination:
      plugin: 'entity:node'
      default_bundle: he_person
    migration_dependencies:
      required:
        - he_user
        - he_fc_education
    

    Note, migration uses revision data, so if you have issues, make sure the data exists in the revisions tables. I had a D7 module installed to delete old revision data to help keep the database small. That is what caused my field collections to paragraphs migration to not work properly. It would migrate without data. To fix it, I simply truncated the field collection revision tables and did an INSERT of the corresponding data tables into them. Make backups first so you don't screw anything up. Another option is to make a custom source plugin that doesn't use the revision data.

    zebda’s picture

    Although there are some hopeful comments is this thread I can't seem to find out how to migrate my content types with paragraphs from Drupal 7 to Drupal 8. Comment #9 and #10 look promising. But they are only a small part of the full solution.

    If i'm right they show a part of the code for migrating the paragraph field and not the paragraph entities. Can someone help me out with a working example or good documentation on this paragraph migrating? Thanks in advance.

    mirsoft’s picture

    @zebda, yes the #9 and #10 (#10 is recommended as it's simpler) just show migration of the content nodes that have paragraphs attached. The items like paragraph_box, paragraph_grid, paragraph_text_embedded are paragraph entities, and you need to create a migration for each of these. That's an another topic, however a simpler one as these migrations are quite straightforward. An example of that migration (D7 paragraph to D8 paragrah) that worked for me was like following:

    id: paragraph_box
    label: Example paragraph box migration from D7 to D8
    migration_tags:
      - 'Drupal 7'
    
    migration_group: mygroup
    source:
      plugin: d7_paragraphs_item
      bundle: pg_box # change to your D7 paragraph bundle here
    process:
      # example fields here, adapt to your needs
      field_head: field_head
      field_introduction: field_intro
      field_text/format:
        plugin: default_value
        default_value: full_html
      field_text/value: field_text/0/value
    destination:
      plugin: 'entity_reference_revisions:paragraph'
      default_bundle: pg_box_d8 # change to your D8 paragraph bundle here
    

    Common case however is that in D7 you do not paragraphs as a source. For instance, a popular pattern in D7 was field collections instead of paragraphs. In that case, you need to change source part a bit. For field collections the source would look like following:

    source:
      plugin: d7_field_collection_item
      field_name: field_myfield # enter field name of source field collection field in your D7 here
    
    mirsoft’s picture

    For the ones who were missing the full node migration in #10, I'm adding the full yml that worked for me:

    id: my_node
    label: Example node migration with paragraph field field_paragraphs inside
    migration_tags:
      - Drupal 7
    deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
    
    migration_group: mygroup
    source:
      plugin: d7_node
      node_type: my_node # enter D7 source node type here
    destination:
      plugin: entity:node
    process:
      type:
        plugin: default_value
        default_value: my_node_d8 # enter D8 destination node type here
      title: title
      # other node fields come here
      # now copied the paragraph field from #10
      field_paragraphs:
        plugin: sub_process
        source: field_source_paragraphs
        process:
          target_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 0
          target_revision_id: '@target_id'
      # end of copy from #10
      status: status
      created: created
      changed: changed
      promote: promote
      sticky: sticky
      revision_uid: revision_uid
      revision_log: log
      revision_timestamp: timestamp
    migration_dependencies:
      required:
          # these are "expected" migrations, as I wrote in example in #18. You need to adapt this to your own paragraph migrations
          - paragraph_box
          - paragraph_grid
          - paragraph_text_embedded
    
    zebda’s picture

    @mirsoft, thanks a lot that was just what I was looking for. You made my day. I have one follow up question. For some reason only half of the nodes is created the others have an error Array index missing, extraction failed.. A quick search on the internet points me in the direction a skip_on_empty plugin. To skip when a field is empty.

    Is this the solution and how can I use this in the code you provided? Thanks again!

    agileadam’s picture

    I am going to see if I can eliminate my need for this plugin (because I'd rather use out-of-the-box functionality), but this is what I use to process node and paragraph lookups the same way:

    http://agileadam.com/2017/09/drupal-8-migrate-process-plugin-migration_l...

    I can just use the migration_lookup_target_ids after any migration_lookup and it just works (on my sites, at least). Please test for yourself.

    Note that I've had to patch migrate to handle multiple values: https://www.drupal.org/project/drupal/issues/2890844#comment-13158685.

    steinmb’s picture

    Category: Support request » Task
    Status: Needs review » Active

    Changing this to a task. If there is no docs. there should perhaps be one at https://www.drupal.org/docs/contributed-modules/paragraphs ?

    steinmb’s picture

    Project: Entity Reference Revisions » Paragraphs
    Version: 8.x-1.5 » 8.x-1.x-dev

    And I think it should be in here.

    santhosh-kumar-rengasamy’s picture

    @mirsoft, I followed the method described in the #19 and it was working fine in my local. Migrating the paragraphs and the nodes having paragraphs. The lookup table in my local was below:

    | source_ids_hash                                                  | sourceid1 | destid1 | destid2 | source_row_status | rollback_action | last_imported | hash |
    +------------------------------------------------------------------+-----------+---------+---------+-------------------+-----------------+---------------+------+
    | 03c0d876fdff8735c14d4b47d74dd95c4fbe03973f7d96d3450b8ff5e55b56b5 |     73791 |      66 |      66 |           0 |               0 |             0 |      |
    | 0649d449bb0c6287362f6304be143e043587b3a316a312804eda4bee94272687 |     69705 |      34 |      34 |     0 |               0 |             0 |      |
    | 098d568d9eb9066bd9b696d73a150a67efa7c9be6ff2006748e1a3a8a470f1a4 |     73746 |      57 |      57 |        0 |               0 |             0 |      |

    But if i do the same in my development the destid1 and destid2 are different. like below:

    | source_ids_hash                                                  | sourceid1 | destid1 | destid2 | source_row_status | rollback_action | last_imported | hash |
    +------------------------------------------------------------------+-----------+---------+---------+-------------------+-----------------+---------------+------+
    | 03c0d876fdff8735c14d4b47d74dd95c4fbe03973f7d96d3450b8ff5e55b56b5 |     73791 |      4787 |      4566 |           0 |     0 |           0 |      |
    | 0649d449bb0c6287362f6304be143e043587b3a316a312804eda4bee94272687 |     69705 |      4788 |      4567 |     0 |     0 |            0 |      |
    

    Do you have any solution or the cause for the issue identified?

    santhosh-kumar-rengasamy’s picture

    #24 error occurs when we had existing contents or manually added content in the site. This issue is solved after mapping the target_revision_id with index 1 like below

      field_paragraphs:
        plugin: sub_process
        source: field_source_paragraphs
        process:
          target_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 0
          target_revision_id:
            -
              plugin: migration_lookup
              migration:
                - paragraph_box
                - paragraph_grid
                - paragraph_text_embedded
              source: value
            -
              plugin: extract
              index:
                - 1
    nevkatz’s picture

    Hi there,

    Thanks for all the great insight, everyone. The approach below worked for me.

    My original approach came from this Softecu post. After replacing the plugins and changing the custom machine names, I was able to migrate the paragraphs but not wire up the paragraphs to the nodes.

    https://softescu.com/blog/how-migrate-paragraphs-drupal-7-drupal-8

    function sstone_migrate_migrate_d7_node_gta_slide_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { 
    	$values = $row->getSourceProperty('field_branch_points'); 
    	$value_new = []; 
    	if ($values) {
    		foreach ($values as $value) { 
    
            //$value_new[] = ['item_id' => $value['value']]; 
    
    			$value_new[] = [
    				'item_id' => $value['value'],
    			];
    			echo 'pushing ' . $value['value'] . '/' . $value['revision_id'];
    		}
    	} 
    	echo 'count: ' . count($value_new);
    	$row->setSourceProperty('field_branch_points', $value_new); 
    
    	return $row;
    }
    
    

    And my YAML file looks like this:

    langcode: en
    status: true  
    dependencies: {  }  
    id: d7_node_gta_slide
    class: null  
    field_plugin_method: null  
    cck_plugin_method: null  
    migration_tags:  
     - 'Drupal 7'  
     - Content  
    migration_group: sstone_general 
    label: 'Nodes (GTA Slide)'  
    source:  
     plugin: d7_node  
     node_type: gta_slide 
    process:  
     # debug logic
     dump_sourcevar:
       plugin: callback
       callable: var_dump
       source: sourcevar
     nid: nid  
     vid: vid  
     langcode:  
       plugin: default_value  
       source: language  
       default_value: und  
     title: title  
     uid: node_uid  
     status: status  
     created: created  
     changed: changed  
     promote: promote  
     sticky: sticky  
     revision_uid: revision_uid  
     revision_log: log  
     revision_timestamp: timestamp  
     field_branch_points:  
       -  
         plugin: skip_on_empty  
         method: process  
         source: field_branch_points
       -  
         plugin: migration_lookup  
         # custom paragraph name
         migration: d7_paragraph_branch
         no_stub: true  
       -  
         plugin: sub_process
         process:  
           target_id: '0'  
           target_revision_id: '1'  
    destination:  
     plugin: 'entity:node'  
     default_bundle: gta_slide
    migration_dependencies:  
     required: {  }  
     optional: {  }
    

    Best,
    Nevin

    vacilando’s picture

    This is I think the clearest article on the topic of migrating paragraphs to Drupal 8: https://understanddrupal.com/articles/introduction-paragraphs-migrations...

    It has helped us enormously.

    (Check the whole series at https://understanddrupal.com/31-days-of-migrations.)

    stevenx’s picture

    Hi, I am having 3 paragraphs contents which I need to combine to one paragraph field.

      para_image:
        plugin: migration_lookup
        migration: d7_paragraph_gallery
        no_stub: true
        source: nid
      para_text:
        plugin: migration_lookup
        no_stub: true
        migration: d7_paragraph
        source: nid
      para_split:
        plugin: split_into_paragraphs
        source: body_value
    
      field_paragraphs:
        plugin: sub_process
        source:
          - '@para_image'
          - '@para_text'
          - '@para_split'
        process:
          target_id: '0'
          target_revision_id: '1'
    

    when I add @para_split the migration does not work anymore, when only adding @para_image and @para_text it works. My question is on how to add the split_into_paragraphs to it so it gets imported… when running only the following it works:

    field_paragraphs:
            plugin: split_into_paragraphs
            source: body_value 
    
    stevenx’s picture

    I found how its working, I had to use merge
    https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugi...

      para_image:
        plugin: migration_lookup
        migration: d7_paragraph_gallery
        no_stub: true
        source: nid
      para_text:
        plugin: migration_lookup
        no_stub: true
        migration: d7_paragraph
        source: nid
    
      temp_images:
        plugin: sub_process
        source:
          - '@para_image'
          - '@para_text'
        process:
          target_id: '0'
          target_revision_id: '1'
    
      temp_body:
        plugin: split_into_paragraphs
        source: body_value
    
      field_paragraphs:
        plugin: merge
        source:
          - '@temp_images'
          - '@temp_body'
    
    mkindred’s picture

    I couldn't find an example that worked for my situation (multiple paragraph fields per content type, multiple values per field, and multiple paragraph types to choose from within each field). I was able to write a custom module that handled it. After I finished, I abstracted the module out to make it a bit easier to understand and created its own repository for it.

    The repositories and an explanation video are available here: https://www.arsnova.cc/drupal-7-drupal-8-paragraph-migration

    franz’s picture

    As someone suggested using the same value for target_id and target_revision_id I'd like to point out I had weird outcomes. Data was migrated but paragraphs would load incorrectly (a different paragraph showing up). This is hard to replicate because it depends on the data previously stored. In the end I used https://www.drupal.org/project/migrate_process_entity to pull the current revision_id after doing the lookup for the paragraph. This seems to have worked well, the field process section became:

      field_contacts:
        plugin: sub_process
        source: field_contacts
        process:
          target_id:
            plugin: migration_lookup
            migration: contact_section
            source: target_id
            no_stub: true
          target_revision_id:
          - plugin: entity_value
            source: '@target_id'
            entity_type: paragraph
            field: revision_id
          - plugin: extract
            index:
              - 0
              - value
    
    cmah’s picture

    Thank you for #25, which finally helped me with my field_collection migration -- all the older examples didn't help at all. I'm posting this in the hopes it will save someone else a bit of time.

      field_my_paragraph:
        -
          plugin: sub_process
          source: field_my_field_collection
          process:
            target_id:
              -
                plugin: migration_lookup
                method: process
                migration: my_field_collection_migration
                source: value
              -
                plugin: extract
                index:
                  - 0
            target_revision_id:
              -
                plugin: migration_lookup
                method: process
                migration: my_field_collection_migration
                source: value
              -
                plugin: extract
                index:
                  - 1
    
    ccarnnia’s picture

    ++ on #25 thank you Santhosh and cmeh.

    ressa’s picture

    As an alternative to Paragraphs, in case a more basic solution might work, migrating nested multivalue data into Custom Field is easy. A structure like this:

    "hyperlink":
      [
        {
          "linkurl_source": "https://drupal.org",
          "linktext_source": "Drupal"
        },
        {
          "linkurl_source": "https://gitlab.com/",
          "linktext_source": "GitLab"
        }
      ]

    ... can be mapped with this:

    fields:
        -
          name: hyperlink
          selector: hyperlink
    [...]
    process:
      field_hyperlink:
        plugin: sub_process
        source: hyperlink
        process:
          linkurl: linkurl_source
          linktext: linktext_source

    From Migrate multivalue fields into unlimited Custom Fields field.

    jepster_’s picture

    I am working on a Drupal 10 to Drupal 10 migration. All the examples here did not help. Looks like they are related to Drupal 7.

    My solution was an own migrate process plugin. You can find it here: https://git.drupalcode.org/project/sites_migrator/-/blob/1.0.x/src/Plugi...

    The plugin must be implemented into the process like this:

      field_paragraphs:
        plugin: paragraphs_sub_process
        source: field_paragraphs
        migration:
          - wg_paragraph_media_text
        process:
          target_id: '0'
          target_revision_id: '1'
    

    I hope this example helps somebody.

    baikho’s picture

    #35 works fine, but made a slight adjustment for multiple migrate sources #3531281: ParagraphsSubProcess plugin does not merge paragraph items when using += operator