diff --git a/app/assets/stylesheets/admin/commons/filters.sass b/app/assets/stylesheets/admin/commons/filters.sass
index 2b94774f13aaa6b9d462d87285c60feb49b586cd..25d1f6b524b8a68f39eb0a46489fd7ae438bdbe1 100644
--- a/app/assets/stylesheets/admin/commons/filters.sass
+++ b/app/assets/stylesheets/admin/commons/filters.sass
@@ -1,5 +1,4 @@
 .filters
-    position: relative
     &__button
         min-width: 100px
         position: absolute
diff --git a/app/controllers/server/websites_controller.rb b/app/controllers/server/websites_controller.rb
index 4bb90fbb83a6f4037dd13b5edb3d319c14af27b3..2c73354976e21218825ee704f50d4c2333904ec4 100644
--- a/app/controllers/server/websites_controller.rb
+++ b/app/controllers/server/websites_controller.rb
@@ -1,4 +1,5 @@
 class Server::WebsitesController < Server::ApplicationController
+  before_action :load_website, only: [:sync_theme_version, :update_theme]
 
   has_scope :for_theme_version
   has_scope :for_production
@@ -10,9 +11,17 @@ class Server::WebsitesController < Server::ApplicationController
     add_breadcrumb Communication::Website.model_name.human(count: 2), server_websites_path
   end
 
-  def refresh
-    @website = Communication::Website.find params[:id]
+  def sync_theme_version
     @website.get_current_theme_version!
   end
 
+  def update_theme
+    @website.update_theme_version
+  end
+
+  protected
+
+  def load_website
+    @website = Communication::Website.find params[:id]
+  end
 end
diff --git a/app/models/communication/website/with_git_repository.rb b/app/models/communication/website/with_git_repository.rb
index 36b9649903435f0530dcee93025d5891d4d80f0c..e78f7db898bc0b3424258651ab17ef2208915282 100644
--- a/app/models/communication/website/with_git_repository.rb
+++ b/app/models/communication/website/with_git_repository.rb
@@ -49,6 +49,11 @@ module Communication::Website::WithGitRepository
     (saved_change_to_about_id? && about_id_before_last_save.present?) || language_was_removed
   end
 
+  def update_theme_version
+    git_repository.update_theme_version!
+  end
+  handle_asynchronously :update_theme_version, queue: :default
+
   protected
 
   def add_direct_source_to_sync(direct_source)
diff --git a/app/services/git/providers/abstract.rb b/app/services/git/providers/abstract.rb
index cd53c2d0d9b31ad20a09dc7f1de4241f7be85f31..ceb8a34233d77c6fbd425b6bf253b2435ab0ad13 100644
--- a/app/services/git/providers/abstract.rb
+++ b/app/services/git/providers/abstract.rb
@@ -28,6 +28,10 @@ class Git::Providers::Abstract
     raise NotImplementedError
   end
 
+  def update_theme
+    raise NotImplementedError
+  end
+
   def push(commit_message)
     raise NotImplementedError
   end
diff --git a/app/services/git/providers/github.rb b/app/services/git/providers/github.rb
index 407dc39a7633ba4a7cfc52d06b43238cfbb550ca..58dae24af78b811997848c1e4daf03e6620319e6 100644
--- a/app/services/git/providers/github.rb
+++ b/app/services/git/providers/github.rb
@@ -44,6 +44,16 @@ class Git::Providers::Github < Git::Providers::Abstract
     }
   end
 
+  def update_theme
+    previous_theme_sha = git_sha(ENV["GITHUB_WEBSITE_THEME_PATH"])
+    batch << {
+      path: ENV["GITHUB_WEBSITE_THEME_PATH"],
+      mode: '160000',
+      type: 'commit',
+      sha: current_theme_sha
+    } if previous_theme_sha != current_theme_sha
+  end
+
   def push(commit_message)
     return if !valid? || batch.empty?
     new_tree = client.create_tree repository, batch, base_tree: tree[:sha]
@@ -91,6 +101,10 @@ class Git::Providers::Github < Git::Providers::Abstract
     @branch_sha ||= client.branch(repository, default_branch)[:commit][:sha]
   end
 
+  def current_theme_sha
+    @current_theme_sha ||= client.branch(ENV["GITHUB_WEBSITE_THEME_REPOSITORY"], ENV["GITHUB_WEBSITE_THEME_BRANCH"])[:commit][:sha]
+  end
+
   def tree_item_at_path(path)
     tree_items_by_path[path] if tree_items_by_path.has_key? path
   end
diff --git a/app/services/git/repository.rb b/app/services/git/repository.rb
index 5ccb56b744ea25bf75136cd12b5f2563bd72d82c..7863c6de0738ad1b3a5066840a976a25daa26970 100644
--- a/app/services/git/repository.rb
+++ b/app/services/git/repository.rb
@@ -26,6 +26,12 @@ class Git::Repository
     mark_as_synced if provider.push(commit_message)
   end
 
+  def update_theme_version!
+    provider.update_theme
+    theme_name = ENV["GITHUB_WEBSITE_THEME_REPOSITORY"].to_s.split("/").last
+    provider.push("Updated #{theme_name} version")
+  end
+
   # Based on content, with the provider's algorithm (sha1 or sha256)
   def computed_sha(string)
     provider.computed_sha(string)
diff --git a/app/views/server/websites/_list.html.erb b/app/views/server/websites/_list.html.erb
index dab2389a5eebec82b2fec25d201cd130cc7ed60b..7ee7d50ee198025de88dfed683e97295deb425b4 100644
--- a/app/views/server/websites/_list.html.erb
+++ b/app/views/server/websites/_list.html.erb
@@ -5,10 +5,9 @@
         <th><%= Communication::Website.human_attribute_name('name') %></th>
         <th><%= Communication::Website.human_attribute_name('about') %></th>
         <th><%= Communication::Website.human_attribute_name('url') %></th>
-        <th><%= Communication::Website.human_attribute_name('theme_version') %></th>
         <th><%= University.model_name.human %></th>
         <th><%= Communication::Website.human_attribute_name('created_at') %></th>
-        <th width="160"></th>
+        <th><%= Communication::Website.human_attribute_name('theme_version') %></th>
       </tr>
     </thead>
     <tbody>
@@ -22,10 +21,13 @@
               <span class="badge bg-success">Prod</span>
             <% end %>
           </td>
-          <td class="js-version"><%= link_to website.theme_version, website.theme_version_url, target: :_blank if website.theme_version_url.present? %></td>
           <td><%= link_to website.university, [:server, website.university] %></td>
           <td><%= l website.created_at.to_date %></td>
-          <td><%= link_to "Sync version", refresh_server_website_path(website), method: :post, remote: true, class: button_classes if website.url.present? %></td>
+          <td>
+            <span class="js-version"><%= link_to website.theme_version, website.theme_version_url, target: :_blank if website.theme_version_url.present? %></span><br>
+            <%= link_to t('server_admin.websites.buttons.theme.sync'), sync_theme_version_server_website_path(website), method: :post, remote: true, class: button_classes if website.url.present? %>
+            <%= link_to t('server_admin.websites.buttons.theme.update'), update_theme_server_website_path(website), method: :post, remote: true, class: button_classes if website.url.present? && website.github? %>
+          </td>
         </tr>
       <% end %>
     </tbody>
diff --git a/app/views/server/websites/refresh.js.erb b/app/views/server/websites/refresh.js.erb
deleted file mode 100644
index 5e4a1ebd8298c0d7c701ec736c224d555e1be6fb..0000000000000000000000000000000000000000
--- a/app/views/server/websites/refresh.js.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-var column = document.querySelector('#website-<%= @website.id %> .js-version');
-column.innerHTML = "<%= j link_to @website.theme_version, @website.theme_version_url, target: '_blank' if @website.theme_version_url.present? %>";
diff --git a/app/views/server/websites/sync_theme_version.js.erb b/app/views/server/websites/sync_theme_version.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..794e28d3a132b5137d3f7fc5adb18885f3d553d1
--- /dev/null
+++ b/app/views/server/websites/sync_theme_version.js.erb
@@ -0,0 +1,2 @@
+var version = document.querySelector('#website-<%= @website.id %> .js-version');
+version.innerHTML = "<%= j link_to @website.theme_version, @website.theme_version_url, target: '_blank' if @website.theme_version_url.present? %>";
diff --git a/app/views/server/websites/update_theme.js.erb b/app/views/server/websites/update_theme.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3c122c7eea2bb109d7f73ea935dd97acc853a9e6
--- /dev/null
+++ b/app/views/server/websites/update_theme.js.erb
@@ -0,0 +1,12 @@
+var notyf = new Notyf();
+notyf.open({
+    type: 'success',
+    position: {
+        x: 'center',
+        y: 'bottom'
+    },
+    message: "<%= j(t('server_admin.websites.update_theme_notice', website: @website.to_s)) %>",
+    duration: 5000,
+    ripple: true,
+    dismissible: true
+});
diff --git a/config/application.sample.yml b/config/application.sample.yml
index 105eafe9569a653dec5c252a097c1b38266b1254..998c1a2b9f7e6a66e11b3daf282e9b2c661261ef 100644
--- a/config/application.sample.yml
+++ b/config/application.sample.yml
@@ -3,10 +3,14 @@ APPLICATION_ENV: development
 BUGSNAG_JAVASCRIPT_KEY:
 BUGSNAG_RUBY_KEY:
 
+GITHUB_WEBSITE_THEME_BRANCH:
+GITHUB_WEBSITE_THEME_PATH:
+GITHUB_WEBSITE_THEME_REPOSITORY:
+
 MAIL_FROM_DEFAULT_ADDRESS:
 MAIL_FROM_DEFAULT_NAME:
 
-  # Can be used when working on two incompatible branches (e.g. main & i18n)
+# Can be used when working on two incompatible branches (e.g. main & i18n)
 OSUNY_DEVELOPMENT_DBNAME:
 
 OSUNY_STAGING_APP_NAME:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 321184eb6f438da8519bcd3c52716fc8ecd34ade..fbb389e853f30ad23025f3f1604328466249778b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -282,8 +282,14 @@ en:
   reset: Reset
   save: Save
   search: Search
-  server: Server
   select_language: Select language
+  server_admin:
+    websites:
+      buttons:
+        theme:
+          sync: Sync
+          update: Update
+      update_theme_notice: The theme of %{website} will be updated in a moment
   seo: SEO
   simple_form:
     error_notification:
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index cbb370b1ffb8264584ebb25047f1470ad422ad0d..b11726e561be19e475c5c3f7b14ada78221cff34 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -283,7 +283,13 @@ fr:
   save: Enregistrer
   search: Rechercher
   select_language: Sélectionnez une langue
-  server: Serveur
+  server_admin:
+    websites:
+      buttons:
+        theme:
+          sync: Sync
+          update: Mettre à jour
+      update_theme_notice: Le thème de %{website} va être mis à jour dans quelques instants
   seo: SEO
   simple_form:
     error_notification:
diff --git a/config/routes/server.rb b/config/routes/server.rb
index 576fba530c50028275c2641e6dd7ffdf10610731..9feaac85cca9f81c568a08f00b8598de37c51856 100644
--- a/config/routes/server.rb
+++ b/config/routes/server.rb
@@ -2,7 +2,10 @@ namespace :server do
   resources :universities
   resources :languages
   resources :websites, only: :index do
-    post :refresh, on: :member
+    member do
+      post :sync_theme_version
+      post :update_theme
+    end
   end
   resources :blocks, only: [:index, :show] do
     post :resave, on: :member
diff --git a/db/schema.rb b/db/schema.rb
index 547544b729fd027f74d520b8a9f1a49ff5ff466b..53ef9884f8c238a1d7e493d9b79ab1da0ab666d2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -105,8 +105,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_03_142438) do
     t.datetime "updated_at", null: false
     t.string "title"
     t.boolean "published", default: true
-    t.uuid "communication_website_id"
     t.uuid "heading_id"
+    t.uuid "communication_website_id"
     t.index ["about_type", "about_id"], name: "index_communication_website_blocks_on_about"
     t.index ["communication_website_id"], name: "index_communication_blocks_on_communication_website_id"
     t.index ["heading_id"], name: "index_communication_blocks_on_heading_id"
@@ -223,7 +223,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_03_142438) do
     t.text "home_sentence"
     t.text "sass"
     t.text "css"
-    t.boolean "allow_experiences_modification", default: true
     t.index ["about_type", "about_id"], name: "index_communication_extranets_on_about"
     t.index ["university_id"], name: "index_communication_extranets_on_university_id"
   end
@@ -462,7 +461,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_03_142438) do
     t.index ["university_id"], name: "index_communication_website_pages_on_university_id"
   end
 
-  create_table "communication_website_permalinks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_permalinks", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.string "about_type", null: false
diff --git a/test/controllers/server/websites_controller_test.rb b/test/controllers/server/websites_controller_test.rb
index a05c068ebd584e2133d8dafdd5fe095a34569fde..f6d9b44f888c0d1e2fe5344b4850a9d23b460eb6 100644
--- a/test/controllers/server/websites_controller_test.rb
+++ b/test/controllers/server/websites_controller_test.rb
@@ -8,8 +8,8 @@ class Server::WebsitesControllerTest < ActionDispatch::IntegrationTest
     assert_response(:success)
   end
 
-  def test_refresh
-    post(refresh_server_website_path(communication_websites(:website_with_github)), xhr: true)
+  def test_sync_theme_version
+    post(sync_theme_version_server_website_path(communication_websites(:website_with_github)), xhr: true)
     assert_response(:success)
   end
 end