diff --git a/commerce_shipping.commerce_package_types.yml b/commerce_shipping.commerce_package_types.yml
new file mode 100644
index 0000000..b1a0368
--- /dev/null
+++ b/commerce_shipping.commerce_package_types.yml
@@ -0,0 +1,8 @@
+custom_box:
+  remote_id: custom
+  label: 'Custom box (1x1x1 mm)'
+  dimensions:
+    length: 1
+    width: 1
+    height: 1
+    unit: 'mm'
diff --git a/commerce_shipping.info.yml b/commerce_shipping.info.yml
index 53999be..3201b12 100644
--- a/commerce_shipping.info.yml
+++ b/commerce_shipping.info.yml
@@ -7,3 +7,4 @@ configure: commerce_shipping.admin_overview
 dependencies:
   - commerce
   - commerce_price
+  - physical
diff --git a/commerce_shipping.services.yml b/commerce_shipping.services.yml
index e7aa51d..a09cbd2 100644
--- a/commerce_shipping.services.yml
+++ b/commerce_shipping.services.yml
@@ -2,3 +2,7 @@ services:
   plugin.manager.commerce_shipping_method:
     class: Drupal\commerce_shipping\ShippingMethodManager
     parent: default_plugin_manager
+
+  plugin.manager.commerce_package_type:
+    class: Drupal\commerce_shipping\PackageTypeManager
+    arguments: ['@module_handler', '@cache.discovery']
diff --git a/config/schema/commerce_shipping.schema.yml b/config/schema/commerce_shipping.schema.yml
index 82d50ff..cf85ddd 100644
--- a/config/schema/commerce_shipping.schema.yml
+++ b/config/schema/commerce_shipping.schema.yml
@@ -33,6 +33,9 @@ commerce_shipping.commerce_shipping_method.plugin.flat_rate:
 commerce_shipping_method_configuration:
   type: mapping
   mapping:
+    default_package_type:
+      type: string
+      label: 'Default package type'
     services:
       type: sequence
       label: 'Services'
diff --git a/src/PackageTypeManager.php b/src/PackageTypeManager.php
new file mode 100644
index 0000000..b93f800
--- /dev/null
+++ b/src/PackageTypeManager.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\commerce_shipping;
+
+use Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageType;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\YamlDiscovery;
+
+/**
+ * Manages discovery and instantiation of package type plugins.
+ *
+ * @see \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageTypeInterface
+ * @see plugin_api
+ */
+class PackageTypeManager extends DefaultPluginManager implements PackageTypeManagerInterface {
+
+  /**
+   * Default values for each package type plugin.
+   *
+   * @var array
+   */
+  protected $defaults = [
+    'id' => '',
+    'remote_id' => '',
+    'label' => '',
+    'dimensions' => [],
+    'weight' => NULL,
+    // A shipping method plugin ID. Used to optionally restrict the package type
+    // to shipping methods with the specified plugin.
+    'shipping_method' => NULL,
+    'class' => PackageType::class,
+  ];
+
+  /**
+   * Constructs a new PackageTypeManager object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend) {
+    $this->moduleHandler = $module_handler;
+    $this->setCacheBackend($cache_backend, 'commerce_package_type_plugins');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDiscovery() {
+    if (!isset($this->discovery)) {
+      $this->discovery = new YamlDiscovery('commerce_package_types', $this->moduleHandler->getModuleDirectories());
+      $this->discovery->addTranslatableProperty('label', 'label_context');
+      $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
+    }
+    return $this->discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    $definition['id'] = $plugin_id;
+    foreach (['remote_id', 'label', 'dimensions'] as $required_property) {
+      if (empty($definition[$required_property])) {
+        throw new PluginException(sprintf('The package_type "%s" must define the "%s" property.', $plugin_id, $required_property));
+      }
+    }
+    foreach (['length', 'width', 'height', 'unit'] as $dimension_property) {
+      if (empty($definition['dimensions'][$dimension_property])) {
+        throw new PluginException(sprintf('The package type "%s" property "dimensions" must have a "%s" key.', $plugin_id, $dimension_property));
+      }
+    }
+    if (isset($definition['weight'])) {
+      foreach (['number', 'unit'] as $weight_property) {
+        if (empty($definition['weight'][$weight_property])) {
+          throw new PluginException(sprintf('The package type "%s" property "weight" must have a "%s" key.', $plugin_id, $weight_property));
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinitionsByShippingMethod($shipping_method) {
+    $definitions = $this->getDefinitions();
+    foreach ($definitions as $id => $definition) {
+      if (!empty($definition['shipping_method']) && $definition['shipping_method'] != $shipping_method) {
+        unset($definitions[$id]);
+      }
+    }
+    uasort($definitions, function ($a, $b) {
+      return strnatcasecmp($a['label'], $b['label']);
+    });
+
+    return $definitions;
+  }
+
+}
diff --git a/src/PackageTypeManagerInterface.php b/src/PackageTypeManagerInterface.php
new file mode 100644
index 0000000..a005656
--- /dev/null
+++ b/src/PackageTypeManagerInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\commerce_shipping;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+
+/**
+ * Defines the interface for commerce_package_type plugin managers.
+ */
+interface PackageTypeManagerInterface extends PluginManagerInterface {
+
+  /**
+   * Gets the definitions for the given shipping method plugin ID.
+   *
+   * @param string $shipping_method
+   *   The shipping method plugin ID.
+   *
+   * @return array
+   *   The definitions.
+   */
+  public function getDefinitionsByShippingMethod($shipping_method);
+
+}
diff --git a/src/Plugin/Commerce/PackageType/PackageType.php b/src/Plugin/Commerce/PackageType/PackageType.php
new file mode 100644
index 0000000..ab82ceb
--- /dev/null
+++ b/src/Plugin/Commerce/PackageType/PackageType.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\commerce_shipping\Plugin\Commerce\PackageType;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\physical\Length;
+use Drupal\physical\Weight;
+
+/**
+ * Defines the class for package types.
+ */
+class PackageType extends PluginBase implements PackageTypeInterface {
+
+  /**
+   * The package type length.
+   *
+   * @var \Drupal\physical\Length
+   */
+  protected $length;
+
+  /**
+   * The package type width.
+   *
+   * @var \Drupal\physical\Length
+   */
+  protected $width;
+
+  /**
+   * The package type height.
+   *
+   * @var \Drupal\physical\Length
+   */
+  protected $height;
+
+  /**
+   * The package type weight.
+   *
+   * @var \Drupal\physical\Weight
+   */
+  protected $weight;
+
+  /**
+   * Constructs a new PackageType object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $dimensions = $plugin_definition['dimensions'];
+    $this->length = new Length($dimensions['length'], $dimensions['unit']);
+    $this->width = new Length($dimensions['width'], $dimensions['unit']);
+    $this->height = new Length($dimensions['height'], $dimensions['unit']);
+    if (!empty($plugin_definition['weight'])) {
+      $weight = $plugin_definition['weight'];
+      $this->weight = new Weight($weight['number'], $weight['unit']);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->pluginDefinition['id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRemoteId() {
+    return $this->pluginDefinition['remote_id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->pluginDefinition['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLength() {
+    return $this->length;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidth() {
+    return $this->width;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHeight() {
+    return $this->height;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->weight;
+  }
+
+}
diff --git a/src/Plugin/Commerce/PackageType/PackageTypeInterface.php b/src/Plugin/Commerce/PackageType/PackageTypeInterface.php
new file mode 100644
index 0000000..473484f
--- /dev/null
+++ b/src/Plugin/Commerce/PackageType/PackageTypeInterface.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\commerce_shipping\Plugin\Commerce\PackageType;
+
+/**
+ * Defines the interface for package types.
+ */
+interface PackageTypeInterface {
+
+  /**
+   * Gets the package type ID.
+   *
+   * @return string
+   *   The package type ID.
+   */
+  public function getId();
+
+  /**
+   * Gets the package type remote ID.
+   *
+   * @return string
+   *   The package type remote ID, or "custom" if the package type was not
+   *   predefined by the remote API.
+   */
+  public function getRemoteId();
+
+  /**
+   * Gets the translated label.
+   *
+   * @return string
+   *   The translated label.
+   */
+  public function getLabel();
+
+  /**
+   * Gets the package type length.
+   *
+   * @return \Drupal\physical\Length
+   *   The package type length.
+   */
+  public function getLength();
+
+  /**
+   * Gets the package type width.
+   *
+   * @return \Drupal\physical\Length
+   *   The package type width.
+   */
+  public function getWidth();
+
+  /**
+   * Gets the package type height.
+   *
+   * @return \Drupal\physical\Length
+   *   The package type height.
+   */
+  public function getHeight();
+
+  /**
+   * Gets the package type weight.
+   *
+   * @return \Drupal\physical\Weight|null
+   *   The package type weight, or NULL if not defined.
+   */
+  public function getWeight();
+
+}
diff --git a/src/Plugin/Commerce/ShippingMethod/FlatRate.php b/src/Plugin/Commerce/ShippingMethod/FlatRate.php
index 2da2a13..a667c34 100644
--- a/src/Plugin/Commerce/ShippingMethod/FlatRate.php
+++ b/src/Plugin/Commerce/ShippingMethod/FlatRate.php
@@ -4,6 +4,7 @@ namespace Drupal\commerce_shipping\Plugin\Commerce\ShippingMethod;
 
 use Drupal\commerce_price\Price;
 use Drupal\commerce_shipping\Entity\ShipmentInterface;
+use Drupal\commerce_shipping\PackageTypeManagerInterface;
 use Drupal\commerce_shipping\ShippingRate;
 use Drupal\commerce_shipping\ShippingService;
 use Drupal\Core\Form\FormStateInterface;
@@ -27,9 +28,11 @@ class FlatRate extends ShippingMethodBase {
    *   The plugin_id for the plugin instance.
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
+   * @param \Drupal\commerce_shipping\PackageTypeManagerInterface $package_type_manager
+   *   The package type manager.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, PackageTypeManagerInterface $package_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $package_type_manager);
 
     $this->services['default'] = new ShippingService('default', $this->configuration['rate_label']);
   }
diff --git a/src/Plugin/Commerce/ShippingMethod/ShippingMethodBase.php b/src/Plugin/Commerce/ShippingMethod/ShippingMethodBase.php
index db7626c..ac9e116 100644
--- a/src/Plugin/Commerce/ShippingMethod/ShippingMethodBase.php
+++ b/src/Plugin/Commerce/ShippingMethod/ShippingMethodBase.php
@@ -2,15 +2,25 @@
 
 namespace Drupal\commerce_shipping\Plugin\Commerce\ShippingMethod;
 
+use Drupal\commerce_shipping\PackageTypeManagerInterface;
 use Drupal\commerce_shipping\ShippingService;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides the base class for shipping methods.
  */
-abstract class ShippingMethodBase extends PluginBase implements ShippingMethodInterface {
+abstract class ShippingMethodBase extends PluginBase implements ContainerFactoryPluginInterface, ShippingMethodInterface {
+
+  /**
+   * The package type manager.
+   *
+   * @var \Drupal\commerce_shipping\PackageTypeManagerInterface
+   */
+  protected $packageTypeManager;
 
   /**
    * The shipping services.
@@ -28,10 +38,13 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
    *   The plugin_id for the plugin instance.
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
+   * @param \Drupal\commerce_shipping\PackageTypeManagerInterface $package_type_manager
+   *   The package type manager.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, PackageTypeManagerInterface $package_type_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
+    $this->packageTypeManager = $package_type_manager;
     foreach ($this->pluginDefinition['services'] as $id => $label) {
       $this->services[$id] = new ShippingService($id, (string) $label);
     }
@@ -41,6 +54,18 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
   /**
    * {@inheritdoc}
    */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.commerce_package_type')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getLabel() {
     return (string) $this->pluginDefinition['label'];
   }
@@ -48,6 +73,14 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
   /**
    * {@inheritdoc}
    */
+  public function getDefaultPackageType() {
+    $package_type_id = $this->configuration['default_package_type'];
+    return $this->packageTypeManager->createInstance($package_type_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getServices() {
     // Filter out shipping services disabled by the merchant.
     return array_intersect_key($this->services, array_flip($this->configuration['services']));
@@ -65,6 +98,7 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
    */
   public function defaultConfiguration() {
     return [
+      'default_package_type' => 'default_box',
       'services' => [],
     ];
   }
@@ -87,6 +121,10 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $package_types = $this->packageTypeManager->getDefinitionsByShippingMethod($this->pluginId);
+    $package_types = array_map(function ($package_type) {
+      return $package_type['label'];
+    }, $package_types);
     $services = array_map(function ($service) {
       return $service->getLabel();
     }, $this->services);
@@ -96,6 +134,14 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
       $this->configuration['services'] = array_combine($service_ids, $service_ids);
     }
 
+    $form['default_package_type'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Default package type'),
+      '#options' => $package_types,
+      '#default_value' => $this->configuration['default_package_type'],
+      '#required' => TRUE,
+      '#access' => count($package_types) > 1,
+    ];
     $form['services'] = [
       '#type' => 'checkboxes',
       '#title' => $this->t('Shipping services'),
@@ -121,6 +167,8 @@ abstract class ShippingMethodBase extends PluginBase implements ShippingMethodIn
       $values = $form_state->getValue($form['#parents']);
       if (!empty($values['services'])) {
         $values['services'] = array_filter($values['services']);
+
+        $this->configuration['default_package_type'] = $values['default_package_type'];
         $this->configuration['services'] = array_keys($values['services']);
       }
     }
diff --git a/src/Plugin/Commerce/ShippingMethod/ShippingMethodInterface.php b/src/Plugin/Commerce/ShippingMethod/ShippingMethodInterface.php
index efda9fe..f18d5e7 100644
--- a/src/Plugin/Commerce/ShippingMethod/ShippingMethodInterface.php
+++ b/src/Plugin/Commerce/ShippingMethod/ShippingMethodInterface.php
@@ -21,6 +21,13 @@ interface ShippingMethodInterface extends ConfigurablePluginInterface, PluginFor
   public function getLabel();
 
   /**
+   * Gets the default package type.
+   *
+   * @return \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageTypeInterface
+   */
+  public function getDefaultPackageType();
+
+  /**
    * Gets the shipping services.
    *
    * @return \Drupal\commerce_shipping\ShippingService[]
diff --git a/src/ShippingMethodManager.php b/src/ShippingMethodManager.php
index 6089d6f..e982037 100644
--- a/src/ShippingMethodManager.php
+++ b/src/ShippingMethodManager.php
@@ -19,7 +19,7 @@ class ShippingMethodManager extends DefaultPluginManager {
    *
    * @param \Traversable $namespaces
    *   An object that implements \Traversable which contains the root paths
-   *   keyed by the corresponding namespace to look for plugin implementations,
+   *   keyed by the corresponding namespace to look for plugin implementations.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
    *   Cache backend instance to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
diff --git a/tests/src/Unit/Plugin/Commerce/PackageType/PackageTypeTest.php b/tests/src/Unit/Plugin/Commerce/PackageType/PackageTypeTest.php
new file mode 100644
index 0000000..8992f47
--- /dev/null
+++ b/tests/src/Unit/Plugin/Commerce/PackageType/PackageTypeTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\Tests\commerce_shipping\Unit;
+
+use Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageType;
+use Drupal\physical\Length;
+use Drupal\physical\Weight;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageType
+ * @group commerce_shipping
+ */
+class PackageTypeTest extends UnitTestCase {
+
+  /**
+   * The test package type.
+   *
+   * @var \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageType
+   */
+  protected $packageType;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $plugin_definition = [
+      'id' => 'test id',
+      'remote_id' => 'test remote id',
+      'label' => 'test label',
+      'dimensions' => [
+        'length' => '1',
+        'width' => '2',
+        'height' => '3',
+        'unit' => 'mm',
+      ],
+      'weight' => [
+        'number' => '4',
+        'unit' => 'kg',
+      ],
+    ];
+    $this->packageType = new PackageType([], 'test', $plugin_definition);
+  }
+
+  /**
+   * @covers ::getId
+   */
+  public function testGetId() {
+    $this->assertEquals('test id', $this->packageType->getId());
+  }
+
+  /**
+   * @covers ::getRemoteId
+   */
+  public function testGetRemoteId() {
+    $this->assertEquals('test remote id', $this->packageType->getRemoteId());
+  }
+
+  /**
+   * @covers ::getLabel
+   */
+  public function testGetLabel() {
+    $this->assertEquals('test label', $this->packageType->getLabel());
+  }
+
+  /**
+   * @covers ::getLength
+   * @covers ::getWidth
+   * @covers ::getHeight
+   */
+  public function testGetDimensions() {
+    $this->assertEquals(new Length('1', 'mm'), $this->packageType->getLength());
+    $this->assertEquals(new Length('2', 'mm'), $this->packageType->getWidth());
+    $this->assertEquals(new Length('3', 'mm'), $this->packageType->getHeight());
+  }
+
+  /**
+   * @covers ::getWeight
+   */
+  public function testGetWeight() {
+    $this->assertEquals(new Weight('4', 'kg'), $this->packageType->getWeight());
+  }
+
+}
diff --git a/yaml_translation_patterns.yml b/yaml_translation_patterns.yml
new file mode 100644
index 0000000..4701840
--- /dev/null
+++ b/yaml_translation_patterns.yml
@@ -0,0 +1,2 @@
+*.commerce_package_types.yml:
+  - label
