diff --git a/src/Plugin/Action/CreateAdjustmentAction.php b/src/Plugin/Action/CreateAdjustmentAction.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd86b6e8b0ef658b72a999baed2c0e1467b88242
--- /dev/null
+++ b/src/Plugin/Action/CreateAdjustmentAction.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace Drupal\eca_commerce\Plugin\Action;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\commerce_order\Adjustment;
+use Drupal\commerce_order\AdjustmentTypeManager;
+use Drupal\commerce_order\EntityAdjustableInterface;
+use Drupal\commerce_price\Price;
+use Drupal\commerce_store\Resolver\StoreResolverInterface;
+use Drupal\eca\Plugin\Action\ConfigurableActionBase;
+use Drupal\eca\Plugin\ECA\PluginFormTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Describes the eca_commerce add_adjustment action.
+ *
+ * This allows users to add price adjustments when added to cart based on ECA.
+ *
+ * @Action(
+ *   id = "eca_commerce_add_adjustment",
+ *   label = @Translation("Order Item: Add Price Adjustment"),
+ *   eca_version_introduced = "1.0.0",
+ *   type = "commerce_order_item"
+ * )
+ */
+class CreateAdjustmentAction extends ConfigurableActionBase {
+
+  use CurrencyActionTrait;
+  use PluginFormTrait;
+
+  /**
+   * The adjustment type manager.
+   *
+   * @var \Drupal\commerce_order\AdjustmentTypeManager|null
+   */
+  protected ?AdjustmentTypeManager $adjustmentTypeManager;
+
+  /**
+   * The default store resolver.
+   *
+   * @var \Drupal\commerce_store\Resolver\StoreResolverInterface|null
+   */
+  protected ?StoreResolverInterface $defaultStoreResolver;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
+    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+    $instance->adjustmentTypeManager = $container->get('plugin.manager.commerce_adjustment_type', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+    $instance->defaultStoreResolver = $container->get('commerce_store.default_store_resolver', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+    return $instance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $access_result = AccessResult::AllowedIf($object instanceof EntityAdjustableInterface);
+
+    return $return_as_object ? $access_result : $access_result->isAllowed();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute(mixed $entity = NULL): void {
+    if (!class_exists(Adjustment::class) || !class_exists(Price::class)) {
+      // Early return.
+      return;
+    }
+
+    $label = $this->tokenService->replaceClear($this->configuration['label']);
+    $amount = $this->tokenService->replaceClear($this->configuration['amount']);
+    $fallback_currency = $this->getFallbackCurrency($entity);
+    $currency = $this->configuration['currency'] ?: $fallback_currency;
+    if ($currency === '_eca_token') {
+      $currency = $this->getTokenValue('currency', $fallback_currency);
+    }
+    $percentage = $this->tokenService->replaceClear($this->configuration['percentage']);
+    $definition = [
+      'type' => $this->configuration['type'],
+      'label' => $label,
+      'amount' => new Price($amount, $currency),
+      'percentage' => $percentage ?: NULL,
+      'source_id' => 'custom',
+      'included' => $this->configuration['included'],
+      'locked' => $this->configuration['locked'],
+    ];
+    $adjustment = new Adjustment($definition);
+    switch ($this->configuration['method']) {
+      case 'set:clear':
+        $entity->setAdjustments([]);
+
+      case 'append:drop_first':
+        $entity->addAdjustment($adjustment);
+        break;
+    }
+
+    if ($this->configuration['save_entity']) {
+      $entity->save();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration(): array {
+    return [
+      'method' => 'set:clear',
+      'type' => '_none',
+      'label' => '',
+      'amount' => '',
+      'currency' => '',
+      'percentage' => '',
+      'included' => FALSE,
+      'locked' => TRUE,
+      'save_entity' => FALSE,
+    ] + parent::defaultConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
+    $form['method'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Method'),
+      '#default_value' => $this->configuration['method'],
+      '#description' => $this->t('The method to set an entity, like cleaning the old one, etc..'),
+      '#weight' => -40,
+      '#options' => [
+        'set:clear' => $this->t('Set and clear previous value'),
+        'append:drop_first' => $this->t('Append and drop first when full'),
+      ],
+    ];
+    $types = [
+      '_none' => $this->t('- Select -'),
+    ];
+    if (isset($this->adjustmentTypeManager)) {
+      foreach ($this->adjustmentTypeManager->getDefinitions() as $id => $definition) {
+        if (!empty($definition['has_ui'])) {
+          $types[$id] = $definition['label'];
+        }
+      }
+    }
+    $form['type'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Type'),
+      '#options' => $types,
+      '#weight' => 1,
+      '#default_value' => $this->configuration['type'],
+      '#required' => TRUE,
+    ];
+    $form['locked'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Locked'),
+      '#description' => $this->t('Note: Adjustments added from UI interactions need to be locked to persist after an order refresh.'),
+      '#default_value' => $this->configuration['locked'],
+    ];
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#size' => 20,
+      '#default_value' => $this->configuration['label'],
+      '#required' => TRUE,
+      '#eca_token_replacement' => TRUE,
+    ];
+    $form['amount'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Amount'),
+      '#default_value' => $this->configuration['amount'],
+      '#required' => TRUE,
+      '#attributes' => ['class' => ['clearfix']],
+      '#eca_token_replacement' => TRUE,
+    ];
+    $form['currency'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Currency'),
+      '#options' => ['_none' => 'Use default'] + $this->getAvailableCurrencies(),
+      '#default_value' => $this->configuration['currency'],
+      '#size' => 5,
+      '#required' => TRUE,
+      '#eca_token_select_option' => TRUE,
+    ];
+    $form['percentage'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Percentage'),
+      '#default_value' => $this->configuration['percentage'],
+      '#attributes' => ['class' => ['clearfix']],
+      '#eca_token_replacement' => TRUE,
+    ];
+    $form['included'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Included in the base price'),
+      '#default_value' => $this->configuration['amount'],
+    ];
+    $form['save_entity'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Save entity'),
+      '#default_value' => $this->configuration['save_entity'],
+      '#description' => $this->t('Saves the entity or not after setting the value.'),
+      '#weight' => -10,
+    ];
+
+    return parent::buildConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
+    $this->configuration['method'] = $form_state->getValue('method');
+    $this->configuration['type'] = $form_state->getValue('type');
+    $this->configuration['locked'] = $form_state->getValue('locked');
+    $this->configuration['label'] = $form_state->getValue('label');
+    $this->configuration['amount'] = $form_state->getValue('amount');
+    if ($form_state->getValue('currency') === '_none') {
+      $currency = '';
+    }
+    else {
+      $currency = $form_state->getValue('currency');
+    }
+    $this->configuration['currency'] = $currency;
+    $this->configuration['percentage'] = $form_state->getValue('percentage');
+    $this->configuration['included'] = $form_state->getValue('included');
+    $this->configuration['save_entity'] = $form_state->getValue('save_entity');
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+}
diff --git a/src/Plugin/Action/CurrencyActionTrait.php b/src/Plugin/Action/CurrencyActionTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..348751de67ac9af42f8c8669680a889ad5dd20b7
--- /dev/null
+++ b/src/Plugin/Action/CurrencyActionTrait.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\eca_commerce\Plugin\Action;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\commerce_order\Entity\OrderItemInterface;
+use Drupal\commerce_store\Entity\EntityStoreInterface;
+use Drupal\commerce_store\Entity\StoreInterface;
+use Drupal\commerce_store\Resolver\StoreResolverInterface;
+
+/**
+ * Trait for resolving the default currency to use for a price adjustment.
+ */
+trait CurrencyActionTrait {
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected EntityTypeManagerInterface $entityTypeManager;
+
+  /**
+   * The default store resolver.
+   *
+   * @var \Drupal\commerce_store\Resolver\StoreResolverInterface|null
+   */
+  protected ?StoreResolverInterface $defaultStoreResolver;
+
+  /**
+   * Gets all available currencies configured on the site.
+   *
+   * Ensures format is compatible with currency select form element.
+   *
+   * @return array
+   *   Array of currency codes with both keys and values populated. For example,
+   *   @code
+   *   ['USD' => 'USD']
+   *   @endcode
+   */
+  protected function getAvailableCurrencies(): array {
+    $currencies = $this->entityTypeManager->getStorage('commerce_currency')->loadMultiple();
+    $currency_codes = array_keys($currencies);
+    return array_combine($currency_codes, $currency_codes);
+  }
+
+  /**
+   * Gets the default currency to use if the action was not configured with one.
+   *
+   * @param mixed $entity
+   *   The entity used as a reference e.g. Order, Order Item.
+   *   The currency of the entity's total price is used if available.
+   *   Otherwise, the default currency of the respective Store is used.
+   *
+   * @return string
+   *   The default currency.
+   */
+  protected function getFallbackCurrency(mixed $entity = NULL): string {
+    return $entity?->getTotalPrice()?->getCurrencyCode()
+      ?? $this->getFallbackStore($entity)?->getDefaultCurrencyCode()
+      ?? 'USD';
+  }
+
+  /**
+   * Gets the given Order or Order Item entity's associated Store.
+   *
+   * Otherwise return the default Store.
+   *
+   * @param mixed $entity
+   *   The entity used as a reference e.g. Order, Order Item.
+   *
+   * @return \Drupal\commerce_store\Entity\StoreInterface|null
+   *   The respective Store.
+   */
+  protected function getFallbackStore(mixed $entity = NULL) : ?StoreInterface {
+    if ($entity instanceof EntityStoreInterface) {
+      $store = $entity->getStore();
+    }
+    elseif ($entity instanceof OrderItemInterface) {
+      $store = $entity->getOrder()?->getStore();
+    }
+    $store ??= $this->defaultStoreResolver?->resolve();
+    return $store;
+  }
+
+}