diff --git a/modules/shareholder_register_webform/shareholder_register_webform.services.yml b/modules/shareholder_register_webform/shareholder_register_webform.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eb857b6b74b7fe5044aea4c7ecc577552c062ce9
--- /dev/null
+++ b/modules/shareholder_register_webform/shareholder_register_webform.services.yml
@@ -0,0 +1,4 @@
+services:
+  shareholder_register_webform.mapping:
+    class: Drupal\shareholder_register_webform\MappingService
+    arguments: ['@entity_type.manager', '@shareholder_register.default', '@event_dispatcher', '@messenger', '@logger.channel.shareholder_register']
diff --git a/modules/shareholder_register_webform/src/MappingService.php b/modules/shareholder_register_webform/src/MappingService.php
new file mode 100644
index 0000000000000000000000000000000000000000..c47841f1fba348084147321c58295c4a70ae3da3
--- /dev/null
+++ b/modules/shareholder_register_webform/src/MappingService.php
@@ -0,0 +1,245 @@
+<?php
+
+namespace Drupal\shareholder_register_webform;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+
+use Drupal\shareholder_register\ShareholderRegisterServiceInterface;
+use Drupal\webform\webformSubmissionInterface;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+
+/**
+ * MappingService service.
+ */
+class MappingService {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The shareholder_register.default service.
+   *
+   * @var \Drupal\shareholder_register\ShareholderRegisterServiceInterface
+   */
+  protected $shareholderRegisterDefault;
+
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
+   * The messenger.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * The logger.channel.shareholder_register service.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * Constructs a MappingService object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\shareholder_register\ShareholderRegisterServiceInterface $shareholder_register_default
+   *   The shareholder_register.default service.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+   *   The event dispatcher.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.channel.shareholder_register service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ShareholderRegisterServiceInterface $shareholder_register_default, EventDispatcherInterface $event_dispatcher, MessengerInterface $messenger, LoggerInterface $logger_channel) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->shareholderRegisterDefault = $shareholder_register_default;
+    $this->eventDispatcher = $event_dispatcher;
+    $this->messenger = $messenger;
+    $this->logger = $logger_channel;
+  }
+
+  /**
+   * Method description.
+   */
+  public function loadMapping(string $mapping) {
+    $mapping_array = [];
+
+    $mapping = str_replace("\r", "\n", str_replace("\r\n", "\n", $mapping));
+    foreach (explode("\n", $mapping) as $mapping_line) {
+      if (!$mapping_line || !trim($mapping_line)) {
+        continue;
+      }
+
+      $map = explode('=', $mapping_line);
+      if (count($map) != 2) {
+        continue;
+      }
+      $property = array_filter(
+        explode('.', trim($map[0]))
+      );
+      if (count($property) < 1 || count($property) > 2) {
+        continue;
+      }
+
+      $data_selector = array_filter(
+        explode('.', trim($map[1]))
+      );
+      if (count($data_selector) < 1) {
+        continue;
+      }
+
+      $mapping_array[] = [
+        'property' => $property,
+        'data_selector' => $data_selector,
+      ];
+    }
+
+    return $mapping_array;
+  }
+
+  /**
+   * Extract data_selector from data array.
+   *
+   * throw Exception if first element doesn't exist.
+   *
+   * @returns Element value or FALSE.
+   */
+  public function dataSelectorFromData(array $data, array $data_selector) {
+    $data_element = $data;
+    if (!array_key_exists($data_selector[0], $data_element)) {
+      throw new \Exception("key doesn't exist");
+    }
+
+    foreach ($data_selector as $ds) {
+      if (!$ds || !array_key_exists($ds, $data_element)) {
+        $data_element = FALSE;
+        break;
+      }
+      $data_element = $data_element[$ds];
+    }
+    return $data_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function propertyFromEntity(EntityInterface $entity, array $property) {
+    if (!$entity->hasField($property[0])) {
+      return NULL;
+    }
+
+    if (count($property) == 1) {
+      return $entity->get(trim($property[0]))->value;
+    }
+    elseif (count($property) == 2) {
+      try {
+        return $entity->get(trim($property[0]))->{trim($property[1])};
+      }
+      catch (Exception $e) {
+        return NULL;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mapSubmissionToEntity(string $mapping, WebformSubmissionInterface $submission, $entity) {
+    $data = $submission->getData();
+
+    foreach ($this->loadMapping($mapping) as $mapping_line) {
+
+      try {
+        $data_element = $this->dataSelectorFromData($data, $mapping_line['data_selector']);
+      }
+      catch (\Exception $exception) {
+        // Data element not found, continue with next mapping line.
+        continue;
+      }
+
+      $property = $mapping_line['property'];
+
+      if (!$entity->hasField($property[0])) {
+        $this->logger->notice(
+          'mapSubmissionToEntity: invalid field @field in mapping.',
+          [
+            '@field' => implode('.', $property),
+          ]
+        );
+        continue;
+      }
+
+      if (count($property) == 1) {
+        $entity->{trim($property[0])} = trim($data_element);
+      }
+      elseif (count($property) == 2) {
+        try {
+          $entity->{trim($property[0])}->{trim($property[1])} = trim($data_element);
+        }
+        catch (Exception $e) {
+          $this->logger->notice(
+            'mapSubmissionToEntity: invalid field item @field in mapping.',
+            [
+              '@field' => implode('.', $data_element),
+            ]
+          );
+          continue;
+        }
+      }
+
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mapEntityToSubmission($mapping, WebformSubmissionInterface $submission, $entity) {
+    $data = $submission->getData();
+
+    foreach ($this->loadMapping($mapping) as $mapping_line) {
+      $data_element_selector = array_slice($mapping_line['data_selector'], 0, 1);
+      try {
+        // Test if first element exists.
+        $data_element = $this->dataSelectorFromData($data, $data_element_selector);
+      }
+      catch (\Exception $exception) {
+        $this->logger->notice(
+          'mapEntityToSubmission: invalid field item @field in mapping',
+          [
+            '@field' => implode('.', $mapping_line['property']),
+          ]
+        );
+      }
+
+      if (count($mapping_line['data_selector'] > 1)) {
+        // FIXME: support multilevel data selector.
+        continue;
+      }
+
+      $property = $this->propertyFromEntity($entity, $mapping_line['property']);
+      if ($property === NULL) {
+        continue;
+      }
+
+      $submission->setElementData(trim($mapping_line['data_selector'][0]), $data_element);
+    }
+  }
+
+}