diff --git a/src/Event/FilterShippingMethodsEvent.php b/src/Event/FilterShippingMethodsEvent.php
new file mode 100644
index 0000000..2eb2f14
--- /dev/null
+++ b/src/Event/FilterShippingMethodsEvent.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\commerce_shipping\Event;
+
+use Drupal\commerce_shipping\Entity\ShipmentInterface;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Defines the event for filtering the available payment gateways.
+ *
+ * @see \Drupal\commerce_shipping\Event\ShippingEvents
+ */
+class FilterShippingMethodsEvent extends Event {
+
+  /**
+   * The shipping methods.
+   *
+   * @var \Drupal\commerce_shipping\Entity\ShippingMethodInterface[]
+   */
+  protected $shippingMethods;
+
+  /**
+   * The shipment.
+   *
+   * @var \Drupal\commerce_shipping\Entity\ShipmentInterface
+   */
+  protected $shipment;
+
+  /**
+   * Constructs a new FilterShippingMethodsEvent object.
+   *
+   * @param \Drupal\commerce_shipping\Entity\ShippingMethodInterface[] $shipping_methods
+   *   The shipping methods.
+   * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment
+   *   The shipment.
+   */
+  public function __construct(array $shipping_methods, ShipmentInterface $shipment) {
+    $this->shippingMethods = $shipping_methods;
+    $this->shipment = $shipment;
+  }
+
+  /**
+   * Gets shipping methods.
+   *
+   * @return array|\Drupal\commerce_shipping\Entity\ShippingMethodInterface[]
+   *   The shipping methods.
+   *
+   */
+  public function getShippingMethods() {
+    return $this->shippingMethods;
+  }
+
+  /**
+   * Sets shipping methods.
+   *
+   * @param \Drupal\commerce_shipping\Entity\ShippingMethodInterface[] $shipping_methods
+   *   The shipping methods.
+   *
+   * @return $this
+   */
+  public function setShippingMethods(array $shipping_methods) {
+    $this->shippingMethods = $shipping_methods;
+    return $this;
+  }
+
+  /**
+   * Gets the shipment.
+   *
+   * @return \Drupal\commerce_shipping\Entity\ShipmentInterface
+   *   The shipment.
+   */
+  public function getShipment() {
+    return $this->shipment;
+  }
+}
diff --git a/src/Event/ShippingEvents.php b/src/Event/ShippingEvents.php
new file mode 100644
index 0000000..099a526
--- /dev/null
+++ b/src/Event/ShippingEvents.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\commerce_shipping\Event;
+
+final class ShippingEvents {
+
+  /**
+   * Name of the event fired when shipping methods are loaded for a shipment.
+   *
+   * @Event
+   *
+   * @see \Drupal\commerce_shipping\Event\FilterShippingMethodsEvent
+   */
+  const FILTER_SHIPPING_METHODS = 'commerce_shipping.filter_shipping_methods';
+
+}
diff --git a/src/ShippingMethodStorage.php b/src/ShippingMethodStorage.php
index c5918f3..3250646 100644
--- a/src/ShippingMethodStorage.php
+++ b/src/ShippingMethodStorage.php
@@ -4,6 +4,8 @@ namespace Drupal\commerce_shipping;
 
 use Drupal\commerce\CommerceContentEntityStorage;
 use Drupal\commerce_shipping\Entity\ShipmentInterface;
+use Drupal\commerce_shipping\Event\FilterShippingMethodsEvent;
+use Drupal\commerce_shipping\Event\ShippingEvents;
 
 /**
  * Defines the shipping method storage.
@@ -27,9 +29,10 @@ class ShippingMethodStorage extends CommerceContentEntityStorage implements Ship
     }
     uasort($shipping_methods, [$this->entityType->getClass(), 'sort']);
     if (!empty($shipping_methods)) {
-      // Allow modules to alter the list of available shipping methods via
-      // hook_commerce_shipping_methods_alter(&$shipping_methods, $shipment).
-      \Drupal::moduleHandler()->alter('commerce_shipping_methods', $shipping_methods, $shipment);
+      // Allow the list of shipping methods to be filtered via code.
+      $event = new FilterShippingMethodsEvent($shipping_methods, $shipment);
+      $this->eventDispatcher->dispatch(ShippingEvents::FILTER_SHIPPING_METHODS, $event);
+      $shipping_methods = $event->getShippingMethods();
     }
 
     return $shipping_methods;
diff --git a/tests/modules/commerce_shipping_test/commerce_shipping_test.services.yml b/tests/modules/commerce_shipping_test/commerce_shipping_test.services.yml
index 319e50e..2119f56 100644
--- a/tests/modules/commerce_shipping_test/commerce_shipping_test.services.yml
+++ b/tests/modules/commerce_shipping_test/commerce_shipping_test.services.yml
@@ -3,3 +3,8 @@ services:
     class: Drupal\commerce_shipping_test\Packer\TestPacker
     tags:
       - { name: commerce_shipping.packer }
+  commerce_shipping_test.filter_shipping_methods_subscriber:
+    class: Drupal\commerce_shipping_test\EventSubscriber\FilterShippingMethodsSubscriber
+    tags:
+      - { name: event_subscriber }
+
diff --git a/tests/modules/commerce_shipping_test/src/EventSubscriber/FilterShippingMethodsSubscriber.php b/tests/modules/commerce_shipping_test/src/EventSubscriber/FilterShippingMethodsSubscriber.php
new file mode 100644
index 0000000..d5ab416
--- /dev/null
+++ b/tests/modules/commerce_shipping_test/src/EventSubscriber/FilterShippingMethodsSubscriber.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\commerce_shipping_test\EventSubscriber;
+
+use Drupal\commerce_shipping\Event\FilterShippingMethodsEvent;
+use Drupal\commerce_shipping\Event\ShippingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class FilterShippingMethodsSubscriber implements EventSubscriberInterface {
+
+  public static function getSubscribedEvents() {
+    return [
+      ShippingEvents::FILTER_SHIPPING_METHODS => 'onFilter',
+    ];
+  }
+
+  /**
+   * Filters out shipping methods listed in a shipment's data attribute.
+   *
+   * @param \Drupal\commerce_shipping\Event\FilterShippingMethodsEvent $event
+   *   The event.
+   */
+  public function onFilter(FilterShippingMethodsEvent $event) {
+    $shipping_methods = $event->getShippingMethods();
+    $excluded_methods = $event->getShipment()->getData('excluded_methods', []);
+    foreach ($shipping_methods as $shipping_method_id => $shipping_method) {
+      if (in_array($shipping_method->id(), $excluded_methods)) {
+        unset($shipping_methods[$shipping_method_id]);
+      }
+    }
+    $event->setShippingMethods($shipping_methods);
+  }
+}
diff --git a/tests/src/Kernel/FilterShippingMethodsEventTest.php b/tests/src/Kernel/FilterShippingMethodsEventTest.php
new file mode 100644
index 0000000..806e00e
--- /dev/null
+++ b/tests/src/Kernel/FilterShippingMethodsEventTest.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Drupal\Tests\commerce_shipping\Kernel;
+
+
+use Drupal\commerce_order\Entity\Order;
+use Drupal\commerce_price\Price;
+use Drupal\commerce_shipping\Entity\Shipment;
+use Drupal\commerce_shipping\Entity\ShippingMethod;
+use Drupal\commerce_shipping\ShipmentItem;
+use Drupal\physical\Weight;
+
+/**
+ * Tests the FilterShippingMethodsEvent.
+ *
+ * @coversDefaultClass \Drupal\commerce_shipping\Event\FilterShippingMethodsEvent
+ * @group commerce_shipping
+ */
+class FilterShippingMethodsEventTest extends ShippingKernelTestBase {
+
+  /**
+   * The shipping methods storage.
+   *
+   * @var \Drupal\commerce_shipping\ShippingMethodStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'address',
+    'entity_reference_revisions',
+    'profile',
+    'state_machine',
+    'commerce_order',
+    'commerce_shipping',
+    'commerce_shipping_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('commerce_shipping_method');
+    $this->installConfig('commerce_shipping');
+    $this->installConfig('commerce_shipping_test');
+
+    $this->storage = $this->container->get('entity_type.manager')->getStorage('commerce_shipping_method');
+  }
+
+  /**
+   * Tests that the shipping method is removed.
+   */
+  public function testEvent() {
+    $shipping_method_example = ShippingMethod::create([
+      'name' => 'Example',
+      'plugin' => [
+        'target_plugin_id' => 'flat_rate',
+        'target_plugin_configuration' => [],
+      ],
+      'status' => 1,
+      'stores' => $this->store->id(),
+    ]);
+    $shipping_method_example->save();
+    $shipping_method_filtered = ShippingMethod::create([
+      'name' => 'Example (Filtered)',
+      'plugin' => [
+        'target_plugin_id' => 'flat_rate',
+        'target_plugin_configuration' => [],
+      ],
+      'status' => 1,
+      'stores' => $this->store->id(),
+    ]);
+    $shipping_method_filtered->save();
+
+    $user = $this->createUser(['mail' => $this->randomString() . '@example.com']);
+    $order = Order::create([
+      'type' => 'default',
+      'state' => 'draft',
+      'mail' => $user->getEmail(),
+      'uid' => $user->id(),
+      'store_id' => $this->store->id(),
+    ]);
+    $order->save();
+
+    $shipment = Shipment::create([
+      'type' => 'default',
+      'order_id' => 10,
+      'shipping_method' => $shipping_method_filtered,
+      'items' => [
+        new ShipmentItem([
+          'order_item_id' => 10,
+          'title' => 'T-shirt (red, large)',
+          'quantity' => 1,
+          'weight' => new Weight('10', 'kg'),
+          'declared_value' => new Price('15', 'USD'),
+        ]),
+      ],
+      'order_id' => $order->id(),
+    ]);
+    $shipment->save();
+
+    $available_methods = $this->storage->loadMultipleForShipment($shipment);
+    $this->assertEquals(2, count($available_methods));
+    $method = array_shift($available_methods);
+    $this->assertEquals($shipping_method_example->label(), $method->label());
+    $method = array_shift($available_methods);
+    $this->assertEquals($shipping_method_filtered->label(), $method->label());
+
+    $shipment->setData('excluded_methods', [$shipping_method_filtered->id()]);
+
+    $available_methods = $this->storage->loadMultipleForShipment($shipment);
+    $this->assertEquals(1, count($available_methods));
+    $method = array_shift($available_methods);
+    $this->assertEquals($shipping_method_example->label(), $method->label());
+  }
+}
