diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb
index e7434dc8071c843e85601bf9b9b63f2100da80a4..e2b54c01669be38d13841b96ff47af6e301e01b8 100644
--- a/app/models/communication/website/page.rb
+++ b/app/models/communication/website/page.rb
@@ -35,6 +35,7 @@
 
 class Communication::Website::Page < ApplicationRecord
   include WithGithub
+  include Communication::Website::WithMedia
   include WithSlug
   include WithTree
 
diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb
index 23d4a915ea70d535257628c0abee654834e66777..51bbcec9b443d3d09c5be483adb9ddd3f48e2088 100644
--- a/app/models/communication/website/post.rb
+++ b/app/models/communication/website/post.rb
@@ -31,6 +31,7 @@
 #
 class Communication::Website::Post < ApplicationRecord
   include WithGithub
+  include Communication::Website::WithMedia
   include WithSlug
 
   has_rich_text :text
diff --git a/app/models/communication/website/with_media.rb b/app/models/communication/website/with_media.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e25ffb8d2907211935db61e8853ff558af6255a2
--- /dev/null
+++ b/app/models/communication/website/with_media.rb
@@ -0,0 +1,28 @@
+module Communication::Website::WithMedia
+  extend ActiveSupport::Concern
+
+  protected
+
+  def active_storage_blobs
+    blob_ids = [featured_image&.blob_id, text.embeds.blobs.pluck(:id)].flatten.compact
+    university.active_storage_blobs.where(id: blob_ids)
+  end
+
+  def publish_to_github
+    super
+    active_storage_blobs.each do |blob|
+      blob.analyze unless blob.analyzed?
+      github.publish(path: "_media/#{blob.id}.md",
+                    commit: "[Medium] Save ##{ blob.id }",
+                    data: blob_to_jekyll(blob))
+    end
+  end
+
+  def blob_to_jekyll(blob)
+    ApplicationController.render(
+      template: 'active_storage/blobs/jekyll',
+      layout: false,
+      assigns: { blob: blob }
+    )
+  end
+end
diff --git a/app/models/university.rb b/app/models/university.rb
index 343c0f97045a655bc2e23de783c5d135dbbc1b5c..2b3ff4bce445dd94d1853707bb07c609e33369ea 100644
--- a/app/models/university.rb
+++ b/app/models/university.rb
@@ -25,9 +25,15 @@ class University < ApplicationRecord
 
   has_one_attached_deletable :logo
 
+  # Can't use dependent: :destroy because of attachments
+  # We use after_destroy to let the attachment go first
+  has_many :active_storage_blobs, class_name: 'ActiveStorage::Blob'
+
   validates_presence_of :name
   validates :sms_sender_name, presence: true, length: { maximum: 11 }
 
+  after_destroy :destroy_remaining_blobs
+
   scope :ordered, -> { order(:name) }
 
   def to_s
@@ -43,4 +49,10 @@ class University < ApplicationRecord
       full: "#{name} <#{address}>"
     }
   end
+
+  private
+
+  def destroy_remaining_blobs
+    active_storage_blobs.delete_all
+  end
 end
diff --git a/app/views/active_storage/blobs/jekyll.html.erb b/app/views/active_storage/blobs/jekyll.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e314b22bda4ceb11b359acd2331840cd98f40a79
--- /dev/null
+++ b/app/views/active_storage/blobs/jekyll.html.erb
@@ -0,0 +1,13 @@
+<%
+width, height = @blob.metadata.values_at('width', 'height')
+ratio = width.present? && height.present? ? (width.to_f / height.to_f) : nil
+%>
+---
+name: <%= @blob.filename.to_s %>
+size: <%= @blob.byte_size %>
+<% if width.present? %>width: <%= width %><% end %>
+<% if height.present? %>height: <%= height %><% end %>
+<% if ratio.present? %>ratio: <%= ratio %><% end %>
+<%# TODO: Replace with the media endpoint when it's ready %>
+url: <%= @blob.url %>
+---
diff --git a/init.js b/init.js
new file mode 100644
index 0000000000000000000000000000000000000000..ca5dbc77126cce4d9d6af840f543c628bfe69555
--- /dev/null
+++ b/init.js
@@ -0,0 +1,46 @@
+/*global window, document, $ */
+window.b2bylon.training.paths = {
+    invoke: function () {
+        'use strict';
+        return {
+            init: this.init.bind(this)
+        };
+    },
+
+    init: function () {
+        'use strict';
+
+        this.$kindSelect = $('select#features_training_path_kind');
+        if (!this.$kindSelect.length) {
+            return;
+        }
+        this.$folderKindForms = $('.kind-folder');
+        this.$activityKindForms = $('.kind-activity');
+        this.$activityField = $('input#features_training_path_activity_uid');
+        this.$kindSelect.on('change', this.kindChanged.bind(this));
+
+        this.kindChanged();
+    },
+
+    kindChanged: function () {
+        'use strict';
+        if (this.$kindSelect.val() === 'activity') {
+            this.$folderKindForms.hide();
+            $('.form-group.required select, .form-group.required input', this.$folderKindForms).removeAttr('required');
+            this.$activityKindForms.show();
+            $('.form-group.required select, .form-group.required input', this.$activityKindForms).attr('required', 'required');
+        } else {
+            this.$folderKindForms.show();
+            $('.form-group.required select, .form-group.required input', this.$folderKindForms).attr('required', 'required');
+            this.$activityKindForms.hide();
+            $('.form-group.required select, .form-group.required input', this.$activityKindForms).removeAttr('required', 'required');
+        }
+    }
+
+}.invoke();
+
+
+document.addEventListener('DOMContentLoaded', function () {
+    'use strict';
+    window.b2bylon.training.paths.init();
+});