diff --git a/app/controllers/admin/education/program/role/people_controller.rb b/app/controllers/admin/education/program/role/people_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6202bf3e80f4bf070433fb13bcb5163d4688596b
--- /dev/null
+++ b/app/controllers/admin/education/program/role/people_controller.rb
@@ -0,0 +1,40 @@
+class Admin::Education::Program::Role::PeopleController < Admin::Education::Program::ApplicationController
+  load_and_authorize_resource :role, class: Education::Program::Role, through: :program
+  load_and_authorize_resource class: Education::Program::Role::Person, through: :role
+
+  include Admin::Reorderable 
+
+  def new
+    breadcrumb
+  end
+
+  def create
+    if @person.save
+      redirect_to admin_education_program_role_path(@role), notice: t('admin.successfully_created_html', model: @person.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def destroy
+    @person.destroy
+    redirect_to admin_education_program_role_path(@role), notice: t('admin.successfully_destroyed_html', model: @person.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb Education::Program::Role.model_name.human(count: 2)
+    breadcrumb_for @role
+    add_breadcrumb Education::Program::Role::Person.model_name.human(count: 2)
+    breadcrumb_for @person
+  end
+
+  def person_params
+    params.require(:education_program_role_person)
+          .permit(:person_id)
+          .merge(role_id: @role.id)
+  end
+end
diff --git a/app/controllers/admin/education/program/roles_controller.rb b/app/controllers/admin/education/program/roles_controller.rb
index b3488e4ad86c359ce2404b33cffcc1b10c3a5450..94d3598b32e5e3ee016155862000a0b19c5e4897 100644
--- a/app/controllers/admin/education/program/roles_controller.rb
+++ b/app/controllers/admin/education/program/roles_controller.rb
@@ -1,6 +1,8 @@
 class Admin::Education::Program::RolesController < Admin::Education::Program::ApplicationController
   load_and_authorize_resource class: Education::Program::Role, through: :program
 
+  include Admin::Reorderable
+
   def show
     breadcrumb
   end
diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb
index 36099f9fed95cad1cfb9c360a425e38a7bebd062..fe3fb85225e9da1d2bdbc5f3fd3ab86eda26e39b 100644
--- a/app/models/communication/website/post.rb
+++ b/app/models/communication/website/post.rb
@@ -59,6 +59,7 @@ class Communication::Website::Post < ApplicationRecord
 
   before_validation :set_published_at, if: :published_changed?
 
+  scope :published, -> { where(published: true) }
   scope :ordered, -> { order(published_at: :desc, created_at: :desc) }
   scope :recent, -> { order(published_at: :desc).limit(5) }
 
diff --git a/app/models/education/program/role.rb b/app/models/education/program/role.rb
index 5a0365f34fd024730760fe4298958fd0c9d4c693..d79798d0670750128e13cd299656cdf71e3c0619 100644
--- a/app/models/education/program/role.rb
+++ b/app/models/education/program/role.rb
@@ -25,6 +25,8 @@ class Education::Program::Role < ApplicationRecord
 
   belongs_to :university
   belongs_to :program, class_name: 'Education::Program'
+  has_many :people, class_name: 'Education::Program::Role::Person', dependent: :destroy
+  has_many :university_people, through: :people, source: :person
 
   def to_s
     "#{title}"
diff --git a/app/models/education/program/role/person.rb b/app/models/education/program/role/person.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f61f1d20ff98b9b25167825d552f6a91768f83a0
--- /dev/null
+++ b/app/models/education/program/role/person.rb
@@ -0,0 +1,37 @@
+# == Schema Information
+#
+# Table name: education_program_role_people
+#
+#  id         :uuid             not null, primary key
+#  position   :integer
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  person_id  :uuid             not null
+#  role_id    :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_role_people_on_person_id  (person_id)
+#  index_education_program_role_people_on_role_id    (role_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (person_id => university_people.id)
+#  fk_rails_...  (role_id => education_program_roles.id)
+#
+class Education::Program::Role::Person < ApplicationRecord
+  include WithPosition
+
+  belongs_to :person, class_name: 'University::Person'
+  belongs_to :role, class_name: 'Education::Program::Role'
+
+  def to_s
+    person.to_s
+  end
+
+  protected
+
+  def last_ordered_element
+    role.people.ordered.last
+  end
+end
diff --git a/app/models/university/person.rb b/app/models/university/person.rb
index 68299e2fa74a136970c24d63b7be6eef2e7ef09c..0a8223ff4d9a1449b8d16df3446a2f7e44009083 100644
--- a/app/models/university/person.rb
+++ b/app/models/university/person.rb
@@ -100,16 +100,31 @@ class University::Person < ApplicationRecord
   end
 
   def identifiers(website: nil)
-    website_id = website&.id
     list = []
-    # TODO :administrator
-    [:author, :researcher, :teacher].each do |role|
-      list << role if send("#{role.to_s}_websites").pluck(:id).include?(website_id)
+    [:author, :researcher, :teacher, :administrator].each do |role|
+      list << role if public_send("is_#{role.to_s}_for_website", website)
     end
     list << :static unless list.empty?
     list
   end
 
+  def is_author_for_website(website)
+    is_author && communication_website_posts.published.where(communication_website_id: website&.id).any?
+  end
+
+  def is_researcher_for_website(website)
+    is_researcher
+  end
+
+  def is_teacher_for_website(website)
+    is_teacher && website.programs.published.joins(:teachers).where(education_program_teachers: { person_id: id }).any?
+  end
+
+  def is_administrator_for_website(website)
+    # TODO
+    is_administrative
+  end
+
   def git_path_static
     "content/persons/#{slug}.html"
   end
diff --git a/app/views/admin/education/program/role/people/_list.html.erb b/app/views/admin/education/program/role/people/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..fc31ddd11a709929630337c2f87dde710821fc42
--- /dev/null
+++ b/app/views/admin/education/program/role/people/_list.html.erb
@@ -0,0 +1,30 @@
+<% if people.any? %>
+  <table class="table table-sortable">
+    <thead>
+      <tr>
+        <th><%= Education::Program::Role::Person.model_name.human %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody data-reorder-url="<%= reorder_admin_education_program_role_people_path(program_id: @program.id, role_id: @role.id) %>">
+      <% people.each do |person| %>
+        <tr class="handle" data-id="<%= person.id %>">
+          <td>
+            <%= link_to_if  can?(:read, person.person),
+                            person.person,
+                            admin_university_person_path(person.person) %>
+          </td>
+          <td class="text-end pe-0">
+            <div class="btn-group" role="group">
+              <%= link_to t('delete'),
+                          admin_education_program_role_person_path(person, { role_id: @role.id }),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/program/role/people/new.html.erb b/app/views/admin/education/program/role/people/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..402d1727d98c843d5b6807ec06bdd7d73a1f607b
--- /dev/null
+++ b/app/views/admin/education/program/role/people/new.html.erb
@@ -0,0 +1,20 @@
+<% content_for :title, Education::Program::Role::Person.model_name.human %>
+
+<%= simple_form_for [:admin, @person] do |f| %>
+  <div class="row">
+    <div class="col-md-8">
+      <div class="card flex-fill w-100">
+        <div class="card-header">
+          <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
+        </div>
+        <div class="card-body">
+          <% used_person_ids = @role.people.where.not(id: @person.id).pluck(:person_id) %>
+          <%= f.association :person, collection: current_university.people.where.not(id: used_person_ids).ordered %>
+        </div>
+      </div>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/education/program/roles/_list.html.erb b/app/views/admin/education/program/roles/_list.html.erb
index b3a3b5fee618931e112cf5e7958f796ff8e657cb..9fbda8e94303c28ea6539804cf6efe4186e3961f 100644
--- a/app/views/admin/education/program/roles/_list.html.erb
+++ b/app/views/admin/education/program/roles/_list.html.erb
@@ -1,18 +1,20 @@
-<table class="table">
+<table class="table table-sortable">
   <thead>
     <tr>
       <th><%= Education::Program::Role.model_name.human %></th>
+      <th><%= Education::Program::Role.human_attribute_name('people') %></th>
       <th></th>
     </tr>
   </thead>
-  <tbody>
+  <tbody data-reorder-url="<%= reorder_admin_education_program_roles_path(program_id: @program.id) %>">
     <% roles.each do |role| %>
-      <tr>
+      <tr class="handle" data-id="<%= role.id %>">
         <td>
           <%= link_to_if  can?(:read, role),
                           role,
                           admin_education_program_role_path(role, { program_id: @program.id }) %>
         </td>
+        <td><%= role.people.includes(:person).ordered.map { |person| person.person.to_s }.to_sentence %></td>
         <td class="text-end pe-0">
           <div class="btn-group" role="group">
             <%= link_to t('edit'),
diff --git a/app/views/admin/education/program/roles/show.html.erb b/app/views/admin/education/program/roles/show.html.erb
index e6d4117f32fda150675fcedacdeab4a3c54ae5d0..051d0859a6b646ee517b4ced7cfab8b9568fae08 100644
--- a/app/views/admin/education/program/roles/show.html.erb
+++ b/app/views/admin/education/program/roles/show.html.erb
@@ -12,6 +12,17 @@
       </div>
     </div>
   </div>
+  <div class="col-md-8">
+    <div class="card flex-fill w-100">
+      <div class="card-header">
+        <h2 class="card-title mb-0 h5"><%= Education::Program::Role.human_attribute_name('people') %></h2>
+      </div>
+      <div class="card-body">
+        <p><%= link_to t('create'), new_admin_education_program_role_person_path(role_id: @role.id), class: 'btn btn-primary' %></p>
+        <%= render 'admin/education/program/role/people/list', people: @role.people.includes(:person).ordered %>
+      </div>
+    </div>
+  </div>
 </div>
 
 <% content_for :action_bar_right do %>
diff --git a/app/views/admin/education/program/teachers/_form.html.erb b/app/views/admin/education/program/teachers/_form.html.erb
index e8c8b9067196b35f5d0599974470e4820b0a1d1d..503032b23e866226f69cd4455e81ada146777148 100644
--- a/app/views/admin/education/program/teachers/_form.html.erb
+++ b/app/views/admin/education/program/teachers/_form.html.erb
@@ -6,8 +6,8 @@
     <div class="card-body">
       <div class="row">
         <div class="col-md-6">
-          <% teacher_ids = @program.teachers.where.not(id: teacher.id).pluck(:person_id) %>
-          <%= f.association :person, collection: current_university.people.teachers.where.not(id: teacher_ids).ordered %>
+          <% used_teacher_ids = @program.teachers.where.not(id: teacher.id).pluck(:person_id) %>
+          <%= f.association :person, collection: current_university.people.teachers.where.not(id: used_teacher_ids).ordered %>
         </div>
         <div class="col-md-6">
           <%= f.input :description, as: :string %>
diff --git a/app/views/admin/university/people/static.html.erb b/app/views/admin/university/people/static.html.erb
index 0bbfffa7de21387fcaecd227f1ab6c70eff0c26e..c6e4a98351f7edf255c7b498f423a1fcc26358fa 100644
--- a/app/views/admin/university/people/static.html.erb
+++ b/app/views/admin/university/people/static.html.erb
@@ -7,16 +7,16 @@ last_name: "<%= @person.last_name %>"
 phone: "<%= @person.phone %>"
 email: "<%= @person.email %>"
 roles:
-<% if @person.is_author %>
+<% if @person.is_author_for_website(@website) %>
   - author
 <% end %>
-<% if @person.is_teacher %>
+<% if @person.is_teacher_for_website(@website) %>
   - teacher
 <% end %>
-<% if @person.is_researcher %>
+<% if @person.is_researcher_for_website(@website) %>
   - researcher
 <% end %>
-<% if @person.is_administrative %>
+<% if @person.is_administrator_for_website(@website) %>
   - administrator
 <% end %>
 ---
diff --git a/config/locales/education/en.yml b/config/locales/education/en.yml
index a5e6d3c66d384495ce2d0d80a1bd3e996a457c0a..1cb84dcbc556de93a10b05279a7898ffd1a9c800 100644
--- a/config/locales/education/en.yml
+++ b/config/locales/education/en.yml
@@ -10,6 +10,9 @@ en:
       education/program/role:
         one: Role
         other: Roles
+      education/program/role/person:
+        one: Person
+        other: People
       education/program/teacher:
         one: Teacher
         other: Teachers
@@ -45,6 +48,8 @@ en:
       education/program/role:
         people: People
         title: Title
+      education/program/role/person:
+        person: Person
       education/program/teacher:
         description: Description
         person: Person
diff --git a/config/locales/education/fr.yml b/config/locales/education/fr.yml
index c087f4a27aea731ad9d5d2bc7fc87a8d1bd20b7b..755c0845c9fe9548966d38adfe7106f00dfd79d7 100644
--- a/config/locales/education/fr.yml
+++ b/config/locales/education/fr.yml
@@ -10,6 +10,9 @@ fr:
       education/program/role:
         one: Rôle
         other: Rôles
+      education/program/role/person:
+        one: Personne
+        other: Personnes
       education/program/teacher:
         one: Enseignant·e
         other: Enseignants·es
@@ -47,6 +50,8 @@ fr:
       education/program/role:
         people: Personnes
         title: Titre
+      education/program/role/person:
+        person: Personne
       education/program/teacher:
         description: Description
         person: Personne
diff --git a/config/routes/admin/education.rb b/config/routes/admin/education.rb
index 75ca3697efed49b6227f94127c53190c52936003..79e0e2555fda0126e8550d8739cf36ea24c06e6e 100644
--- a/config/routes/admin/education.rb
+++ b/config/routes/admin/education.rb
@@ -2,7 +2,17 @@ namespace :education do
   resources :teachers, only: [:index, :show]
   resources :schools
   resources :programs do
-    resources :roles, controller: 'program/roles', except: :index
+    resources :roles, controller: 'program/roles', except: :index do
+      resources :people, controller: 'program/role/people', except: [:index, :show, :edit, :update] do
+        collection do
+          post :reorder
+        end
+      end
+
+      collection do
+        post :reorder
+      end
+    end
     resources :teachers, controller: 'program/teachers', except: [:index, :show]
     collection do
       post :reorder
diff --git a/db/migrate/20220106134525_create_education_program_role_people.rb b/db/migrate/20220106134525_create_education_program_role_people.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a967cad41471e5339d6cec0380ee8e77b09a33a9
--- /dev/null
+++ b/db/migrate/20220106134525_create_education_program_role_people.rb
@@ -0,0 +1,11 @@
+class CreateEducationProgramRolePeople < ActiveRecord::Migration[6.1]
+  def change
+    create_table :education_program_role_people, id: :uuid do |t|
+      t.integer :position
+      t.references :person, null: false, foreign_key: { to_table: :university_people }, type: :uuid
+      t.references :role, null: false, foreign_key: { to_table: :education_program_roles }, type: :uuid
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4e15658bfe91a906df335ee677292f24ea11eb68..b8e59bfdb2fe32620e035ad58b35db13a791e2b1 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_06_104521) do
+ActiveRecord::Schema.define(version: 2022_01_06_134525) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -347,6 +347,16 @@ ActiveRecord::Schema.define(version: 2022_01_06_104521) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
+  create_table "education_program_role_people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.integer "position"
+    t.uuid "person_id", null: false
+    t.uuid "role_id", null: false
+    t.datetime "created_at", precision: 6, null: false
+    t.datetime "updated_at", precision: 6, null: false
+    t.index ["person_id"], name: "index_education_program_role_people_on_person_id"
+    t.index ["role_id"], name: "index_education_program_role_people_on_role_id"
+  end
+
   create_table "education_program_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.string "title"
     t.integer "position"
@@ -590,6 +600,8 @@ ActiveRecord::Schema.define(version: 2022_01_06_104521) do
   add_foreign_key "communication_website_posts", "universities"
   add_foreign_key "communication_website_posts", "university_people", column: "author_id"
   add_foreign_key "communication_websites", "universities"
+  add_foreign_key "education_program_role_people", "education_program_roles", column: "role_id"
+  add_foreign_key "education_program_role_people", "university_people", column: "person_id"
   add_foreign_key "education_program_roles", "education_programs", column: "program_id"
   add_foreign_key "education_program_roles", "universities"
   add_foreign_key "education_program_teachers", "education_programs", column: "program_id"
diff --git a/test/fixtures/education/program/role/people.yml b/test/fixtures/education/program/role/people.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50a25e47a89ea4b3a4cda4a15f37db6331820438
--- /dev/null
+++ b/test/fixtures/education/program/role/people.yml
@@ -0,0 +1,31 @@
+# == Schema Information
+#
+# Table name: education_program_role_people
+#
+#  id         :uuid             not null, primary key
+#  position   :integer
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  person_id  :uuid             not null
+#  role_id    :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_role_people_on_person_id  (person_id)
+#  index_education_program_role_people_on_role_id    (role_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (person_id => university_people.id)
+#  fk_rails_...  (role_id => education_program_roles.id)
+#
+
+one:
+  position: 1
+  person: one
+  role: one
+
+two:
+  position: 1
+  person: two
+  role: two
diff --git a/test/models/education/program/role/person_test.rb b/test/models/education/program/role/person_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a257dd899084f03b9b60d8a932b174c47621aed8
--- /dev/null
+++ b/test/models/education/program/role/person_test.rb
@@ -0,0 +1,28 @@
+# == Schema Information
+#
+# Table name: education_program_role_people
+#
+#  id         :uuid             not null, primary key
+#  position   :integer
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  person_id  :uuid             not null
+#  role_id    :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_role_people_on_person_id  (person_id)
+#  index_education_program_role_people_on_role_id    (role_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (person_id => university_people.id)
+#  fk_rails_...  (role_id => education_program_roles.id)
+#
+require "test_helper"
+
+class Education::Program::Role::PersonTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end