diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php
index ac5c7f4638c24cd16d068def03d591d0bbb1e482..da0cd335eece0b7b2198a0b7cf78a6caab8f90a2 100644
--- a/app/Config/Mimes.php
+++ b/app/Config/Mimes.php
@@ -307,7 +307,7 @@ class Mimes
         ],
         'svg' => ['image/svg+xml', 'application/xml', 'text/xml'],
         'vcf' => 'text/x-vcard',
-        'srt' => ['text/srt', 'text/plain'],
+        'srt' => ['text/srt', 'text/plain', 'application/octet-stream'],
         'vtt' => ['text/vtt', 'text/plain'],
         'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
     ];
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index ada6c2c10e4d4803cf40da28f2a21d28a461cdd2..23c5e95152c5ce5e87ace05e444b0df2fc0d4cd4 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -237,6 +237,22 @@ $routes->group(
                             'as' => 'episode-delete',
                             'filter' => 'permission:podcast_episodes-delete',
                         ]);
+                        $routes->get(
+                            'transcript-delete',
+                            'Episode::transcriptDelete/$1/$2',
+                            [
+                                'as' => 'transcript-delete',
+                                'filter' => 'permission:podcast_episodes-edit',
+                            ]
+                        );
+                        $routes->get(
+                            'chapters-delete',
+                            'Episode::chaptersDelete/$1/$2',
+                            [
+                                'as' => 'chapters-delete',
+                                'filter' => 'permission:podcast_episodes-edit',
+                            ]
+                        );
                     });
                 });
 
diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php
index 0e1d348f61ee19eeac0ffd31e59df2ea408a0b5a..4f2531c8cdb02fb00602fd86e4502ffc53869449 100644
--- a/app/Controllers/Admin/Episode.php
+++ b/app/Controllers/Admin/Episode.php
@@ -96,6 +96,8 @@ class Episode extends BaseController
             'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
             'image' =>
                 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
+            'transcript' => 'ext_in[transcript,txt,html,srt,json]',
+            'chapters' => 'ext_in[chapters,json]',
             'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
         ];
 
@@ -114,6 +116,8 @@ class Episode extends BaseController
             'enclosure' => $this->request->getFile('enclosure'),
             'description_markdown' => $this->request->getPost('description'),
             'image' => $this->request->getFile('image'),
+            'transcript' => $this->request->getFile('transcript'),
+            'chapters' => $this->request->getFile('chapters'),
             'parental_advisory' =>
                 $this->request->getPost('parental_advisory') !== 'undefined'
                     ? $this->request->getPost('parental_advisory')
@@ -189,6 +193,8 @@ class Episode extends BaseController
                 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
             'image' =>
                 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
+            'transcript' => 'ext_in[transcript,txt,html,srt,json]',
+            'chapters' => 'ext_in[chapters,json]',
             'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
         ];
 
@@ -231,6 +237,14 @@ class Episode extends BaseController
         if ($image) {
             $this->episode->image = $image;
         }
+        $transcript = $this->request->getFile('transcript');
+        if ($transcript->isValid()) {
+            $this->episode->transcript = $transcript;
+        }
+        $chapters = $this->request->getFile('chapters');
+        if ($chapters->isValid()) {
+            $this->episode->chapters = $chapters;
+        }
 
         $episodeModel = new EpisodeModel();
 
@@ -262,6 +276,40 @@ class Episode extends BaseController
         ]);
     }
 
+    public function transcriptDelete()
+    {
+        unlink($this->episode->transcript);
+        $this->episode->transcript_uri = null;
+
+        $episodeModel = new EpisodeModel();
+
+        if (!$episodeModel->update($this->episode->id, $this->episode)) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $episodeModel->errors());
+        }
+
+        return redirect()->back();
+    }
+
+    public function chaptersDelete()
+    {
+        unlink($this->episode->chapters);
+        $this->episode->chapters_uri = null;
+
+        $episodeModel = new EpisodeModel();
+
+        if (!$episodeModel->update($this->episode->id, $this->episode)) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $episodeModel->errors());
+        }
+
+        return redirect()->back();
+    }
+
     public function delete()
     {
         (new EpisodeModel())->delete($this->episode->id);
diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
index 8449dbc70fc1e81148d902700ba0a3a9d0033281..8147df8fb11a583214072096dac4703031de3fa7 100644
--- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php
+++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
@@ -73,6 +73,16 @@ class AddEpisodes extends Migration
                 'constraint' => 255,
                 'null' => true,
             ],
+            'transcript_uri' => [
+                'type' => 'VARCHAR',
+                'constraint' => 255,
+                'null' => true,
+            ],
+            'chapters_uri' => [
+                'type' => 'VARCHAR',
+                'constraint' => 255,
+                'null' => true,
+            ],
             'parental_advisory' => [
                 'type' => 'ENUM',
                 'constraint' => ['clean', 'explicit'],
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index 6b4939a5a044b461bd6402558109974b7df102b5..b316e97e292d75f09fbe075e7cb1e3ea96c1a0a6 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -35,6 +35,16 @@ class Episode extends Entity
      */
     protected $enclosure;
 
+    /**
+     * @var \CodeIgniter\Files\File
+     */
+    protected $transcript;
+
+    /**
+     * @var \CodeIgniter\Files\File
+     */
+    protected $chapters;
+
     /**
      * @var string
      */
@@ -55,6 +65,16 @@ class Episode extends Entity
      */
     protected $enclosure_opengraph_url;
 
+    /**
+     * @var string
+     */
+    protected $transcript_url;
+
+    /**
+     * @var string
+     */
+    protected $chapters_url;
+
     /**
      * Holds text only description, striped of any markdown or html special characters
      *
@@ -86,6 +106,8 @@ class Episode extends Entity
         'description_markdown' => 'string',
         'description_html' => 'string',
         'image_uri' => '?string',
+        'transcript_uri' => '?string',
+        'chapters_uri' => '?string',
         'parental_advisory' => '?string',
         'number' => '?integer',
         'season_number' => '?integer',
@@ -170,11 +192,75 @@ class Episode extends Entity
         }
     }
 
+    /**
+     * Saves an episode transcript
+     *
+     * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $transcript
+     *
+     */
+    public function setTranscript($transcript)
+    {
+        if (
+            !empty($transcript) &&
+            (!($transcript instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
+                $transcript->isValid())
+        ) {
+            helper('media');
+
+            $this->attributes['transcript_uri'] = save_podcast_media(
+                $transcript,
+                $this->getPodcast()->name,
+                $this->attributes['slug'] . '-transcript'
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * Saves an episode chapters
+     *
+     * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $chapters
+     *
+     */
+    public function setChapters($chapters)
+    {
+        if (
+            !empty($chapters) &&
+            (!($chapters instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
+                $chapters->isValid())
+        ) {
+            helper('media');
+
+            $this->attributes['chapters_uri'] = save_podcast_media(
+                $chapters,
+                $this->getPodcast()->name,
+                $this->attributes['slug'] . '-chapters'
+            );
+        }
+
+        return $this;
+    }
+
     public function getEnclosure()
     {
         return new \CodeIgniter\Files\File($this->getEnclosureMediaPath());
     }
 
+    public function getTranscript()
+    {
+        return $this->attributes['transcript_uri']
+            ? new \CodeIgniter\Files\File($this->getTranscriptMediaPath())
+            : null;
+    }
+
+    public function getChapters()
+    {
+        return $this->attributes['chapters_uri']
+            ? new \CodeIgniter\Files\File($this->getChaptersMediaPath())
+            : null;
+    }
+
     public function getEnclosureMediaPath()
     {
         helper('media');
@@ -182,6 +268,24 @@ class Episode extends Entity
         return media_path($this->attributes['enclosure_uri']);
     }
 
+    public function getTranscriptMediaPath()
+    {
+        helper('media');
+
+        return $this->attributes['transcript_uri']
+            ? media_path($this->attributes['transcript_uri'])
+            : null;
+    }
+
+    public function getChaptersMediaPath()
+    {
+        helper('media');
+
+        return $this->attributes['chapters_uri']
+            ? media_path($this->attributes['chapters_uri'])
+            : null;
+    }
+
     public function getEnclosureUrl()
     {
         helper('analytics');
@@ -230,6 +334,20 @@ class Episode extends Entity
         return $this->getEnclosureUrl() . '?_from=-+Open+Graph+-';
     }
 
+    public function getTranscriptUrl()
+    {
+        return $this->attributes['transcript_uri']
+            ? base_url($this->getTranscriptMediaPath())
+            : null;
+    }
+
+    public function getChaptersUrl()
+    {
+        return $this->attributes['chapters_uri']
+            ? base_url($this->getChaptersMediaPath())
+            : null;
+    }
+
     public function getLink()
     {
         return base_url(
diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php
index db7dad440b4f57f6b1c070dab4324458413b0543..e7bc4ffbdab5458198cfb62d0b08ffabae02db18 100644
--- a/app/Helpers/rss_helper.php
+++ b/app/Helpers/rss_helper.php
@@ -8,6 +8,7 @@
 
 use App\Libraries\SimpleRSSElement;
 use CodeIgniter\I18n\Time;
+use Config\Mimes;
 
 /**
  * Generates the rss feed for a given podcast entity
@@ -217,6 +218,35 @@ function get_rss_feed($podcast, $serviceName = '')
             );
         $item->addChild('episodeType', $episode->type, $itunes_namespace);
 
+        if ($episode->transcript) {
+            $transcriptElement = $item->addChild(
+                'transcript',
+                null,
+                $podcast_namespace
+            );
+            $transcriptElement->addAttribute('url', $episode->transcriptUrl);
+            $transcriptElement->addAttribute(
+                'type',
+                Mimes::guessTypeFromExtension(
+                    pathinfo($episode->transcript_uri, PATHINFO_EXTENSION)
+                )
+            );
+            $transcriptElement->addAttribute(
+                'language',
+                $podcast->language_code
+            );
+        }
+
+        if ($episode->chapters) {
+            $chaptersElement = $item->addChild(
+                'chapters',
+                null,
+                $podcast_namespace
+            );
+            $chaptersElement->addAttribute('url', $episode->chaptersUrl);
+            $chaptersElement->addAttribute('type', 'application/json+chapters');
+        }
+
         $episode->is_blocked &&
             $item->addChild('block', 'Yes', $itunes_namespace);
     }
diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php
index 652bad3ac932aa0cd4d627f4926fcc325a5d23aa..ea045e49d44b773e38932b8a1c2e169be1afc212 100644
--- a/app/Language/en/Episode.php
+++ b/app/Language/en/Episode.php
@@ -70,6 +70,15 @@ return [
         'block' => 'Episode should be hidden from all platforms',
         'block_hint' =>
             'The episode show or hide status. If you want this episode removed from the Apple directory, toggle this on.',
+        'additional_files_section_title' => 'Additional files',
+        'additional_files_section_subtitle' =>
+            'These files may be used by other platforms to provide better experience to your audience.<br />See the {podcastNamespaceLink} for more information.',
+        'transcript' => 'Transcript or closed captions',
+        'transcript_hint' => 'Allowed formats are txt, html, srt or json.',
+        'transcript_delete' => 'Delete transcript',
+        'chapters' => 'Chapters',
+        'chapters_hint' => 'File should be in JSON Chapters Format.',
+        'chapters_delete' => 'Delete chapters',
         'submit_create' => 'Create episode',
         'submit_edit' => 'Save episode',
     ],
diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php
index a98e63b974bbf45058fd1d21db64119c2af939e8..25911e2a5d87f793689ba66ae272f9fa86d49183 100644
--- a/app/Language/fr/Episode.php
+++ b/app/Language/fr/Episode.php
@@ -70,6 +70,16 @@ return [
         'block' => 'L’épisode doit être masqué de toutes les plateformes',
         'block_hint' =>
             'La visibilité de l’épisode. Si vous souhaitez retirer cet épisode de l’index Apple, activez ce champ.',
+        'additional_files_section_title' => 'Fichiers additionels',
+        'additional_files_section_subtitle' =>
+            'Ces fichiers pourront être utilisées par d’autres plate-formes pour procurer une meilleure expérience à vos auditeurs.<br />Consulter le {podcastNamespaceLink} pour plus d’informations.',
+        'transcript' => 'Transcription ou sous-titrage',
+        'transcript_hint' =>
+            'Les formats autorisés sont txt, html, srt ou json.',
+        'transcript_delete' => 'Supprimer la transcription',
+        'chapters' => 'Chapitrage',
+        'chapters_hint' => 'Le fichier doit être en "JSON Chapters Format".',
+        'chapters_delete' => 'Supprimer le chaptrage',
         'submit_create' => 'Créer l’épisode',
         'submit_edit' => 'Enregistrer l’épisode',
     ],
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index 9ce7ce0cf29e0a4ee6ddc635452dbdca57b0cb59..c60803a215dada75aef0f7ab20ffb368446959d5 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -28,6 +28,8 @@ class EpisodeModel extends Model
         'description_markdown',
         'description_html',
         'image_uri',
+        'transcript_uri',
+        'chapters_uri',
         'parental_advisory',
         'number',
         'season_number',
diff --git a/app/Views/_assets/icons/file.svg b/app/Views/_assets/icons/file.svg
new file mode 100644
index 0000000000000000000000000000000000000000..dcddb3965b71517e06e85c2a1265f18062a0b6d6
--- /dev/null
+++ b/app/Views/_assets/icons/file.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M19 22H5a3 3 0 0 1-3-3V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v12h4v4a3 3 0 0 1-3 3zm-1-5v2a1 1 0 0 0 2 0v-2h-2zm-2 3V4H4v15a1 1 0 0 0 1 1h11zM6 7h8v2H6V7zm0 4h8v2H6v-2zm0 4h5v2H6v-2z"/></svg>
\ No newline at end of file
diff --git a/app/Views/admin/episode/create.php b/app/Views/admin/episode/create.php
index 6d1e50c96edd403134d0c4823a23c12ab0a53876..d5e39664a978247d08c8f8b1525dbbb1b1dc7656 100644
--- a/app/Views/admin/episode/create.php
+++ b/app/Views/admin/episode/create.php
@@ -264,6 +264,40 @@
 
 <?= form_section_close() ?>
 
+<?= form_section(
+    lang('Episode.form.additional_files_section_title'),
+    lang('Episode.form.additional_files_section_subtitle')
+) ?>
+<?= form_label(
+    lang('Episode.form.transcript'),
+    'transcript',
+    [],
+    lang('Episode.form.transcript_hint'),
+    true
+) ?>
+<?= form_input([
+    'id' => 'transcript',
+    'name' => 'transcript',
+    'class' => 'form-input mb-4',
+    'type' => 'file',
+    'accept' => '.txt,.html,.srt,.json',
+]) ?>
+<?= form_label(
+    lang('Episode.form.chapters'),
+    'chapters',
+    [],
+    lang('Episode.form.chapters_hint'),
+    true
+) ?>
+<?= form_input([
+    'id' => 'chapters',
+    'name' => 'chapters',
+    'class' => 'form-input mb-4',
+    'type' => 'file',
+    'accept' => '.json',
+]) ?>
+<?= form_section_close() ?>
+
 <?= button(
     lang('Episode.form.submit_create'),
     null,
diff --git a/app/Views/admin/episode/edit.php b/app/Views/admin/episode/edit.php
index 2b5db05fdf3caf0c8b486eaf9270e6c28e857859..541a37897bc1a0e93f856613ea2128216865f410 100644
--- a/app/Views/admin/episode/edit.php
+++ b/app/Views/admin/episode/edit.php
@@ -136,7 +136,6 @@
     </label>
     <?= form_radio(
         ['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'],
-
         'bonus',
         old('type') ? old('type') === 'bonus' : $episode->type === 'bonus'
     ) ?>
@@ -273,6 +272,91 @@
     old('block', $episode->is_blocked)
 ) ?>
 
+<?= form_section_close() ?>
+<?= form_section(
+    lang('Episode.form.additional_files_section_title'),
+    lang('Episode.form.additional_files_section_subtitle')
+) ?>
+<div class="flex flex-col flex-1">
+<?= form_label(
+    lang('Episode.form.transcript'),
+    'transcript',
+    [],
+    lang('Episode.form.transcript_hint'),
+    true
+) ?>
+<?php if ($episode->transcript): ?>
+    <div class="flex justify-between">
+        <?= anchor(
+            $episode->transcriptUrl,
+            icon('file', 'mr-2') . $episode->transcript,
+            [
+                'class' => 'inline-flex items-center text-xs',
+                'target' => '_blank',
+                'rel' => 'noreferrer noopener',
+            ]
+        ) .
+            anchor(
+                route_to('transcript-delete', $podcast->id, $episode->id),
+                icon('delete-bin', 'mx-auto'),
+                [
+                    'class' =>
+                        'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
+                    'data-toggle' => 'tooltip',
+                    'data-placement' => 'bottom',
+                    'title' => lang('Episode.form.transcript_delete'),
+                ]
+            ) ?>
+    </div>
+<?php endif; ?>
+<?= form_input([
+    'id' => 'transcript',
+    'name' => 'transcript',
+    'class' => 'form-input mb-4',
+    'type' => 'file',
+    'accept' => '.txt,.html,.srt,.json',
+]) ?>
+</div>
+<div class="flex flex-col flex-1">
+<?= form_label(
+    lang('Episode.form.chapters'),
+    'chapters',
+    [],
+    lang('Episode.form.chapters_hint'),
+    true
+) ?>
+<?php if ($episode->chapters): ?>
+    <div class="flex justify-between">
+        <?= anchor(
+            $episode->chaptersUrl,
+            icon('file', 'mr-2') . $episode->chapters,
+            [
+                'class' => 'inline-flex items-center text-xs',
+                'target' => '_blank',
+                'rel' => 'noreferrer noopener',
+            ]
+        ) .
+            anchor(
+                route_to('chapters-delete', $podcast->id, $episode->id),
+                icon('delete-bin', 'mx-auto'),
+                [
+                    'class' =>
+                        'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
+                    'data-toggle' => 'tooltip',
+                    'data-placement' => 'bottom',
+                    'title' => lang('Episode.form.chapters_delete'),
+                ]
+            ) ?>
+    </div>
+<?php endif; ?>
+<?= form_input([
+    'id' => 'chapters',
+    'name' => 'chapters',
+    'class' => 'form-input mb-4',
+    'type' => 'file',
+    'accept' => '.json',
+]) ?>
+</div>
 <?= form_section_close() ?>
 
 <?= button(