diff --git a/app/assets/javascripts/admin/commons/users.js b/app/assets/javascripts/admin/commons/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..5ed2861f2dd2e95d9485645c424df4a315805ad0
--- /dev/null
+++ b/app/assets/javascripts/admin/commons/users.js
@@ -0,0 +1,34 @@
+/*global $ */
+$(function () {
+    'use strict';
+
+    var changeRole = function () {
+        var value = $('select[name="user[role]"]').val(),
+            showForRoles,
+            required;
+
+        $('*[data-show-for-roles]').each(function () {
+            showForRoles = $(this)
+                .attr('data-show-for-roles')
+                .split(',');
+            if ($.inArray(value, showForRoles) > -1) {
+                required = $(this).attr('data-required');
+                if (required) {
+                    $('input, select', this).attr('required', 'required');
+                } else {
+                    $('input, select', this).removeAttr('required');
+                }
+                $(this).show();
+            } else {
+                $(this).hide();
+                // hidden field cannot be required
+                $('input, select', this).removeAttr('required');
+            }
+        });
+    };
+
+    if ($('body').is('.users-edit, .users-new, .users-update, .users-create')) {
+        changeRole();
+        $('select[name="user[role]"]').change(changeRole);
+    }
+});
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a9525a068f29656d69c280b9235544c03c2b50ff..1d00d143ee4a55d2065f34f21947215c3da09892 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -74,7 +74,7 @@ class Admin::UsersController < Admin::ApplicationController
 
   def user_params
     params.require(:user)
-          .permit(:email, :first_name, :last_name, :role, :password, :language_id, :picture, :picture_delete, :picture_infos, :mobile_phone)
+          .permit(:email, :first_name, :last_name, :role, :password, :language_id, :picture, :picture_delete, :picture_infos, :mobile_phone, programs_to_manage_ids: [])
           .merge(university_id: current_university.id)
   end
 
diff --git a/app/models/user/with_roles.rb b/app/models/user/with_roles.rb
index 84fb51a7d8304eb19685f7a6aebcbe78ab525e5c..aeced21b870114381519cb54074d7d648eaa9879 100644
--- a/app/models/user/with_roles.rb
+++ b/app/models/user/with_roles.rb
@@ -6,6 +6,11 @@ module User::WithRoles
 
     enum role: { visitor: 0, teacher: 10, program_manager: 12, admin: 20, server_admin: 30 }
 
+    has_and_belongs_to_many :programs_to_manage,
+                            class_name: 'Education::Program',
+                            join_table: 'education_programs_users',
+                            association_foreign_key: 'education_program_id'
+
     scope :for_role, -> (role) { where(role: role) }
 
     before_validation :set_default_role, on: :create
diff --git a/app/views/admin/users/_form.html.erb b/app/views/admin/users/_form.html.erb
index 15f7c0d6dcbab185cd9c2497597f2180e4fe6112..6123085cb096c77b6e8ceca267f5e6f78b83db83 100644
--- a/app/views/admin/users/_form.html.erb
+++ b/app/views/admin/users/_form.html.erb
@@ -30,6 +30,13 @@
                       input_html: { autocomplete: "new-password" } %>
           <%= f.input :mobile_phone %>
           <%= f.input :role, include_blank: false, collection: current_user.managed_roles, label_method: lambda { |k| t("activerecord.attributes.user.roles.#{k[1]}")} %>
+          <div data-show-for-roles="program_manager">
+            <%= f.association :programs_to_manage,
+                              as: :check_boxes,
+                              collection: collection_tree(current_university.education_programs.ordered),
+                              label_method: ->(p) { sanitize p[:label] },
+                              value_method: ->(p) { p[:id] } %>
+          </div>
         </div>
       </div>
     </div>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 06e4495933c11bbc3086c5105dda897b6d7d5e47..d773525f30427ab2f04f0a94d4cf1b0a46438b86 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -12,6 +12,7 @@ en:
         person: Person
         mobile_phone: Mobile phone
         picture: Profile picture
+        programs_to_manage: Program(s) managed
         role: Role
         roles:
           admin: Administrator
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a9e297ee7b7d1a3176669eac3eb90c09c82a983f..aeac58ec06554824efc8ca347268a16586d28606 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -12,6 +12,7 @@ fr:
         person: Personne
         mobile_phone: Téléphone portable
         picture: Photo de profil
+        programs_to_manage: Formation(s) gérée(s)
         role: Rôle
         roles:
           admin: Administrateur
diff --git a/db/migrate/20220203160802_create_program_user_join_table.rb b/db/migrate/20220203160802_create_program_user_join_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8a3b6fd41847c04de257445984e4eba4cb1a69ed
--- /dev/null
+++ b/db/migrate/20220203160802_create_program_user_join_table.rb
@@ -0,0 +1,9 @@
+class CreateProgramUserJoinTable < ActiveRecord::Migration[6.1]
+  def change
+    create_table "education_programs_users", id: false, force: :cascade do |t|
+      t.uuid "education_program_id", null: false
+      t.uuid "user_id", null: false
+      t.index ["education_program_id", "user_id"], name: "index_education_programs_users_on_program_id_and_user_id"
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c2d823d76cade935dd6213bf06ead3cf8d8c8a28..69faf1dd08219eaefd1d8aa3edc4015d2403dba9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2022_01_31_163458) do
+ActiveRecord::Schema.define(version: 2022_02_03_160802) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -379,6 +379,12 @@ ActiveRecord::Schema.define(version: 2022_01_31_163458) do
     t.index ["education_school_id", "education_program_id"], name: "school_program"
   end
 
+  create_table "education_programs_users", id: false, force: :cascade do |t|
+    t.uuid "education_program_id", null: false
+    t.uuid "user_id", null: false
+    t.index ["education_program_id", "user_id"], name: "index_education_programs_users_on_program_id_and_user_id"
+  end
+
   create_table "education_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"