diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index a4bb39e0764d235f25343ca497cc6f381d9eb653..31d45ad3a28559f632bed89ec25e3ab738083be7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -655,6 +655,7 @@ $status-icon-size: 22px;
 */
 $discord: #5865f2;
 $linkedin: #2867b2;
+$mastodon: #6364ff;
 $skype: #0078d7;
 $twitter: #1d9bf0;
 
diff --git a/app/assets/stylesheets/page_bundles/profile.scss b/app/assets/stylesheets/page_bundles/profile.scss
index dbe82f583d1e898a2cb974cf9b8b8bb15b54bdab..6b31c6678323f90ddfe25c811839d3f0e6f1cd3e 100644
--- a/app/assets/stylesheets/page_bundles/profile.scss
+++ b/app/assets/stylesheets/page_bundles/profile.scss
@@ -242,6 +242,10 @@
   color: $discord;
 }
 
+.mastodon-icon {
+  color: $mastodon;
+}
+
 .key-created-at {
   line-height: 42px;
 }
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 1f05e4e7b21c267ae5712ee4d86f0c8f8179d7e8..50e0c5cc5ffedabd8d5d57567a4be266e6f62fd0 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -342,6 +342,7 @@ def allowed_user_params
       :bio,
       :can_create_group,
       :color_scheme_id,
+      :discord,
       :email,
       :extern_uid,
       :external,
@@ -350,6 +351,7 @@ def allowed_user_params
       :hide_no_ssh_key,
       :key_id,
       :linkedin,
+      :mastodon,
       :name,
       :password_expires_at,
       :projects_limit,
@@ -358,7 +360,6 @@ def allowed_user_params
       :skype,
       :theme_id,
       :twitter,
-      :discord,
       :username,
       :website_url,
       :note,
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index da15b393e6c9bade0a5984330fc1903169d1de78..cb29f0f35392ca023583d563e80789208244696b 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -111,6 +111,7 @@ def user_params_attributes
     [
       :avatar,
       :bio,
+      :discord,
       :email,
       :role,
       :gitpod_enabled,
@@ -119,12 +120,12 @@ def user_params_attributes
       :hide_project_limit,
       :linkedin,
       :location,
+      :mastodon,
       :name,
       :public_email,
       :commit_email,
       :skype,
       :twitter,
-      :discord,
       :username,
       :website_url,
       :organization,
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 579373539558c01b01374821347f308d4d5bfda7..541a71d3302f80afbdaa9406dbac11fe5d1d8913 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -371,6 +371,14 @@ def discord_url(user)
     "https://discord.com/users/#{user.discord}"
   end
 
+  def mastodon_url(user)
+    return '' if user.mastodon.blank?
+
+    url = user.mastodon.match UserDetail::MASTODON_VALIDATION_REGEX
+
+    "https://#{url[2]}/@#{url[1]}"
+  end
+
   def collapsed_sidebar?
     cookies["sidebar_collapsed"] == "true"
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4034677509f82500a1aaacad94de5b2e57b8126a..74a09c966f725ad2a436f42d11802ed89d2dfd4b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -417,6 +417,7 @@ def update_tracked_fields!(request)
   delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
   delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
   delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
+  delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
   delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true
   delegate :twitter, :twitter=, to: :user_detail, allow_nil: true
   delegate :skype, :skype=, to: :user_detail, allow_nil: true
diff --git a/app/models/user_detail.rb b/app/models/user_detail.rb
index 9ac814eebdabd31ecd954fc48858b2dd85969836..bbb08ed577430c047ddd50b96e9ba58124a57460 100644
--- a/app/models/user_detail.rb
+++ b/app/models/user_detail.rb
@@ -17,10 +17,24 @@ class UserDetail < MainClusterwide::ApplicationRecord
 
   DEFAULT_FIELD_LENGTH = 500
 
+  MASTODON_VALIDATION_REGEX = /
+    \A            # beginning of string
+    @?\b          # optional leading at
+    ([\w\d.%+-]+) # character group to pick up words in user portion of username
+    @             # separator between user and host
+    (             # beginning of charagter group for host portion
+      [\w\d.-]+   # character group to pick up words in host portion of username
+      \.\w{2,}    # pick up tld of host domain, 2 chars or more
+    )\b           # end of character group to pick up words in host portion of username
+    \z            # end of string
+  /x
+
   validates :discord, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
   validate :discord_format
   validates :linkedin, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
   validates :location, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
+  validates :mastodon, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
+  validate :mastodon_format
   validates :organization, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
   validates :skype, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
   validates :twitter, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
@@ -32,7 +46,7 @@ class UserDetail < MainClusterwide::ApplicationRecord
   enum registration_objective: REGISTRATION_OBJECTIVE_PAIRS, _suffix: true
 
   def sanitize_attrs
-    %i[discord linkedin skype twitter website_url].each do |attr|
+    %i[discord linkedin mastodon skype twitter website_url].each do |attr|
       value = self[attr]
       self[attr] = Sanitize.clean(value) if value.present?
     end
@@ -49,6 +63,7 @@ def prevent_nil_fields
     self.discord = '' if discord.nil?
     self.linkedin = '' if linkedin.nil?
     self.location = '' if location.nil?
+    self.mastodon = '' if mastodon.nil?
     self.organization = '' if organization.nil?
     self.skype = '' if skype.nil?
     self.twitter = '' if twitter.nil?
@@ -62,4 +77,10 @@ def discord_format
   errors.add(:discord, _('must contain only a discord user ID.'))
 end
 
+def mastodon_format
+  return if mastodon.blank? || mastodon =~ UserDetail::MASTODON_VALIDATION_REGEX
+
+  errors.add(:mastodon, _('must contain only a mastodon username.'))
+end
+
 UserDetail.prepend_mod_with('UserDetail')
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 4da48771ba341a5c4d9d095bfe54afaa4f64af01..002953b165831c64b60451e3a9914788fce88e58 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -122,6 +122,10 @@
           allow_empty: true}
         %small.form-text.text-gl-muted
           = external_accounts_docs_link
+      - if Feature.enabled?(:mastodon_social_ui, @user)
+        .form-group.gl-form-group
+          = f.label :mastodon
+          = f.text_field :mastodon, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: "@robin@example.com"
 
       .form-group.gl-form-group
         = f.label :website_url, s_('Profiles|Website url')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0881c5bba5430a50bc2cf7ccfa82434ce14f51a2..29360dc7c9484d21720dd24c8fe1cfb4e5d99aba 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -111,6 +111,10 @@
                   = render 'middle_dot_divider', breakpoint: 'sm' do
                     = link_to discord_url(@user), class: 'gl-hover-text-decoration-none', title: "Discord", target: '_blank', rel: 'noopener noreferrer nofollow' do
                       = sprite_icon('discord', css_class: 'discord-icon')
+                - if Feature.enabled?(:mastodon_social_ui, @user) && @user.mastodon.present?
+                  = render 'middle_dot_divider', breakpoint: 'sm' do
+                    = link_to mastodon_url(@user), class: 'gl-hover-text-decoration-none', title: "Mastodon", target: '_blank', rel: 'noopener noreferrer nofollow' do
+                      = sprite_icon('mastodon', css_class: 'mastodon-icon')
                 - if @user.website_url.present?
                   = render 'middle_dot_divider', stacking: true do
                     - if Feature.enabled?(:security_auto_fix) && @user.bot?
diff --git a/config/feature_flags/development/mastodon_social_ui.yml b/config/feature_flags/development/mastodon_social_ui.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5e04d8176e4924d1353dfb4d61f315aeeac1188b
--- /dev/null
+++ b/config/feature_flags/development/mastodon_social_ui.yml
@@ -0,0 +1,8 @@
+---
+name: mastodon_social_ui
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132892
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/428163
+milestone: '16.5'
+type: development
+group: group::tenant scale
+default_enabled: false
diff --git a/db/migrate/20230927124202_add_mastodon_to_user_details.rb b/db/migrate/20230927124202_add_mastodon_to_user_details.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1aa099087b98c51a3ccea141777e3a930e082c2
--- /dev/null
+++ b/db/migrate/20230927124202_add_mastodon_to_user_details.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddMastodonToUserDetails < Gitlab::Database::Migration[2.1]
+  disable_ddl_transaction!
+
+  USER_DETAILS_FIELD_LIMIT = 500
+
+  def up
+    with_lock_retries do
+      add_column :user_details, :mastodon, :text, default: '', null: false, if_not_exists: true
+    end
+
+    add_text_limit :user_details, :mastodon, USER_DETAILS_FIELD_LIMIT
+  end
+
+  def down
+    with_lock_retries do
+      remove_column :user_details, :mastodon
+    end
+  end
+end
diff --git a/db/schema_migrations/20230927124202 b/db/schema_migrations/20230927124202
new file mode 100644
index 0000000000000000000000000000000000000000..a4089994e9799c7cb3aed26b67930ce7d6a3aef2
--- /dev/null
+++ b/db/schema_migrations/20230927124202
@@ -0,0 +1 @@
+652375e6b7318fe85b4b23eac3cce88618136341cee7721522adacbe52a52c66
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d7d5d469d9eb12a7f5199c0ecd6758671a271089..6a9cdb508214152b66349728a56662b5d5a7536a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -24088,6 +24088,7 @@ CREATE TABLE user_details (
     enterprise_group_id bigint,
     enterprise_group_associated_at timestamp with time zone,
     email_reset_offered_at timestamp with time zone,
+    mastodon text DEFAULT ''::text NOT NULL,
     CONSTRAINT check_245664af82 CHECK ((char_length(webauthn_xid) <= 100)),
     CONSTRAINT check_444573ee52 CHECK ((char_length(skype) <= 500)),
     CONSTRAINT check_466a25be35 CHECK ((char_length(twitter) <= 500)),
@@ -24099,6 +24100,7 @@ CREATE TABLE user_details (
     CONSTRAINT check_8a7fcf8a60 CHECK ((char_length(location) <= 500)),
     CONSTRAINT check_a73b398c60 CHECK ((char_length(phone) <= 50)),
     CONSTRAINT check_eeeaf8d4f0 CHECK ((char_length(pronouns) <= 50)),
+    CONSTRAINT check_f1a8a05b9a CHECK ((char_length(mastodon) <= 500)),
     CONSTRAINT check_f932ed37db CHECK ((char_length(pronunciation) <= 255))
 );
 
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 6536a992292e4fe0ff6feaa8b9b0be00cff512be..fea9bc491cf9a4b75cae6a5ca86bc16b29cb927d 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -128,6 +128,8 @@ to match your username.
 
 ## Add external accounts to your user profile page
 
+> Mastodon user account [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132892) as a beta feature in 16.5 [with a flag](../feature_flags.md) named `mastodon_social_ui`. Disabled by default.
+
 You can add links to certain other external accounts you might have, like Skype and Twitter.
 They can help other users connect with you on other platforms.
 
@@ -138,6 +140,7 @@ To add links to other accounts:
 1. In the **Main settings** section, add your:
    - Discord [user ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-).
    - LinkedIn profile name.
+   - Mastodon username.
    - Skype username.
    - Twitter @username.
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f4e362f05d15f8a22434ea09a5358f177ee71020..9c223a5381cceaa246a4313936cc58ecc402c494 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -57148,6 +57148,9 @@ msgstr ""
 msgid "must contain only a discord user ID."
 msgstr ""
 
+msgid "must contain only a mastodon username."
+msgstr ""
+
 msgid "must have a repository"
 msgstr ""
 
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 2bcb47f97ab00a25c11dddcb99b3123e2a33c4e4..4f350ddf1ef4da4a3d452e4531747f19a3eda8f3 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -128,6 +128,16 @@
       expect(user.reload.discord).to eq(discord_user_id)
       expect(response).to have_gitlab_http_status(:found)
     end
+
+    it 'allows updating user specified mastodon username', :aggregate_failures do
+      mastodon_username = '@robin@example.com'
+      sign_in(user)
+
+      put :update, params: { user: { mastodon: mastodon_username } }
+
+      expect(user.reload.mastodon).to eq(mastodon_username)
+      expect(response).to have_gitlab_http_status(:found)
+    end
   end
 
   describe 'GET audit_log' do
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 7cf64c6e049befd763793b899f83dbc64c6d21bc..3e95cb25b1208f9cdc167e69298d6cba2dbd0d1c 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -637,6 +637,21 @@ def stub_controller_method(method_name, value)
         expect(discord).to eq('https://discord.com/users/1234567890123456789')
       end
     end
+
+    context 'when mastodon is set' do
+      let_it_be(:user) { build(:user) }
+      let(:mastodon) { mastodon_url(user) }
+
+      it 'returns an empty string if mastodon username is not set' do
+        expect(mastodon).to eq('')
+      end
+
+      it 'returns mastodon url when mastodon username is set' do
+        user.mastodon = '@robin@example.com'
+
+        expect(mastodon).to eq('https://example.com/@robin')
+      end
+    end
   end
 
   describe '#gitlab_ui_form_for' do
diff --git a/spec/models/user_detail_spec.rb b/spec/models/user_detail_spec.rb
index 428fd5470c3127e38b98e8f3e3e53b28e6265b6b..b443988cde9aa200ec0797ec9eba17b52e137275 100644
--- a/spec/models/user_detail_spec.rb
+++ b/spec/models/user_detail_spec.rb
@@ -59,6 +59,27 @@
       end
     end
 
+    describe '#mastodon' do
+      it { is_expected.to validate_length_of(:mastodon).is_at_most(500) }
+
+      context 'when mastodon is set' do
+        let_it_be(:user_detail) { create(:user_detail) }
+
+        it 'accepts a valid mastodon username' do
+          user_detail.mastodon = '@robin@example.com'
+
+          expect(user_detail).to be_valid
+        end
+
+        it 'throws an error when mastodon username format is wrong' do
+          user_detail.mastodon = '@robin'
+
+          expect(user_detail).not_to be_valid
+          expect(user_detail.errors.full_messages).to match_array([_('Mastodon must contain only a mastodon username.')])
+        end
+      end
+    end
+
     describe '#location' do
       it { is_expected.to validate_length_of(:location).is_at_most(500) }
     end
@@ -97,6 +118,7 @@
         discord: '1234567890123456789',
         linkedin: 'linkedin',
         location: 'location',
+        mastodon: '@robin@example.com',
         organization: 'organization',
         skype: 'skype',
         twitter: 'twitter',
@@ -117,6 +139,7 @@
     it_behaves_like 'prevents `nil` value', :discord
     it_behaves_like 'prevents `nil` value', :linkedin
     it_behaves_like 'prevents `nil` value', :location
+    it_behaves_like 'prevents `nil` value', :mastodon
     it_behaves_like 'prevents `nil` value', :organization
     it_behaves_like 'prevents `nil` value', :skype
     it_behaves_like 'prevents `nil` value', :twitter
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 947d83badf6365314a8006dfbb0d738b7ecdccfe..c9da1a31c8686e516dfa233bb8086dd0806535f4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -113,6 +113,9 @@
     it { is_expected.to delegate_method(:linkedin).to(:user_detail).allow_nil }
     it { is_expected.to delegate_method(:linkedin=).to(:user_detail).with_arguments(:args).allow_nil }
 
+    it { is_expected.to delegate_method(:mastodon).to(:user_detail).allow_nil }
+    it { is_expected.to delegate_method(:mastodon=).to(:user_detail).with_arguments(:args).allow_nil }
+
     it { is_expected.to delegate_method(:twitter).to(:user_detail).allow_nil }
     it { is_expected.to delegate_method(:twitter=).to(:user_detail).with_arguments(:args).allow_nil }