diff --git a/core/modules/migrate/src/Plugin/migrate/process/Callback.php b/core/modules/migrate/src/Plugin/migrate/process/Callback.php
index a438189d2d..f1b2f98be5 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Callback.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Callback.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\migrate\Plugin\migrate\process;
 
+use Drupal\migrate\MigrateException;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\ProcessPluginBase;
 use Drupal\migrate\Row;
@@ -10,11 +11,13 @@
  * Passes the source value to a callback.
  *
  * The callback process plugin allows simple processing of the value, such as
- * strtolower(). The callable takes the source value as the single mandatory
- * argument. No additional arguments can be passed to the callback.
+ * strtolower(). To pass more than one argument, pass an array as the source
+ * and set the unpack_source option.
  *
  * Available configuration keys:
  * - callable: The name of the callable method.
+ * - unpack_source: (optional) Whether to interpret the source as an array of
+ *   arguments.
  *
  * Examples:
  *
@@ -38,6 +41,25 @@
  *     source: source_field
  * @endcode
  *
+ * An example where the callback accepts more than one argument:
+ *
+ * @code
+ * source:
+ *   plugin: source_plugin_goes_here
+ *   constants:
+ *     slash: /
+ * process:
+ *   field_link_url:
+ *     plugin: callback
+ *     callable: rtrim
+ *     unpack_source: true
+ *     source:
+ *       - url
+ *       - constants/slash
+ * @endcode
+ *
+ * This will remove the trailing '/', if any, from a URL.
+ *
  * @see \Drupal\migrate\Plugin\MigrateProcessInterface
  *
  * @MigrateProcessPlugin(
@@ -63,6 +85,12 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    * {@inheritdoc}
    */
   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    if (!empty($this->configuration['unpack_source'])) {
+      if (!is_array($value)) {
+        throw new MigrateException(sprintf("When 'unpack_source' is set, the source must be an array. Instead it was of type '%s'", gettype($value)));
+      }
+      return call_user_func($this->configuration['callable'], ...$value);
+    }
     return call_user_func($this->configuration['callable'], $value);
   }
 
diff --git a/core/modules/migrate/tests/src/Unit/process/CallbackTest.php b/core/modules/migrate/tests/src/Unit/process/CallbackTest.php
index b34ce15b4b..0cc93ae934 100644
--- a/core/modules/migrate/tests/src/Unit/process/CallbackTest.php
+++ b/core/modules/migrate/tests/src/Unit/process/CallbackTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\migrate\Unit\process;
 
+use Drupal\migrate\MigrateException;
 use Drupal\migrate\Plugin\migrate\process\Callback;
 
 /**
@@ -33,15 +34,60 @@ public function providerCallback() {
     ];
   }
 
+  /**
+   * Test callback with valid "callable" and multiple arguments.
+   *
+   * @dataProvider providerCallbackArray
+   */
+  public function testCallbackArray($callable, $args, $result) {
+    $configuration = ['callable' => $callable, 'unpack_source' => TRUE];
+    $this->plugin = new Callback($configuration, 'map', []);
+    $value = $this->plugin->transform($args, $this->migrateExecutable, $this->row, 'destination_property');
+    $this->assertSame($result, $value);
+  }
+
+  /**
+   * Data provider for ::testCallbackArray().
+   */
+  public function providerCallbackArray() {
+    return [
+      'date format' => [
+        'date',
+        ['Y-m-d', 995328000],
+        '2001-07-17',
+      ],
+      'rtrim' => [
+        'rtrim',
+        ['https://www.example.com/', '/'],
+        'https://www.example.com',
+      ],
+      'str_replace' => [
+        'str_replace',
+        [['One', 'two'], ['1', '2'], 'One, two, three!'],
+        '1, 2, three!',
+      ],
+    ];
+  }
+
   /**
    * Test callback exceptions.
    *
+   * @param string $message
+   *   The expected exception message.
+   * @param array $configuration
+   *   The plugin configuration being tested.
+   * @param string $class
+   *   (optional) The expected exception class.
+   * @param mixed $args
+   *   (optional) Arguments to pass to the transform() method.
+   *
    * @dataProvider providerCallbackExceptions
    */
-  public function testCallbackExceptions($message, $configuration) {
-    $this->expectException(\InvalidArgumentException::class);
+  public function testCallbackExceptions($message, array $configuration, $class = 'InvalidArgumentException', $args = NULL) {
+    $this->expectException($class);
     $this->expectExceptionMessage($message);
     $this->plugin = new Callback($configuration, 'map', []);
+    $this->plugin->transform($args, $this->migrateExecutable, $this->row, 'destination_property');
   }
 
   /**
@@ -57,6 +103,12 @@ public function providerCallbackExceptions() {
         'message' => 'The "callable" must be a valid function or method.',
         'configuration' => ['callable' => 'nonexistent_callable'],
       ],
+      'array required' => [
+        'message' => "When 'unpack_source' is set, the source must be an array. Instead it was of type 'string'",
+        'configuration' => ['callable' => 'count', 'unpack_source' => TRUE],
+        'class' => MigrateException::class,
+        'args' => 'This string is not an array.',
+      ],
     ];
   }
 
