diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 43df8b3b09470ba5a489146b08a8271fc7c185b4..adec234ceb8f5162a65cddd55a2291e4bc566a60 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -30,7 +30,7 @@ $routes->setAutoRoute(false);
  */
 
 $routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,191}');
-$routes->addPlaceholder('episodeSlug', '[a-zA-Z0-9\-]{1,191}');
+$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}');
 
 /**
  * --------------------------------------------------------------------
@@ -53,15 +53,6 @@ $routes->group(config('App')->installGateway, function ($routes) {
     ]);
 });
 
-// Public routes
-$routes->group('@(:podcastName)', function ($routes) {
-    $routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
-    $routes->get('(:episodeSlug)', 'Episode/$1/$2', [
-        'as' => 'episode',
-    ]);
-    $routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
-});
-
 // Route for podcast audio file analytics (/stats/podcast_id/episode_id/podcast_folder/filename.mp3)
 $routes->add('stats/(:num)/(:num)/(:any)', 'Analytics::hit/$1/$2/$3', [
     'as' => 'analytics_hit',
@@ -80,10 +71,6 @@ $routes->group(
             'as' => 'admin',
         ]);
 
-        $routes->get('my-podcasts', 'Podcast::myPodcasts', [
-            'as' => 'my-podcasts',
-        ]);
-
         // Podcasts
         $routes->group('podcasts', function ($routes) {
             $routes->get('/', 'Podcast::list', [
@@ -201,6 +188,27 @@ $routes->group(
             });
         });
 
+        // Pages
+        $routes->group('pages', function ($routes) {
+            $routes->get('/', 'Page::list', ['as' => 'page-list']);
+            $routes->get('new', 'Page::create', [
+                'as' => 'page-create',
+            ]);
+            $routes->post('new', 'Page::attemptCreate');
+
+            $routes->group('(:num)', function ($routes) {
+                $routes->get('/', 'Page::view/$1', ['as' => 'page-view']);
+                $routes->get('edit', 'Page::edit/$1', [
+                    'as' => 'page-edit',
+                ]);
+                $routes->post('edit', 'Page::attemptEdit/$1');
+
+                $routes->add('delete', 'Page::delete/$1', [
+                    'as' => 'page-delete',
+                ]);
+            });
+        });
+
         // Users
         $routes->group('users', function ($routes) {
             $routes->get('/', 'User::list', [
@@ -294,6 +302,16 @@ $routes->group(config('App')->authGateway, function ($routes) {
     $routes->post('reset-password', 'Auth::attemptReset');
 });
 
+// Public routes
+$routes->group('@(:podcastName)', function ($routes) {
+    $routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
+    $routes->get('(:slug)', 'Episode/$1/$2', [
+        'as' => 'episode',
+    ]);
+    $routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
+});
+$routes->get('/(:slug)', 'Page/$1', ['as' => 'page']);
+
 /**
  * --------------------------------------------------------------------
  * Additional Routing
diff --git a/app/Config/Validation.php b/app/Config/Validation.php
index 25ec0ce8e4c1287196508b0dccf66a61797ca2f4..f27fefb992ed0eec4de0823d01f801d945390bbf 100644
--- a/app/Config/Validation.php
+++ b/app/Config/Validation.php
@@ -19,6 +19,7 @@ class Validation
         \CodeIgniter\Validation\FormatRules::class,
         \CodeIgniter\Validation\FileRules::class,
         \CodeIgniter\Validation\CreditCardRules::class,
+        \App\Validation\Rules::class,
         \Myth\Auth\Authentication\Passwords\ValidationRules::class,
     ];
 
diff --git a/app/Controllers/Admin/Page.php b/app/Controllers/Admin/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..384b72bf21485dfc80d7c098ad0f8977da2e859f
--- /dev/null
+++ b/app/Controllers/Admin/Page.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Controllers\Admin;
+
+use App\Models\PageModel;
+
+class Page extends BaseController
+{
+    /**
+     * @var \App\Entities\Page|null
+     */
+    protected $page;
+
+    public function _remap($method, ...$params)
+    {
+        if (count($params) > 0) {
+            if (!($this->page = (new PageModel())->find($params[0]))) {
+                throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+            }
+        }
+
+        return $this->$method();
+    }
+
+    function list()
+    {
+        $data = [
+            'pages' => (new PageModel())->findAll(),
+        ];
+
+        return view('admin/page/list', $data);
+    }
+
+    function view()
+    {
+        return view('admin/page/view', ['page' => $this->page]);
+    }
+
+    function create()
+    {
+        helper('form');
+
+        return view('admin/page/create');
+    }
+
+    function attemptCreate()
+    {
+        $page = new \App\Entities\Page([
+            'title' => $this->request->getPost('title'),
+            'slug' => $this->request->getPost('slug'),
+            'content' => $this->request->getPost('content'),
+        ]);
+
+        $pageModel = new PageModel();
+
+        if (!$pageModel->save($page)) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $pageModel->errors());
+        }
+
+        return redirect()
+            ->route('page-list')
+            ->with(
+                'message',
+                lang('Page.messages.createSuccess', [
+                    'pageTitle' => $page->title,
+                ])
+            );
+    }
+
+    function edit()
+    {
+        helper('form');
+
+        replace_breadcrumb_params([0 => $this->page->title]);
+        return view('admin/page/edit', ['page' => $this->page]);
+    }
+
+    function attemptEdit()
+    {
+        $this->page->title = $this->request->getPost('title');
+        $this->page->slug = $this->request->getPost('slug');
+        $this->page->content = $this->request->getPost('content');
+
+        $pageModel = new PageModel();
+
+        if (!$pageModel->save($this->page)) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $pageModel->errors());
+        }
+
+        return redirect()->route('page-list');
+    }
+
+    public function delete()
+    {
+        (new PageModel())->delete($this->page->id);
+
+        return redirect()->route('page-list');
+    }
+}
diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php
index 2f9dcef13d201baa7d73cbef02dc446a2eff2a3d..6de23cf9c8c07c16a6ac2810e24636e0c18c40ac 100644
--- a/app/Controllers/Admin/Podcast.php
+++ b/app/Controllers/Admin/Podcast.php
@@ -31,23 +31,16 @@ class Podcast extends BaseController
         return $this->$method();
     }
 
-    public function myPodcasts()
-    {
-        $data = [
-            'podcasts' => (new PodcastModel())->getUserPodcasts(user()->id),
-        ];
-
-        return view('admin/podcast/list', $data);
-    }
-
     public function list()
     {
         if (!has_permission('podcasts-list')) {
-            return redirect()->route('my-podcasts');
+            $data = [
+                'podcasts' => (new PodcastModel())->getUserPodcasts(user()->id),
+            ];
+        } else {
+            $data = ['podcasts' => (new PodcastModel())->findAll()];
         }
 
-        $data = ['podcasts' => (new PodcastModel())->findAll()];
-
         return view('admin/podcast/list', $data);
     }
 
@@ -155,7 +148,7 @@ class Podcast extends BaseController
 
         $db->transComplete();
 
-        return redirect()->route('podcast-list');
+        return redirect()->route('podcast-view', [$newPodcastId]);
     }
 
     public function edit()
diff --git a/app/Controllers/Page.php b/app/Controllers/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..b30b5fd6e479a7210fe326dd6b4c82c5edf8a171
--- /dev/null
+++ b/app/Controllers/Page.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Controllers;
+
+use App\Models\PageModel;
+
+class Page extends BaseController
+{
+    /**
+     * @var \App\Entities\Page|null
+     */
+    protected $page;
+
+    public function _remap($method, ...$params)
+    {
+        if (count($params) > 0) {
+            if (
+                !($this->page = (new PageModel())
+                    ->where('slug', $params[0])
+                    ->first())
+            ) {
+                throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+            }
+        }
+
+        return $this->$method();
+    }
+
+    public function index()
+    {
+        // The page cache is set to a decade so it is deleted manually upon page update
+        $this->cachePage(DECADE);
+
+        $data = [
+            'page' => $this->page,
+        ];
+        return view('page', $data);
+    }
+}
diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php
index 368699c11ad45e7dcb625d2b05cf24bad319cc5f..5aa7aa20c45cb2df3e0a91f3555b6b37f55e72e5 100644
--- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php
+++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php
@@ -54,17 +54,15 @@ class AddPodcasts extends Migration
                 'constraint' => 1,
                 'default' => 0,
             ],
-            'author' => [
+            'owner_name' => [
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
-                'null' => true,
             ],
-            'owner_name' => [
+            'owner_email' => [
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
-                'null' => true,
             ],
-            'owner_email' => [
+            'author' => [
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
                 'null' => true,
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 23bd03ff3725aa305035af61b14bc175c6521491..c0fa74af15843609c1a2cae754107d1364b00730 100644
--- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php
+++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
@@ -41,7 +41,6 @@ class AddEpisodes extends Migration
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
             ],
-
             'description' => [
                 'type' => 'TEXT',
                 'null' => true,
diff --git a/app/Database/Migrations/2020-08-17-150000_add_pages.php b/app/Database/Migrations/2020-08-17-150000_add_pages.php
new file mode 100644
index 0000000000000000000000000000000000000000..32f22ca0c5ed216a0a3158a9214c0d41cd2d0841
--- /dev/null
+++ b/app/Database/Migrations/2020-08-17-150000_add_pages.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Class AddLanguages
+ * Creates languages table in database
+ *
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Database\Migrations;
+
+use CodeIgniter\Database\Migration;
+
+class AddPages extends Migration
+{
+    public function up()
+    {
+        $this->forge->addField([
+            'id' => [
+                'type' => 'INT',
+                'constraint' => 11,
+                'unsigned' => true,
+                'auto_increment' => true,
+            ],
+            'title' => [
+                'type' => 'VARCHAR',
+                'constraint' => 1024,
+            ],
+            'slug' => [
+                'type' => 'VARCHAR',
+                'constraint' => 191,
+                'unique' => true,
+            ],
+            'content' => [
+                'type' => 'TEXT',
+            ],
+            'created_at' => [
+                'type' => 'TIMESTAMP',
+            ],
+            'updated_at' => [
+                'type' => 'TIMESTAMP',
+            ],
+            'deleted_at' => [
+                'type' => 'DATETIME',
+                'null' => true,
+            ],
+        ]);
+        $this->forge->addKey('id', true);
+        $this->forge->createTable('pages');
+    }
+
+    public function down()
+    {
+        $this->forge->dropTable('pages');
+    }
+}
diff --git a/app/Entities/Page.php b/app/Entities/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb40d68a02d4ee971140f41aab3658c118c5080f
--- /dev/null
+++ b/app/Entities/Page.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Entities;
+
+use CodeIgniter\Entity;
+use League\CommonMark\CommonMarkConverter;
+
+class Page extends Entity
+{
+    /**
+     * @var string
+     */
+    protected $link;
+
+    /**
+     * @var string
+     */
+    protected $content_html;
+
+    protected $casts = [
+        'id' => 'integer',
+        'title' => 'string',
+        'slug' => 'string',
+        'content' => 'string',
+    ];
+
+    public function getLink()
+    {
+        return base_url($this->attributes['slug']);
+    }
+
+    public function getContentHtml()
+    {
+        $converter = new CommonMarkConverter([
+            'html_input' => 'strip',
+            'allow_unsafe_links' => false,
+        ]);
+
+        return $converter->convertToHtml($this->attributes['content']);
+    }
+}
diff --git a/app/Helpers/page_helper.php b/app/Helpers/page_helper.php
new file mode 100644
index 0000000000000000000000000000000000000000..488a6740e9a6907270fe889e96bda9e26304d250
--- /dev/null
+++ b/app/Helpers/page_helper.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+use App\Models\PageModel;
+
+/**
+ * Returns instance pages as links inside nav tag
+ *
+ * @return string html pages navigation
+ */
+function render_page_links()
+{
+    $pages = (new PageModel())->findAll();
+    $links = '';
+    foreach ($pages as $page) {
+        $links .= anchor($page->link, $page->title, [
+            'class' => 'px-2 underline hover:no-underline',
+        ]);
+    }
+
+    return '<nav class="inline-flex">' . $links . '</nav>';
+}
diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php
index 690049f2e492eb6767fa7de487a4a87747d141c7..58f5072628ff45e2ec6cb03f61512f4f229f035a 100644
--- a/app/Helpers/rss_helper.php
+++ b/app/Helpers/rss_helper.php
@@ -95,13 +95,9 @@ function get_rss_feed($podcast)
         $channel->addChild('author', $podcast->author, $itunes_namespace);
     $channel->addChild('link', $podcast->link);
 
-    if ($podcast->owner_name || $podcast->owner_email) {
-        $owner = $channel->addChild('owner', null, $itunes_namespace);
-        $podcast->owner_name &&
-            $owner->addChild('name', $podcast->owner_name, $itunes_namespace);
-        $podcast->owner_email &&
-            $owner->addChild('email', $podcast->owner_email, $itunes_namespace);
-    }
+    $owner = $channel->addChild('owner', null, $itunes_namespace);
+    $owner->addChild('name', $podcast->owner_name, $itunes_namespace);
+    $owner->addChild('email', $podcast->owner_email, $itunes_namespace);
 
     $channel->addChild('type', $podcast->type, $itunes_namespace);
     $podcast->copyright && $channel->addChild('copyright', $podcast->copyright);
diff --git a/app/Language/en/AdminNavigation.php b/app/Language/en/AdminNavigation.php
index 4f22158013794bec3af8461d4b2d1d2f64d7eac7..4f03423a360f99bc35f3226446e64edfc252519d 100644
--- a/app/Language/en/AdminNavigation.php
+++ b/app/Language/en/AdminNavigation.php
@@ -10,11 +10,13 @@ return [
     'dashboard' => 'Dashboard',
     'podcasts' => 'Podcasts',
     'users' => 'Users',
+    'pages' => 'Pages',
     'admin' => 'Home',
-    'my-podcasts' => 'My podcasts',
     'podcast-list' => 'All podcasts',
     'podcast-create' => 'New podcast',
     'user-list' => 'All users',
     'user-create' => 'New user',
+    'page-list' => 'All pages',
+    'page-create' => 'New Page',
     'go_to_website' => 'Go to website',
 ];
diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php
index 6ef22d33d2b1a3e972595ad51cb0edc6be069d66..bd5d4b61d12dfaa760dd2f7848802ac53c325352 100644
--- a/app/Language/en/Breadcrumb.php
+++ b/app/Language/en/Breadcrumb.php
@@ -9,10 +9,10 @@
 return [
     'label' => 'breadcrumb',
     config('App')->adminGateway => 'Home',
-    'my-podcasts' => 'my podcasts',
     'podcasts' => 'podcasts',
     'episodes' => 'episodes',
     'contributors' => 'contributors',
+    'pages' => 'pages',
     'add' => 'add',
     'new' => 'new',
     'edit' => 'edit',
diff --git a/app/Language/en/Page.php b/app/Language/en/Page.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0f5f032ee54de21a4e87acf186026763a83c18e
--- /dev/null
+++ b/app/Language/en/Page.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+return [
+    'all_pages' => 'All pages',
+    'create' => 'New page',
+    'go_to_page' => 'Go to page',
+    'edit' => 'Edit page',
+    'delete' => 'Delete page',
+    'form' => [
+        'title' => 'Title',
+        'slug' => 'Slug',
+        'content' => 'Content',
+        'submit_create' => 'Create page',
+        'submit_edit' => 'Save',
+    ],
+    'messages' => [
+        'createSuccess' => 'The {pageTitle} page was created successfully!',
+    ],
+];
diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php
index 03bae0199cbe74caf58686e67c7a55f1cba1924e..b64c6725464b131d4e7416e6cf3abeff77289498 100644
--- a/app/Language/en/Podcast.php
+++ b/app/Language/en/Podcast.php
@@ -42,15 +42,15 @@ return [
         'explicit' => 'Explicit',
         'explicit_help' =>
             'The podcast parental advisory information. Does it contain explicit content?',
-        'author' => 'Author',
-        'author_help' =>
-            'The group responsible for creating the show. Show author most often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.',
         'owner_name' => 'Owner name',
         'owner_name_help' =>
             'The podcast owner contact name. For administrative use only. It will not be shown on podcasts platforms (such as Apple Podcasts) nor players (such as Podcast Addict) but it is visible in the public RSS feed.',
         'owner_email' => 'Owner email',
         'owner_email_help' =>
             'The podcast owner contact e-mail. For administrative use only. It will mostly be used by some platforms to verify this podcast ownerhip. It will not be shown on podcasts platforms (such as Apple Podcasts) nor players (such as Podcast Addict) but it is visible in the public RSS feed.',
+        'author' => 'Author',
+        'author_help' =>
+            'The group responsible for creating the show. Show author most often refers to the parent company or network of a podcast. This field is sometimes labeled as ’Author’.',
         'type' => [
             'label' => 'Type',
             'episodic' => 'Episodic',
diff --git a/app/Language/en/Validation.php b/app/Language/en/Validation.php
new file mode 100644
index 0000000000000000000000000000000000000000..6672dd852cbbccbbf94437ea9620c16844b7741f
--- /dev/null
+++ b/app/Language/en/Validation.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+return [
+    'not_in_protected_slugs' =>
+        'The {field} field conflicts with one of the gateway routes (admin, auth or install).',
+];
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index cc68834c7bb35eb58a51df66fa11795ce5476b9e..9f911d770bb681d35dac9de95120bf6899d24ad5 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -53,8 +53,10 @@ class EpisodeModel extends Model
     ];
     protected $validationMessages = [];
 
-    protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
-    protected $afterUpdate = ['writeEnclosureMetadata', 'clearCache'];
+    protected $afterInsert = ['writeEnclosureMetadata'];
+    // clear cache beforeUpdate because if slug changes, so will the episode link
+    protected $beforeUpdate = ['clearCache'];
+    protected $afterUpdate = ['writeEnclosureMetadata'];
     protected $beforeDelete = ['clearCache'];
 
     protected function writeEnclosureMetadata(array $data)
diff --git a/app/Models/PageModel.php b/app/Models/PageModel.php
new file mode 100644
index 0000000000000000000000000000000000000000..38224fba86ce4bd1b6d11de70e40d7ca7de238e8
--- /dev/null
+++ b/app/Models/PageModel.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Models;
+
+use CodeIgniter\Model;
+
+class PageModel extends Model
+{
+    protected $table = 'pages';
+    protected $primaryKey = 'id';
+
+    protected $allowedFields = ['id', 'title', 'slug', 'content'];
+
+    protected $returnType = \App\Entities\Page::class;
+    protected $useSoftDeletes = true;
+
+    protected $useTimestamps = true;
+
+    protected $validationRules = [
+        'title' => 'required',
+        'slug' =>
+            'required|regex_match[/^[a-zA-Z0-9\-]{1,191}$/]|is_unique[pages.slug,id,{id}]|not_in_protected_slugs',
+        'content' => 'required',
+    ];
+    protected $validationMessages = [];
+
+    // Before update because slug might change
+    protected $beforeUpdate = ['clearCache'];
+    protected $beforeDelete = ['clearCache'];
+
+    protected function clearCache(array $data)
+    {
+        $page = (new PageModel())->find(
+            is_array($data['id']) ? $data['id'][0] : $data['id']
+        );
+
+        // delete page cache
+        cache()->delete(md5($page->link));
+
+        // Clear the cache of all podcast and episode pages
+        // TODO: change the logic of page caching to prevent clearing all cache every time
+        cache()->clean();
+
+        return $data;
+    }
+}
diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php
index 01bbdbb4dc8d8ffcea0a47079cb66e7b7a18cf9c..9a3bc6ac4900f4dce293db2361f0fdeee74e9d6c 100644
--- a/app/Models/PodcastModel.php
+++ b/app/Models/PodcastModel.php
@@ -25,9 +25,9 @@ class PodcastModel extends Model
         'language',
         'category',
         'explicit',
-        'author',
         'owner_name',
         'owner_email',
+        'author',
         'type',
         'copyright',
         'block',
@@ -50,6 +50,7 @@ class PodcastModel extends Model
         'image_uri' => 'required',
         'language' => 'required',
         'category' => 'required',
+        'owner_name' => 'required',
         'owner_email' => 'required|valid_email',
         'type' => 'required',
         'created_by' => 'required',
@@ -57,8 +58,8 @@ class PodcastModel extends Model
     ];
     protected $validationMessages = [];
 
-    protected $afterInsert = ['clearCache'];
-    protected $afterUpdate = ['clearCache'];
+    // clear cache before update if by any chance, the podcast name changes, and so will the podcast link
+    protected $beforeUpdate = ['clearCache'];
     protected $beforeDelete = ['clearCache'];
 
     /**
diff --git a/app/Validation/Rules.php b/app/Validation/Rules.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f264caecbaef3a3579604893a0e5ea308097168
--- /dev/null
+++ b/app/Validation/Rules.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Validation;
+
+class Rules
+{
+    /**
+     * Value should not be within the array of protected slugs (adminGateway, authGateway or installGateway)
+     *
+     * @param string $value
+     *
+     * @return boolean
+     */
+    public function not_in_protected_slugs(string $value = null): bool
+    {
+        $appConfig = config('App');
+        $protectedSlugs = [
+            $appConfig->adminGateway,
+            $appConfig->authGateway,
+            $appConfig->installGateway,
+        ];
+        return !in_array($value, $protectedSlugs, true);
+    }
+}
diff --git a/app/Views/_assets/icons/pages.svg b/app/Views/_assets/icons/pages.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e33ed93fdafe8ff84bd56f9a1cc57f620aa53d15
--- /dev/null
+++ b/app/Views/_assets/icons/pages.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g>
+        <path fill="none" d="M0 0h24v24H0z"/>
+        <path d="M5 8v12h14V8H5zm0-2h14V4H5v2zm15 16H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1zM7 10h4v4H7v-4zm0 6h10v2H7v-2zm6-5h4v2h-4v-2z"/>
+    </g>
+</svg>
diff --git a/app/Views/_assets/modules/MarkdownEditor.ts b/app/Views/_assets/modules/MarkdownEditor.ts
index 4074ef8b69ed33663de295207ab31ca3fc5ddc60..cb38bca969fc7f2f5c365d7b17dca6c039143edf 100644
--- a/app/Views/_assets/modules/MarkdownEditor.ts
+++ b/app/Views/_assets/modules/MarkdownEditor.ts
@@ -38,13 +38,7 @@ class ProseMirrorView {
 
   constructor(target: HTMLTextAreaElement, content: string) {
     this.editorContainer = document.createElement("div");
-    this.editorContainer.classList.add(
-      "bg-white",
-      "border",
-      "px-2",
-      "min-h-full",
-      "prose-sm"
-    );
+    this.editorContainer.classList.add("bg-white", "border");
     this.editorContainer.style.minHeight = "200px";
     const editor = target.parentNode?.insertBefore(
       this.editorContainer,
@@ -64,6 +58,10 @@ class ProseMirrorView {
           target.innerHTML = this.content;
         }
       },
+      attributes: {
+        class: "prose-sm px-3 py-2 overflow-y-auto",
+        style: "min-height: 200px; max-height: 500px",
+      },
     });
   }
 
diff --git a/app/Views/_layout.php b/app/Views/_layout.php
index 9bc3e3714bfb440a1beea6fa782d80a0a37f444d..5bd01391eecfe163ffa327580961b93ace537aac 100644
--- a/app/Views/_layout.php
+++ b/app/Views/_layout.php
@@ -1,9 +1,10 @@
+<?= helper('page') ?>
 <!DOCTYPE html>
 <html lang="en">
 
 <head>
     <meta charset="UTF-8"/>
-    <title>Castopod</title>
+    <title><?= $this->renderSection('title') ?></title>
     <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/>
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
@@ -22,7 +23,8 @@
     <main class="container flex-1 px-4 py-10 mx-auto">
         <?= $this->renderSection('content') ?>
     </main>
-    <footer class="container px-2 py-4 mx-auto text-sm text-right border-t">
-        Powered by <a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>, a <a class="underline hover:no-underline" href="https://podlibre.org/" target="_blank" rel="noreferrer noopener">Podlibre</a> initiative.
+    <footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t">
+        <?= render_page_links() ?>
+        <p>Powered by <a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>, a <a class="underline hover:no-underline" href="https://podlibre.org/" target="_blank" rel="noreferrer noopener">Podlibre</a> initiative.</p>
     </footer>
 </body>
diff --git a/app/Views/admin/_sidenav.php b/app/Views/admin/_sidenav.php
index 5cec5d8b749e071d3519178f16580de723591a95..6fea0a1cabca2129e56ed12f83b626590f12a98c 100644
--- a/app/Views/admin/_sidenav.php
+++ b/app/Views/admin/_sidenav.php
@@ -3,9 +3,10 @@ $navigation = [
     'dashboard' => ['icon' => 'dashboard', 'items' => ['admin']],
     'podcasts' => [
         'icon' => 'mic',
-        'items' => ['my-podcasts', 'podcast-list', 'podcast-create'],
+        'items' => ['podcast-list', 'podcast-create'],
     ],
     'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']],
+    'pages' => ['icon' => 'pages', 'items' => ['page-list', 'page-create']],
 ]; ?>
 
 <nav class="<?= $class ?>">
diff --git a/app/Views/admin/page/create.php b/app/Views/admin/page/create.php
new file mode 100644
index 0000000000000000000000000000000000000000..817a6c833fe1a35f48dee50b31397292d667b347
--- /dev/null
+++ b/app/Views/admin/page/create.php
@@ -0,0 +1,57 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Page.create') ?>
+<?= $this->endSection() ?>
+
+
+<?= $this->section('content') ?>
+
+<?= form_open(route_to('page-create'), [
+    'class' => 'flex flex-col max-w-3xl',
+]) ?>
+<?= csrf_field() ?>
+
+<?= form_label(lang('Page.form.title'), 'title', ['class' => 'max-w-sm']) ?>
+<?= form_input([
+    'id' => 'title',
+    'name' => 'title',
+    'class' => 'form-input mb-4 max-w-sm',
+    'value' => old('title'),
+    'required' => 'required',
+    'data-slugify' => 'title',
+]) ?>
+
+<?= form_label(lang('Page.form.slug'), 'slug', ['class' => 'max-w-sm']) ?>
+<?= form_input([
+    'id' => 'slug',
+    'name' => 'slug',
+    'class' => 'form-input mb-4 max-w-sm',
+    'value' => old('slug'),
+    'required' => 'required',
+    'data-slugify' => 'slug',
+]) ?>
+
+<div class="mb-4">
+    <?= form_label(lang('Page.form.content'), 'content') ?>
+    <?= form_textarea(
+        [
+            'id' => 'content',
+            'name' => 'content',
+            'class' => 'form-textarea',
+            'required' => 'required',
+        ],
+        old('content', '', false),
+        'data-editor="markdown"'
+    ) ?>
+</div>
+
+<?= form_button([
+    'content' => lang('Page.form.submit_create'),
+    'type' => 'submit',
+    'class' => 'self-end px-4 py-2 bg-gray-200',
+]) ?>
+
+<?= form_close() ?>
+
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/page/edit.php b/app/Views/admin/page/edit.php
new file mode 100644
index 0000000000000000000000000000000000000000..58b99b5e1221c969e2a039b6dcacff5cb019ae4a
--- /dev/null
+++ b/app/Views/admin/page/edit.php
@@ -0,0 +1,57 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Page.edit') ?>
+<?= $this->endSection() ?>
+
+
+<?= $this->section('content') ?>
+
+<?= form_open(route_to('page-edit', $page->id), [
+    'class' => 'flex flex-col max-w-3xl',
+]) ?>
+<?= csrf_field() ?>
+
+<?= form_label(lang('Page.form.title'), 'title', ['class' => 'max-w-sm']) ?>
+<?= form_input([
+    'id' => 'title',
+    'name' => 'title',
+    'class' => 'form-input mb-4 max-w-sm',
+    'value' => old('title', $page->title),
+    'required' => 'required',
+    'data-slugify' => 'title',
+]) ?>
+
+<?= form_label(lang('Page.form.slug'), 'slug', ['class' => 'max-w-sm']) ?>
+<?= form_input([
+    'id' => 'slug',
+    'name' => 'slug',
+    'class' => 'form-input mb-4 max-w-sm',
+    'value' => old('slug', $page->slug),
+    'required' => 'required',
+    'data-slugify' => 'slug',
+]) ?>
+
+<div class="mb-4">
+    <?= form_label(lang('Page.form.content'), 'content') ?>
+    <?= form_textarea(
+        [
+            'id' => 'content',
+            'name' => 'content',
+            'class' => 'form-textarea',
+            'required' => 'required',
+        ],
+        old('content', $page->content, false),
+        'data-editor="markdown"'
+    ) ?>
+</div>
+
+<?= form_button([
+    'content' => lang('Page.form.submit_edit'),
+    'type' => 'submit',
+    'class' => 'self-end px-4 py-2 bg-gray-200',
+]) ?>
+
+<?= form_close() ?>
+
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/page/list.php b/app/Views/admin/page/list.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd2a962c7b16ef8027e2093eafbb7f7109700d8f
--- /dev/null
+++ b/app/Views/admin/page/list.php
@@ -0,0 +1,47 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Page.all_pages') ?> (<?= count($pages) ?>)
+<a class="inline-flex items-center px-2 py-1 mb-2 ml-4 text-sm text-white bg-green-500 rounded shadow-xs outline-none hover:bg-green-600 focus:shadow-outline" href="<?= route_to(
+    'page-create'
+) ?>">
+<?= icon('add', 'mr-2') ?>
+<?= lang('Page.create') ?></a>
+<?= $this->endSection() ?>
+
+
+<?= $this->section('content') ?>
+
+<table class="table-auto">
+    <thead>
+        <tr>
+            <th class="px-4 py-2">Title</th>
+            <th class="px-4 py-2">Slug</th>
+            <th class="px-4 py-2">Actions</th>
+        </tr>
+    </thead>
+    <tbody>
+        <?php foreach ($pages as $page): ?>
+        <tr>
+            <td class="px-4 py-2 border"><?= $page->title ?></td>
+            <td class="px-4 py-2 border"><?= $page->slug ?></td>
+            <td class="px-4 py-2 border">
+                <a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-gray-700 hover:bg-gray-800" href="<?= route_to(
+                    'page',
+                    $page->slug
+                ) ?>"><?= lang('Page.go_to_page') ?></a>
+                <a class="inline-flex px-2 py-1 mb-2 text-sm text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
+                    'page-edit',
+                    $page->id
+                ) ?>"><?= lang('Page.edit') ?></a>
+                <a class="inline-flex px-2 py-1 text-sm text-white bg-red-700 hover:bg-red-800" href="<?= route_to(
+                    'page-delete',
+                    $page->id
+                ) ?>"><?= lang('Page.delete') ?></a>
+            </td>
+        </tr>
+        <?php endforeach; ?>
+    </tbody>
+</table>
+
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/page/view.php b/app/Views/admin/page/view.php
new file mode 100644
index 0000000000000000000000000000000000000000..992550e4a0c54fef0baa64835af7c63f7bf834bc
--- /dev/null
+++ b/app/Views/admin/page/view.php
@@ -0,0 +1,17 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= $page->title ?>
+<a class="inline-flex items-center px-2 py-1 mb-2 ml-4 text-sm text-white bg-teal-500 rounded shadow-xs outline-none hover:bg-teal-600 focus:shadow-outline" href="<?= route_to(
+    'page-edit',
+    $page->id
+) ?>">
+<?= icon('edit', 'mr-2') ?>
+<?= lang('Page.edit') ?></a>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+<div class="prose">
+    <?= $page->content_html ?>
+</div>
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/podcast/create.php b/app/Views/admin/podcast/create.php
index 498199051fb7dbea0838fd9782fb6195569b2142..e27277c9a2507d25476f60b7ad438a993a2bbe79 100644
--- a/app/Views/admin/podcast/create.php
+++ b/app/Views/admin/podcast/create.php
@@ -54,6 +54,7 @@
         [
             'id' => 'episode_description_footer',
             'name' => 'episode_description_footer',
+
             'class' => 'form-textarea',
         ],
         old('episode_description_footer', '', false),
@@ -94,20 +95,13 @@
     <span class="ml-2"><?= lang('Podcast.form.explicit') ?></span>
 </label>
 
-<?= form_label(lang('Podcast.form.author'), 'author') ?>
-<?= form_input([
-    'id' => 'author',
-    'name' => 'author',
-    'class' => 'form-input mb-4',
-    'value' => old('author'),
-]) ?>
-
 <?= form_label(lang('Podcast.form.owner_name'), 'owner_name') ?>
 <?= form_input([
     'id' => 'owner_name',
     'name' => 'owner_name',
     'class' => 'form-input mb-4',
     'value' => old('owner_name'),
+    'required' => 'required',
 ]) ?>
 
 <?= form_label(lang('Podcast.form.owner_email'), 'owner_email') ?>
@@ -120,9 +114,15 @@
     'required' => 'required',
 ]) ?>
 
-<?= form_fieldset('', [
-    'class' => 'flex flex-col mb-4',
+<?= form_label(lang('Podcast.form.author'), 'author') ?>
+<?= form_input([
+    'id' => 'author',
+    'name' => 'author',
+    'class' => 'form-input mb-4',
+    'value' => old('author'),
 ]) ?>
+
+<?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
     <legend><?= lang('Podcast.form.type.label') ?></legend>
     <label for="episodic" class="inline-flex items-center">
         <?= form_radio(
diff --git a/app/Views/admin/podcast/edit.php b/app/Views/admin/podcast/edit.php
index b40f82fd37d8a470eb1548d202dd0976e8ac1307..7581a64831c57e6ead822c45111e7618400fb0ff 100644
--- a/app/Views/admin/podcast/edit.php
+++ b/app/Views/admin/podcast/edit.php
@@ -109,20 +109,13 @@
     <span class="ml-2"><?= lang('Podcast.form.explicit') ?></span>
 </label>
 
-<?= form_label(lang('Podcast.form.author'), 'author') ?>
-<?= form_input([
-    'id' => 'author',
-    'name' => 'author',
-    'class' => 'form-input mb-4',
-    'value' => old('author', $podcast->author),
-]) ?>
-
 <?= form_label(lang('Podcast.form.owner_name'), 'owner_name') ?>
 <?= form_input([
     'id' => 'owner_name',
     'name' => 'owner_name',
     'class' => 'form-input mb-4',
     'value' => old('owner_name', $podcast->owner_name),
+    'required' => 'required',
 ]) ?>
 
 <?= form_label(lang('Podcast.form.owner_email'), 'owner_email') ?>
@@ -135,6 +128,14 @@
     'required' => 'required',
 ]) ?>
 
+<?= form_label(lang('Podcast.form.author'), 'author') ?>
+<?= form_input([
+    'id' => 'author',
+    'name' => 'author',
+    'class' => 'form-input mb-4',
+    'value' => old('author', $podcast->author),
+]) ?>
+
 <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
     <legend><?= lang('Podcast.form.type.label') ?></legend>
     <label for="episodic" class="inline-flex items-center">
diff --git a/app/Views/episode.php b/app/Views/episode.php
index 9ed19e3736caad2eb3f42d60e7706770800af394..859387647645b45d6ef6f27767d63f821315727c 100644
--- a/app/Views/episode.php
+++ b/app/Views/episode.php
@@ -1,5 +1,9 @@
 <?= $this->extend('_layout') ?>
 
+<?= $this->section('title') ?>
+<?= $episode->title ?>
+<?= $this->endSection() ?>
+
 <?= $this->section('content') ?>
 
 <a class="underline hover:no-underline" href="<?= route_to(
diff --git a/app/Views/home.php b/app/Views/home.php
index 1cd0048d96870f9b1fc89cf9277b870c10dd3d80..8a7e7f962b913c4e8c1adabf36fbf55e7906f7a4 100644
--- a/app/Views/home.php
+++ b/app/Views/home.php
@@ -1,5 +1,7 @@
 <?= $this->extend('_layout') ?>
 
+<?= $this->section('title') ?>Castopod<?= $this->endSection() ?>
+
 <?= $this->section('content') ?>
 
 <h1 class="mb-2 text-xl"><?= lang('Home.all_podcasts') ?> (<?= count(
diff --git a/app/Views/page.php b/app/Views/page.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa6b502e9d5bec0c7df9115e87b229697f4cd798
--- /dev/null
+++ b/app/Views/page.php
@@ -0,0 +1,11 @@
+<?= $this->extend('_layout') ?>
+
+<?= $this->section('title') ?>
+<?= $page->title ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+<div class="prose">
+    <?= $page->content_html ?>
+</div>
+<?= $this->endSection() ?>
diff --git a/app/Views/podcast.php b/app/Views/podcast.php
index b64782b0c900a8ebd4caeeec154cb66285d00955..9e0b4ce95dccd803879d8683d4ee06963b2e0c7d 100644
--- a/app/Views/podcast.php
+++ b/app/Views/podcast.php
@@ -1,5 +1,9 @@
 <?= $this->extend('_layout') ?>
 
+<?= $this->section('title') ?>
+<?= $podcast->title ?>
+<?= $this->endSection() ?>
+
 <?= $this->section('content') ?>
 <header class="py-4 border-b">
     <h1 class="text-2xl"><?= $podcast->title ?></h1>