Repository: chaoss/grimoirelab-sortinghat (language: python)

Issue:
Organization aliases
Sometimes we have been in the situation that an organization can have different names.  The proposal is that the organizations can have aliases.

This new feature should:

- Add aliases
- Remove aliases
- When one organization is merged into another one, its name will become an alias
- When searching for organizations, if the alias is given, the search should return the organization that has that alias
- If a name exists as an alias, no organization can be created with that name.


Discussion / hints:

Fix patch:
diff --git a/releases/unreleased/organization-aliases.yml b/releases/unreleased/organization-aliases.yml
new file mode 100644
index 00000000..5182371f
--- /dev/null
+++ b/releases/unreleased/organization-aliases.yml
@@ -0,0 +1,16 @@
+---
+title: Organization aliases
+category: added
+author: Eva Millán <evamillan@bitergia.com>
+issue: 857
+notes: >
+  Organizations can be known by different names. To avoid
+  duplicates, organizations can have aliases.
+  Searching for an organization using one of its aliases returns
+  the organization.
+  When an organization is merged into another, its name becomes
+  an alias of the target organization.
+  If a name exists as an alias, no organization can be created
+  with that name and viceversa.
+  An organization's aliases can be added and deleted both on the
+  organizations table and the single organization view.
diff --git a/sortinghat/core/api.py b/sortinghat/core/api.py
index c892113f..88930284 100644
--- a/sortinghat/core/api.py
+++ b/sortinghat/core/api.py
@@ -31,6 +31,7 @@
                  find_team,
                  find_group,
                  find_scheduled_task,
+                 find_alias,
                  search_enrollments_in_period,
                  add_individual as add_individual_db,
                  add_identity as add_identity_db,
@@ -38,12 +39,14 @@
                  add_scheduled_task as add_scheduled_task_db,
                  add_team as add_team_db,
                  add_domain as add_domain_db,
+                 add_alias as add_alias_db,
                  delete_individual as delete_individual_db,
                  delete_identity as delete_identity_db,
                  delete_organization as delete_organization_db,
                  delete_team as delete_team_db,
                  delete_scheduled_task as delete_scheduled_task_db,
                  delete_domain as delete_domain_db,
+                 delete_alias as delete_alias_db,
                  update_profile as update_profile_db,
                  update_scheduled_task as update_scheduled_task_db,
                  move_identity as move_identity_db,
@@ -52,7 +55,8 @@
                  add_enrollment,
                  delete_enrollment,
                  move_domain,
-                 move_team)
+                 move_team,
+                 move_alias)
 from .errors import (InvalidValueError,
                      AlreadyExistsError,
                      NotFoundError,
@@ -512,6 +516,63 @@ def add_domain(ctx, organization, domain_name, is_top_domain=True):
     return domain
 
 
+@atomic_using_tenant
+def add_alias(ctx, organization, name):
+    """Add an alias to the registry.
+
+    This function adds a new alias to a given organization.
+    The organization must exist on the registry prior to insert the new
+    alias. Otherwise, it will raise a `NotFoundError` exception. Moreover,
+    if the given alias is already in the registry an `AlreadyExistsError`
+    exception will be raised.
+
+    :param ctx: context from where this method is called
+    :param organization: name of the organization
+    :param name: alias to add to the registry
+
+    :returns the new alias object
+
+    :raises InvalidValueError: raised when alias is None or an empty string or
+        when it is the same as the organization name
+    :raises NotFoundError: raised when the given organization is not found
+        in the registry
+    :raises AlreadyExistsError: raised when the alias already exists
+        in the registry
+    """
+    if organization is None:
+        raise InvalidValueError(msg="'organization' cannot be None")
+    if organization == '':
+        raise InvalidValueError(msg="'organization' cannot be an empty string")
+    if name is None:
+        raise InvalidValueError(msg="'name' cannot be None")
+    if name == '':
+        raise InvalidValueError(msg="'name' cannot be an empty string")
+    if name == organization:
+        raise InvalidValueError(msg="'name' cannot be the same as 'organization'")
+
+    trxl = TransactionsLog.open('add_alias', ctx)
+
+    try:
+        organization = find_organization(organization)
+    except ValueError as e:
+        raise InvalidValueError(msg=str(e))
+    except NotFoundError as exc:
+        raise exc
+
+    try:
+        alias = add_alias_db(trxl, organization=organization, name=name)
+    except ValueError as e:
+        raise InvalidValueError(msg=str(e))
+    except AlreadyExistsError as exc:
+        raise exc
+
+    trxl.close()
+
+    logger.info(f"Alias {alias.alias} created for organization {organization.name}")
+
+    return alias
+
+
 @atomic_using_tenant
 def add_team(ctx, team_name, organization=None, parent_name=None):
     """Add a team to the registry.
@@ -658,6 +719,45 @@ def delete_domain(ctx, domain_name):
     return domain
 
 
+@atomic_using_tenant
+def delete_alias(ctx, name):
+    """Remove an alias from the registry.
+
+    This function removes the given alias from the registry.
+    Alias must exist in the registry. Otherwise, it will raise
+    a `NotFoundError` exception.
+
+    :param ctx: context from where this method is called
+    :param name: name to remove
+
+    :returns the removed alias object
+
+    :raises NotFoundError: raised when the alias
+        does not exist in the registry.
+    """
+    if name is None:
+        raise InvalidValueError(msg="'name' cannot be None")
+    if name == '':
+        raise InvalidValueError(msg="'name' cannot be an empty string")
+
+    trxl = TransactionsLog.open('delete_alias', ctx)
+
+    try:
+        alias = find_alias(name)
+    except ValueError as e:
+        raise InvalidValueError(msg=str(e))
+    except NotFoundError as exc:
+        raise exc
+
+    delete_alias_db(trxl, alias)
+
+    trxl.close()
+
+    logger.info(f"Alias {name} deleted")
+
+    return alias
+
+
 @atomic_using_tenant
 def delete_team(ctx, team_name, organization=None):
     """Remove a team from the registry.
@@ -1302,7 +1402,7 @@ def merge_organizations(ctx, from_org, to_org):
     This function moves the domains, teams and affiliations from
     an origin organization identified by `from_org` to the target
     organization identified by `to_org`. The origin organization is
-    then deleted.
+    then deleted and its name is added as an alias.
 
     The function raises a `NotFoundError` exception when either `from_org`
     or `to_org` do not exist in the registry and both identifiers are
@@ -1330,6 +1430,13 @@ def _move_domains(trxl, from_org, to_org):
         for domain in domains:
             move_domain(trxl, domain, to_org)
 
+    def _move_aliases(trxl, from_org, to_org):
+        """Move the aliases from the origin organization to the target"""
+
+        aliases = from_org.aliases.all()
+        for alias in aliases:
+            move_alias(trxl, alias, to_org)
+
     def _move_teams(trxl, from_org, to_org):
         """Move the teams from the origin organization to the target"""
 
@@ -1378,9 +1485,12 @@ def _move_enrollments(trxl, from_org, to_org):
     _move_domains(trxl, origin, target)
     _move_teams(trxl, origin, target)
     _move_enrollments(trxl, origin, target)
+    _move_aliases(trxl, origin, target)
 
     delete_organization_db(trxl, organization=origin)
 
+    add_alias_db(trxl, organization=target, name=origin.name)
+
     trxl.close()
 
     logger.info(f"Organization {from_org} merged in {to_org}")
diff --git a/sortinghat/core/db.py b/sortinghat/core/db.py
index c8c748b7..830cfdca 100644
--- a/sortinghat/core/db.py
+++ b/sortinghat/core/db.py
@@ -45,7 +45,8 @@
                      Profile,
                      Enrollment,
                      Operation,
-                     ScheduledTask)
+                     ScheduledTask,
+                     Alias)
 from .aux import validate_field
 
 
@@ -149,9 +150,9 @@ def find_identity(uuid):
 def find_organization(name):
     """Find an organization.
 
-    Find an organization by its name in the database. When the
-    organization does not exist the function will raise
-    a `NotFoundError`.
+    Find an organization by its name or one of its aliases
+    in the database. When the organization does not exist the
+    function will raise a `NotFoundError`.
 
     :param name: name of the organization to find
 
@@ -164,7 +165,8 @@ def find_organization(name):
 
     try:
         logger.debug(f"Finding organization '{name}' ...")
-        organization = Organization.objects.all_organizations().get(name=name)
+        organization = Organization.objects.all_organizations().distinct().get(Q(name=name) |
+                                                                               Q(aliases__alias=name))
     except Organization.DoesNotExist:
         logger.debug(f"Organization with name '{name}' does not exist")
         raise NotFoundError(entity=name)
@@ -222,7 +224,8 @@ def find_group(name, parent_org=None):
 
     try:
         logger.debug(f"Finding group '{name}'" + f"in '{parent_org}' ..." if parent_org else "...")
-        group = Group.objects.get(name=name, parent_org__name=parent_org)
+        group = Group.objects.distinct().get(Q(name=name, parent_org__name=parent_org) |
+                                             Q(aliases__alias=name, parent_org__name=parent_org))
     except Group.DoesNotExist:
         logger.debug(f"Group with name '{name}' does not exist")
         raise NotFoundError(entity=name)
@@ -258,6 +261,33 @@ def find_domain(domain_name):
         return domain
 
 
+def find_alias(name):
+    """Find an alias.
+
+    Find an alias by its name in the database. When the
+    alias does not exist the function will raise
+    a `NotFoundError`.
+
+    :param name: name of the alias to find
+
+    :returns: an alias object
+
+    :raises NotFoundError: when the alias with the
+        given `name` does not exists.
+    """
+    validate_field('name', name)
+
+    try:
+        logger.debug(f"Finding alias '{name}' ...")
+        alias = Alias.objects.get(alias=name)
+    except Alias.DoesNotExist:
+        logger.debug(f"Alias with name '{name}' does not exist")
+        raise NotFoundError(entity=name)
+    else:
+        logger.debug(f"Alias with name '{name}' was found")
+        return alias
+
+
 def search_enrollments_in_period(mk, group_name,
                                  parent_org=None,
                                  from_date=MIN_PERIOD_DATE,
@@ -319,7 +349,7 @@ def add_organization(trxl, name):
     # Groups have a unique together constraint for the 'name' and 'parent_org'
     # fields, but the latter is always null for organizations and MySQL doesn't
     # treat null values as equal
-    if Organization.objects.all_organizations().filter(name=name).exists():
+    if Organization.objects.all_organizations().filter(Q(name=name) | Q(aliases__alias=name)).exists():
         raise AlreadyExistsError(entity=Organization.__name__, eid=name)
 
     organization = Organization(name=name)
@@ -1018,6 +1048,45 @@ def move_domain(trxl, domain, organization):
     return domain
 
 
+def move_alias(trxl, alias, organization):
+    """Move an alias to an organization
+
+    Shifts the given `alias` object to the given `organization`.
+
+    As a result, it returns the `Alias` object with the updated data.
+
+    :param trxl: TransactionsLog object from the method calling this one
+    :param alias: alias to be moved
+    :param organization: organization to move the domain to
+
+    :returns: alias object with the updated information
+
+    :raises ValueError: raised when the alias is already assigned to
+    the organization
+    """
+    op_args = {
+        'alias': alias.alias,
+        'organization': organization.name
+    }
+
+    if alias.organization.name == organization.name:
+        msg = f"Alias '{alias.alias}' is already assigned to '{organization.name}'"
+        raise ValueError(msg)
+
+    old_organization = alias.organization
+    alias.organization = organization
+
+    alias.save()
+    old_organization.save()
+    organization.save()
+
+    trxl.log_operation(op_type=Operation.OpType.UPDATE, entity_type='alias',
+                       timestamp=datetime_utcnow(), args=op_args,
+                       target=op_args['alias'])
+
+    return alias
+
+
 def move_team(trxl, team, organization):
     """Move a team to an organization
 
@@ -1195,3 +1264,67 @@ def find_scheduled_task(task_id):
     else:
         logger.debug(f"Task with id '{task_id}' was found")
         return task
+
+
+def add_alias(trxl, organization, name):
+    """Add an alias to the database.
+
+    This function adds a new alias to the database linked to
+    the organization object in `organization`.
+
+    Values assigned to `name` cannot be `None` or empty.
+
+    As a result, the function returns a new `Alias` object.
+
+    :param trxl: TransactionsLog object from the method calling this one
+    :param organization: links the new alias to this organization object
+    :param name: name of the alias
+
+    :returns: a new alias
+
+    :raises ValueError: raised when `name` is `None` or an empty string;
+    """
+    # Setting operation arguments before they are modified
+    op_args = {
+        'organization': organization.name,
+        'name': name
+    }
+
+    validate_field('name', name)
+
+    # Check if there is an organization with the same name.
+    if Organization.objects.all_organizations().filter(name=name).exists():
+        raise AlreadyExistsError(entity=Organization.__name__, eid=name)
+
+    alias = Alias(alias=name, organization=organization)
+
+    try:
+        alias.save()
+    except django.db.utils.IntegrityError as exc:
+        _handle_integrity_error(Alias, exc)
+
+    trxl.log_operation(op_type=Operation.OpType.ADD, entity_type='alias',
+                       timestamp=datetime_utcnow(), args=op_args,
+                       target=op_args['organization'])
+
+    return alias
+
+
+def delete_alias(trxl, alias):
+    """Remove an alias from the database.
+
+    Deletes from the database the alias given in `alias`.
+
+    :param trxl: TransactionsLog object from the method calling this one
+    :param alias: alias to remove
+    """
+    # Setting operation arguments before they are modified
+    op_args = {
+        'alias': alias.alias
+    }
+
+    alias.delete()
+
+    trxl.log_operation(op_type=Operation.OpType.DELETE, entity_type='alias',
+                       timestamp=datetime_utcnow(), args=op_args,
+                       target=op_args['alias'])
diff --git a/sortinghat/core/migrations/0008_alias.py b/sortinghat/core/migrations/0008_alias.py
new file mode 100644
index 00000000..eda38a60
--- /dev/null
+++ b/sortinghat/core/migrations/0008_alias.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.23 on 2024-02-13 11:17
+
+from django.db import migrations, models
+import django.db.models.deletion
+import grimoirelab_toolkit.datetime
+import sortinghat.core.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0007_delete_importidentitiestask'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Alias',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created_at', sortinghat.core.models.CreationDateTimeField(default=grimoirelab_toolkit.datetime.datetime_utcnow, editable=False)),
+                ('last_modified', sortinghat.core.models.LastModificationDateTimeField(default=grimoirelab_toolkit.datetime.datetime_utcnow, editable=False)),
+                ('alias', models.CharField(max_length=128)),
+                ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='core.group')),
+            ],
+            options={
+                'db_table': 'aliases_organizations',
+                'ordering': ('alias',),
+                'unique_together': {('alias',)},
+            },
+        ),
+    ]
diff --git a/sortinghat/core/models.py b/sortinghat/core/models.py
index 57b4e91f..c37bb3e3 100644
--- a/sortinghat/core/models.py
+++ b/sortinghat/core/models.py
@@ -214,6 +214,19 @@ def __str__(self):
         return self.domain
 
 
+class Alias(EntityBase):
+    alias = CharField(max_length=MAX_SIZE_CHAR_FIELD)
+    organization = ForeignKey(Group, related_name='aliases', on_delete=CASCADE)
+
+    class Meta:
+        db_table = 'aliases_organizations'
+        unique_together = ('alias',)
+        ordering = ('alias',)
+
+    def __str__(self):
+        return self.alias
+
+
 class Country(EntityBase):
     code = CharField(max_length=2, primary_key=True)
     name = CharField(max_length=MAX_SIZE_CHAR_INDEX)
diff --git a/sortinghat/core/schema.py b/sortinghat/core/schema.py
index 38eaca48..38eafb4d 100644
--- a/sortinghat/core/schema.py
+++ b/sortinghat/core/schema.py
@@ -55,9 +55,11 @@
                   add_organization,
                   add_team,
                   add_domain,
+                  add_alias,
                   delete_organization,
                   delete_team,
                   delete_domain,
+                  delete_alias,
                   enroll,
                   withdraw,
                   update_enrollment,
@@ -95,6 +97,7 @@
                      MergeRecommendation,
                      GenderRecommendation,
                      ScheduledTask,
+                     Alias,
                      MIN_PERIOD_DATE)
 from .recommendations.exclusion import delete_recommend_exclusion_term, add_recommender_exclusion_term
 
@@ -205,7 +208,7 @@ class Meta:
 class TeamType(DjangoObjectType):
     class Meta:
         model = Group
-        exclude = ('path', 'depth', 'type', 'domains', 'teams')
+        exclude = ('path', 'depth', 'type', 'domains', 'teams', 'aliases')
 
     subteams = graphene.List(lambda: TeamType)
     parent_org = graphene.Field(OrganizationType)
@@ -219,6 +222,11 @@ class Meta:
         model = Domain
 
 
+class AliasType(DjangoObjectType):
+    class Meta:
+        model = Alias
+
+
 class CountryType(DjangoObjectType):
     class Meta:
         model = Country
@@ -767,6 +775,47 @@ def mutate(self, info, domain):
         )
 
 
+class AddAlias(graphene.Mutation):
+    class Arguments:
+        organization = graphene.String()
+        alias = graphene.String()
+
+    alias = graphene.Field(lambda: AliasType)
+
+    @check_auth
+    def mutate(self, info, organization, alias):
+        user = info.context.user
+        tenant = get_db_tenant()
+        ctx = SortingHatContext(user=user, tenant=tenant)
+
+        als = add_alias(ctx,
+                        organization,
+                        alias)
+
+        return AddAlias(
+            alias=als
+        )
+
+
+class DeleteAlias(graphene.Mutation):
+    class Arguments:
+        alias = graphene.String()
+
+    alias = graphene.Field(lambda: AliasType)
+
+    @check_auth
+    def mutate(self, info, alias):
+        user = info.context.user
+        tenant = get_db_tenant()
+        ctx = SortingHatContext(user=user, tenant=tenant)
+
+        als = delete_alias(ctx, alias)
+
+        return DeleteAlias(
+            alias=als
+        )
+
+
 class AddIdentity(graphene.Mutation):
     class Arguments:
         source = graphene.String()
@@ -1600,12 +1649,15 @@ def resolve_organizations(self, info, filters=None,
         query = Organization.objects.all_organizations().order_by('name')
 
         if filters and 'name' in filters:
-            query = query.filter(name=filters['name'])
+            query = query.distinct().filter(Q(name=filters['name']) | Q(aliases__alias=filters['name']))
         if filters and 'term' in filters:
             search_term = filters['term']
             query = query.filter(Q(name__icontains=search_term) |
                                  Q(name__in=Subquery(Domain.objects
                                                     .filter(domain__icontains=search_term)
+                                                    .values_list('organization__name'))) |
+                                 Q(name__in=Subquery(Alias.objects
+                                                    .filter(alias__icontains=search_term)
                                                     .values_list('organization__name'))))
 
         return OrganizationPaginatedType.create_paginated_result(query,
@@ -1997,6 +2049,13 @@ class SortingHatMutation(graphene.ObjectType):
     delete_domain = DeleteDomain.Field(
         description='Remove a domain from the registry.'
     )
+    add_alias = AddAlias.Field(
+        description='Add a new alias to an organization. An alias can only be assigned\
+        to one organization and cannot share a name with any organization.'
+    )
+    delete_alias = DeleteAlias.Field(
+        description='Remove an alias from the registry.'
+    )
     add_identity = AddIdentity.Field(
         description='Add a new identity to the registry. A new individual will be\
         also added and associated to the new identity unless an `uuid` is provided.\
diff --git a/ui/src/apollo/mutations.js b/ui/src/apollo/mutations.js
index bea5a9c7..82ac7b93 100644
--- a/ui/src/apollo/mutations.js
+++ b/ui/src/apollo/mutations.js
@@ -408,6 +408,26 @@ const UPDATE_TASK = gql`
   }
 `;
 
+const ADD_ALIAS = gql`
+  mutation addAlias($alias: String!, $organization: String!) {
+    addAlias(alias: $alias, organization: $organization) {
+      alias {
+        alias
+      }
+    }
+  }
+`;
+
+const DELETE_ALIAS = gql`
+  mutation deleteAlias($alias: String!) {
+    deleteAlias(alias: $alias) {
+      alias {
+        alias
+      }
+    }
+  }
+`;
+
 const tokenAuth = (apollo, username, password) => {
   const response = apollo.mutate({
     mutation: TOKEN_AUTH,
@@ -793,6 +813,23 @@ const updateTask = (apollo, taskId, data) => {
   });
 };
 
+const addAlias = (apollo, alias, organization) => {
+  return apollo.mutate({
+    mutation: ADD_ALIAS,
+    variables: {
+      alias,
+      organization,
+    },
+  });
+};
+
+const deleteAlias = (apollo, alias) => {
+  return apollo.mutate({
+    mutation: DELETE_ALIAS,
+    variables: { alias },
+  });
+};
+
 export {
   tokenAuth,
   lockIndividual,
@@ -824,4 +861,6 @@ export {
   scheduleTask,
   deleteTask,
   updateTask,
+  addAlias,
+  deleteAlias
 };
diff --git a/ui/src/apollo/queries.js b/ui/src/apollo/queries.js
index cb7c73bb..8d9949e8 100644
--- a/ui/src/apollo/queries.js
+++ b/ui/src/apollo/queries.js
@@ -120,6 +120,9 @@ const GET_PAGINATED_ORGANIZATIONS = gql`
           domain
           isTopDomain
         }
+        aliases {
+          alias
+        }
       }
       pageInfo {
         page
@@ -253,6 +256,9 @@ const GET_ORGANIZATION = gql`
           domain
           isTopDomain
         }
+        aliases {
+          alias
+        }
       }
     }
   }
@@ -284,6 +290,9 @@ const FIND_ORGANIZATION = gql`
       entities {
         id
         name
+        aliases {
+          alias
+        }
       }
     }
   }
diff --git a/ui/src/components/OrganizationEntry.vue b/ui/src/components/OrganizationEntry.vue
index a1806e7c..7b5fbbc2 100644
--- a/ui/src/components/OrganizationEntry.vue
+++ b/ui/src/components/OrganizationEntry.vue
@@ -43,7 +43,7 @@
         </template>
         <v-list dense>
           <v-list-item @click="$emit('edit')">
-            <v-list-item-title> View/edit domains </v-list-item-title>
+            <v-list-item-title> Edit domains and aliases </v-list-item-title>
           </v-list-item>
           <v-edit-dialog
             large
diff --git a/ui/src/components/OrganizationModal.stories.js b/ui/src/components/OrganizationModal.stories.js
index 8c0a78f7..0a00312d 100644
--- a/ui/src/components/OrganizationModal.stories.js
+++ b/ui/src/components/OrganizationModal.stories.js
@@ -14,7 +14,9 @@ const OrganizationModalTemplate = `
       :is-open.sync="isOpen"
       :add-domain="addDomain"
       :add-organization="addDomain"
-      :delete-domain="addDomain"                           l
+      :delete-domain="addDomain"
+      :add-alias="() => {}"
+      :delete-alias="() => {}"
     />
   </div>
 `;
diff --git a/ui/src/components/OrganizationModal.vue b/ui/src/components/OrganizationModal.vue
index 9cbed16b..9f9e58fe 100644
--- a/ui/src/components/OrganizationModal.vue
+++ b/ui/src/components/OrganizationModal.vue
@@ -50,12 +50,55 @@
               </v-btn>
             </v-col>
           </v-row>
-          <v-row class="pl-3 mt-2">
-            <v-btn text small left outlined color="primary" @click="addInput">
-              <v-icon small color="primary">mdi-plus-circle-outline</v-icon>
+          <v-row class="pl-3 mt-2 mb-4">
+            <v-btn text small outlined color="primary" @click="addInput">
+              <v-icon small left color="primary">
+                mdi-plus-circle-outline
+              </v-icon>
               Add domain
             </v-btn>
           </v-row>
+          <v-row class="pl-4">
+            <span class="subheader">Aliases</span>
+          </v-row>
+          <v-row
+            v-for="(alias, index) in form.aliases"
+            :key="`alias-${index}`"
+            class="align-bottom"
+          >
+            <v-col cols="10">
+              <v-text-field
+                v-model="form.aliases[index]"
+                label="Alias"
+                hide-details
+                outlined
+                dense
+              />
+            </v-col>
+            <v-col cols="2" class="pt-3">
+              <v-btn
+                icon
+                color="primary"
+                @click="form.aliases.splice(index, 1)"
+              >
+                <v-icon color="primary">mdi-delete</v-icon>
+              </v-btn>
+            </v-col>
+          </v-row>
+          <v-row class="pl-3 mt-2">
+            <v-btn
+              text
+              small
+              outlined
+              color="primary"
+              @click="form.aliases.push('')"
+            >
+              <v-icon small left color="primary">
+                mdi-plus-circle-outline
+              </v-icon>
+              Add alias
+            </v-btn>
+          </v-row>
           <v-alert v-if="errorMessage" text type="error" class="mt-3">
             {{ errorMessage }}
           </v-alert>
@@ -114,12 +157,26 @@ export default {
       required: false,
       default: () => [],
     },
+    aliases: {
+      type: Array,
+      required: false,
+      default: () => [""],
+    },
+    addAlias: {
+      type: Function,
+      required: true,
+    },
+    deleteAlias: {
+      type: Function,
+      required: true,
+    },
   },
   data() {
     return {
       form: {
         name: "",
         domains: [emptyDomain],
+        aliases: [""],
       },
       errorMessage: "",
       savedData: {
@@ -133,6 +190,7 @@ export default {
       this.form.domains.splice(index, 1);
     },
     async onSave() {
+      this.errorMessage = "";
       if (!this.savedData.name) {
         try {
           const response = await this.addOrganization(this.form.name);
@@ -141,20 +199,30 @@ export default {
             this.$logger.debug(`Added organization ${this.form.name}`);
             if (
               this.form.domains.length === 0 &&
-              this.savedData.domains.length === 0
+              this.savedData.domains.length === 0 &&
+              this.form.aliases.length === 0 &&
+              this.savedData.aliases.length === 0
             ) {
               this.closeModal();
               this.$emit("updateOrganizations");
               return;
             } else {
-              this.handleDomains();
+              await this.handleDomains();
+              await this.handleAliases();
+              if (!this.errorMessage) {
+                this.closeModal();
+              }
             }
           }
         } catch (error) {
           this.errorMessage = this.$getErrorMessage(error);
         }
       } else {
-        this.handleDomains();
+        await this.handleDomains();
+        await this.handleAliases();
+        if (!this.errorMessage) {
+          this.closeModal();
+        }
       }
     },
     closeModal() {
@@ -186,7 +254,6 @@ export default {
           deletedDomains.map((domain) => this.deleteOrganizationDomain(domain))
         );
         if (responseNew || responseDeleted) {
-          this.closeModal();
           this.$emit("updateOrganizations");
         }
       } catch (error) {
@@ -225,6 +292,60 @@ export default {
     addInput() {
       this.form.domains.push({ ...emptyDomain });
     },
+    async handleAliases() {
+      const newAliases = this.form.aliases.filter(
+        (alias) =>
+          alias.length > 0 &&
+          !this.savedData.aliases.some((savedAlias) => savedAlias === alias)
+      );
+      const deletedAliases = this.savedData.aliases.filter(
+        (alias) =>
+          alias.length > 0 &&
+          !this.form.aliases.some((savedAlias) => savedAlias === alias)
+      );
+      try {
+        const responseNew = await Promise.all(
+          newAliases.map((alias) =>
+            this.addOrganizationAlias(alias, this.form.name)
+          )
+        );
+        const responseDeleted = await Promise.all(
+          deletedAliases.map((alias) => this.deleteOrganizationAlias(alias))
+        );
+        if (responseNew || responseDeleted) {
+          this.$emit("updateOrganizations");
+        }
+      } catch (error) {
+        this.errorMessage = this.$getErrorMessage(error);
+        this.$logger.error(`Error updating aliases: ${error}`, {
+          organization: this.form.name,
+          newAliases,
+          deletedAliases,
+        });
+      }
+    },
+    async addOrganizationAlias(alias, organization) {
+      const response = await this.addAlias(alias, organization);
+      if (response && !response.error) {
+        this.savedData.aliases.push(alias);
+        this.$logger.debug(
+          `Added alias ${alias} to organization ${organization}`
+        );
+        return response;
+      } else if (response.errors) {
+        this.$logger.error(
+          `Error adding alias: ${response.errors[0].message}`,
+          { alias, organization }
+        );
+      }
+    },
+    async deleteOrganizationAlias(alias) {
+      const response = await this.deleteAlias(alias);
+      if (response) {
+        this.$logger.debug(`Deleted alias ${alias}`);
+        return response;
+      }
+    },
   },
   watch: {
     isOpen(value) {
@@ -232,16 +353,23 @@ export default {
         Object.assign(this.savedData, {
           name: this.organization,
           domains: this.domains.map((domain) => ({ ...domain })),
+          aliases: this.aliases.map((alias) => alias),
         });
         Object.assign(this.form, {
           name: this.organization || "",
           domains: this.domains.map((domain) => ({ ...domain })),
+          aliases: this.aliases.map((alias) => alias),
         });
       } else {
-        Object.assign(this.form, { name: "", domains: [{ ...emptyDomain }] });
+        Object.assign(this.form, {
+          name: "",
+          domains: [{ ...emptyDomain }],
+          aliases: [""],
+        });
         Object.assign(this.savedData, {
           name: undefined,
           domains: [{ ...emptyDomain }],
+          aliases: [""],
         });
       }
     },
diff --git a/ui/src/components/OrganizationSelector.vue b/ui/src/components/OrganizationSelector.vue
index b8515d2f..8054030d 100644
--- a/ui/src/components/OrganizationSelector.vue
+++ b/ui/src/components/OrganizationSelector.vue
@@ -5,11 +5,11 @@
     :search-input.sync="search"
     :label="label"
     :loading="isLoading"
+    :filter="filterItems"
     item-text="name"
     item-value="name"
     :no-data-text="`No matches for &quot;${search}&quot;`"
     :hide-no-data="!search || isLoading"
-    cache-items
     clearable
     dense
     outlined
@@ -86,20 +86,28 @@ export default {
         this.$emit("error", this.$getErrorMessage(error));
       }
     },
+    filterItems(item) {
+      // Return all items because the query is already filtered
+      return item;
+    },
   },
   computed: {
     appendContent() {
       return (
         this.search &&
         !this.organizations.some(
-          (org) => org.name.toLowerCase() === this.search.toLowerCase()
+          (org) =>
+            org.name.toLowerCase() === this.search.toLowerCase() ||
+            org.aliases.some(
+              (alias) => alias.alias.toLowerCase() === this.search.toLowerCase()
+            )
         )
       );
     },
   },
   watch: {
     search(value) {
-      if (value && value.length > 2) {
+      if (!value || value.length > 2) {
         this.isLoading = true;
         this.debounceSearch(value);
       }
diff --git a/ui/src/components/OrganizationsTable.stories.js b/ui/src/components/OrganizationsTable.stories.js
index fff665f8..aae28b86 100644
--- a/ui/src/components/OrganizationsTable.stories.js
+++ b/ui/src/components/OrganizationsTable.stories.js
@@ -18,6 +18,8 @@ const OrganizationsTableTemplate = `
     :add-team="addTeam"
     :delete-team="deleteTeam"
     :fetch-teams="fetchTeams"
+    :add-alias="() => {}"
+    :delete-alias="() => {}"
   />
 `;
 
@@ -105,6 +107,7 @@ export const Organizations = () => ({
                   { id: 5 },
                 ],
                 domains: [{ domain: "griffyndor.hogwarts.edu" }],
+                aliases: [{ alias: "Griffyndor House" }],
               },
               {
                 id: 2,
@@ -114,6 +117,7 @@ export const Organizations = () => ({
                   { id: 2 },
                 ],
                 domains: [{ domain: "slytherin.hogwarts.edu" }],
+                aliases: [{ alias: "Slytherin House" }],
               },
               {
                 id: 3,
@@ -124,6 +128,7 @@ export const Organizations = () => ({
                   { id: 3 },
                 ],
                 domains: [{ domain: "ravenclaw.hogwarts.edu" }],
+                aliases: [{ alias: "Ravenclaw House" }],
               },
             ],
             pageInfo: {
diff --git a/ui/src/components/OrganizationsTable.vue b/ui/src/components/OrganizationsTable.vue
index 31fe2e15..c1881730 100644
--- a/ui/src/components/OrganizationsTable.vue
+++ b/ui/src/components/OrganizationsTable.vue
@@ -125,6 +125,9 @@
       :delete-domain="deleteDomain"
       :organization="modal.organization"
       :domains="modal.domains"
+      :aliases="modal.aliases"
+      :add-alias="addAlias"
+      :delete-alias="deleteAlias"
       @updateOrganizations="getTableItems(page)"
     />
 
@@ -233,6 +236,14 @@ export default {
       type: Function,
       required: false,
     },
+    addAlias: {
+      type: Function,
+      required: false,
+    },
+    deleteAlias: {
+      type: Function,
+      required: false,
+    },
   },
   data() {
     return {
@@ -258,6 +269,7 @@ export default {
         open: false,
         organization: undefined,
         domains: [],
+        aliases: [],
       },
       selectedOrganization: "",
       filters: {},
@@ -374,10 +386,12 @@ export default {
         organization.domains && organization.domains.length > 0
           ? organization.domains.map((domain) => domain)
           : [{ domain: "", isTopDomain: false }];
+      const aliases = organization.aliases?.map(item => item.alias) || [""];
       Object.assign(this.modal, {
         open: true,
         organization: organization ? organization.name : "",
         domains: domains,
+        aliases: aliases,
       });
     },
     confirmDelete(group) {
diff --git a/ui/src/views/Dashboard.vue b/ui/src/views/Dashboard.vue
index 7842474d..012ce0fd 100644
--- a/ui/src/views/Dashboard.vue
+++ b/ui/src/views/Dashboard.vue
@@ -62,6 +62,8 @@
           :delete-team="deleteTeam"
           :fetch-teams="fetchTeams"
           :merge-items="mergeOrgs"
+          :add-alias="addAlias"
+          :delete-alias="deleteAlias"
           @getEnrollments="getEnrollments"
           @updateIndividuals="updateTable"
           @updateWorkspace="updateWorkspace"
@@ -106,6 +108,8 @@ import {
   manageMergeRecommendation,
   recommendMatches,
   mergeOrganizations,
+  addAlias,
+  deleteAlias,
 } from "../apollo/mutations";
 import IndividualsTable from "../components/IndividualsTable";
 import OrganizationsTable from "../components/OrganizationsTable";
@@ -353,6 +357,14 @@ export default {
       const response = await mergeOrganizations(this.$apollo, fromOrg, toOrg);
       return response;
     },
+    async addAlias(alias, organization) {
+      const response = await addAlias(this.$apollo, alias, organization);
+      return response;
+    },
+    async deleteAlias(alias) {
+      const response = await deleteAlias(this.$apollo, alias);
+      return response;
+    }
   },
   async mounted() {
     if (this.workspace && this.workspace.length > 0) {
diff --git a/ui/src/views/Organization.vue b/ui/src/views/Organization.vue
index 531f6706..72490d38 100644
--- a/ui/src/views/Organization.vue
+++ b/ui/src/views/Organization.vue
@@ -162,6 +162,50 @@
                       </v-list-item-icon>
                     </v-list-item>
                   </v-list>
+                  <v-list>
+                    <v-subheader class="d-flex justify-space-between">
+                      Aliases
+                      <v-edit-dialog
+                        large
+                        @save="addAlias"
+                        @cancel="form.alias = null"
+                      >
+                        <v-btn text small outlined>
+                          <v-icon small left>mdi-plus</v-icon>
+                          Add
+                        </v-btn>
+                        <template v-slot:input>
+                          <v-subheader class="pl-0">New alias</v-subheader>
+                          <v-text-field
+                            v-model="form.alias"
+                            label="Alias"
+                            single-line
+                            dense
+                          ></v-text-field>
+                        </template>
+                      </v-edit-dialog>
+                    </v-subheader>
+                    <v-list-item
+                      v-for="(alias, i) in organization.aliases"
+                      :key="i"
+                    >
+                      <v-list-item-content>
+                        <v-list-item-title>
+                          {{ alias.alias }}
+                        </v-list-item-title>
+                      </v-list-item-content>
+                      <v-list-item-icon>
+                        <v-btn
+                          aria-label="Delete alias"
+                          small
+                          icon
+                          @click="confirmDelete(deleteAlias, alias.alias)"
+                        >
+                          <v-icon small>mdi-delete</v-icon>
+                        </v-btn>
+                      </v-list-item-icon>
+                    </v-list-item>
+                  </v-list>
                 </v-tab-item>
                 <v-tab-item eager>
                   <individuals-table
@@ -240,6 +284,8 @@ import {
   updateProfile,
   lockIndividual,
   unlockIndividual,
+  addAlias,
+  deleteAlias,
 } from "../apollo/mutations";
 import IndividualsTable from "../components/IndividualsTable.vue";
 
@@ -266,6 +312,7 @@ export default {
         domain: null,
         isTopDomain: true,
         team: null,
+        alias: null,
       },
       filters: null,
     };
@@ -459,6 +506,39 @@ export default {
         color: "primary",
       });
     },
+    async addAlias() {
+      if (!this.form.alias) return;
+
+      try {
+        const response = await addAlias(
+          this.$apollo,
+          this.form.alias,
+          this.name
+        );
+        this.organization.aliases.push(response.data.addAlias.alias);
+        this.form.alias = null;
+      } catch (error) {
+        Object.assign(this.dialog, {
+          isOpen: true,
+          title: "Error creating alias",
+          text: this.$getErrorMessage(error),
+        });
+      }
+    },
+    async deleteAlias(alias) {
+      try {
+        const response = await deleteAlias(this.$apollo, alias);
+        if (response && !response.errors) {
+          const index = this.organization.aliases.findIndex(
+            (item) => item.alias === alias
+          );
+          this.organization.aliases.splice(index, 1);
+          this.closeDialog();
+        }
+      } catch (error) {
+        Object.assign(this.dialog, { text: error });
+      }
+    },
   },
   async mounted() {
     await this.fetchOrganization(this.name);


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index 6ddcd28c..dab4195e 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -48,7 +48,8 @@
                                     Domain,
                                     Transaction,
                                     Operation,
-                                    ScheduledTask)
+                                    ScheduledTask,
+                                    Alias)
 
 NOT_FOUND_ERROR = "{entity} not found in the registry"
 ENROLLMENT_RANGE_INVALID = "range date '{start}'-'{end}' is part of an existing range for {org}"
@@ -90,6 +91,11 @@
 FORMAT_NONE_OR_EMPTY_ERROR = "'{}' cannot be"
 INTERVAL_MUST_BE_NUMBER_ERROR = "'interval' must be a positive number"
 FROM_ORG_TO_ORG_EQUAL_ERROR = "'to_org' cannot be the same as 'from_org'"
+ALIAS_ALREADY_EXISTS_ERROR = "'{name}' already exists in the registry"
+ALIAS_NAME_NONE_OR_EMPTY_ERROR = "'name' cannot be"
+ALIAS_VALUE_ERROR = "field value must be a string; int given"
+ALIAS_ORG_NAME_NONE_OR_EMPTY_ERROR = "'organization' cannot be"
+ALIAS_NOT_FOUND_ERROR = "{name} not found in the registry"
 
 
 class TestAddIdentity(TestCase):
@@ -1849,6 +1855,30 @@ def test_add_duplicate_organization(self):
         transactions = Transaction.objects.filter(created_at__gt=trx_date)
         self.assertEqual(len(transactions), 0)
 
+    def test_add_existing_alias(self):
+        """Check if it fails when adding an organization when it exists as an alias"""
+
+        org = api.add_organization(self.ctx, name='Example')
+        als = api.add_alias(self.ctx, name='Example Inc.', organization=org.name)
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(AlreadyExistsError, ORGANIZATION_ALREADY_EXISTS_ERROR.format(name=als.alias)):
+            org = api.add_organization(self.ctx, name=als.alias)
+
+        organizations = Organization.objects.filter(name='Example')
+        self.assertEqual(len(organizations), 1)
+
+        organizations = Organization.objects.filter(name='Example Inc.')
+        self.assertEqual(len(organizations), 0)
+
+        organizations = Organization.objects.all()
+        self.assertEqual(len(organizations), 1)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
     def test_organization_name_none(self):
         """Check if it fails when organization name is `None`"""
 
@@ -5747,6 +5777,10 @@ def setUp(self):
                    from_date=datetime.datetime(1999, 1, 1),
                    to_date=datetime.datetime(2000, 1, 1))
 
+        api.add_alias(self.ctx, organization='Example', name='Example Inc.')
+        api.add_alias(self.ctx, organization='Example', name='Example Ltd.')
+        api.add_alias(self.ctx, organization='Bitergia', name='Bitergium')
+
     def test_merge_organizations(self):
         """Check whether it merges two organizations, merging their domains, teams and enrollments"""
 
@@ -5785,6 +5819,13 @@ def test_merge_organizations(self):
         enrollment = enrollments[1]
         self.assertEqual(enrollment.individual.mk, self.example_indv.uuid)
 
+        aliases = organization.aliases.all()
+        self.assertEqual(len(aliases), 4)
+        self.assertEqual(aliases[0].alias, 'Bitergium')
+        self.assertEqual(aliases[1].alias, 'Example')
+        self.assertEqual(aliases[2].alias, 'Example Inc.')
+        self.assertEqual(aliases[3].alias, 'Example Ltd.')
+
     def test_from_org_to_org_equal(self):
         """Check if it fails merging two organizations when they are equal"""
 
@@ -5867,7 +5908,7 @@ def test_operations(self):
         trx = transactions[0]
 
         operations = Operation.objects.filter(trx=trx)
-        self.assertEqual(len(operations), 4)
+        self.assertEqual(len(operations), 7)
 
         op1 = operations[0]
         self.assertIsInstance(op1, Operation)
@@ -5912,15 +5953,54 @@ def test_operations(self):
 
         op4 = operations[3]
         self.assertIsInstance(op4, Operation)
-        self.assertEqual(op2.op_type, Operation.OpType.UPDATE.value)
-        self.assertEqual(op4.entity_type, 'organization')
-        self.assertEqual(op4.target, 'Example')
+        self.assertEqual(op4.op_type, Operation.OpType.UPDATE.value)
+        self.assertEqual(op4.entity_type, 'alias')
+        self.assertEqual(op4.target, 'Example Inc.')
         self.assertEqual(op4.trx, trx)
         self.assertGreater(op4.timestamp, timestamp)
 
         op4_args = json.loads(op4.args)
-        self.assertEqual(len(op4_args), 1)
-        self.assertEqual(op4_args['organization'], 'Example')
+        self.assertEqual(len(op4_args), 2)
+        self.assertEqual(op4_args['alias'], 'Example Inc.')
+        self.assertEqual(op4_args['organization'], 'Bitergia')
+
+        op5 = operations[4]
+        self.assertIsInstance(op5, Operation)
+        self.assertEqual(op5.op_type, Operation.OpType.UPDATE.value)
+        self.assertEqual(op5.entity_type, 'alias')
+        self.assertEqual(op5.target, 'Example Ltd.')
+        self.assertEqual(op5.trx, trx)
+        self.assertGreater(op5.timestamp, timestamp)
+
+        op5_args = json.loads(op5.args)
+        self.assertEqual(len(op5_args), 2)
+        self.assertEqual(op5_args['alias'], 'Example Ltd.')
+        self.assertEqual(op5_args['organization'], 'Bitergia')
+
+        op6 = operations[5]
+        self.assertIsInstance(op6, Operation)
+        self.assertEqual(op6.op_type, Operation.OpType.DELETE.value)
+        self.assertEqual(op6.entity_type, 'organization')
+        self.assertEqual(op6.target, 'Example')
+        self.assertEqual(op6.trx, trx)
+        self.assertGreater(op6.timestamp, timestamp)
+
+        op6_args = json.loads(op6.args)
+        self.assertEqual(len(op6_args), 1)
+        self.assertEqual(op6_args['organization'], 'Example')
+
+        op7 = operations[6]
+        self.assertIsInstance(op7, Operation)
+        self.assertEqual(op7.op_type, Operation.OpType.ADD.value)
+        self.assertEqual(op7.entity_type, 'alias')
+        self.assertEqual(op7.target, 'Bitergia')
+        self.assertEqual(op7.trx, trx)
+        self.assertGreater(op7.timestamp, timestamp)
+
+        op7_args = json.loads(op7.args)
+        self.assertEqual(len(op7_args), 2)
+        self.assertEqual(op7_args['organization'], 'Bitergia')
+        self.assertEqual(op7_args['name'], 'Example')
 
 
 class TestUpdateScheduledTask(TestCase):
@@ -6176,3 +6256,467 @@ def test_operations(self):
         op1_args = json.loads(op1.args)
         self.assertEqual(len(op1_args), 1)
         self.assertEqual(op1_args['task'], str(self.task_foo.id))
+
+
+class TestAddAlias(TestCase):
+    """Unit tests for add_alias"""
+
+    def setUp(self):
+        """Load initial dataset"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.ctx = SortingHatContext(self.user)
+
+        api.add_organization(self.ctx, name='Example')
+        api.add_organization(self.ctx, name='Bitergia')
+
+    def test_add_new_alias(self):
+        """Check if everything goes OK when adding a new alias"""
+
+        alias = api.add_alias(self.ctx,
+                              organization='Example',
+                              name='Example Inc.')
+
+        # Tests
+        self.assertIsInstance(alias, Alias)
+        self.assertEqual(alias.organization.name, 'Example')
+        self.assertEqual(alias.alias, 'Example Inc.')
+
+        aliases_db = Alias.objects.filter(alias='Example Inc.')
+        self.assertEqual(len(aliases_db), 1)
+
+        alias1 = aliases_db[0]
+        self.assertEqual(alias, alias1)
+
+    def test_add_multiple_aliases(self):
+        """Check if everything goes OK when adding several aliases"""
+
+        alias1 = api.add_alias(self.ctx,
+                               organization='Example',
+                               name='Example Inc.')
+
+        alias2 = api.add_alias(self.ctx,
+                               organization='Example',
+                               name='Example Ltd.')
+
+        # Tests
+        self.assertEqual(alias1.organization.name, 'Example')
+        self.assertEqual(alias1.alias, 'Example Inc.')
+
+        self.assertEqual(alias2.organization.name, 'Example')
+        self.assertEqual(alias2.alias, 'Example Ltd.')
+
+        aliases_db = Alias.objects.filter(alias='Example Inc.')
+        self.assertEqual(len(aliases_db), 1)
+
+        als1 = aliases_db[0]
+        self.assertEqual(alias1, als1)
+
+        aliases_db = Alias.objects.filter(alias='Example Ltd.')
+        self.assertEqual(len(aliases_db), 1)
+
+        als2 = aliases_db[0]
+        self.assertEqual(alias2, als2)
+
+    def test_add_duplicate_alias(self):
+        """Check if it fails when adding a duplicate alias"""
+
+        alias = api.add_alias(self.ctx,
+                              organization='Example',
+                              name='Example Inc.')
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(AlreadyExistsError, ALIAS_ALREADY_EXISTS_ERROR.format(name='Example Inc.')):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='Example Inc.')
+
+        aliases = Alias.objects.filter(alias='Example Inc.')
+        self.assertEqual(len(aliases), 1)
+
+        als1 = aliases[0]
+        self.assertEqual(als1, alias)
+
+        aliases = Alias.objects.all()
+        self.assertEqual(len(aliases), 1)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_add_alias_different_org(self):
+        """Check if it fails when adding the same alias to a different organization"""
+
+        alias = api.add_alias(self.ctx,
+                              organization='Example',
+                              name='Example Inc.')
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(AlreadyExistsError, ALIAS_ALREADY_EXISTS_ERROR.format(name='Example Inc.')):
+            api.add_alias(self.ctx,
+                          organization='Bitergia',
+                          name='Example Inc.')
+
+        aliases = Alias.objects.filter(alias='Example Inc.')
+        self.assertEqual(len(aliases), 1)
+
+        als1 = aliases[0]
+        self.assertEqual(als1.organization.name, 'Example')
+        self.assertEqual(als1.organization.name, alias.organization.name)
+
+        aliases = Alias.objects.all()
+        self.assertEqual(len(aliases), 1)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_organization_not_found(self):
+        """Check if it fails when the organization is not found"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(NotFoundError, ORGANIZATION_NOT_FOUND_ERROR.format(name='Botergia')):
+            api.add_alias(self.ctx,
+                          organization='Botergia',
+                          name='Example Inc.')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_none(self):
+        """Check if it fails when alias name is `None`"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name=None)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_empty(self):
+        """Check if it fails when alias name is empty"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_whitespaces(self):
+        """Check if it fails when alias name is composed by whitespaces"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='    ')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='\t')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='  \t  ')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_int(self):
+        """Check if it fails when domain name is an integer"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(TypeError, ALIAS_VALUE_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name=12345)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_org_same_name(self):
+        """Check if it fails when the alias and the organization names are the same"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='Example',
+                          name='Example')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_organization_name_none(self):
+        """Check if it fails when organization name is `None`"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_ORG_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization=None,
+                          name='Example Inc.')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_organization_name_empty(self):
+        """Check if it fails when organization name is empty"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_ORG_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='',
+                          name='Example Inc.')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_organization_name_whitespaces(self):
+        """Check if it fails when organization name is composed by whitespaces"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='    ',
+                          name='Example Inc.')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='\t',
+                          name='Example Inc.')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.add_alias(self.ctx,
+                          organization='  \t  ',
+                          name='Example Inc.')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_organization_name_int(self):
+        """Check if it fails when organization name is an integer"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(TypeError, ORGANIZATION_VALUE_ERROR):
+            api.add_alias(self.ctx,
+                          organization=12345,
+                          name='Example Inc.')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_transaction(self):
+        """Check if a transaction is created when adding an alias"""
+
+        timestamp = datetime_utcnow()
+
+        api.add_alias(self.ctx,
+                      organization='Example',
+                      name='Example Inc.')
+
+        transactions = Transaction.objects.filter(created_at__gte=timestamp)
+        self.assertEqual(len(transactions), 1)
+
+        trx = transactions[0]
+        self.assertIsInstance(trx, Transaction)
+        self.assertEqual(trx.name, 'add_alias')
+        self.assertGreater(trx.created_at, timestamp)
+        self.assertEqual(trx.authored_by, self.ctx.user.username)
+
+    def test_operations(self):
+        """Check if the right operations are created when adding an alias"""
+
+        timestamp = datetime_utcnow()
+
+        api.add_alias(self.ctx,
+                      organization='Example',
+                      name='Example Inc.')
+
+        transactions = Transaction.objects.filter(created_at__gte=timestamp)
+        trx = transactions[0]
+
+        operations = Operation.objects.filter(trx=trx)
+        self.assertEqual(len(operations), 1)
+
+        op1 = operations[0]
+        self.assertIsInstance(op1, Operation)
+        self.assertEqual(op1.op_type, Operation.OpType.ADD.value)
+        self.assertEqual(op1.entity_type, 'alias')
+        self.assertEqual(op1.target, 'Example')
+        self.assertEqual(op1.trx, trx)
+        self.assertGreater(op1.timestamp, timestamp)
+
+        op1_args = json.loads(op1.args)
+        self.assertEqual(len(op1_args), 2)
+        self.assertEqual(op1_args['organization'], 'Example')
+        self.assertEqual(op1_args['name'], 'Example Inc.')
+
+
+class TestDeleteAlias(TestCase):
+    """Unit tests for delete_alias"""
+
+    def setUp(self):
+        """Load initial dataset"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.ctx = SortingHatContext(self.user)
+
+        api.add_organization(self.ctx, name='Example')
+        api.add_alias(self.ctx, name='Example Inc.', organization='Example')
+        api.add_alias(self.ctx, name='Example Ltd.', organization='Example')
+
+    def test_delete_alias(self):
+        """Check if everything goes OK when deleting an alias"""
+
+        alias = api.delete_alias(self.ctx, 'Example Inc.')
+
+        # Tests
+        self.assertIsInstance(alias, Alias)
+        self.assertEqual(alias.organization.name, 'Example')
+        self.assertEqual(alias.alias, 'Example Inc.')
+
+        aliases = Alias.objects.filter(alias='Example Inc.')
+        self.assertEqual(len(aliases), 0)
+
+        # Check if the rest of aliases were not removed
+        aliases = Alias.objects.all()
+        self.assertEqual(len(aliases), 1)
+
+        alias = aliases[0]
+        self.assertEqual(alias.organization.name, 'Example')
+        self.assertEqual(alias.alias, 'Example Ltd.')
+
+    def test_alias_not_found(self):
+        """Check if it fails when the alias is not found"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(NotFoundError, ALIAS_NOT_FOUND_ERROR.format(name='Example SL')):
+            api.delete_alias(self.ctx, 'Example SL')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_none(self):
+        """Check if it fails when alias name is `None`"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.delete_alias(self.ctx, name=None)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_empty(self):
+        """Check if it fails when alias name is empty"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.delete_alias(self.ctx, name='')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_whitespaces(self):
+        """Check if it fails when alias name is composed by whitespaces"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.delete_alias(self.ctx, name='    ')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.delete_alias(self.ctx, name='\t')
+
+        with self.assertRaisesRegex(InvalidValueError, ALIAS_NAME_NONE_OR_EMPTY_ERROR):
+            api.delete_alias(self.ctx, name='  \t  ')
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_alias_name_int(self):
+        """Check if it fails when alias name is an integer"""
+
+        trx_date = datetime_utcnow()  # After this datetime no transactions should be created
+
+        with self.assertRaisesRegex(TypeError, ALIAS_VALUE_ERROR):
+            api.delete_alias(self.ctx, name=12345)
+
+        # Check if there are no transactions created when there is an error
+        transactions = Transaction.objects.filter(created_at__gt=trx_date)
+        self.assertEqual(len(transactions), 0)
+
+    def test_transaction(self):
+        """Check if a transaction is created when deleting an alias"""
+
+        timestamp = datetime_utcnow()
+
+        api.delete_alias(self.ctx, 'Example Inc.')
+
+        transactions = Transaction.objects.filter(created_at__gte=timestamp)
+        self.assertEqual(len(transactions), 1)
+
+        trx = transactions[0]
+        self.assertIsInstance(trx, Transaction)
+        self.assertEqual(trx.name, 'delete_alias')
+        self.assertGreater(trx.created_at, timestamp)
+        self.assertEqual(trx.authored_by, self.ctx.user.username)
+
+    def test_operations(self):
+        """Check if the right operations are created when deleting an alias"""
+
+        timestamp = datetime_utcnow()
+
+        api.delete_alias(self.ctx, 'Example Inc.')
+
+        transactions = Transaction.objects.filter(created_at__gte=timestamp)
+        trx = transactions[0]
+
+        operations = Operation.objects.filter(trx=trx)
+        self.assertEqual(len(operations), 1)
+
+        op1 = operations[0]
+        self.assertIsInstance(op1, Operation)
+        self.assertEqual(op1.op_type, Operation.OpType.DELETE.value)
+        self.assertEqual(op1.entity_type, 'alias')
+        self.assertEqual(op1.target, 'Example Inc.')
+        self.assertEqual(op1.trx, trx)
+        self.assertGreater(op1.timestamp, timestamp)
+
+        op1_args = json.loads(op1.args)
+        self.assertEqual(len(op1_args), 1)
+        self.assertEqual(op1_args['alias'], 'Example Inc.')
diff --git a/tests/test_db.py b/tests/test_db.py
index 617343e4..97ec142e 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -49,7 +49,8 @@
                                     Enrollment,
                                     Transaction,
                                     Operation,
-                                    ScheduledTask)
+                                    ScheduledTask,
+                                    Alias)
 
 DUPLICATED_ORG_ERROR = "Organization 'Example' already exists in the registry"
 DUPLICATED_DOM_ERROR = "Domain 'example.org' already exists in the registry"
@@ -58,6 +59,7 @@
 DUPLICATED_ID_ERROR = "Identity '1234567890ABCDFE' already exists in the registry"
 DUPLICATED_ID_DATA_ERROR = "Identity 'John Smith-jsmith@example.org-jsmith-scm' already exists in the registry"
 DUPLICATED_ENROLLMENT_ERROR = r"Identity '1234567890ABCDFE-.+' already exists in the registry"
+DUPLICATED_ALIAS_ERROR = "Alias 'Example Inc.' already exists in the registry"
 NAME_NONE_ERROR = "'name' cannot be None"
 NAME_EMPTY_ERROR = "'name' cannot be an empty string"
 NAME_WHITESPACES_ERROR = "'name' cannot be composed by whitespaces only"
@@ -79,11 +81,14 @@
 IDENTITY_DATA_EMPTY_ERROR = "'{name}' cannot be an empty string"
 IDENTITY_DATA_NONE_ERROR = "'{name}' cannot be None"
 IDENTITY_DATA_WHITESPACES_ERROR = "'{name}' cannot be composed by whitespaces only"
+ALIAS_NAME_NONE_ERROR = "'name' cannot be None"
+ALIAS_NAME_EMPTY_ERROR = "'name' cannot be an empty string"
 INDIVIDUAL_NOT_FOUND_ERROR = "zyxwuv not found in the registry"
 IDENTITY_NOT_FOUND_ERROR = "zyxwuv not found in the registry"
 ORGANIZATION_NOT_FOUND_ERROR = "Bitergia not found in the registry"
 GROUP_NOT_FOUND_ERROR = "Bitergia not found in the registry"
 DOMAIN_NOT_FOUND_ERROR = "example.net not found in the registry"
+ALIAS_NOT_FOUND_ERROR = "Example Ltd. not found in the registry"
 TEAM_NOT_FOUND_ERROR = "subTeam not found in the registry"
 TEAM_NAME_NONE_ERROR = "'team_name' cannot be None"
 TEAM_NAME_EMPTY_ERROR = "'{var}' cannot be an empty string"
@@ -105,6 +110,7 @@
 FORMAT_ALREADY_EXISTS = "'{}' already exists in the registry"
 MOVE_DOMAIN_ERROR = "Domain 'example.com' is already assigned to 'Example'"
 MOVE_TEAM_ERROR = "Team 'Example team' is already assigned to 'Example organization'"
+ALIAS_NAME_WHITESPACES_ERROR = "'name' cannot be composed by whitespaces only"
 
 
 class TestFindIndividual(TestCase):
@@ -216,6 +222,26 @@ def test_organization_not_found(self):
         with self.assertRaisesRegex(NotFoundError, ORGANIZATION_NOT_FOUND_ERROR):
             db.find_organization('Bitergia')
 
+    def test_find_alias(self):
+        """Test if an organization is found by its alias"""
+
+        org = Organization.add_root(name='Example')
+        alias_name = 'Example Inc.'
+        Alias.objects.create(alias=alias_name, organization=org)
+
+        organization = db.find_organization(alias_name)
+        self.assertIsInstance(organization, Organization)
+
+    def test_find_multiple_aliases(self):
+        """Test if an organization is found by one of its aliases"""
+
+        org = Organization.add_root(name='Example')
+        Alias.objects.create(alias='Example Inc.', organization=org)
+        Alias.objects.create(alias='Example Ltd.', organization=org)
+
+        organization = db.find_organization('Example Ltd.')
+        self.assertIsInstance(organization, Organization)
+
 
 class TestFindTeam(TestCase):
     """Unit tests for find_team"""
@@ -373,6 +399,56 @@ def test_domain_name_int(self):
             db.find_domain(12345)
 
 
+class TestFindAlias(TestCase):
+    """Unit tests for find_alias"""
+
+    def setUp(self):
+        """Load initial dataset"""
+
+        org = Organization.add_root(name='Example')
+        Alias.objects.create(alias='Example Inc.', organization=org)
+
+    def test_find_alias(self):
+        """Test if an alias is found by its name"""
+
+        alias = db.find_alias('Example Inc.')
+
+        # Tests
+        self.assertIsInstance(alias, Alias)
+        self.assertEqual(alias.organization.name, 'Example')
+        self.assertEqual(alias.alias, 'Example Inc.')
+
+    def test_alias_not_found(self):
+        """Test whether it raises an exception when the alias is not found"""
+
+        with self.assertRaisesRegex(NotFoundError, ALIAS_NOT_FOUND_ERROR):
+            db.find_alias('Example Ltd.')
+
+    def test_alias_name_none(self):
+        """Check if it fails when alias name is `None`"""
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_NONE_ERROR):
+            db.find_alias(None)
+
+    def test_alias_name_empty(self):
+        """Check if it fails when alias name is empty"""
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_EMPTY_ERROR):
+            db.find_alias('')
+
+    def test_alias_name_whitespaces(self):
+        """Check if it fails when alias name is composed by whitespaces"""
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.find_alias('    ')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.find_alias('\t')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.find_alias('  \t  ')
+
+
 class TestSearchEnrollmentsInPeriod(TestCase):
     """Unit tests for search_enrollments_in_period"""
 
@@ -653,6 +729,7 @@ def test_delete_organization(self):
         org_ex = Organization.add_root(name='Example')
         Domain.objects.create(domain='example.org',
                               organization=org_ex)
+        Alias.objects.create(alias='Example Inc.', organization=org_ex)
         org_bit = Organization.add_root(name='Bitergia')
 
         jsmith = Individual.objects.create(mk='AAAA')
@@ -685,6 +762,9 @@ def test_delete_organization(self):
         with self.assertRaises(ObjectDoesNotExist):
             Domain.objects.get(domain='example.org')
 
+        with self.assertRaises(ObjectDoesNotExist):
+            Alias.objects.get(alias='Example Inc.')
+
         enrollments = Enrollment.objects.filter(group__name='Example')
         self.assertEqual(len(enrollments), 0)
 
@@ -3189,3 +3269,209 @@ def test_operations(self):
         op1_args = json.loads(op1.args)
         self.assertEqual(len(op1_args), 1)
         self.assertEqual(op1_args['task'], str(task_id))
+
+
+class TestAddAlias(TestCase):
+    """"Unit tests for add_alias"""
+
+    def setUp(self):
+        """Load initial dataset"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.ctx = SortingHatContext(self.user)
+
+        self.trxl = TransactionsLog.open('add_alias', self.ctx)
+
+    def test_add_alias(self):
+        """Check if a new alias is added"""
+
+        name = 'Example'
+        alias = 'Example Inc.'
+
+        org = Organization.add_root(name=name)
+        als = db.add_alias(self.trxl, org, alias)
+        self.assertIsInstance(als, Alias)
+        self.assertEqual(als.alias, alias)
+        self.assertEqual(als.organization, org)
+
+        org = Organization.objects.get(name='Example')
+        aliases = org.aliases.all()
+        self.assertEqual(len(aliases), 1)
+
+        als = aliases[0]
+        self.assertIsInstance(als, Alias)
+        self.assertEqual(als.alias, alias)
+
+    def test_add_multiple_aliases(self):
+        """Check if multiple aliases can be added"""
+
+        org = Organization.add_root(name='Example')
+        db.add_alias(self.trxl, org, 'Example Inc.')
+        db.add_alias(self.trxl, org, 'Example Inc')
+
+        org = Organization.objects.get(name='Example')
+        aliases = org.aliases.all()
+        self.assertIsInstance(org, Organization)
+        self.assertEqual(org.name, 'Example')
+
+        self.assertEqual(len(aliases), 2)
+
+        als = aliases[0]
+        self.assertIsInstance(als, Alias)
+        self.assertEqual(als.alias, 'Example Inc')
+
+        als = aliases[1]
+        self.assertIsInstance(als, Alias)
+        self.assertEqual(als.alias, 'Example Inc.')
+
+    def test_existing_organization_error(self):
+        """Check if aliases with the same name as an organization cannot be added"""
+
+        org1 = Organization.add_root(name='Example')
+        org2 = Organization.add_root(name='Example Inc.')
+
+        with self.assertRaisesRegex(AlreadyExistsError, DUPLICATED_ORG_ERROR):
+            db.add_alias(self.trxl, org2, org1.name)
+
+        # Check if operations have not been generated after the failure
+        operations = Operation.objects.all()
+        self.assertEqual(len(operations), 0)
+
+    def test_alias_none(self):
+        """Check whether aliases with None name cannot be added"""
+
+        org = Organization.add_root(name='Example')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_NONE_ERROR):
+            db.add_alias(self.trxl, org, None)
+
+        # Check if operations have not been generated after the failure
+        operations = Operation.objects.all()
+        self.assertEqual(len(operations), 0)
+
+    def test_alias_empty(self):
+        """Check whether aliases with empty names cannot be added"""
+
+        org = Organization.add_root(name='Example')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_EMPTY_ERROR):
+            db.add_alias(self.trxl, org, '')
+
+        # Check if operations have not been generated after the failure
+        operations = Operation.objects.all()
+        self.assertEqual(len(operations), 0)
+
+    def test_aliases_whitespaces(self):
+        """Check whether aliases with names composed by whitespaces cannot be added"""
+
+        org = Organization.add_root(name='Example')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.add_alias(self.trxl, org, ' ')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.add_alias(self.trxl, org, '\t')
+
+        with self.assertRaisesRegex(ValueError, ALIAS_NAME_WHITESPACES_ERROR):
+            db.add_alias(self.trxl, org, ' \t ')
+
+        # Check if operations have not been generated after the failure
+        operations = Operation.objects.all()
+        self.assertEqual(len(operations), 0)
+
+    def test_integrity_error(self):
+        """Check whether aliases with the same name cannot be inserted"""
+
+        org = Organization.add_root(name='Example')
+        alias_name = 'Example Inc.'
+
+        with self.assertRaisesRegex(AlreadyExistsError, DUPLICATED_ALIAS_ERROR):
+            db.add_alias(self.trxl, org, alias_name)
+            db.add_alias(self.trxl, org, alias_name)
+
+    def test_operations(self):
+        """Check if the right operations are created when adding an alias"""
+
+        timestamp = datetime_utcnow()
+        org = Organization.add_root(name='Example')
+
+        db.add_alias(self.trxl, org, 'Example Inc.')
+
+        transactions = Transaction.objects.filter(name='add_alias')
+        trx = transactions[0]
+
+        operations = Operation.objects.filter(trx=trx)
+        self.assertEqual(len(operations), 1)
+
+        op1 = operations[0]
+        self.assertIsInstance(op1, Operation)
+        self.assertEqual(op1.op_type, Operation.OpType.ADD.value)
+        self.assertEqual(op1.entity_type, 'alias')
+        self.assertEqual(op1.trx, trx)
+        self.assertEqual(op1.target, 'Example')
+        self.assertGreater(op1.timestamp, timestamp)
+
+        op1_args = json.loads(op1.args)
+        self.assertEqual(len(op1_args), 2)
+        self.assertEqual(op1_args['organization'], 'Example')
+        self.assertEqual(op1_args['name'], 'Example Inc.')
+
+
+class TestDeleteAlias(TestCase):
+    """Unit tests for delete_alias"""
+
+    def setUp(self):
+        """Load initial dataset"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.ctx = SortingHatContext(self.user)
+
+        self.trxl = TransactionsLog.open('delete_alias', self.ctx)
+
+    def test_delete_alias(self):
+        """Check whether it deletes an alias"""
+
+        org = Organization.add_root(name='Example')
+        als = Alias.objects.create(alias='Example Inc.', organization=org)
+        Alias.objects.create(alias='Example Ltd.', organization=org)
+
+        # Check data and remove domain
+        org.refresh_from_db()
+        self.assertEqual(len(org.aliases.all()), 2)
+
+        als.refresh_from_db()
+        db.delete_alias(self.trxl, als)
+
+        # Tests
+        with self.assertRaises(ObjectDoesNotExist):
+            Alias.objects.get(alias='Example Inc.')
+
+        org.refresh_from_db()
+        self.assertEqual(len(org.aliases.all()), 1)
+
+    def test_operations(self):
+        """Check if the right operations are created when deleting an alias"""
+
+        timestamp = datetime_utcnow()
+        org = Organization.add_root(name='Example')
+        als = Alias.objects.create(alias='Example Inc.', organization=org)
+
+        db.delete_alias(self.trxl, als)
+
+        transactions = Transaction.objects.filter(name='delete_alias')
+        trx = transactions[0]
+
+        operations = Operation.objects.filter(trx=trx)
+        self.assertEqual(len(operations), 1)
+
+        op1 = operations[0]
+        self.assertIsInstance(op1, Operation)
+        self.assertEqual(op1.op_type, Operation.OpType.DELETE.value)
+        self.assertEqual(op1.entity_type, 'alias')
+        self.assertEqual(op1.trx, trx)
+        self.assertEqual(op1.target, 'Example Inc.')
+        self.assertGreater(op1.timestamp, timestamp)
+
+        op1_args = json.loads(op1.args)
+        self.assertEqual(len(op1_args), 1)
+        self.assertEqual(op1_args['alias'], 'Example Inc.')
diff --git a/tests/test_model.py b/tests/test_model.py
index ca4fd8c2..3187e3cf 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -48,7 +48,8 @@
                                     MergeRecommendation,
                                     GenderRecommendation,
                                     Tenant,
-                                    ScheduledTask)
+                                    ScheduledTask,
+                                    Alias)
 
 # Test check errors messages
 DUPLICATE_CHECK_ERROR = "Duplicate entry .+"
@@ -333,6 +334,63 @@ def test_last_modified(self):
         self.assertLessEqual(dom.last_modified, after_modified_dt)
 
 
+class TestAlias(TransactionTestCase):
+    """Unit tests for Alias class"""
+
+    def test_unique_aliases(self):
+        """Check whether aliases are unique"""
+
+        with self.assertRaisesRegex(IntegrityError, DUPLICATE_CHECK_ERROR):
+            org = Organization.add_root(name='Example')
+            Alias.objects.create(alias='Example Inc.', organization=org)
+            Alias.objects.create(alias='Example Inc.', organization=org)
+
+    def test_not_null_organizations(self):
+        """Check whether every alias is assigned to an organization"""
+
+        with self.assertRaisesRegex(IntegrityError, NULL_VALUE_CHECK_ERROR):
+            Alias.objects.create(alias='Example Inc.')
+
+    def test_created_at(self):
+        """Check creation date is only set when the object is created"""
+
+        before_dt = datetime_utcnow()
+        org = Organization.add_root(name='Example')
+        als = Alias.objects.create(alias='Example Inc.', organization=org)
+
+        after_dt = datetime_utcnow()
+
+        self.assertGreaterEqual(als.created_at, before_dt)
+        self.assertLessEqual(als.created_at, after_dt)
+
+        als.alias = 'Example Inc'
+        als.save()
+
+        self.assertEqual(als.alias, 'Example Inc')
+        self.assertGreaterEqual(als.created_at, before_dt)
+        self.assertLessEqual(als.created_at, after_dt)
+
+    def test_last_modified(self):
+        """Check last modification date is set when the object is updated"""
+
+        before_dt = datetime_utcnow()
+        org = Organization.add_root(name='Example')
+        als = Alias.objects.create(alias='Example Inc.', organization=org)
+        after_dt = datetime_utcnow()
+
+        self.assertGreaterEqual(als.last_modified, before_dt)
+        self.assertLessEqual(als.last_modified, after_dt)
+
+        before_modified_dt = datetime_utcnow()
+        als.alias = 'Example Inc'
+        als.save()
+        after_modified_dt = datetime_utcnow()
+
+        self.assertEqual(als.alias, 'Example Inc')
+        self.assertGreaterEqual(als.last_modified, before_modified_dt)
+        self.assertLessEqual(als.last_modified, after_modified_dt)
+
+
 class TestCountry(TransactionTestCase):
     """Unit tests for Country class"""
 
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 164fe12f..ff9c8fec 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -64,7 +64,8 @@
                                     MergeRecommendation,
                                     GenderRecommendation,
                                     AffiliationRecommendation,
-                                    ScheduledTask)
+                                    ScheduledTask,
+                                    Alias)
 from sortinghat.core.schema import (SortingHatQuery,
                                     SortingHatMutation,
                                     parse_date_filter)
@@ -75,6 +76,7 @@
 DUPLICATED_INDIVIDUAL = "Individual 'eda9f62ad321b1fbe5f283cc05e2484516203117' already exists in the registry"
 DUPLICATED_ENROLLMENT_ERROR = "range date '{}'-'{}' is part of an existing range for {}"
 DUPLICATED_RET_ERROR = "RecommenderExclusionTerm 'John Smith' already exists in the registry"
+DUPLICATED_ALIAS_ERROR = "Alias 'Example Inc.' already exists in the registry"
 TERM_EMPTY_ERROR = "'term' cannot be an empty string"
 TERM_EXAMPLE_DOES_NOT_EXIST_ERROR = "John Smith not found in the registry"
 RECOMMENDATION_MERGE_DOES_NOT_EXIST_ERROR = "MergeRecommendation matching query does not exist."
@@ -121,6 +123,7 @@
 TO_ORG_EMPTY_ERROR = "'to_org' cannot be an empty string"
 EQUAL_ORGS_ERROR = "'to_org' cannot be the same as 'from_org'"
 UNSUPPORTED_JOB_ERROR = "Job '{}' cannot be scheduled."
+ALIAS_NOT_FOUND_ERROR = "Example Inc. not found in the registry"
 
 # Test queries
 SH_COUNTRIES_QUERY = """{
@@ -182,6 +185,9 @@
         domain
         isTopDomain
       }
+      aliases {
+        alias
+      }
     }
   }
 }"""
@@ -1678,7 +1684,10 @@ def test_organizations(self):
         org = Organization.add_root(name='Example')
         Domain.objects.create(domain='example.com', organization=org)
         Domain.objects.create(domain='example.org', organization=org)
+        Alias.objects.create(alias='Example Inc.', organization=org)
+        Alias.objects.create(alias='Example Ltd.', organization=org)
         org = Organization.add_root(name='Bitergia')
+        Alias.objects.create(alias='Bitergium', organization=org)
         Domain.objects.create(domain='bitergia.com', organization=org)
         _ = Organization.add_root(name='LibreSoft')
 
@@ -1693,14 +1702,17 @@ def test_organizations(self):
         org1 = orgs[0]
         self.assertEqual(org1['name'], 'Bitergia')
         self.assertEqual(len(org1['domains']), 1)
+        self.assertEqual(len(org1['aliases']), 1)
 
         org2 = orgs[1]
         self.assertEqual(org2['name'], 'Example')
         self.assertEqual(len(org2['domains']), 2)
+        self.assertEqual(len(org2['aliases']), 2)
 
         org3 = orgs[2]
         self.assertEqual(org3['name'], 'LibreSoft')
         self.assertEqual(len(org3['domains']), 0)
+        self.assertEqual(len(org3['aliases']), 0)
 
     def test_empty_registry(self):
         """Check whether it returns an empty list when the registry is empty"""
@@ -1731,6 +1743,27 @@ def test_filter_registry(self):
         org = orgs[0]
         self.assertEqual(org['name'], org2.name)
 
+    def test_filter_registry_alias(self):
+        """Check whether it returns the organization with a matching alias when using name filter"""
+
+        org1 = Organization.add_root(name='Example')
+        org2 = Organization.add_root(name='Bitergia')
+        org3 = Organization.add_root(name='LibreSoft')
+
+        Alias.objects.create(alias='Example Inc.', organization=org1)
+
+        client = graphene.test.Client(schema)
+        test_query = SH_ORGS_QUERY_FILTER % 'Example Inc.'
+        executed = client.execute(test_query,
+                                  context_value=self.context_value)
+
+        orgs = executed['data']['organizations']['entities']
+        self.assertEqual(len(orgs), 1)
+
+        # As organizations are sorted by name, the first one will be org2
+        org = orgs[0]
+        self.assertEqual(org['name'], org1.name)
+
     def test_filter_non_exist_registry(self):
         """Check whether it returns an empty list when searched with a non existing organization"""
 
@@ -11089,6 +11122,9 @@ class TestMergeOrganizationsMutation(django.test.TestCase):
                     mk
                   }
                 }
+                aliases {
+                  alias
+                }
               }
             }
           }
@@ -11118,6 +11154,9 @@ def setUp(self):
                        domain_name='bitergia.com',
                        is_top_domain=True)
 
+        api.add_alias(self.ctx, organization='Example', name='Example Inc.')
+        api.add_alias(self.ctx, organization='Bitergia', name='Bitergium')
+
         api.add_team(self.ctx, 'Example team', organization='Example')
         api.add_team(self.ctx, 'Bitergia team', organization='Bitergia')
 
@@ -11169,6 +11208,12 @@ def test_merge_organizations(self):
         individual = enrollments[1]['individual']['mk']
         self.assertEqual(individual, 'c79e5c1587ac851b9b44720bd2242e9358a6eb70')
 
+        aliases = organization['aliases']
+        self.assertEqual(len(aliases), 3)
+        self.assertEqual(aliases[0]['alias'], 'Bitergium')
+        self.assertEqual(aliases[1]['alias'], 'Example')
+        self.assertEqual(aliases[2]['alias'], 'Example Inc.')
+
         # Check database objects
         organization_db = Organization.objects.get(name='Bitergia')
         self.assertIsInstance(organization_db, Organization)
@@ -11182,6 +11227,9 @@ def test_merge_organizations(self):
         enrollments = organization_db.enrollments.all()
         self.assertEqual(len(enrollments), 2)
 
+        aliases = organization_db.aliases.all()
+        self.assertEqual(len(aliases), 3)
+
         with self.assertRaises(django.core.exceptions.ObjectDoesNotExist):
             Organization.objects.get(name='Example')
 
@@ -11423,3 +11471,195 @@ def test_authentication(self):
         msg = executed['errors'][0]['message']
 
         self.assertEqual(msg, AUTHENTICATION_ERROR)
+
+
+class TestAddAliasMutation(django.test.TestCase):
+    """Unit tests for mutation to add aliases"""
+
+    SH_ADD_ALIAS = """
+      mutation addAlias {
+        addAlias(organization: "Example", alias: "Example Inc.")
+        {
+          alias {
+            alias
+            organization {
+              name
+            }
+          }
+        }
+      }
+    """
+
+    SH_ADD_ALIAS_EMPTY = """
+      mutation addAlias {
+        addAlias(organization: "Example", alias: "")
+        {
+          alias {
+            alias
+            organization {
+              name
+            }
+          }
+        }
+      }
+    """
+
+    def setUp(self):
+        """Set queries context"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.context_value = RequestFactory().get(GRAPHQL_ENDPOINT)
+        self.context_value.user = self.user
+
+    def test_add_alias(self):
+        """Check if a new alias is added"""
+
+        Organization.add_root(name='Example')
+
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_ADD_ALIAS,
+                                  context_value=self.context_value)
+
+        # Check result
+        alias = executed['data']['addAlias']['alias']
+        self.assertEqual(alias['alias'], 'Example Inc.')
+        self.assertEqual(alias['organization']['name'], 'Example')
+
+        # Check database
+        org = Organization.objects.get(name='Example')
+        aliases = org.aliases.all()
+        self.assertEqual(len(aliases), 1)
+
+        alias = aliases[0]
+        self.assertEqual(alias.alias, 'Example Inc.')
+
+    def test_alias_empty(self):
+        """Check whether aliases with empty names cannot be added"""
+
+        Organization.add_root(name='Example')
+
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_ADD_ALIAS_EMPTY,
+                                  context_value=self.context_value)
+
+        # Check error
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, NAME_EMPTY_ERROR)
+
+        # Check database
+        aliases = Alias.objects.all()
+        self.assertEqual(len(aliases), 0)
+
+    def test_integrity_error(self):
+        """Check whether duplicated aliases cannot be inserted"""
+
+        Organization.add_root(name='Example')
+
+        client = graphene.test.Client(schema)
+        client.execute(self.SH_ADD_ALIAS, context_value=self.context_value)
+
+        # Check database
+        alias = Alias.objects.get(alias='Example Inc.')
+        self.assertEqual(alias.alias, 'Example Inc.')
+
+        # Try to insert it twice
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_ADD_ALIAS,
+                                  context_value=self.context_value)
+
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, DUPLICATED_ALIAS_ERROR)
+
+    def test_not_found_organization(self):
+        """Check if it returns an error when an organization does not exist"""
+
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_ADD_ALIAS,
+                                  context_value=self.context_value)
+
+        # Check error
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, ORGANIZATION_EXAMPLE_DOES_NOT_EXIST_ERROR)
+
+    def test_authentication(self):
+        """Check if it fails when a non-authenticated user executes the query"""
+
+        context_value = RequestFactory().get(GRAPHQL_ENDPOINT)
+        context_value.user = AnonymousUser()
+
+        client = graphene.test.Client(schema)
+
+        executed = client.execute(self.SH_ADD_ALIAS,
+                                  context_value=context_value)
+
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, AUTHENTICATION_ERROR)
+
+
+class TestDeleteAliasMutation(django.test.TestCase):
+    """Unit tests for mutation to delete aliases"""
+
+    SH_DELETE_ALIAS = """
+      mutation deleteAlias {
+        deleteAlias(alias: "Example Inc.") {
+          alias {
+            alias
+          }
+        }
+      }
+    """
+
+    def setUp(self):
+        """Set queries context"""
+
+        self.user = get_user_model().objects.create(username='test')
+        self.context_value = RequestFactory().get(GRAPHQL_ENDPOINT)
+        self.context_value.user = self.user
+
+    def test_delete_alias(self):
+        """Check whether it deletes an alias"""
+
+        org = Organization.add_root(name='Example')
+        Alias.objects.create(alias='Example Inc.', organization=org)
+        Alias.objects.create(alias='Example Ltd.', organization=org)
+
+        # Delete organization
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_DELETE_ALIAS,
+                                  context_value=self.context_value)
+
+        # Check result
+        alias = executed['data']['deleteAlias']['alias']
+        self.assertEqual(alias['alias'], 'Example Inc.')
+
+        # Tests
+        with self.assertRaises(django.core.exceptions.ObjectDoesNotExist):
+            Alias.objects.get(alias='Example Inc.')
+
+        aliases = Alias.objects.all()
+        self.assertEqual(len(aliases), 1)
+
+    def test_not_found_alias(self):
+        """Check if it returns an error when an alias does not exist"""
+
+        client = graphene.test.Client(schema)
+        executed = client.execute(self.SH_DELETE_ALIAS,
+                                  context_value=self.context_value)
+
+        # Check error
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, ALIAS_NOT_FOUND_ERROR)
+
+    def test_authentication(self):
+        """Check if it fails when a non-authenticated user executes the query"""
+
+        context_value = RequestFactory().get(GRAPHQL_ENDPOINT)
+        context_value.user = AnonymousUser()
+
+        client = graphene.test.Client(schema)
+
+        executed = client.execute(self.SH_DELETE_ALIAS,
+                                  context_value=context_value)
+
+        msg = executed['errors'][0]['message']
+        self.assertEqual(msg, AUTHENTICATION_ERROR)
diff --git a/ui/tests/unit/__snapshots__/mutations.spec.js.snap b/ui/tests/unit/__snapshots__/mutations.spec.js.snap
index 8ecc9f11..75bd3677 100644
--- a/ui/tests/unit/__snapshots__/mutations.spec.js.snap
+++ b/ui/tests/unit/__snapshots__/mutations.spec.js.snap
@@ -2085,8 +2085,11 @@ exports[`OrganizationsTable Mock mutation for addDomain 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
@@ -2281,8 +2284,11 @@ exports[`OrganizationsTable Mock mutation for addOrganization 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
@@ -2477,8 +2483,11 @@ exports[`OrganizationsTable Mock mutation for deleteDomain 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
@@ -2673,8 +2682,11 @@ exports[`OrganizationsTable Mock mutation for deleteOrganization 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
@@ -2869,8 +2881,11 @@ exports[`OrganizationsTable Mock mutation for enroll 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
diff --git a/ui/tests/unit/__snapshots__/queries.spec.js.snap b/ui/tests/unit/__snapshots__/queries.spec.js.snap
index 2a438cd2..f38a92ab 100644
--- a/ui/tests/unit/__snapshots__/queries.spec.js.snap
+++ b/ui/tests/unit/__snapshots__/queries.spec.js.snap
@@ -804,8 +804,11 @@ exports[`OrganizationsTable Mock query for getPaginatedOrganizations 1`] = `
   </div>
    
   <organization-modal-stub
+    addalias="[Function]"
     adddomain="[Function]"
     addorganization="[Function]"
+    aliases=""
+    deletealias="[Function]"
     deletedomain="[Function]"
     domains=""
   />
diff --git a/ui/tests/unit/__snapshots__/storybook.spec.js.snap b/ui/tests/unit/__snapshots__/storybook.spec.js.snap
index ec118774..a549a2d4 100644
--- a/ui/tests/unit/__snapshots__/storybook.spec.js.snap
+++ b/ui/tests/unit/__snapshots__/storybook.spec.js.snap
@@ -10203,7 +10203,6 @@ exports[`Storyshots OrganizationModal Default 1`] = `
        
       <div
         class="v-dialog__container"
-        l=""
       >
         <!---->
       </div>
@@ -10239,7 +10238,6 @@ exports[`Storyshots OrganizationModal Error On Save 1`] = `
        
       <div
         class="v-dialog__container"
-        l=""
       >
         <!---->
       </div>
@@ -10367,7 +10365,7 @@ exports[`Storyshots OrganizationSelector Selected Organization 1`] = `
           <div
             aria-expanded="false"
             aria-haspopup="listbox"
-            aria-owns="list-1674"
+            aria-owns="list-1676"
             class="v-input__slot"
             role="combobox"
           >
@@ -10389,14 +10387,14 @@ exports[`Storyshots OrganizationSelector Selected Organization 1`] = `
             >
               <label
                 class="v-label v-label--active theme--light"
-                for="input-1674"
+                for="input-1676"
                 style="left: 0px; position: absolute;"
               >
                 Organization
               </label>
               <input
                 autocomplete="off"
-                id="input-1674"
+                id="input-1676"
                 type="text"
               />
               <div
@@ -10563,13 +10561,13 @@ exports[`Storyshots OrganizationsTable Groups 1`] = `
                 >
                   <label
                     class="v-label theme--light"
-                    for="input-1694"
+                    for="input-1698"
                     style="left: 0px; position: absolute;"
                   >
                     Search
                   </label>
                   <input
-                    id="input-1694"
+                    id="input-1698"
                     type="text"
                   />
                 </div>
@@ -10729,13 +10727,13 @@ exports[`Storyshots OrganizationsTable Groups 1`] = `
               >
                 <label
                   class="v-label v-label--active theme--light"
-                  for="input-1706"
+                  for="input-1710"
                   style="left: 0px; position: absolute;"
                 >
                   Items per page
                 </label>
                 <input
-                  id="input-1706"
+                  id="input-1710"
                   max="0"
                   min="1"
                   type="number"
@@ -10854,13 +10852,13 @@ exports[`Storyshots OrganizationsTable Organizations 1`] = `
                 >
                   <label
                     class="v-label theme--light"
-                    for="input-1747"
+                    for="input-1751"
                     style="left: 0px; position: absolute;"
                   >
                     Search
                   </label>
                   <input
-                    id="input-1747"
+                    id="input-1751"
                     type="text"
                   />
                 </div>
@@ -11020,13 +11018,13 @@ exports[`Storyshots OrganizationsTable Organizations 1`] = `
               >
                 <label
                   class="v-label v-label--active theme--light"
-                  for="input-1759"
+                  for="input-1763"
                   style="left: 0px; position: absolute;"
                 >
                   Items per page
                 </label>
                 <input
-                  id="input-1759"
+                  id="input-1763"
                   max="0"
                   min="1"
                   type="number"
@@ -11400,13 +11398,13 @@ exports[`Storyshots Search Default 1`] = `
               >
                 <label
                   class="v-label theme--light"
-                  for="input-1850"
+                  for="input-1854"
                   style="left: 0px; position: absolute;"
                 >
                   Search
                 </label>
                 <input
-                  id="input-1850"
+                  id="input-1854"
                   type="text"
                 />
               </div>
@@ -11527,13 +11525,13 @@ exports[`Storyshots Search Filter Selector 1`] = `
               >
                 <label
                   class="v-label theme--light"
-                  for="input-1859"
+                  for="input-1863"
                   style="left: 0px; position: absolute;"
                 >
                   Search
                 </label>
                 <input
-                  id="input-1859"
+                  id="input-1863"
                   type="text"
                 />
               </div>
@@ -11624,13 +11622,13 @@ exports[`Storyshots Search Order Selector 1`] = `
               >
                 <label
                   class="v-label theme--light"
-                  for="input-1871"
+                  for="input-1875"
                   style="left: 0px; position: absolute;"
                 >
                   Search
                 </label>
                 <input
-                  id="input-1871"
+                  id="input-1875"
                   type="text"
                 />
               </div>
@@ -11693,7 +11691,7 @@ exports[`Storyshots Search Order Selector 1`] = `
             <div
               aria-expanded="false"
               aria-haspopup="listbox"
-              aria-owns="list-1875"
+              aria-owns="list-1879"
               class="v-input__slot"
               role="button"
             >
@@ -11713,7 +11711,7 @@ exports[`Storyshots Search Order Selector 1`] = `
               >
                 <label
                   class="v-label theme--light"
-                  for="input-1875"
+                  for="input-1879"
                   style="left: 0px; position: absolute;"
                 >
                   Order by
@@ -11724,7 +11722,7 @@ exports[`Storyshots Search Order Selector 1`] = `
                   <input
                     aria-readonly="false"
                     autocomplete="off"
-                    id="input-1875"
+                    id="input-1879"
                     readonly="readonly"
                     type="text"
                   />
@@ -12660,7 +12658,7 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                   />
                   <input
                     aria-checked="false"
-                    id="input-2021"
+                    id="input-2025"
                     role="checkbox"
                     type="checkbox"
                     value=""
@@ -12671,7 +12669,7 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                 </div>
                 <label
                   class="v-label theme--light"
-                  for="input-2021"
+                  for="input-2025"
                   style="left: 0px; position: relative;"
                 >
                   Select all
@@ -12759,13 +12757,13 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                   >
                     <label
                       class="v-label theme--light"
-                      for="input-2026"
+                      for="input-2030"
                       style="left: 0px; position: absolute;"
                     >
                       Search
                     </label>
                     <input
-                      id="input-2026"
+                      id="input-2030"
                       type="text"
                     />
                   </div>
@@ -12828,7 +12826,7 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                 <div
                   aria-expanded="false"
                   aria-haspopup="listbox"
-                  aria-owns="list-2033"
+                  aria-owns="list-2037"
                   class="v-input__slot"
                   role="button"
                 >
@@ -12848,7 +12846,7 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                   >
                     <label
                       class="v-label theme--light"
-                      for="input-2033"
+                      for="input-2037"
                       style="left: 0px; position: absolute;"
                     >
                       Order by
@@ -12859,7 +12857,7 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                       <input
                         aria-readonly="false"
                         autocomplete="off"
-                        id="input-2033"
+                        id="input-2037"
                         readonly="readonly"
                         type="text"
                       />
@@ -13086,13 +13084,13 @@ exports[`Storyshots WorkSpace Drag And Drop 1`] = `
                   >
                     <label
                       class="v-label v-label--active theme--light"
-                      for="input-2055"
+                      for="input-2059"
                       style="left: 0px; position: absolute;"
                     >
                       Items per page
                     </label>
                     <input
-                      id="input-2055"
+                      id="input-2059"
                       max="0"
                       min="1"
                       type="number"
diff --git a/ui/tests/unit/mutations.spec.js b/ui/tests/unit/mutations.spec.js
index 1ef3defe..7ced83fd 100644
--- a/ui/tests/unit/mutations.spec.js
+++ b/ui/tests/unit/mutations.spec.js
@@ -429,6 +429,8 @@ describe("OrganizationsTable", () => {
         addTeam: () => {},
         deleteTeam: () => {},
         fetchTeams: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
@@ -461,6 +463,8 @@ describe("OrganizationsTable", () => {
         addTeam: () => {},
         deleteTeam: () => {},
         fetchTeams: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
@@ -489,6 +493,8 @@ describe("OrganizationsTable", () => {
         addTeam: () => {},
         deleteTeam: () => {},
         fetchTeams: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
@@ -517,6 +523,8 @@ describe("OrganizationsTable", () => {
         addTeam: () => {},
         deleteTeam: () => {},
         fetchTeams: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
@@ -544,6 +552,8 @@ describe("OrganizationsTable", () => {
         addTeam: () => {},
         deleteTeam: () => {},
         fetchTeams: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
diff --git a/ui/tests/unit/queries.spec.js b/ui/tests/unit/queries.spec.js
index 197e23a5..25b78164 100644
--- a/ui/tests/unit/queries.spec.js
+++ b/ui/tests/unit/queries.spec.js
@@ -436,6 +436,8 @@ describe("OrganizationsTable", () => {
         fetchTeams: () => {},
         addTeam: () => {},
         deleteTeam: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
     });
 
@@ -465,6 +467,8 @@ describe("OrganizationsTable", () => {
         fetchTeams: () => {},
         addTeam: () => {},
         deleteTeam: () => {},
+        addAlias: () => {},
+        deleteAlias: () => {},
       },
       data() {
         return {