diff --git a/modules/graphql_compose_mutations/README.md b/modules/graphql_compose_mutations/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a9be8596c293e9b8916c924d723e5b064a87a6bd
--- /dev/null
+++ b/modules/graphql_compose_mutations/README.md
@@ -0,0 +1,10 @@
+# Generic Mutations for graphql_compose
+
+## Available GraphQL mutations
+
+- genericMutation
+
+## Available GraphQL queries
+
+- operationsByEntityType
+- permissions
diff --git a/modules/graphql_compose_mutations/graphql/generic_mutation.base.graphqls b/modules/graphql_compose_mutations/graphql/generic_mutation.base.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..7040c48b58cfc8c05eab1ecc9ba3d0d52f15ebef
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/generic_mutation.base.graphqls
@@ -0,0 +1,109 @@
+"""
+Helper input for String values.
+"""
+input keyValueString {
+  field: String!
+  value: String
+}
+
+"""
+Metadata values.
+"""
+input genericMetadata {
+  """
+  The entity type (node, user, profile, group etc).
+  """
+  entity_type: EntityType!
+
+  """
+  The bundle of the entity. Actually, we do not need this on DELETE.
+  """
+  entity_bundle: String!
+
+  """
+  The operation to perform
+  """
+  operation: Operation!
+
+  """
+  The id of the entity (if action is not CREATE)
+  """
+  id: Int
+
+  """
+  The parent entity type (node, user, profile, group etc) if exists.
+  """
+  parent_entity_type: EntityType
+
+  """
+  The parent entity ID if exists.
+  """
+  parent_entity_id: Int
+}
+
+"""
+The mutation available operations
+"""
+enum Operation {
+  DEFAULT
+  CREATE
+  UPDATE
+  DELETE
+}
+
+"""
+The mutation available entity types
+"""
+enum EntityType {
+  ACTIVITY
+  COMMENT
+  COMMERCE_ORDER
+  COMMERCE_PRODUCT
+  CONTACT_MESSAGE
+  EVENT_ENROLLMENT
+  FLAGGING
+  GROUP
+  GROUP_CONTENT
+  MEDIA
+  MESSAGE
+  NODE
+  POST
+  PRIVATE_MESSAGE
+  PRIVATE_MESSAGE_THREAD
+  PROFILE
+  TAXONOMY_TERM
+  USER
+  VOTE
+}
+
+"""
+A generic object containing any Drupal entity all values
+"""
+type AnyEntity {
+  id: Int
+  uuid: String
+  entity_type: String
+  entity_bundle: String
+  values: Json
+}
+
+type GenericEntityResponse implements Response {
+  errors: [Violation]
+  success: Boolean
+  entity: AnyEntity
+}
+
+scalar Violation
+
+"""
+The Json FieldType
+See graphql_compose/src/Plugin/GraphQLCompose/FieldType/JsonItem.php
+"""
+scalar Json
+
+"""
+ToDo: There is already a graphql Response... Why do we need this?
+"""
+interface Response {
+  errors: [Violation]
+}
diff --git a/modules/graphql_compose_mutations/graphql/generic_mutation.extension.graphqls b/modules/graphql_compose_mutations/graphql/generic_mutation.extension.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..912a47c0e7337ff77e69c6043048e7f3ab8bb304
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/generic_mutation.extension.graphqls
@@ -0,0 +1,6 @@
+extend type Mutation {
+  """
+  Generic entity mutation
+  """
+  genericMutation(data: Json!, metadata: genericMetadata!): GenericEntityResponse
+}
diff --git a/modules/graphql_compose_mutations/graphql/operations_by_entity_type.base.graphqls b/modules/graphql_compose_mutations/graphql/operations_by_entity_type.base.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..27879eebccf2c0500d51d8917119229189e1d0e2
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/operations_by_entity_type.base.graphqls
@@ -0,0 +1,4 @@
+type OperationsByEntityTypeResponse implements Response {
+  errors: [Violation]
+  operations: Json
+}
diff --git a/modules/graphql_compose_mutations/graphql/operations_by_entity_type.extension.graphqls b/modules/graphql_compose_mutations/graphql/operations_by_entity_type.extension.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..33f944c7b0dd04150762e8cd9e2531dfe7fbedd0
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/operations_by_entity_type.extension.graphqls
@@ -0,0 +1,8 @@
+extend type Query {
+  """
+  Query to get operations by entity_type
+  """
+  operationsByEntityType(
+    entity_type: EntityType!,
+  ): OperationsByEntityTypeResponse
+}
diff --git a/modules/graphql_compose_mutations/graphql/permissions.base.graphqls b/modules/graphql_compose_mutations/graphql/permissions.base.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..adcd6067c7d224b023fcbd422b7729ba1264c0fe
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/permissions.base.graphqls
@@ -0,0 +1,14 @@
+"""
+The permissions available operations, extended
+"""
+enum OperationExtended {
+  VIEW
+  CREATE
+  UPDATE
+  DELETE
+}
+
+type PermissionsResponse implements Response {
+  errors: [Violation]
+  success: Boolean
+}
diff --git a/modules/graphql_compose_mutations/graphql/permissions.extension.graphqls b/modules/graphql_compose_mutations/graphql/permissions.extension.graphqls
new file mode 100644
index 0000000000000000000000000000000000000000..feba89b190458346f8f737651c17ccf238948323
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql/permissions.extension.graphqls
@@ -0,0 +1,14 @@
+extend type Query {
+  """
+  Query to get operation permissions by uid
+  """
+  permissions(
+    operation: OperationExtended!,
+    uid: String!,
+    entity_id: String,
+    entity_type: EntityType!,
+    entity_bundle: String!,
+    parent_entity_type: EntityType,
+    parent_entity_id: String,
+  ): PermissionsResponse
+}
diff --git a/modules/graphql_compose_mutations/graphql_compose_mutations.info.yml b/modules/graphql_compose_mutations/graphql_compose_mutations.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..32e98101d53ee3571ddb0770a9bdb1ac782dfb94
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql_compose_mutations.info.yml
@@ -0,0 +1,9 @@
+name: "GraphQL Compose: Mutations"
+description: "Enable mutations for any Entity to the schema."
+type: module
+core_version_requirement: ^10.1 || ^11
+php: 8.1
+dependencies:
+  - graphql:graphql
+  - graphql_compose:graphql_compose
+package: GraphQL Compose
diff --git a/modules/graphql_compose_mutations/graphql_compose_mutations.module b/modules/graphql_compose_mutations/graphql_compose_mutations.module
new file mode 100644
index 0000000000000000000000000000000000000000..f449561e5e09254ef1fe5070fd1cb94b028ca841
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql_compose_mutations.module
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Extends module graphql_compose_mutations.
+ */
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\graphql\GraphQL\Execution\FieldContext;
+use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeInterface;
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function graphql_compose_mutations_entity_base_field_info(EntityTypeInterface $entity_type): ?array {
+  $fields = [];
+  $machine_names = graphql_compose_mutations_get_machine_names();
+
+  foreach ($machine_names as $machine_name) {
+    $field_type = str_contains($machine_name, "_user_") ? "boolean" : "string";
+    $fields[$machine_name] = BaseFieldDefinition::create($field_type)
+      ->setLabel(t('System field value of @name', ["@name" => $machine_name]))
+      ->setDescription(t('System field value of @name field', ["@name" => $machine_name]))
+      ->setComputed(TRUE)
+      ->setRequired(FALSE)
+      ->setReadOnly(TRUE)
+      ->setDefaultValue([]);
+  }
+
+  return $fields;
+}
+
+/**
+ * Implements hook_graphql_compose_field_results_alter().
+ */
+function graphql_compose_mutations_graphql_compose_field_results_alter(array &$results, $entity, GraphQLComposeFieldTypeInterface $plugin, FieldContext $context):void {
+  $field_definition = $plugin->getFieldDefinition();
+  $field_name = $field_definition->getName();
+  $entity_type_id = $entity->getEntityTypeId();
+  $current_uid = Drupal::currentUser()->id();
+
+  if ($entity instanceof EntityInterface) {
+    $uid = "";
+    $parent_entity_type = "";
+    $parent_entity_id = "";
+
+    // Common case of author.
+    if (property_exists($entity, "uid") && $entity->get("uid")->getValue()) {
+      if (isset($entity->get("uid")->getValue()[0])) {
+        $uid = $entity->get("uid")->getValue()[0]["target_id"];
+      }
+    }
+    // Entity type activity, post, vote.
+    if (in_array($entity_type_id, ["activity", "post", "vote"]) && property_exists($entity, "user_id") && $entity->get("user_id")->getValue()) {
+      if (isset($entity->get("user_id")->getValue()[0])) {
+        $uid = $entity->get("user_id")->getValue()[0]["target_id"];
+      }
+    }
+    // Entity type private_message
+    // private_message_thread has "members" array to get participants.
+    if ($entity_type_id === "private_message" && property_exists($entity, "owner") && $entity->get("owner")->getValue()) {
+      if (isset($entity->get("owner")->getValue()[0])) {
+        $uid = $entity->get("owner")->getValue()[0]["target_id"];
+      }
+    }
+
+    $entity_id = $entity->id();
+    $system_entity_type = $entity->getEntityTypeId();
+    $system_entity_bundle = $entity->bundle();
+    $system_entity_bundle_object = \Drupal::service('entity_type.bundle.info')->getBundleInfo($system_entity_type);
+
+    if (isset($system_entity_bundle_object[$system_entity_bundle])) {
+      $system_entity_bundle_label = $system_entity_bundle_object[$system_entity_bundle]['label'];
+    }
+    else {
+      $system_entity_bundle_label = "";
+    }
+
+    // Some entities need the Parent to check for Permissions.
+    if ($entity_type_id === "vote") {
+      $parent_entity_type = $$entity->get("entity_type")->toString();
+      $parent_entity_id = $$entity->get("entity_id")->toString();
+    }
+
+    // Get Permissions to update, delete current entity.
+    $permissions = \Drupal::service("graphql_compose_mutations.user_permissions");
+    $permissions_update = $permissions->userCanDoActionOnEntityByType(
+      "update",
+      $current_uid,
+      $entity_id,
+      $system_entity_type,
+      $system_entity_bundle,
+      $parent_entity_type,
+      $parent_entity_id
+    );
+    $permissions_delete = $permissions->userCanDoActionOnEntityByType(
+      "delete",
+      $current_uid,
+      $entity_id,
+      $system_entity_type,
+      $system_entity_bundle,
+      $parent_entity_type,
+      $parent_entity_id
+    );
+
+    $values = [
+      "system_entity_id" => $entity->id(),
+      "system_entity_type" => $system_entity_type,
+      "system_entity_bundle" => $system_entity_bundle,
+      "system_entity_bundle_label" => $system_entity_bundle_label,
+      "system_entity_user_is_owner" => $current_uid == $uid,
+      "system_entity_user_has_update_access" => $permissions_update["access"],
+      "system_entity_user_has_delete_access" => $permissions_delete["access"],
+    ];
+
+    $machine_names = graphql_compose_mutations_get_machine_names();
+    foreach ($machine_names as $machine_name) {
+      if ($field_name === $machine_name) {
+        $results = [$values[$machine_name]];
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_graphql_compose_entity_base_fields_alter().
+ */
+function graphql_compose_mutations_graphql_compose_entity_base_fields_alter(array &$fields, string $entity_type_id): void {
+  $machine_names = graphql_compose_mutations_get_machine_names();
+
+  foreach ($machine_names as $machine_name) {
+    $fields[$machine_name] = [
+      "required" => FALSE,
+    ];
+  }
+}
+
+/**
+ * Return the available machine names.
+ *
+ * @return array
+ *   The array of the fields.
+ */
+function graphql_compose_mutations_get_machine_names(): array {
+  return [
+    "system_entity_id",
+    "system_entity_type",
+    "system_entity_bundle",
+    "system_entity_bundle_label",
+    "system_entity_user_is_owner",
+    "system_entity_user_has_update_access",
+    "system_entity_user_has_delete_access",
+  ];
+}
diff --git a/modules/graphql_compose_mutations/graphql_compose_mutations.services.yml b/modules/graphql_compose_mutations/graphql_compose_mutations.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..04115f07df9805cf281559559b5e6ca0b61e9f9b
--- /dev/null
+++ b/modules/graphql_compose_mutations/graphql_compose_mutations.services.yml
@@ -0,0 +1,9 @@
+services:
+  graphql_compose_mutations.user_token_subscriber:
+    class: Drupal\graphql_compose_mutations\EventSubscriber\UserTokenInvalidationSubscriber
+    tags:
+      - { name: event_subscriber }
+  graphql_compose_mutations.user_permissions:
+    class: Drupal\graphql_compose_mutations\Services\UserPermissions
+    arguments:
+      [ '@module_handler','@current_user', '@entity_type.manager', '@logger.channel.graphql', '@entity_type.bundle.info' ]
diff --git a/modules/graphql_compose_mutations/src/EventSubscriber/UserTokenInvalidationSubscriber.php b/modules/graphql_compose_mutations/src/EventSubscriber/UserTokenInvalidationSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..b229423d5e2f362007ad8b46d9b1ebb61d422223
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/EventSubscriber/UserTokenInvalidationSubscriber.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\EventSubscriber;
+
+use Drupal\simple_oauth\Event\UserUpdateTokenInvalidationEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Event subscriber for user token invalidation events.
+ *
+ * Stolen from https://dgo.to/2946882#comment-15751851
+ */
+class UserTokenInvalidationSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      UserUpdateTokenInvalidationEvent::class => 'onUserUpdateTokenInvalidation',
+    ];
+  }
+
+  /**
+   * Reacts to the UserUpdateTokenInvalidationEvent.
+   *
+   * @param \Drupal\simple_oauth\Event\UserUpdateTokenInvalidationEvent $event
+   *   The event object.
+   */
+  public function onUserUpdateTokenInvalidation(UserUpdateTokenInvalidationEvent $event): void {
+    // TRUE if the user's roles, password or status has changed.
+    $shouldInvalidate = $event->haveUserAccessCharacteristicsChanged();
+    $event->setInvalidateAccessTokens($shouldInvalidate);
+
+    if ($shouldInvalidate) {
+      // Invalidate refresh tokens when access characteristics change.
+      $event->setInvalidateRefreshTokens(TRUE);
+    }
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/GraphQL/Response/GenericEntityResponse.php b/modules/graphql_compose_mutations/src/GraphQL/Response/GenericEntityResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..bfac409f4a56b35b8a6c9258c74663f1b96961cc
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/GraphQL/Response/GenericEntityResponse.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\GraphQL\Response;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\graphql\GraphQL\Response\Response;
+
+/**
+ * Type of response used when a generic entity is returned.
+ */
+class GenericEntityResponse extends Response {
+
+  /**
+   * The entity to be served.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|null
+   */
+  protected ?EntityInterface $genericEntity = NULL;
+
+  /**
+   * The success value to be served.
+   *
+   * @var bool
+   */
+  protected bool $success = FALSE;
+
+  /**
+   * Sets the entity data.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface|null $genericEntity
+   *   The entity to be served.
+   */
+  public function setGenericEntity(?EntityInterface $genericEntity): void {
+    $this->genericEntity = $genericEntity;
+  }
+
+  /**
+   * Gets the array values to be served.
+   *
+   * @return array|null
+   *   The array values to be served.
+   */
+  public function getGenericEntity(): ?array {
+    $entity = $this->genericEntity;
+    if ($entity) {
+      return [
+        "uuid" => $entity->uuid(),
+        "values" => $entity->toArray(),
+        "id" => $entity->id(),
+        "entity_type" => $entity->getEntityTypeId(),
+        "entity_bundle" => $entity->bundle(),
+      ];
+    }
+    return NULL;
+  }
+
+  /**
+   * Gets the success.
+   *
+   * @return bool
+   *   The success to be served.
+   */
+  public function getSuccess(): bool {
+    return $this->success;
+  }
+
+  /**
+   * Sets the success.
+   *
+   * @param bool $success
+   *   The success value.
+   */
+  public function setSuccess(bool $success): void {
+    $this->success = $success;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/GraphQL/Response/OperationsByEntityTypeResponse.php b/modules/graphql_compose_mutations/src/GraphQL/Response/OperationsByEntityTypeResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..7337efafc6d615c767761b5d9ab732584a278d04
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/GraphQL/Response/OperationsByEntityTypeResponse.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\GraphQL\Response;
+
+use Drupal\graphql\GraphQL\Response\Response;
+
+/**
+ * Type of response used when an OperationsByEntityType is returned.
+ */
+class OperationsByEntityTypeResponse extends Response {
+
+  /**
+   * The operations to be served.
+   *
+   * @var array|null
+   */
+  protected ?array $operations;
+
+  /**
+   * Sets the entity data.
+   *
+   * @param array|null $operations
+   *   The operations to be served.
+   */
+  public function setOperations(?array $operations): void {
+    $this->operations = $operations;
+  }
+
+  /**
+   * Gets the array values to be served.
+   *
+   * @return array|null
+   *   The array values to be served.
+   */
+  public function getOperations(): ?array {
+    return $this->operations ?? NULL;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/GraphQL/Response/PermissionsResponse.php b/modules/graphql_compose_mutations/src/GraphQL/Response/PermissionsResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0889a9dc64f64d1353710997e56460717948ece
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/GraphQL/Response/PermissionsResponse.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\GraphQL\Response;
+
+use Drupal\graphql\GraphQL\Response\Response;
+
+/**
+ * Type of response used when a permission is returned.
+ */
+class PermissionsResponse extends Response {
+  /**
+   * The access value to be served.
+   *
+   * @var bool
+   */
+  protected bool $access = FALSE;
+
+  /**
+   * Gets the access.
+   *
+   * @return bool
+   *   The access to be served.
+   */
+  public function getAccess(): bool {
+    return $this->access;
+  }
+
+  /**
+   * Sets the access.
+   *
+   * @param bool $access
+   *   The access value.
+   */
+  public function setAccess(bool $access): void {
+    $this->access = $access;
+  }
+
+  /**
+   * Sets the success value.
+   */
+  public function getSuccess(): bool {
+    return $this->access === TRUE;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/GenericMutationProducer.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/GenericMutationProducer.php
new file mode 100644
index 0000000000000000000000000000000000000000..e5be32d8bcfa7da1b0b9e81d889e9d8b840e75c6
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/GenericMutationProducer.php
@@ -0,0 +1,519 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\DataProducer;
+
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\EntityTypeBundleInfo;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Logger\LoggerChannelFactory;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\graphql\GraphQL\Execution\FieldContext;
+use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\GenericEntityResponse;
+use Drupal\graphql_compose_mutations\Services\UserPermissions;
+use Drupal\graphql_compose_mutations\Services\UserPermissionsInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use function Symfony\Component\String\u;
+
+/**
+ * This is a generic mutation class used by the SchemaExtension.
+ *
+ * @DataProducer(
+ *   id = "generic_mutation_producer",
+ *   name = @Translation("Mutation"),
+ *   description = @Translation("Generic entity mutation extension."),
+ *   produces = @ContextDefinition("any",
+ *     label = @Translation("Any"),
+ *   ),
+ *   consumes = {
+ *     "data" = @ContextDefinition("any",
+ *       label = @Translation("Entity related data"),
+ *     ),
+ *     "metadata" = @ContextDefinition("any",
+ *       label = @Translation("Generic mutation metadata"),
+ *     ),
+ *   },
+ * )
+ */
+class GenericMutationProducer extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected AccountInterface $currentUser;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected EntityTypeManagerInterface $entityTypeManager;
+
+  /**
+   * The entity type bundle info.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfo
+   */
+  protected EntityTypeBundleInfo $entityTypeBundleInfo;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected EntityFieldManagerInterface $entityFieldManager;
+
+  /**
+   * The Drupal logger factory service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelFactory
+   */
+  protected LoggerChannelFactory $loggerChannelFactory;
+
+  /**
+   * The current module user_permissions service.
+   *
+   * @var \Drupal\graphql_compose_mutations\Services\UserPermissionsInterface
+   */
+  protected UserPermissionsInterface $userPermissions;
+
+  /**
+   * Graphql conpose configuration to perform the fields mapping.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected ImmutableConfig $graphqlComposeConfiguration;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $instance = new self(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+    );
+
+    $instance->currentUser = $container->get('current_user');
+    $instance->entityTypeManager = $container->get('entity_type.manager');
+    $instance->entityTypeBundleInfo = $container->get('entity_type.bundle.info');
+    $instance->entityFieldManager = $container->get('entity_field.manager');
+    $instance->loggerChannelFactory = $container->get('logger.factory');
+    $instance->userPermissions = $container->get('graphql_compose_mutations.user_permissions');
+    $instance->graphqlComposeConfiguration = $container->get('config.factory')->get('graphql_compose.settings');
+
+    return $instance;
+  }
+
+  /**
+   * A reusable generic mutation.
+   *
+   * @param array $data
+   *   The mutation data.
+   * @param array $metadata
+   *   Several, entity related metadata required to create the mutation.
+   * @param \Drupal\graphql\GraphQL\Execution\FieldContext $field_context
+   *   The cache context.
+   *
+   * @return \Drupal\graphql_compose_mutations\GraphQL\Response\GenericEntityResponse|null
+   *   The updated or new Entity or a GraphQL response.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function resolve(array $data, array $metadata, FieldContext $field_context): ?GenericEntityResponse {
+    $response = new GenericEntityResponse();
+
+    // node, group, message, profile etc.
+    // @todo Limit allowed types from graphql_compose settings
+    // This is already done on .graphqls files.
+    $entity_type = strtolower($metadata["entity_type"]);
+    $entity_bundle = strtolower($metadata["entity_bundle"]);
+    $parent_entity_type = isset($metadata["parent_entity_type"]) ? strtolower($metadata["parent_entity_type"]) : NULL;
+    $parent_entity_id = isset($metadata["parent_entity_id"]) ? (int) $metadata["parent_entity_id"] : NULL;
+    $operation = strtolower($metadata["operation"]);
+    if ($operation === "default") {
+      $operation = "create";
+    }
+
+    $id = isset($metadata["id"]) ? (int) $metadata["id"] : NULL;
+    // Default user. We can override this on the $data input argument.
+    $user = $this->currentUser;
+    $uid = $user->id();
+    $entity = NULL;
+    $custom_uid = NULL;
+    $response->setSuccess(FALSE);
+    $final_values = [];
+    // @todo Check if there are other owner like fields on custom entity types.
+    $user_ids = [
+      "uid",
+      "user_id",
+    ];
+
+    // Add default uid to the data array and the metadata.
+    if (in_array($operation, ["update", "delete"]) && $entity_type === "user" && !$id) {
+      $id = $uid;
+    }
+
+    // Protect some entities from being deleted.
+    if ($operation === "delete" && in_array($entity_type, ["user", "profile"]) && (int) $id === 1) {
+      $protected_user = $this->t('User with uid @uid is protected', [
+        "@uid" => $uid,
+      ]);
+      $response->addViolation($protected_user);
+      return $response;
+    }
+
+    // Get parent entity (if we need it. E.g. for the votingapi entities).
+    if ($parent_entity_type && $parent_entity_id) {
+      $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+      if (!$parent_entity) {
+        $missing_parent_entity = $this->t('Parent entity with id: @id and type: @type does not exist', [
+          "@id" => $parent_entity_id,
+          "@type" => $parent_entity_type,
+        ]);
+        $response->addViolation($missing_parent_entity);
+        return $response;
+      }
+
+      // For some Mutations we can get some fields from metadata.
+      if ($entity_type === "vote") {
+        if (!isset($data["entity_type"]) && !isset($data["entity_id"])) {
+          $data["entity_type"] = $parent_entity_type;
+          $data["entity_id"] = $parent_entity_id;
+        }
+
+        if ($operation === "create" && !isset($data["value"])) {
+          $data["value"] = 1;
+        }
+      }
+    }
+
+    // Get custom uid from data values.
+    foreach ($user_ids as $user_id_field) {
+      foreach ($data as $field => $value) {
+        if ($field === $user_id_field) {
+          $custom_uid = (int) $value;
+        }
+      }
+    }
+
+    if ($custom_uid && $custom_uid != $uid) {
+      $uid = $custom_uid;
+
+      // Load another User.
+      $user = $this->entityTypeManager
+        ->getStorage("user")
+        ->load($uid);
+
+      if (!$user) {
+        $missing_user = $this->t('User with uid @uid does not exist', [
+          "@uid" => $uid,
+        ]);
+        $response->addViolation($missing_user);
+        return $response;
+      }
+    }
+
+    $revision_log = $this->t("GraphQL entry. Operation: @operation, Type: @entity_type, Bundle: @entity_bundle, Uid: @uid", [
+      '@operation' => $operation,
+      '@entity_type' => $entity_type,
+      '@entity_bundle' => $entity_bundle,
+      '@uid' => $uid,
+    ]);
+    $final_values["revision_log"] = $revision_log;
+
+    // Check entity bundle is valid.
+    $entity_bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type));
+
+    if (!$entity_bundle or !in_array($entity_bundle, $entity_bundles)) {
+      $entity_bundles_error = $this->t('Entity bundle is required or is not correct. Please use one of @entity_bundles', [
+        "@entity_bundles" => implode(", ", $entity_bundles),
+      ]);
+      $response->addViolation($entity_bundles_error);
+      return $response;
+    }
+
+    // Check permissions early. We use a Service for that.
+    // This also checks if there is an existing entity.
+    $access = $this->userPermissions->userCanDoActionOnEntityByType(
+      $operation,
+      $user->id(),
+      $id,
+      $entity_type,
+      $entity_bundle,
+      $parent_entity_type,
+      $parent_entity_id,
+    );
+
+    if ($access["access"] === FALSE) {
+      $access_error = $access["error"] ?? $this->t('GenericMutationProducer: You do not have permission to perform this operation.');
+      $response->addViolation($access_error);
+      return $response;
+    }
+
+    if ($operation !== "create") {
+      $entity = $this->entityTypeManager
+        ->getStorage($entity_type)
+        ->load($id);
+    }
+
+    // @todo Should we add the line below? Or we need an object as parameter?
+    //   $field_context->addCacheableDependency($access);
+    // Delete operation (does not need values) but may have errors.
+    if ($operation === "delete") {
+      try {
+        $delete_status = $entity->delete();
+      }
+      catch (\Exception $e) {
+        $message = $e->getMessage();
+        $response->addViolation($message);
+        return $response;
+      }
+
+      if ($delete_status instanceof EntityStorageException) {
+        $delete_error = $delete_status->getMessage();
+        $response->addViolation($delete_error);
+      }
+      else {
+        $response->setSuccess(TRUE);
+      }
+
+      $response->setGenericEntity(NULL);
+      return $response;
+    }
+
+    // For non delete operations check additional values exist on input.
+    if (empty($data)) {
+      // If we have no entity values throw an error.
+      $missing_data = $this->t('Missing data values.');
+      $response->addViolation($missing_data);
+      return $response;
+    }
+
+    // Final fields mapping
+    // Get all the entity fields for this bundle.
+    $entity_fields = [];
+    $read_only_fields_to_keep = [
+      "system_entity_is_from_api",
+    ];
+    $entity_fields_all = $this->entityFieldManager->getFieldDefinitions($entity_type, $entity_bundle);
+    // Remove computed fields from graphql_compose_* modules.
+    foreach ($entity_fields_all as $field_name => $field) {
+      $field_array = $field->toArray();
+      $provider = $field_array["provider"] ?? "";
+      $is_computed = $field_array["computed"] ?? FALSE;
+      $is_graphql_compose = $provider && str_starts_with($provider, "graphql_compose");
+      if (!($is_computed && $is_graphql_compose)) {
+        $entity_fields[$field_name] = $field;
+      }
+      // Exceptions for some special read only fields.
+      if (in_array($field_name, $read_only_fields_to_keep)) {
+        $entity_fields[$field_name] = $field;
+      }
+    }
+    $entity_fields_keys = array_keys($entity_fields);
+
+    $fields_mapping = $this->getFieldsMapping($entity_type, $entity_bundle);
+
+    // Check if $data contains "bad" fields and throw an error.
+    foreach ($data as $field => $value) {
+      if (!in_array($field, $entity_fields_keys) && !in_array($field, $fields_mapping)) {
+        $bad_field = $this->t('Field "@field" is not valid key for data.', [
+          "@field" => $field,
+        ]);
+        $response->addViolation($bad_field);
+        return $response;
+      }
+    }
+
+
+    // @todo Validate complex or composite values (e.g. entity_reference)
+    // @todo Find a way to save the mapping to a config yml
+    // E.g. the body field needs ["value" => "", "format" => "basic_html"].
+    // @codingStandards
+    foreach ($entity_fields_keys as $machine_name) {
+      $field_name = $fields_mapping[$machine_name] ?? $machine_name;
+
+      // Do not process computed fields.
+      if (!empty($data[$field_name])) {
+        $value = $data[$field_name];
+        if (!is_array($value)) {
+          $value = Xss::filter($value);
+        }
+        $final_values[$machine_name] = $value;
+      }
+    }
+
+    // Special treatment with the uid. Some entity types have another field.
+    if (in_array("uid", $entity_fields_keys) && $operation === "create") {
+      $final_values["uid"] = $uid;
+    }
+    if (in_array("user_id", $entity_fields_keys)) {
+      $final_values["user_id"] = $uid;
+    }
+    else {
+      unset($final_values["user_id"]);
+    }
+
+    // Clean up manual added values.
+    if (!isset($entity_fields["revision_log"])) {
+      unset($final_values["revision_log"]);
+    }
+
+    try {
+      if ($operation === "create") {
+        $entity = $this->createEntity($entity_type, $entity_bundle, $final_values);
+      }
+      else {
+        if ($entity instanceof ContentEntityInterface) {
+          $this->updateEntityValues($entity, $final_values);
+        }
+      }
+    }
+    catch (\Exception $e) {
+      $message = $e->getMessage();
+      $response->addViolation($message);
+      return $response;
+    }
+
+    // Validate entity.
+    if ($entity instanceof ContentEntityInterface) {
+      $violations = $entity->validate();
+      if ($violations->count() > 0) {
+        /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
+        foreach ($violations as $violation) {
+          $response->addViolation($violation->getMessage(), ['property' => $violation->getPropertyPath()]);
+        }
+        return $response;
+      }
+    }
+
+    try {
+      $save_status = $entity->save();
+    }
+    catch (\Exception $e) {
+      $message = $e->getMessage();
+      $response->addViolation($message);
+      return $response;
+    }
+
+    // If an error occurred (wrong values etc) we do not get Int values.
+    // Type "private_message_thread" returns NULL on UPDATE action.
+    // @todo May need to provide a patch on the module private_message itself.
+    if ($entity_type === "private_message_thread") {
+      if ($operation !== "create") {
+        $entity = $this->entityTypeManager
+          ->getStorage($entity_type)
+          ->load($id);
+        if (!$entity) {
+          $save_error = "Error on entity Save for " . $revision_log;
+          $this->loggerChannelFactory->get("graphql_compose_mutations")
+            ->error($save_error);
+          $response->addViolation($save_error);
+          return $response;
+        }
+      }
+      else {
+        if ($entity->id()) {
+          // Manual set $save_status to 1 for the new private_message_thread.
+          $save_status = 1;
+        }
+      }
+    }
+    elseif ($save_status !== 1 && $save_status !== 2) {
+      // 1: SAVED_NEW, 2: SAVED_UPDATED.
+      $save_error = "Wrong Save status number: " . $save_status . " on entity Save for " . $revision_log;
+      $this->loggerChannelFactory->get("graphql_compose_mutations")->error($save_error);
+      $response->addViolation($save_error);
+      return $response;
+    }
+
+    // @todo Should we add cache context for the entity?
+    //   $field_context->addCacheableDependency($entity);
+    // Pass results to the response.
+    $response->setGenericEntity($entity);
+    if (empty($response->getViolations())) {
+      $response->setSuccess(TRUE);
+    }
+
+    return $response;
+  }
+
+  /**
+   * Create the entity.
+   *
+   * @param string $entity_type
+   *   Entity type.
+   * @param string $entity_bundle
+   *   Entity bundle.
+   * @param array $values
+   *   Values.
+   */
+  protected function createEntity($entity_type, $entity_bundle, $values) {
+
+    // Some entity types do not use "type" as the bundle machine name.
+    // We need to get this through the entityTypeManager.
+    $storage = $this->entityTypeManager->getStorage($entity_type);
+    $type_keys = $storage->getEntityType()->getKeys();
+    $type_machine_name = $type_keys["bundle"] ?? "";
+
+    return $storage->create([$type_machine_name => $entity_bundle] + $values);
+  }
+
+  /**
+   * Update the values of an existing entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   Entity.
+   * @param array $values
+   *   Values.
+   */
+  protected function updateEntityValues(ContentEntityInterface $entity, array $values) {
+    // Add extra values and save the entity.
+    foreach ($values as $machine_name => $value) {
+      $entity->set($machine_name, $value);
+    }
+  }
+
+  /**
+   * Gets the field map between graphql api names and machine names.
+   *
+   * @param string $entity_type
+   *   Entity type.
+   * @param string $entity_bundle
+   *   Entity bundle.
+   *
+   * @return array
+   *   They key is the name_sdl, and the value is the machine name.
+   */
+  protected function getFieldsMapping(string $entity_type, string $entity_bundle) {
+    if ($this->graphqlComposeConfiguration->get('entity_config.' . $entity_type . '.' . $entity_bundle . '.enabled')) {
+      $fields_config = $this->graphqlComposeConfiguration->get('field_config.' . $entity_type . '.' . $entity_bundle) ?? [];
+      $fields_mapping = [];
+      foreach ($fields_config as $field_name => $configuration) {
+        $fields_mapping[$field_name] = !empty($configuration['enabled']) ? $configuration['name_sdl'] ?? u($field_name)
+          ->trimPrefix('field_')
+          ->camel()
+          ->toString() : $field_name;
+      }
+    }
+    else {
+      $fields_mapping = [];
+    }
+
+    return $fields_mapping;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/OperationsByEntityTypeProducer.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/OperationsByEntityTypeProducer.php
new file mode 100644
index 0000000000000000000000000000000000000000..06ee386e36bc4d989baaf2c63d6e74fccc14024b
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/OperationsByEntityTypeProducer.php
@@ -0,0 +1,157 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\DataProducer;
+
+use Drupal\Core\Entity\EntityTypeBundleInfo;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\OperationsByEntityTypeResponse;
+use Drupal\graphql_compose_mutations\Services\UserPermissions;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * This is a helper class that returns the allowed operations by entity type.
+ *
+ * @DataProducer(
+ *   id = "operations_by_entity_type_producer",
+ *   name = @Translation("Operations by Entity Type"),
+ *   description = @Translation("a helper class that returns allowed operations by entity type."),
+ *   produces = @ContextDefinition("any",
+ *     label = @Translation("Any"),
+ *   ),
+ *   consumes = {
+ *     "entity_type" = @ContextDefinition("any",
+ *       label = @Translation("Entity type"),
+ *     ),
+ *   },
+ * )
+ */
+class OperationsByEntityTypeProducer extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected AccountInterface $currentUser;
+
+  /**
+   * The entity type bundle info.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfo
+   */
+  protected EntityTypeBundleInfo $entityTypeBundleInfo;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected EntityTypeManagerInterface $entityTypeManager;
+
+  /**
+   * The current module user_permissions service.
+   *
+   * @var \Drupal\graphql_compose_mutations\Services\UserPermissions
+   */
+  protected UserPermissions $userPermissions;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $instance = new self(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+    );
+
+    $instance->currentUser = $container->get('current_user');
+    $instance->entityTypeBundleInfo = $container->get('entity_type.bundle.info');
+    $instance->entityTypeManager = $container->get('entity_type.manager');
+    $instance->userPermissions = $container->get('graphql_compose_mutations.user_permissions');
+
+    return $instance;
+  }
+
+  /**
+   * A query to return allowed operations by entity type.
+   *
+   * @param string $entity_type
+   *   The entity_type.
+   *
+   * @return \Drupal\graphql_compose_mutations\GraphQL\Response\OperationsByEntityTypeResponse|null
+   *   The OperationsByEntityType response.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function resolve(string $entity_type): ?OperationsByEntityTypeResponse {
+    $response = new OperationsByEntityTypeResponse();
+    $results = [];
+    $user = $this->currentUser;
+    $uid = $user->id();
+    $entity_type = strtolower($entity_type);
+    // Some entity types do not have bundles. So they have the type as bundle.
+    $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type) ?? [$entity_type];
+    // @todo add more operations if needed.
+    $operations = [
+      "create",
+    ];
+
+    if (!$entity_type) {
+      $entity_type_error = $this->t('Entity type is required.');
+      $response->addViolation($entity_type_error);
+      return $response;
+    }
+
+    foreach ($bundles as $bundle_key => $bundle_values) {
+      $bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type)->getBundleEntityType();
+      $bundle_description = "";
+      $bundle_label = $bundle_key;
+
+      if ($bundle_entity_type) {
+        $bundle_definition = $this->entityTypeManager
+          ->getStorage($bundle_entity_type)
+          ->load($bundle_key);
+        $bundle_description = $bundle_definition->get("description");
+        $bundle_label = $bundle_definition->label();
+      }
+
+      foreach ($operations as $operation) {
+        $label = $operation . " " . $bundle_label;
+        if ($operation === "create") {
+          $label = "New " . $bundle_label;
+        }
+        // Important. The "create" operation does not need an entity_id.
+        // But we should make it more generic for other operations.
+        // In same cases it needs the Parent entity.
+        $access = $this->userPermissions->userCanDoActionOnEntityByType(
+          $operation,
+          $uid,
+          "",
+          $entity_type,
+          (string) $bundle_key,
+          "",
+          ""
+        );
+        $results[$entity_type][$bundle_key][$operation] = [
+          "access" => $access["access"],
+          "label" => $label,
+          "description" => $bundle_description,
+        ];
+      }
+    }
+
+    if (empty($response->getViolations())) {
+      $response->setOperations($results);
+    }
+
+    return $response;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/PermissionsProducer.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/PermissionsProducer.php
new file mode 100644
index 0000000000000000000000000000000000000000..8aa4059dd5fe436ef9d9502ff2389de70f3b73a3
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/DataProducer/PermissionsProducer.php
@@ -0,0 +1,174 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\DataProducer;
+
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Logger\LoggerChannelFactory;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\PermissionsResponse;
+use Drupal\graphql_compose_mutations\Services\UserPermissions;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * This is a helper class that checks for User operations.
+ *
+ * @DataProducer(
+ *   id = "permissions_producer",
+ *   name = @Translation("Permissions"),
+ *   description = @Translation("a helper class that checks for user operations."),
+ *   produces = @ContextDefinition("any",
+ *     label = @Translation("Any"),
+ *   ),
+ *   consumes = {
+ *     "operation" = @ContextDefinition("any",
+ *       label = @Translation("Operation"),
+ *     ),
+ *     "uid" = @ContextDefinition("any",
+ *       label = @Translation("The User uid"),
+ *     ),
+ *     "entity_id" = @ContextDefinition("any",
+ *       label = @Translation("Entity ID"),
+ *       required = FALSE,
+ *     ),
+ *     "entity_type" = @ContextDefinition("any",
+ *       label = @Translation("Entity type"),
+ *     ),
+ *     "entity_bundle" = @ContextDefinition("any",
+ *       label = @Translation("Entity bundle"),
+ *     ),
+ *     "parent_entity_type" = @ContextDefinition("any",
+ *       label = @Translation("Parent Entity type (optional)"),
+ *       default_value = "",
+ *       required = FALSE,
+ *     ),
+ *     "parent_entity_id" = @ContextDefinition("any",
+ *       label = @Translation("Parent Entity ID (optional)"),
+ *       default_value = "",
+ *       required = FALSE,
+ *     ),
+ *   },
+ * )
+ */
+class PermissionsProducer extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The current module user_permissions service.
+   *
+   * @var \Drupal\graphql_compose_mutations\Services\UserPermissions
+   */
+  protected UserPermissions $userPermissions;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected EntityTypeManagerInterface $entityTypeManager;
+
+  /**
+   * The Drupal logger factory service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelFactory
+   */
+  protected LoggerChannelFactory $loggerChannelFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $instance = new self(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+    );
+
+    $instance->userPermissions = $container->get('graphql_compose_mutations.user_permissions');
+    $instance->entityTypeManager = $container->get('entity_type.manager');
+    $instance->loggerChannelFactory = $container->get('logger.factory');
+
+    return $instance;
+  }
+
+  /**
+   * A query to check for operation permissions by uid.
+   *
+   * @return \Drupal\graphql_compose_mutations\GraphQL\Response\PermissionsResponse|null
+   *   The access Permissions response.
+   */
+  public function resolve(
+    string $operation,
+    mixed $uid,
+    mixed $entity_id,
+    string $entity_type,
+    string $entity_bundle,
+    mixed $parent_entity_type,
+    mixed $parent_entity_id,
+  ): ?PermissionsResponse {
+    $response = new PermissionsResponse();
+    $response->setAccess(FALSE);
+
+    $entity_type = strtolower($entity_type);
+    $entity_bundle = strtolower($entity_bundle);
+    $operation = strtolower($operation);
+
+    $parent_entity_type = $parent_entity_type ? strtolower($parent_entity_type) : NULL;
+    $parent_entity_id = $parent_entity_id ? (int) $parent_entity_id : NULL;
+
+    if ($parent_entity_type && $parent_entity_id) {
+      try {
+        $parent = $this->entityTypeManager->getStorage($parent_entity_type)
+          ->load($parent_entity_id);
+        if (!$parent) {
+          $missing_parent_entity = $this->t('Parent entity with id @uid and type @type does not exist', [
+            "@id" => $parent_entity_id,
+            "@type" => $parent_entity_type,
+          ]);
+          $response->addViolation($missing_parent_entity);
+          return $response;
+        }
+      }
+      catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+        $message = $e->getMessage();
+        $this->loggerChannelFactory->get("graphql_compose_mutations")->error($message);
+        $response->addViolation($message);
+        return $response;
+      }
+    }
+
+    // Get access array from Service.
+    try {
+      $access = $this->userPermissions->userCanDoActionOnEntityByType(
+        $operation,
+        $uid,
+        $entity_id,
+        $entity_type,
+        $entity_bundle,
+        $parent_entity_type,
+        $parent_entity_id,
+      );
+
+      // Disallowed.
+      if ($access["access"] === FALSE && isset($access["error"])) {
+        $access_error = $access["error"];
+        $response->addViolation($access_error);
+        return $response;
+      }
+
+      $response->setAccess($access["access"]);
+    }
+    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+      $message = $e->getMessage();
+      $this->loggerChannelFactory->get("graphql_compose_mutations")->error($message);
+      $response->addViolation($message);
+      return $response;
+    }
+
+    return $response;
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/GenericMutationExtension.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/GenericMutationExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..91c0c19bd4f71736b6315dfeabde66082f861fcd
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/GenericMutationExtension.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\SchemaExtension;
+
+use Drupal\graphql\GraphQL\ResolverBuilder;
+use Drupal\graphql\GraphQL\ResolverRegistryInterface;
+use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\GenericEntityResponse;
+
+/**
+ * Generic Mutation schema.
+ *
+ * @SchemaExtension(
+ *   id = "generic_mutation",
+ *   name = "A generic mutation extension",
+ *   description = "A simple extension to mutate any entity.",
+ *   schema = "graphql_compose"
+ * )
+ */
+class GenericMutationExtension extends SdlSchemaExtensionPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function registerResolvers(ResolverRegistryInterface $registry): void {
+    $builder = new ResolverBuilder();
+
+    $registry->addFieldResolver('GenericEntityResponse', 'errors',
+      $builder->callback(function (GenericEntityResponse $response) {
+        return $response->getViolations();
+      })
+    );
+
+    $registry->addFieldResolver('GenericEntityResponse', 'success',
+      $builder->callback(function (GenericEntityResponse $response) {
+        return $response->getSuccess();
+      })
+    );
+
+    $registry->addFieldResolver('GenericEntityResponse', 'entity',
+      $builder->callback(function (GenericEntityResponse $response) {
+        return $response->getGenericEntity();
+      })
+    );
+
+    $registry->addFieldResolver(
+      'Mutation',
+      'genericMutation',
+      $builder->produce('generic_mutation_producer')
+        ->map('data', $builder->fromArgument('data'))
+        ->map('metadata', $builder->fromArgument('metadata'))
+    );
+
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/OperationsByEntityTypeExtension.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/OperationsByEntityTypeExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..376b95d55a8d4ba37fa695993f3236335d329d07
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/OperationsByEntityTypeExtension.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\SchemaExtension;
+
+use Drupal\graphql\GraphQL\ResolverBuilder;
+use Drupal\graphql\GraphQL\ResolverRegistryInterface;
+use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\OperationsByEntityTypeResponse;
+
+/**
+ * Generic Mutation schema.
+ *
+ * @SchemaExtension(
+ *   id = "operations_by_entity_type",
+ *   name = "Get operations by entity type",
+ *   description = "A query to get operations by entity type extension.",
+ *   schema = "graphql_compose"
+ * )
+ */
+class OperationsByEntityTypeExtension extends SdlSchemaExtensionPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function registerResolvers(ResolverRegistryInterface $registry): void {
+    $builder = new ResolverBuilder();
+
+    $registry->addFieldResolver('OperationsByEntityTypeResponse', 'errors',
+      $builder->callback(function (OperationsByEntityTypeResponse $response) {
+        return $response->getViolations();
+      })
+    );
+
+    $registry->addFieldResolver('OperationsByEntityTypeResponse', 'operations',
+      $builder->callback(function (OperationsByEntityTypeResponse $response) {
+        return $response->getOperations();
+      })
+    );
+
+    $registry->addFieldResolver(
+      'Query',
+      'operationsByEntityType',
+      $builder->produce('operations_by_entity_type_producer')
+        ->map('entity_type', $builder->fromArgument('entity_type'))
+        ->map('parent_entity_type', $builder->fromArgument('parent_entity_type'))
+        ->map('parent_entity_id', $builder->fromArgument('parent_entity_id'))
+    );
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/PermissionsExtension.php b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/PermissionsExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..54feb403e6ce031e5d2a444a7ee6014d0a43424c
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Plugin/GraphQL/SchemaExtension/PermissionsExtension.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\Plugin\GraphQL\SchemaExtension;
+
+use Drupal\graphql\GraphQL\ResolverBuilder;
+use Drupal\graphql\GraphQL\ResolverRegistryInterface;
+use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
+use Drupal\graphql_compose_mutations\GraphQL\Response\PermissionsResponse;
+
+/**
+ * Permissions Query schema.
+ *
+ * @SchemaExtension(
+ *   id = "permissions",
+ *   name = "A permissions query extension",
+ *   description = "A simple extension to get permissions.",
+ *   schema = "graphql_compose"
+ * )
+ */
+class PermissionsExtension extends SdlSchemaExtensionPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function registerResolvers(ResolverRegistryInterface $registry): void {
+    $builder = new ResolverBuilder();
+
+    $registry->addFieldResolver('PermissionsResponse', 'errors',
+      $builder->callback(function (PermissionsResponse $response) {
+        return $response->getViolations();
+      })
+    );
+
+    $registry->addFieldResolver('PermissionsResponse', 'success',
+      $builder->callback(function (PermissionsResponse $response) {
+        return $response->getSuccess();
+      })
+    );
+
+    $registry->addFieldResolver(
+      'Query',
+      'permissions',
+      $builder->produce('permissions_producer')
+        ->map('operation', $builder->fromArgument('operation'))
+        ->map('uid', $builder->fromArgument('uid'))
+        ->map('entity_id', $builder->fromArgument('entity_id'))
+        ->map('entity_type', $builder->fromArgument('entity_type'))
+        ->map('entity_bundle', $builder->fromArgument('entity_bundle'))
+        ->map('parent_entity_type', $builder->fromArgument('parent_entity_type'))
+        ->map('parent_entity_id', $builder->fromArgument('parent_entity_id'))
+    );
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Services/UserPermissions.php b/modules/graphql_compose_mutations/src/Services/UserPermissions.php
new file mode 100644
index 0000000000000000000000000000000000000000..402e228a34cf9611e0376bcbdc39101d91845ce4
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Services/UserPermissions.php
@@ -0,0 +1,655 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\Services;
+
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\EntityTypeBundleInfo;
+use Drupal\Core\Entity\EntityTypeManager;
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Core\Logger\LoggerChannel;
+use Drupal\Core\Logger\LoggerChannelInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\node\NodeInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * A helper service to get User permissions by operation.
+ */
+class UserPermissions implements UserPermissionsInterface {
+  /**
+   * The User operations allowed to check for.
+   *
+   * @var array
+   *
+   * @todo This is a duplicate of enum OperationExtended at generic_mutation.base.graphqls
+   * @todo Maybe we could use the existing GraphQL data producer "entity_access".
+   * @todo Add more non-core operation verbs: "like, dislike, flag, unflag, enroll, comment, message etc"
+   */
+  public array $operations = [
+    // Core operations.
+    "view",
+    "create",
+    "update",
+    "delete",
+  ];
+
+  /**
+   * The ModuleHandler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandler
+   */
+  protected ModuleHandler $moduleHandler;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected AccountInterface $currentUser;
+
+  /**
+   * Drupal\Core\Entity\EntityTypeManager definition.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManager
+   */
+  protected EntityTypeManager $entityTypeManager;
+
+  /**
+   * Drupal\Core\Entity\EntityTypeBundleInfo definition.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfo
+   */
+  protected EntityTypeBundleInfo $entityTypeBundleInfo;
+
+  /**
+   * The logger service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected LoggerChannelInterface $logger;
+
+  /**
+   * The Service constructor.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
+   *   The ModuleHandler service.
+   * @param \Drupal\Core\Session\AccountInterface $currentUser
+   *   The current user.
+   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
+   *   The entity_type.manager service.
+   * @param \Drupal\Core\Logger\LoggerChannel $logger
+   *   The logger.channel.graphql service.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entityTypeBundleInfo
+   *   The entity_type.manager service.
+   */
+  public function __construct(
+    ModuleHandler $moduleHandler,
+    AccountInterface $currentUser,
+    EntityTypeManager $entityTypeManager,
+    LoggerChannel $logger,
+    EntityTypeBundleInfo $entityTypeBundleInfo,
+  ) {
+    // Get required services.
+    $this->moduleHandler = $moduleHandler;
+    $this->currentUser = $currentUser;
+    $this->entityTypeManager = $entityTypeManager;
+    $this->logger = $logger;
+    $this->entityTypeBundleInfo = $entityTypeBundleInfo;
+  }
+
+  /**
+   * Checks if an entity type (e.g. node, taxonomy_term) exists.
+   *
+   * @param string $entity_type
+   *   The entity type to check for.
+   *
+   * @return bool
+   *   Return if entity type exists.
+   */
+  private function entityTypeExists(string $entity_type):bool {
+    $entity_types = $this->entityTypeBundleInfo->getAllBundleInfo();
+    $entity_type_keys = array_keys($entity_types);
+
+    return in_array($entity_type, $entity_type_keys);
+  }
+
+  /**
+   * Checks if an entity bundle (e.g. event, page of type Node) exists.
+   *
+   * @param string $entity_type
+   *   The entity type to check for.
+   * @param string $entity_bundle
+   *   The entity bundle to check for.
+   *
+   * @return bool
+   *   Return if entity bundle of this type exists.
+   */
+  private function entityBundleOfTypeExists(string $entity_type, string $entity_bundle):bool {
+    $entity_bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
+    $entity_bundles_keys = array_keys($entity_bundles);
+
+    return in_array($entity_bundle, $entity_bundles_keys);
+  }
+
+  /**
+   * Get generic permissions manually added. This is a helper method.
+   *
+   * @param string $entity_bundle
+   *   The entity bundle to get the generic permissions for.
+   *
+   * @return array
+   *   Array of manually added permissions.
+   */
+  private function getGenericPermissionsByEntityBundle(string $entity_bundle): array {
+    // Unfortunately, modules do not use a pattern for permissions.
+    // We have to generate this list manually...
+    // @todo only add permissions if entity types exist.
+    $permissions = [
+      "comment" => [
+        "administer comments",
+        "post comments",
+        "create " . $entity_bundle . " comment",
+      ],
+      "contact_message" => [
+        "administer contact forms",
+        "access site-wide contact form",
+      ],
+      "message" => [
+        // @todo This is a system entity so there are no other permissions.
+        "administer messages",
+      ],
+      "node" => [
+        "administer nodes",
+        "bypass node access",
+        "create " . $entity_bundle . " content",
+      ],
+      "group" => [
+        "administer group",
+        "bypass create group access",
+        "bypass group access",
+        "manage all groups",
+        "create " . $entity_bundle . " group",
+      ],
+      "private_message" => [
+        "administer private messages",
+        "use private messaging system",
+      ],
+      "profile" => [
+        "administer profile",
+        "create " . $entity_bundle . " profile",
+      ],
+      "taxonomy_term" => [
+        "administer taxonomy",
+        "create terms in " . $entity_bundle,
+      ],
+    ];
+
+    // Module flag.
+    if ($this->moduleHandler->moduleExists('flag')) {
+      $permissions['flagging'] = [
+        "flag " . $entity_bundle,
+        "unflag " . $entity_bundle,
+      ];
+    }
+
+    // Module contact_storage.
+    if ($this->moduleHandler->moduleExists('contact_storage')) {
+      $permissions['contact_storage'] = [
+        "administer contact forms",
+        "access site-wide contact form",
+      ];
+    }
+
+    // Open Social additional conditions.
+    if ($this->moduleHandler->moduleExists('activity_creator')) {
+      $permissions['activity'] = [
+        "administer activity entities",
+        "add activity entities",
+      ];
+    }
+
+    if ($this->moduleHandler->moduleExists('social_event')) {
+      $permissions['event_enrollment'] = [
+        "administer event enrollment entities",
+        "add event enrollment entities",
+      ];
+    }
+
+    if ($this->moduleHandler->moduleExists('social_private_message')) {
+      $permissions['private_message'][] = "create private messages thread";
+    }
+
+    if ($this->moduleHandler->moduleExists('social_post')) {
+      $permissions['post'] = [
+        "administer post entities",
+        "add post entities",
+        "add " . $entity_bundle . " post entities",
+      ];
+    }
+
+    ksort($permissions);
+
+    return $permissions;
+  }
+
+  /**
+   * Load user from uid. We use the entityTypeManager for dependency injection.
+   *
+   * @param mixed $uid
+   *   The uid.
+   *
+   * @return \Drupal\user\UserInterface|null
+   *   The final User object.
+   */
+  private function loadAccountFromUid(mixed $uid): ?UserInterface {
+    try {
+      $user_storage = $this->entityTypeManager->getStorage("user");
+      $account = $user_storage->load($uid);
+      if ($account instanceof UserInterface) {
+        return $account;
+      }
+    }
+    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+      $this->logger->error($e->getMessage());
+    }
+    return NULL;
+  }
+
+  /**
+   * Check if a user (loaded from uid) has a permission to CREATE.
+   *
+   * @param mixed $uid
+   *   The uid.
+   * @param string $entity_type
+   *   The entity type (node, post, taxonomy_term, etc)
+   * @param string $entity_bundle
+   *   The entity bundle.
+   * @param mixed $parent_entity_type
+   *   The parent entity type that may be related to this operation. E.g. for
+   *   vote entity types this may be the Node or Comment we are voting for.
+   * @param mixed $parent_entity_id
+   *   The parent entity ID. See above.
+   *
+   * @return bool
+   *   Return final access.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function userCanCreateEntityByType(
+    mixed $uid,
+    string $entity_type,
+    string $entity_bundle,
+    mixed $parent_entity_type,
+    mixed $parent_entity_id,
+  ):bool {
+    $final_access = FALSE;
+    $account = $this->loadAccountFromUid($uid);
+
+    if ($entity_type === "group_content") {
+      if (!$parent_entity_type || !$parent_entity_id) {
+        return FALSE;
+      }
+      $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+      // phpcs:disable
+      if ($parent_entity instanceof \Drupal\group\Entity\GroupInterface) {
+        // phpcs:enable
+        /** @var \Drupal\group\Entity\GroupInterface $parent_entity */
+        // If this is a try to CREATE a new Group Membership.
+        if (str_contains($entity_bundle, "-group_membership")) {
+          // @todo Check field allow_request value also.
+          $owner_id = $parent_entity->getOwner()->id();
+          // This is the owner already.
+          if ((int) $owner_id === (int) $uid) {
+            return FALSE;
+          }
+
+          $group_content = $this->entityTypeManager
+            ->getStorage("group_content")
+            ->loadByProperties([
+              "type" => $entity_bundle,
+              "gid" => $parent_entity_id,
+              "entity_id" => $uid,
+            ]);
+
+          // There is already a Group Membership. Skip.
+          if ($group_content) {
+            return FALSE;
+          }
+
+          return TRUE;
+        }
+
+        // @todo What are the Permissions by Group for the other bundles?
+        // @todo Maybe there is a more dedicated Service to handle this.
+        return TRUE;
+      }
+      else {
+        return FALSE;
+      }
+    }
+
+    if ($entity_type === "event_enrollment") {
+      if ($this->moduleHandler->moduleExists('social_event')) {
+        if (!$parent_entity_type || !$parent_entity_id) {
+          return FALSE;
+        }
+        $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+        if (!$parent_entity instanceof NodeInterface) {
+          return FALSE;
+        }
+        else {
+          /** @var \Drupal\social_event\Entity\Node\Event $parent_entity */
+          $event_is_enroll_enabled = $parent_entity->isEnrollmentEnabled();
+          if (!$event_is_enroll_enabled) {
+            return FALSE;
+          }
+
+          $event_finished = $this->socialEventHasBeenFinished($parent_entity);
+          if ($event_finished) {
+            return FALSE;
+          }
+
+          // Get enrollment for this Parent entity and this User.
+          $enrollment = $this->entityTypeManager
+            ->getStorage("event_enrollment")
+            ->loadByProperties([
+              "field_event" => $parent_entity_id,
+              "field_account" => $uid,
+            ]);
+          // Already enrolled to the Event.
+          if ($enrollment) {
+            return FALSE;
+          }
+        }
+      }
+    }
+
+    if ($entity_type === "vote") {
+      if (!$parent_entity_type || !$parent_entity_id) {
+        return FALSE;
+      }
+      $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+      return like_and_dislike_can_vote($account, $entity_bundle, $parent_entity);
+    }
+
+    if ($entity_type === "flagging") {
+      if (!$parent_entity_type || !$parent_entity_id) {
+        return FALSE;
+      }
+      $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+      if (!$parent_entity) {
+        return FALSE;
+      }
+      /** @var \Drupal\Core\Entity\EntityInterface $parent_entity */
+      $flagging_entity = $this->entityTypeManager->getStorage($entity_type)
+        ->loadByProperties([
+          "flag_id" => $entity_bundle,
+          "uid" => $uid,
+          "entity_type" => $parent_entity_type,
+          "entity_id" => $parent_entity_id,
+        ]);
+
+      if ($flagging_entity) {
+        return FALSE;
+      }
+    }
+
+    if ((int) $uid === 1) {
+      return TRUE;
+    }
+
+    $roles = $account->getRoles();
+    foreach ($roles as $role_id) {
+      /** @var \Drupal\user\Entity\Role $role */
+      $role = $this->entityTypeManager->getStorage("user_role")->load($role_id);
+      if ($role->isAdmin()) {
+        return TRUE;
+      }
+    }
+
+    // Core entity access calculations.
+    $access_handler = $this->entityTypeManager->getAccessControlHandler($entity_type);
+    $access_result = (bool) $access_handler->createAccess($entity_bundle, $account);
+    if ($access_result) {
+      return TRUE;
+    }
+
+    // Final, manual added access checks.
+    $create_permissions = $this->getGenericPermissionsByEntityBundle($entity_bundle);
+    if (isset($create_permissions[$entity_type])) {
+      foreach ($create_permissions[$entity_type] as $permission) {
+        $access = $account->hasPermission($permission);
+        if ($access === TRUE) {
+          $final_access = TRUE;
+        }
+      }
+    }
+
+    return $final_access;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function userCanDoActionOnEntityByType(
+    string $operation,
+    mixed $uid,
+    mixed $entity_id,
+    string $entity_type,
+    mixed $entity_bundle,
+    mixed $parent_entity_type,
+    mixed $parent_entity_id,
+  ):array {
+    $current_user = $this->currentUser;
+    $final_access = FALSE;
+    $account = $this->loadAccountFromUid($uid);
+    $operations = $this->operations;
+    $entity_type_exists = $this->entityTypeExists($entity_type);
+    $entity_bundle_exists = $this->entityBundleOfTypeExists($entity_type, $entity_bundle);
+
+    if (!in_array($operation, $operations)) {
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType. Operation is not allowed.",
+      ];
+    }
+
+    if (!$account) {
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType. Account for uid " . $uid . " does not exist.",
+      ];
+    }
+
+    if (!$entity_type_exists) {
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType. Entity type" . $entity_type . " does not exist.",
+      ];
+    }
+
+    if (!$entity_bundle_exists) {
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType. Entity bundle " . $entity_bundle . " of type " . $entity_type . " does not exist.",
+      ];
+    }
+
+    if (in_array($operation, ["update", "delete"]) && $entity_type === "user" && (int) $uid !== (int) $current_user->id()) {
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType. Only owner of the User account can delete-update the account.",
+      ];
+    }
+
+    if ($operation === "create") {
+      $types_with_parent = [
+        "event_enrollment",
+        "flagging",
+        "group_content",
+        "vote",
+      ];
+
+      if (in_array($entity_type, $types_with_parent) && (!$parent_entity_type || !$parent_entity_id)) {
+        return [
+          "access" => FALSE,
+          "error" => "userCanDoActionOnEntityByType. Values for parent_entity_type and parent_entity_id fields are required for type: " . $entity_type,
+        ];
+      }
+
+      $access = $this->userCanCreateEntityByType($uid, $entity_type, $entity_bundle, $parent_entity_type, $parent_entity_id);
+      $result = [
+        "access" => $access,
+      ];
+
+      if (!$access) {
+        $result["error"] = "userCanDoActionOnEntityByType: Access denied for uid " . $uid;
+      }
+      return $result;
+    }
+
+    // Load existing entity.
+    try {
+      $entity = $this->entityTypeManager
+        ->getStorage($entity_type)
+        ->load($entity_id);
+      if ($entity) {
+        if ($entity_type === "group_content") {
+          if ($operation === "delete") {
+            /** @var \Drupal\group\Entity\GroupContent $entity */
+            $gc_id = $entity->get("entity_id")->getString();
+            if ((int) $gc_id === (int) $uid) {
+              return [
+                "access" => TRUE,
+              ];
+            }
+          }
+        }
+
+        if ($entity_type === "flagging") {
+          if ($operation === "delete") {
+            /** @var \Drupal\flag\Entity\Flagging $entity */
+            $flagging_uid = $entity->getOwner()->id();
+            if ((int) $flagging_uid === (int) $uid) {
+              return [
+                "access" => TRUE,
+              ];
+            }
+          }
+        }
+
+        if ($entity_type === "event_enrollment") {
+          if ($operation === "delete") {
+            /** @var \Drupal\social_event\Entity\EventEnrollment $entity */
+            $enrollment_uid = $entity->getOwner()->id();
+            if ((int) $enrollment_uid === (int) $uid) {
+              return [
+                "access" => TRUE,
+              ];
+            }
+          }
+        }
+
+        // Special override for the votingapi module. Check access early.
+        if ($entity_type === "vote") {
+          if (!$parent_entity_type || !$parent_entity_id) {
+            return [
+              "access" => FALSE,
+              "error" => "userCanDoActionOnEntityByType. Parent entity is required for bundle: " . $entity_bundle . " of type: " . $entity_type,
+            ];
+          }
+          $parent_entity = $this->entityTypeManager->getStorage($parent_entity_type)->load($parent_entity_id);
+          if ($parent_entity) {
+            if ($operation === "delete") {
+              /** @var \Drupal\votingapi\Entity\Vote $entity */
+              $vote_uid = $entity->get("user_id")->getString();
+              if ($vote_uid !== $uid) {
+                return [
+                  "access" => FALSE,
+                  "error" => "userCanDoActionOnEntityByType. Vote entity with id: " . $entity_id . " is not from this User.",
+                ];
+              }
+            }
+            $final_access = like_and_dislike_can_vote($account, $entity_bundle, $parent_entity);
+          }
+        }
+
+        // private_message_thread.
+        if ($entity_type === "private_message_thread") {
+          /** @var \Drupal\private_message\Entity\PrivateMessageThread $entity */
+          $members = $entity->getMembersId();
+          if (in_array($current_user->id(), $members)) {
+            $final_access = TRUE;
+          }
+
+          if ($current_user->hasPermission("use private messaging system")) {
+            $final_access = TRUE;
+          }
+        }
+
+        // Initial try to get access and exit with success.
+        if (!$final_access) {
+          $final_access = $entity->access($operation, $account);
+        }
+        if ($final_access === TRUE) {
+          return [
+            "access" => TRUE,
+          ];
+        }
+
+        $result = [
+          "access" => $final_access,
+        ];
+
+        if (!$final_access && !isset($result["error"])) {
+          $result["error"] = "userCanDoActionOnEntityByType. Entity exists. Access denied for uid " . $uid;
+        }
+        return $result;
+      }
+      else {
+        $entity_error = 'userCanDoActionOnEntityByType. Entity of type: ' . $entity_type . ', bundle: ' . $entity_bundle . ' with ID: ' . $entity_id . ' does not exist.';
+        $this->logger->warning($entity_error);
+        return [
+          "access" => FALSE,
+          "error" => $entity_error,
+        ];
+      }
+    }
+    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+      $this->logger->error($e->getMessage());
+      return [
+        "access" => FALSE,
+        "error" => "userCanDoActionOnEntityByType | " . $e->getMessage(),
+      ];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function socialEventHasBeenFinished(NodeInterface $node): bool {
+    $current_time = new DrupalDateTime();
+
+    /** @var \Drupal\Core\Datetime\DrupalDateTime $check_end_date */
+    $check_end_date = $node->get('field_event_date_end')->isEmpty()
+      // @phpstan-ignore-next-line
+      ? $node->get('field_event_date')->date
+      // @phpstan-ignore-next-line
+      : $node->get('field_event_date_end')->date;
+
+    if (!$check_end_date instanceof DrupalDateTime) {
+      return FALSE;
+    }
+
+    $check_all_day = !$node->get('field_event_all_day')->isEmpty()
+      ? $node->get('field_event_all_day')->getString()
+      : NULL;
+
+    return $current_time->getTimestamp() > $check_end_date->getTimestamp() &&
+      !($check_all_day && $check_end_date->format('Y-m-d') === $current_time->format('Y-m-d'));
+  }
+
+}
diff --git a/modules/graphql_compose_mutations/src/Services/UserPermissionsInterface.php b/modules/graphql_compose_mutations/src/Services/UserPermissionsInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..abebc1d601d99c60bca17f6962c3b253b14a1f06
--- /dev/null
+++ b/modules/graphql_compose_mutations/src/Services/UserPermissionsInterface.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\graphql_compose_mutations\Services;
+
+use Drupal\node\NodeInterface;
+
+/**
+ * A helper service to get User permissions by operation.
+ */
+interface UserPermissionsInterface {
+
+  /**
+   * Check if a user (loaded from uid) has a permission to CREATE.
+   *
+   * @param string $operation
+   *   The operation to check for,.
+   * @param mixed $uid
+   *   The uid.
+   * @param mixed $entity_id
+   *   The entity id to load.
+   * @param string $entity_type
+   *   The entity type (node, post, taxonomy_term etc.)
+   * @param string $entity_bundle
+   *   The entity bundle.
+   * @param mixed $parent_entity_type
+   *   The parent entity type that may be related to this operation. E.g. for
+   *   vote entity types this may be the Node or Comment we are voting for.
+   * @param mixed $parent_entity_id
+   *   The parent entity ID. See above.
+   *
+   * @return array
+   *   Return final access array with useful info.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function userCanDoActionOnEntityByType(
+    string $operation,
+    mixed $uid,
+    mixed $entity_id,
+    string $entity_type,
+    mixed $entity_bundle,
+    mixed $parent_entity_type,
+    mixed $parent_entity_id,
+  ) : array;
+
+  /**
+   * Function to determine if an event has been finished.
+   *
+   * This is a pure copy from Class social_event/src/SocialEventTrait.php
+   * Unfortunately, we can use a private method from a Trait.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The event to check for.
+   *
+   * @return bool
+   *   TRUE if the evens is finished/completed.
+   */
+  public function socialEventHasBeenFinished(NodeInterface $node): bool;
+
+}