Answers.Body: <p>Apparently this was broken because of the die() statements in the above code. I'm still not sure exactly what's going on, but commenting those out fixes the issue. I'm thinking Craft must do some cleanup at the end of a request that sets everything right.</p>
  <p>Rather than delete the question, I'll leave it in case anyone happens to run into something similar. Basically, don't die!</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-01-07T09:24:58.570Z
Answers.LastActivityDate: 2023-01-07T09:24:58.570Z
Answers.UserDisplayName: Eli
Body: <p>I'm somewhat new to Craft, and struggling a bit with copying matrix block types. Any help would be greatly appreciated.</p>
  <p>So, I have some code that copies a matrix block and its fields, but when I go to delete the newly copied block from the admin, it doesn't go away. I can get it to go away by first deleting all the fields, but not by just deleting the block itself. Am I doing something wrong in the following code that creates the new block?</p>
  <p>Thanks in advance for any insight you can give!</p>
  <pre><code>private function copyMatrixBlock(string $matrix_handle, string $block_handle){
  
      //get the matrix field
      $matrix_field = Craft::$app-&gt;fields-&gt;getFieldByHandle($matrix_handle);
  
      //look up the block type id from the $block_handle
      $query = new Query;
  
      $query-&gt;select('mbt.id');
      $query-&gt;from(TABLE::MATRIXBLOCKTYPES . ' mbt');
      $query-&gt;where('mbt.handle = \''.$block_handle.'\'');
  
      $row = $query-&gt;one();
  
      $block_type_id = $row['id'];
  
      //get the block types of the matrix field
      $block_types = $matrix_field-&gt;getBlockTypes();
  
      //get the fields that we want to copy
      $the_fields_to_copy = $matrix_field-&gt;getBlockTypeFields([$block_type_id]);
      
      $save_these_blocks = [];
  
      $field_index = 0;
      $block_type_index = 0;
  
      foreach($block_types as $k =&gt; $block_type){
  
          $save_these_blocks[$block_type-&gt;id] = $block_type;
  
          if($block_type-&gt;id == $block_type_id){ //match on the looked up id from the $block_handle
              // copy this block type
              
              $copy_id = StringHelper::randomString(10);
              
              $config['name'] = $block_type-&gt;name . '_' . $copy_id;
              $config['handle'] = $block_type-&gt;handle . '_' . $copy_id;
              $config['fields'] = [];
  
              //loop through the fields to copy and add them to the block
              foreach($the_fields_to_copy as $field_to_copy){
  
                  $field_config = [];
                  
                  $query = new Query;
                  $query-&gt;select('f.*');
                  $query-&gt;from(TABLE::FIELDS . ' f');
                  $query-&gt;where('f.id = \''.$field_to_copy-&gt;id.'\''); 
  
                  $row = $query-&gt;one();
                  foreach($row as $k =&gt; $v){ //copy 
                      $field_config[$k] = $v; 
                  }
                  //replace the name and handle, unset 'uid'
                  $field_config['name'] = $row['name'] . '_' . $copy_id;
                  $field_config['handle'] = $row['handle'] . '_' . $copy_id;
                  $field_config['uid'] = '';
                  $field_config['settings'] = '';
                  $field_config['typesettings'] = $row['settings'];
  
                  $config['fields']['new' . ++$field_index] = $field_config; //the key needs to be non-numeric
              }
  
              $new_block = $this-&gt;makeBlockType($matrix_field-&gt;id, $config, $copy_id);
  
              $save_these_blocks['new' . ++$block_type_index] = $new_block; //the key needs to be non-numeric
  
          } else {
              //don't copy this block type
          }
      }
  
      //save the new blocks to the matrix field
      $matrix_field-&gt;setBlockTypes($save_these_blocks);
  
      //attempt to save
      $fieldsService = Craft::$app-&gt;getFields();
      if(!$fieldsService-&gt;saveField($matrix_field)){
          die('SOMETHING WENT WRONG');
      } else {
          die('SOMETHING WENT RIGHT');
      }
  }
  
  // mostly copied from Matrix::setBlockTypes
  private function makeBlockType($matrix_field_id, $config, $copy_id):MatrixBlockType{
  
      $defaultFieldConfig = [
          'type' =&gt; null,
          'name' =&gt; null,
          'handle' =&gt; null,
          'instructions' =&gt; null,
          'required' =&gt; false,
          'searchable' =&gt; true,
          'translationMethod' =&gt; Field::TRANSLATION_METHOD_NONE,
          'translationKeyFormat' =&gt; null,
          'typesettings' =&gt; null,
      ];
  
      $blockType = new MatrixBlockType();
      $blockType-&gt;fieldId = $matrix_field_id;
      $blockType-&gt;name = $config['name'] . '_' . $copy_id;
      $blockType-&gt;handle = $config['handle'] . '_' . $copy_id;
  
      $fieldLayout = $blockType-&gt;getFieldLayout();
      if (($fieldLayoutTab = $fieldLayout-&gt;getTabs()[0] ?? null) === null) {
          $fieldLayoutTab = new FieldLayoutTab();
          $fieldLayoutTab-&gt;name = 'Content';
          $fieldLayoutTab-&gt;sortOrder = 1;
          $fieldLayout-&gt;setTabs([$fieldLayoutTab]);
      }
  
      $fields = [];
      $layoutElements = [];
  
      if (!empty($config['fields'])) {
          foreach ($config['fields'] as $fieldId =&gt; $fieldConfig) {
              // If the field doesn't specify a type, then it probably wasn't meant to be submitted
              if (!isset($fieldConfig['type'])) {
                  continue;
              }
  
              $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig);
              $field = $fields[] = Craft::$app-&gt;getFields()-&gt;createField([
                  'type' =&gt; $fieldConfig['type'],
                  'id' =&gt; is_numeric($fieldId) ? $fieldId : null,
                  'name' =&gt; $fieldConfig['name'],
                  'handle' =&gt; $fieldConfig['handle'],
                  'columnSuffix' =&gt; $fieldConfig['columnSuffix'] ?? null,
                  'instructions' =&gt; $fieldConfig['instructions'],
                  'required' =&gt; (bool)$fieldConfig['required'],
                  'searchable' =&gt; (bool)$fieldConfig['searchable'],
                  'translationMethod' =&gt; $fieldConfig['translationMethod'],
                  'translationKeyFormat' =&gt; $fieldConfig['translationKeyFormat'],
                  'settings' =&gt; $fieldConfig['typesettings'],
              ]);
  
              $layoutElements[] = Craft::createObject([
                  'class' =&gt; CustomField::class,
                  'uid' =&gt; $fieldConfig['uid'] ?? null,
                  'required' =&gt; (bool)$fieldConfig['required'],
                  'width' =&gt; (int)($fieldConfig['width'] ?? 0) ?: 100,
              ], [$field]);
          }
      }
  
      $fieldLayoutTab-&gt;setElements($layoutElements);
      return $blockType;
  }
  </code></pre>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-01-06T15:42:47.743Z
Comments.Text: Hi. just some ideas to try. ignore create layoutElements[] and setElement() . try creating new blocktype as array without setting fieldId:  $newBlockType= []; $newBlockType['handle'] = $handle; $newBlockType['name'] = $name; 
  after your createField function -without passing id param- push field to new block type  $newBlockType['fields'][$fieldId] = $field; and return $newBlockType
Comments.UserDisplayName: user9975
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-01-07T09:21:05.580Z
Comments.Text: Thanks for the suggestions. I *think* I figured it out. By taking out the die() statements after $fieldsService->saveField(), it appears to work correctly. I think by dying, I wasn't letting Craft do some operations it needed for the changes to take hold. Thinking it's something related to ProjectConfig.
Comments.UserDisplayName: Eli
Tags: <craft3><plugin-development><matrix><craft4>
Title: Deleting a matrix block type that was copied in code
ThreadText: # Deleting a matrix block type that was copied in code
  
  (Asked by: username_0 on 2023-01-05)
  
  I'm somewhat new to Craft, and struggling a bit with copying matrix block types. Any help would be greatly appreciated.
  
  So, I have some code that copies a matrix block and its fields, but when I go to delete the newly copied block from the admin, it doesn't go away. I can get it to go away by first deleting all the fields, but not by just deleting the block itself. Am I doing something wrong in the following code that creates the new block?
  
  Thanks in advance for any insight you can give!
  
  ```
  private function copyMatrixBlock(string $matrix_handle, string $block_handle){
  
      //get the matrix field
      $matrix_field = Craft::$app->fields->getFieldByHandle($matrix_handle);
  
      //look up the block type id from the $block_handle
      $query = new Query;
  
      $query->select('mbt.id');
      $query->from(TABLE::MATRIXBLOCKTYPES . ' mbt');
      $query->where('mbt.handle = \''.$block_handle.'\'');
  
      $row = $query->one();
  
      $block_type_id = $row['id'];
  
      //get the block types of the matrix field
      $block_types = $matrix_field->getBlockTypes();
  
      //get the fields that we want to copy
      $the_fields_to_copy = $matrix_field->getBlockTypeFields([$block_type_id]);
      
      $save_these_blocks = [];
  
      $field_index = 0;
      $block_type_index = 0;
  
      foreach($block_types as $k => $block_type){
  
          $save_these_blocks[$block_type->id] = $block_type;
  
          if($block_type->id == $block_type_id){ //match on the looked up id from the $block_handle
              // copy this block type
              
              $copy_id = StringHelper::randomString(10);
              
              $config['name'] = $block_type->name . '_' . $copy_id;
              $config['handle'] = $block_type->handle . '_' . $copy_id;
              $config['fields'] = [];
  
              //loop through the fields to copy and add them to the block
              foreach($the_fields_to_copy as $field_to_copy){
  
                  $field_config = [];
                  
                  $query = new Query;
                  $query->select('f.*');
                  $query->from(TABLE::FIELDS . ' f');
                  $query->where('f.id = \''.$field_to_copy->id.'\''); 
  
                  $row = $query->one();
                  foreach($row as $k => $v){ //copy 
                      $field_config[$k] = $v; 
                  }
                  //replace the name and handle, unset 'uid'
                  $field_config['name'] = $row['name'] . '_' . $copy_id;
                  $field_config['handle'] = $row['handle'] . '_' . $copy_id;
                  $field_config['uid'] = '';
                  $field_config['settings'] = '';
                  $field_config['typesettings'] = $row['settings'];
  
                  $config['fields']['new' . ++$field_index] = $field_config; //the key needs to be non-numeric
              }
  
              $new_block = $this->makeBlockType($matrix_field->id, $config, $copy_id);
  
              $save_these_blocks['new' . ++$block_type_index] = $new_block; //the key needs to be non-numeric
  
          } else {
              //don't copy this block type
          }
      }
  
      //save the new blocks to the matrix field
      $matrix_field->setBlockTypes($save_these_blocks);
  
      //attempt to save
      $fieldsService = Craft::$app->getFields();
      if(!$fieldsService->saveField($matrix_field)){
          die('SOMETHING WENT WRONG');
      } else {
          die('SOMETHING WENT RIGHT');
      }
  }
  
  // mostly copied from Matrix::setBlockTypes
  private function makeBlockType($matrix_field_id, $config, $copy_id):MatrixBlockType{
  
      $defaultFieldConfig = [
          'type' => null,
          'name' => null,
          'handle' => null,
          'instructions' => null,
          'required' => false,
          'searchable' => true,
          'translationMethod' => Field::TRANSLATION_METHOD_NONE,
          'translationKeyFormat' => null,
          'typesettings' => null,
      ];
  
      $blockType = new MatrixBlockType();
      $blockType->fieldId = $matrix_field_id;
      $blockType->name = $config['name'] . '_' . $copy_id;
      $blockType->handle = $config['handle'] . '_' . $copy_id;
  
      $fieldLayout = $blockType->getFieldLayout();
      if (($fieldLayoutTab = $fieldLayout->getTabs()[0] ?? null) === null) {
          $fieldLayoutTab = new FieldLayoutTab();
          $fieldLayoutTab->name = 'Content';
          $fieldLayoutTab->sortOrder = 1;
          $fieldLayout->setTabs([$fieldLayoutTab]);
      }
  
      $fields = [];
      $layoutElements = [];
  
      if (!empty($config['fields'])) {
          foreach ($config['fields'] as $fieldId => $fieldConfig) {
              // If the field doesn't specify a type, then it probably wasn't meant to be submitted
              if (!isset($fieldConfig['type'])) {
                  continue;
              }
  
              $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig);
              $field = $fields[] = Craft::$app->getFields()->createField([
                  'type' => $fieldConfig['type'],
                  'id' => is_numeric($fieldId) ? $fieldId : null,
                  'name' => $fieldConfig['name'],
                  'handle' => $fieldConfig['handle'],
                  'columnSuffix' => $fieldConfig['columnSuffix'] ?? null,
                  'instructions' => $fieldConfig['instructions'],
                  'required' => (bool)$fieldConfig['required'],
                  'searchable' => (bool)$fieldConfig['searchable'],
                  'translationMethod' => $fieldConfig['translationMethod'],
                  'translationKeyFormat' => $fieldConfig['translationKeyFormat'],
                  'settings' => $fieldConfig['typesettings'],
              ]);
  
              $layoutElements[] = Craft::createObject([
                  'class' => CustomField::class,
                  'uid' => $fieldConfig['uid'] ?? null,
                  'required' => (bool)$fieldConfig['required'],
                  'width' => (int)($fieldConfig['width'] ?? 0) ?: 100,
              ], [$field]);
          }
      }
  
      $fieldLayoutTab->setElements($layoutElements);
      return $blockType;
  }
  
  ```
  
  **Tags:** craft3, plugin-development, matrix, craft4
  
  > **Comments:**
  > **username_1**: Hi. just some ideas to try. ignore create layoutElements[] and setElement() . try creating new blocktype as array without setting fieldId:  $newBlockType= []; $newBlockType['handle'] = $handle; $newBlockType['name'] = $name;  after your createField function -without passing id param- push field to new block type  $newBlockType['fields'][$fieldId] = $field; and return $newBlockType  
  > **username_0**: Thanks for the suggestions. I *think* I figured it out. By taking out the die() statements after $fieldsService->saveField(), it appears to work correctly. I think by dying, I wasn't letting Craft do some operations it needed for the changes to take hold. Thinking it's something related to ProjectConfig.  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_0 on 2023-01-07. Score: 0 | Accepted answer
  
  Apparently this was broken because of the die() statements in the above code. I'm still not sure exactly what's going on, but commenting those out fixes the issue. I'm thinking Craft must do some cleanup at the end of a request that sets everything right.
  
  Rather than delete the question, I'll leave it in case anyone happens to run into something similar. Basically, don't die!
  
  
  ---
  
  