diff --git a/app/assets/javascripts/admin/sortable.js b/app/assets/javascripts/admin/sortable.js
new file mode 100644
index 0000000000000000000000000000000000000000..e34e6fbfd08c626604e59e291d180ac10a2ed92b
--- /dev/null
+++ b/app/assets/javascripts/admin/sortable.js
@@ -0,0 +1,26 @@
+/*global $, Sortable */
+$(function () {
+    'use strict';
+    // Re-order elements of a table. Needs a "table-sortable" class on the table, a "data-reorder-url" param on the tbody and a "data-id" param on each tr
+    var nestedSortables = [].slice.call(document.querySelectorAll('.table-sortable tbody')),
+        i;
+    for (i = 0; i < nestedSortables.length; i += 1) {
+        new Sortable(nestedSortables[i], {
+            handle: '.handle',
+            group: 'nested',
+            animation: 150,
+            fallbackOnBody: true,
+            swapThreshold: 0.65,
+            onEnd: function (evt) {
+                var to = evt.to,
+                    ids = [],
+                    url = $(to).attr('data-reorder-url');
+                // get list of ids
+                $('> tr', to).each(function () {
+                    ids.push($(this).attr('data-id'));
+                });
+                $.post(url, { ids: ids });
+            }
+        });
+    }
+});
diff --git a/app/controllers/admin/communication/website/categories_controller.rb b/app/controllers/admin/communication/website/categories_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4ed189322b48bf825a59f7a31cccf10fc940eed
--- /dev/null
+++ b/app/controllers/admin/communication/website/categories_controller.rb
@@ -0,0 +1,64 @@
+class Admin::Communication::Website::CategoriesController < Admin::Communication::Website::ApplicationController
+  load_and_authorize_resource class: Communication::Website::Category
+
+  include Admin::Reorderable
+
+  def index
+    @categories = @website.categories.ordered
+    breadcrumb
+  end
+
+  def show
+    breadcrumb
+  end
+
+  def new
+    @category.website = @website
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    @category.university = current_university
+    @category.website = @website
+    if @category.save
+      redirect_to admin_communication_website_category_path(@category), notice: t('admin.successfully_created_html', model: @category.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @category.update(category_params)
+      redirect_to admin_communication_website_category_path(@category), notice: t('admin.successfully_updated_html', model: @category.to_s)
+    else
+      breadcrumb
+      add_breadcrumb t('edit')
+      render :edit, status: :unprocessable_entity
+    end
+  end
+
+  def destroy
+    @category.destroy
+    redirect_to admin_communication_website_categories_url, notice: t('admin.successfully_destroyed_html', model: @category.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb  Communication::Website::Category.model_name.human(count: 2),
+                    admin_communication_website_categories_path
+    breadcrumb_for @category
+  end
+
+  def category_params
+    params.require(:communication_website_category)
+          .permit(:university_id, :website_id, :name, :description)
+  end
+end
diff --git a/app/controllers/concerns/admin/reorderable.rb b/app/controllers/concerns/admin/reorderable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2ce07bf582985548f7f69d1f07f0363034e669e6
--- /dev/null
+++ b/app/controllers/concerns/admin/reorderable.rb
@@ -0,0 +1,18 @@
+module Admin::Reorderable
+  extend ActiveSupport::Concern
+
+  included do
+    def reorder
+      ids = params[:ids]
+      ids.each.with_index do |id, index|
+        object = model.find_by(id: id)
+        object.update_column(:position, index + 1) unless object.nil?
+      end
+    end
+
+    def model
+      self.class.to_s.remove('Admin::').remove('Controller').singularize.safe_constantize
+    end
+  end
+
+end
diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb
index c41cac826474e3dfc420fe3c9bb66f92960ab53a..eac3c8285a7b9cbfe9873ea92db139369f64e8ab 100644
--- a/app/models/communication/website.rb
+++ b/app/models/communication/website.rb
@@ -27,6 +27,7 @@ class Communication::Website < ApplicationRecord
   belongs_to :about, polymorphic: true, optional: true
   has_many :pages, foreign_key: :communication_website_id
   has_many :posts, foreign_key: :communication_website_id
+  has_many :categories, class_name: 'Communication::Website::Category', foreign_key: :communication_website_id
   has_one :imported_website,
           class_name: 'Communication::Website::Imported::Website',
           dependent: :destroy
diff --git a/app/models/communication/website/category.rb b/app/models/communication/website/category.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8c7724b3b66f06382c8ffed5562eb9c674687ddd
--- /dev/null
+++ b/app/models/communication/website/category.rb
@@ -0,0 +1,53 @@
+# == Schema Information
+#
+# Table name: communication_website_categories
+#
+#  id                       :uuid             not null, primary key
+#  description              :text
+#  name                     :string
+#  position                 :integer
+#  created_at               :datetime         not null
+#  updated_at               :datetime         not null
+#  communication_website_id :uuid             not null
+#  university_id            :uuid             not null
+#
+# Indexes
+#
+#  idx_communication_website_post_cats_on_communication_website_id  (communication_website_id)
+#  index_communication_website_categories_on_university_id          (university_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (communication_website_id => communication_websites.id)
+#  fk_rails_...  (university_id => universities.id)
+#
+class Communication::Website::Category < ApplicationRecord
+
+  belongs_to :university
+  belongs_to :website,
+             foreign_key: :communication_website_id
+
+  validates :name, presence: true
+
+  scope :ordered, -> { order(:position) }
+
+  before_create :set_position
+
+
+  def to_s
+    "#{name}"
+  end
+
+  protected
+
+  def set_position
+    last_element = website.categories.ordered.last
+
+    unless last_element.nil?
+      self.position = last_element.position + 1
+    else
+      self.position = 1
+    end
+  end
+
+end
diff --git a/app/views/admin/communication/website/categories/_form.html.erb b/app/views/admin/communication/website/categories/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1d988e3fa744856c5353c2aef0df3e1a7ce668c2
--- /dev/null
+++ b/app/views/admin/communication/website/categories/_form.html.erb
@@ -0,0 +1,18 @@
+<%= simple_form_for [:admin, category] 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('communication.website.content') %></h5>
+        </div>
+        <div class="card-body">
+          <%= f.input :name %>
+          <%= f.input :description %>
+        </div>
+      </div>
+    </div>
+  </div>
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/communication/website/categories/_list.html.erb b/app/views/admin/communication/website/categories/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..dad41187f932bc67d1d0a660a9a80ac7a5156b2d
--- /dev/null
+++ b/app/views/admin/communication/website/categories/_list.html.erb
@@ -0,0 +1,27 @@
+<table class="<%= table_classes %> table-sortable">
+  <thead>
+    <tr>
+      <% if can? :reorder, Communication::Website::Category %>
+        <th width="20">&nbsp;</th>
+      <% end %>
+      <th><%= Communication::Website::Category.human_attribute_name('title') %></th>
+      <th></th>
+    </tr>
+  </thead>
+  <tbody data-reorder-url="<%= reorder_admin_communication_website_categories_path(@website) %>">
+    <% categories.each do |category| %>
+      <tr data-id="<%= category.id %>">
+        <% if can? :reorder, Communication::Website::Category %>
+          <td><i class="fa fa-bars handle"></i></td>
+        <% end %>
+        <td><%= link_to category, admin_communication_website_category_path(website_id: category.website.id, id: category.id) %></td>
+        <td class="text-end">
+          <div class="btn-group" role="group">
+            <%= edit_link category %>
+            <%= destroy_link category %>
+          </div>
+        </td>
+      </tr>
+    <% end %>
+  </tbody>
+</table>
diff --git a/app/views/admin/communication/website/categories/edit.html.erb b/app/views/admin/communication/website/categories/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..123bc888b09752ba30b4db4eba62ec37bcbd07d2
--- /dev/null
+++ b/app/views/admin/communication/website/categories/edit.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, @category %>
+
+<%= render 'form', category: @category %>
diff --git a/app/views/admin/communication/website/categories/index.html.erb b/app/views/admin/communication/website/categories/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bb47a5f4750937fd78a5d7a2c2141a87ae266782
--- /dev/null
+++ b/app/views/admin/communication/website/categories/index.html.erb
@@ -0,0 +1,7 @@
+<% content_for :title, "#{Communication::Website::Category.model_name.human(count: 2)} (#{@categories.count})" %>
+
+<%= render 'list', categories: @categories %>
+
+<% content_for :action_bar_right do %>
+  <%= create_link Communication::Website::Category %>
+<% end %>
diff --git a/app/views/admin/communication/website/categories/new.html.erb b/app/views/admin/communication/website/categories/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f92532bde4aa34d077cb23a65cfdc18643b689a7
--- /dev/null
+++ b/app/views/admin/communication/website/categories/new.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, Communication::Website::Category.model_name.human %>
+
+<%= render 'form', category: @category %>
diff --git a/app/views/admin/communication/website/categories/show.html.erb b/app/views/admin/communication/website/categories/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..96aa0af3fe3d008b278a5d1891db34d087e95f12
--- /dev/null
+++ b/app/views/admin/communication/website/categories/show.html.erb
@@ -0,0 +1,21 @@
+<% content_for :title, @category %>
+
+<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('communication.website.content') %></h5>
+      </div>
+      <div class="card-body">
+        <p>
+          <strong><%= Communication::Website::Category.human_attribute_name('description') %></strong>
+        </p>
+        <%= sanitize @category.description %>
+      </div>
+    </div>
+  </div>
+</div>
+
+<% content_for :action_bar_right do %>
+  <%= edit_link @category %>
+<% end %>
diff --git a/app/views/admin/communication/websites/show.html.erb b/app/views/admin/communication/websites/show.html.erb
index 074e302de7c4299b43a6884f8130734542370f8c..8f372b3bcb2e39958ecfddf1000bea39a8ed3ee8 100644
--- a/app/views/admin/communication/websites/show.html.erb
+++ b/app/views/admin/communication/websites/show.html.erb
@@ -30,6 +30,7 @@
   </div>
   <%= render 'admin/communication/website/posts/list', posts: @website.posts.recent %>
 </div>
+
 <div class="card mt-5">
   <div class="card-header">
     <div class="float-end">
@@ -50,6 +51,21 @@
   <%= render 'admin/communication/website/pages/list', pages: @website.pages.recent %>
 </div>
 
+<div class="card mt-5">
+  <div class="card-header">
+    <div class="float-end">
+      <%= link_to t('create'),
+                  new_admin_communication_website_category_path(website_id: @website),
+                  class: button_classes %>
+    </div>
+    <h2 class="card-title">
+      <%= Communication::Website::Category.model_name.human(count: 2) %>
+      <%= "(#{@website.categories.count})" %>
+    </h2>
+  </div>
+  <%= render 'admin/communication/website/categories/list', categories: @website.categories.ordered %>
+</div>
+
 
 <% content_for :action_bar_right do %>
   <% if @website.imported? %>
diff --git a/config/locales/communication/en.yml b/config/locales/communication/en.yml
index b04f9923b29609e13850955103ba4d13df13e90c..857bbe2d329396bee0458bcd48f27cc35bb8a523 100644
--- a/config/locales/communication/en.yml
+++ b/config/locales/communication/en.yml
@@ -25,6 +25,13 @@ en:
       communication/website:
         one: Website
         other: Websites
+      communication/website/category:
+        one: Category
+        other: Categories
+        all: All categories
+      communication/website/imported/website:
+        one: Imported website
+        other: Imported websites
       communication/website/page:
         one: Page
         other: Pages
@@ -33,9 +40,6 @@ en:
         one: Post
         other: Posts
         all: All posts
-      communication/website/imported/website:
-        one: Imported website
-        other: Imported websites
     attributes:
       communication/website:
         name: Name
@@ -44,6 +48,9 @@ en:
         about_: Independent website (no specific subject)
         about_Research::Journal: Journal website
         about_Education::School: School website
+      communication/website/category:
+        description: Description
+        title: Title
       communication/website/imported/medium:
         filename: Filename
       communication/website/page:
diff --git a/config/locales/communication/fr.yml b/config/locales/communication/fr.yml
index cfd9c6d99e1541e7e60c3e08a574af44cedd0534..72f4d2b2e844e1fb38332cd27a60fef32f0bbcf4 100644
--- a/config/locales/communication/fr.yml
+++ b/config/locales/communication/fr.yml
@@ -25,6 +25,13 @@ fr:
       communication/website:
         one: Site Web
         other: Sites Web
+      communication/website/category:
+        one: Catégorie
+        other: Catégories
+        all: Toutes les catégories
+      communication/website/imported/website:
+        one: Site importé
+        other: Sites importés
       communication/website/page:
         one: Page
         other: Pages
@@ -33,9 +40,6 @@ fr:
         one: Actualité
         other: Actualités
         all: Toutes les actualités
-      communication/website/imported/website:
-        one: Site importé
-        other: Sites importés
     attributes:
       communication/website:
         name: Nom
@@ -44,6 +48,9 @@ fr:
         about_: Site indépendant (aucun sujet)
         about_Research::Journal: Site de revue scientifique
         about_Education::School: Site d'école
+      communication/website/category:
+        description: Description
+        title: Titre
       communication/website/imported/medium:
         filename: Nom du fichier
       communication/website/page:
diff --git a/config/routes/admin/communication.rb b/config/routes/admin/communication.rb
index 98e4d278f3283e539df38fee03866e27ad678d29..ece4457558c25348270668532e66de271dab4e7d 100644
--- a/config/routes/admin/communication.rb
+++ b/config/routes/admin/communication.rb
@@ -12,6 +12,11 @@ namespace :communication do
         get :children
       end
     end
+    resources :categories, controller: 'website/categories' do
+      collection do
+        post :reorder
+      end
+    end
     resources :posts, controller: 'website/posts'
   end
 end
diff --git a/db/migrate/20211026124139_create_communication_website_categories.rb b/db/migrate/20211026124139_create_communication_website_categories.rb
new file mode 100644
index 0000000000000000000000000000000000000000..febee26b527912f20c22fb12a9e0eed423c1b343
--- /dev/null
+++ b/db/migrate/20211026124139_create_communication_website_categories.rb
@@ -0,0 +1,16 @@
+class CreateCommunicationWebsiteCategories < ActiveRecord::Migration[6.1]
+  def change
+    create_table :communication_website_categories, id: :uuid do |t|
+      t.references :university, null: false, foreign_key: true, type: :uuid
+      t.references :communication_website,
+                    null: false,
+                    foreign_key: { to_table: :communication_websites },
+                    type: :uuid,
+                    index: { name: 'idx_communication_website_post_cats_on_communication_website_id' }
+      t.string :name
+      t.text :description
+      t.integer :position
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 303bcfee2efd01c90219c4c8975c3c60550b4eb9..30cb480defa261ea41539ab542dc04830c4c42e4 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: 2021_10_26_094556) do
+ActiveRecord::Schema.define(version: 2021_10_26_124139) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -78,6 +78,18 @@ ActiveRecord::Schema.define(version: 2021_10_26_094556) do
     t.index ["criterion_id"], name: "index_administration_qualiopi_indicators_on_criterion_id"
   end
 
+  create_table "communication_website_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.uuid "university_id", null: false
+    t.uuid "communication_website_id", null: false
+    t.string "name"
+    t.text "description"
+    t.integer "position"
+    t.datetime "created_at", precision: 6, null: false
+    t.datetime "updated_at", precision: 6, null: false
+    t.index ["communication_website_id"], name: "idx_communication_website_post_cats_on_communication_website_id"
+    t.index ["university_id"], name: "index_communication_website_categories_on_university_id"
+  end
+
   create_table "communication_website_imported_media", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.string "identifier"
     t.jsonb "data"
@@ -386,6 +398,8 @@ ActiveRecord::Schema.define(version: 2021_10_26_094556) do
   add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
   add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
   add_foreign_key "administration_qualiopi_indicators", "administration_qualiopi_criterions", column: "criterion_id"
+  add_foreign_key "communication_website_categories", "communication_websites"
+  add_foreign_key "communication_website_categories", "universities"
   add_foreign_key "communication_website_imported_media", "communication_website_imported_websites", column: "website_id"
   add_foreign_key "communication_website_imported_media", "universities"
   add_foreign_key "communication_website_imported_pages", "communication_website_imported_media", column: "featured_medium_id"