diff --git a/migrations/d7_paragraphs_type.yml b/migrations/d7_paragraphs_type.yml
index 2d385f7..47146ef 100644
--- a/migrations/d7_paragraphs_type.yml
+++ b/migrations/d7_paragraphs_type.yml
@@ -8,7 +8,22 @@ source:
   add_description: true
 process:
   id: bundle
-  label: name
+  label_from_bundle:
+    -
+      plugin: explode
+      delimiter: '_'
+      source: bundle
+    -
+      plugin: concat
+      delimiter: ' '
+    -
+      plugin: callback
+      callable: ucwords
+  label:
+    - plugin: null_coalesce
+      source:
+        - 'name'
+        - '@label_from_bundle'
   description: description
 destination:
   plugin: 'entity:paragraphs_type'
diff --git a/src/Plugin/migrate/source/d7/ParagraphsType.php b/src/Plugin/migrate/source/d7/ParagraphsType.php
index 0528d95..fbcded0 100644
--- a/src/Plugin/migrate/source/d7/ParagraphsType.php
+++ b/src/Plugin/migrate/source/d7/ParagraphsType.php
@@ -32,8 +32,17 @@ class ParagraphsType extends DrupalSqlBase {
    * {@inheritdoc}
    */
   public function query() {
-    $query = $this->select('paragraphs_bundle', 'pb')
-      ->fields('pb');
+    $bundles_query = $this->select('paragraphs_bundle', 'pt')
+      ->fields('pt', ['bundle']);
+    $missing_bundles_from_items_query = $this->select('paragraphs_item', 'pi')
+      ->distinct()
+      ->fields('pi', ['bundle']);
+    $bundles_query->union($missing_bundles_from_items_query);
+
+    $query = $this->select($bundles_query, 'ptpi')
+      ->fields('ptpi', ['bundle'])
+      ->fields('pb', ['name', 'locked']);
+    $query->leftJoin('paragraphs_bundle', 'pb', 'pb.bundle = ptpi.bundle');
 
     return $query;
   }
@@ -45,7 +54,7 @@ class ParagraphsType extends DrupalSqlBase {
 
     // Paragraph bundles did not have descriptions in d7, optionally add one.
     if ($this->configuration['add_description']) {
-      $name = $row->getSourceProperty('name');
+      $name = $row->getSourceProperty('name') ?? preg_replace('/_+/', ' ', $row->getSourceProperty('bundle'));
       $row->setSourceProperty('description', 'Migrated from paragraph bundle ' . $name);
     }
     else {
@@ -71,7 +80,7 @@ class ParagraphsType extends DrupalSqlBase {
    */
   public function getIds() {
     $ids['bundle']['type'] = 'string';
-
+    $ids['bundle']['alias'] = 'ptpi';
     return $ids;
   }
 
diff --git a/tests/src/Functional/Migrate/MigrateUiParagraphsTestBase.php b/tests/src/Functional/Migrate/MigrateUiParagraphsTestBase.php
index 0a34a22..1fd577a 100644
--- a/tests/src/Functional/Migrate/MigrateUiParagraphsTestBase.php
+++ b/tests/src/Functional/Migrate/MigrateUiParagraphsTestBase.php
@@ -506,7 +506,7 @@ abstract class MigrateUiParagraphsTestBase extends MigrateUpgradeTestBase {
           $carry[$entity->id()] = (string) $entity->label();
           return $carry;
         });
-        $this->assertEquals($expected_entity_labels, $actual_labels, sprintf('The expected %s entities are not matching the actual ones.', $entity_type_id));
+        $this->assertEquals(asort($expected_entity_labels), asort($actual_labels), sprintf('The expected %s entities are not matching the actual ones.', $entity_type_id));
       }
       else {
         $this->fail(sprintf('The expected %s entity type is missing.', $entity_type_id));
diff --git a/tests/src/Kernel/Plugin/migrate/source/ParagraphsTypeTest.php b/tests/src/Kernel/Plugin/migrate/source/ParagraphsTypeTest.php
new file mode 100644
index 0000000..d4f7ad8
--- /dev/null
+++ b/tests/src/Kernel/Plugin/migrate/source/ParagraphsTypeTest.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\Tests\paragraphs\Kernel\Plugin\migrate\source;
+
+use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
+
+/**
+ * Tests the "paragraphs_type" migrate source plugin.
+ *
+ * @covers \Drupal\paragraphs\Plugin\migrate\source\d7\ParagraphsType
+ * @group paragraphs
+ */
+class ParagraphsTypeTest extends MigrateSqlSourceTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'migrate_drupal',
+    'paragraphs',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function providerSource() {
+    $default_db = [
+      'system' => [
+        [
+          'name' => 'paragraphs',
+          'status' => 1,
+        ]
+      ],
+      'paragraphs_bundle' => [
+        [
+          'bundle' => 'bundle_one',
+          'name' => 'Bundle One',
+          'locked' => 1,
+        ],
+        [
+          'bundle' => 'bundle_two',
+          'name' => 'Bundle Two',
+          'locked' => 1,
+        ],
+      ],
+      'paragraphs_item' => [
+        [
+          'bundle' => 'bundle_one',
+        ],
+      ],
+    ];
+
+    return [
+      'Without missing items' => [
+        'Database' => [
+          'paragraphs_item' => [
+            [
+              'bundle' => 'bundle_one',
+            ],
+            [
+              'bundle' => 'bundle_two',
+            ]
+          ],
+        ] + $default_db,
+        'Expected results' => [
+          [
+            'bundle' => 'bundle_one',
+            'name' => 'Bundle One',
+            'locked' => 1,
+            'description' => '',
+          ],
+          [
+            'bundle' => 'bundle_two',
+            'name' => 'Bundle Two',
+            'locked' => 1,
+            'description' => '',
+          ],
+        ],
+      ],
+      'Bundles without instances, with description' => [
+        'Database' => $default_db,
+        'Expected results' => [
+          [
+            'bundle' => 'bundle_one',
+            'name' => 'Bundle One',
+            'locked' => 1,
+            'description' => '',
+          ],
+          [
+            'bundle' => 'bundle_two',
+            'name' => 'Bundle Two',
+            'locked' => 1,
+            'description' => '',
+          ],
+        ],
+      ],
+      'Test with missing bundle' => [
+        'Database' => [
+          'paragraphs_item' => [
+            [
+              'bundle' => 'bundle_two',
+            ],
+            [
+              'bundle' => 'missing_bundle',
+            ],
+          ],
+        ] + $default_db,
+        'Expected results' => [
+          [
+            'bundle' => 'bundle_one',
+            'name' => 'Bundle One',
+            'locked' => 1,
+            'description' => 'Migrated from paragraph bundle Bundle One',
+          ],
+          [
+            'bundle' => 'bundle_two',
+            'name' => 'Bundle Two',
+            'locked' => 1,
+            'description' => 'Migrated from paragraph bundle Bundle Two',
+          ],
+          [
+            'bundle' => 'missing_bundle',
+            'name' => NULL,
+            'locked' => NULL,
+            'description' => 'Migrated from paragraph bundle missing bundle',
+          ],
+        ],
+        'Expected count' => NULL,
+        'Plugin config' => [
+          'add_description' => TRUE,
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/migrate/ParagraphsTypeRemoveBundleTest.php b/tests/src/Kernel/migrate/ParagraphsTypeRemoveBundleTest.php
new file mode 100644
index 0000000..f0e4626
--- /dev/null
+++ b/tests/src/Kernel/migrate/ParagraphsTypeRemoveBundleTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\Tests\paragraphs\Kernel\migrate;
+
+/**
+ * Test missing bundle migration.
+ *
+ * @group paragraphs
+ * @require entity_reference_revisions
+ */
+class ParagraphsTypeRemoveBundleTest extends ParagraphContentMigrationTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->sourceDatabase->delete('paragraphs_bundle')->condition('bundle', 'paragraph_bundle_one')->execute();
+  }
+
+}
