diff --git a/modules/Plugins/Controllers/PluginController.php b/modules/Plugins/Controllers/PluginController.php
index c9e4edbb5578b12de132d641d02577475c1ae308..788e5a78b7cf48c247ff01e6a1adddddabdb2bba 100644
--- a/modules/Plugins/Controllers/PluginController.php
+++ b/modules/Plugins/Controllers/PluginController.php
@@ -125,8 +125,15 @@ class PluginController extends BaseController
             $data['episode'] = $episode;
         }
 
+        $fields = $plugin->getSettingsFields($type);
+
+        if ($fields === []) {
+            throw PageNotFoundException::forPageNotFound();
+        }
+
         $data['type'] = $type;
         $data['context'] = $context;
+        $data['fields'] = $fields;
 
         helper('form');
         replace_breadcrumb_params($breadcrumbReplacements);
@@ -164,8 +171,8 @@ class PluginController extends BaseController
         $rules = [];
         foreach ($plugin->getSettingsFields($type) as $field) {
             $typeRules = $plugins::FIELDS_VALIDATIONS[$field->type];
-            if (! in_array('permit_empty', $typeRules, true) && ! $field->optional) {
-                $typeRules[] = 'required';
+            if (! in_array('permit_empty', $typeRules, true)) {
+                $typeRules[] = $field->optional ? 'permit_empty' : 'required';
             }
 
             $rules[$field->key] = $typeRules;
@@ -182,7 +189,7 @@ class PluginController extends BaseController
 
         foreach ($plugin->getSettingsFields('general') as $field) {
             $value = $validatedData[$field->key] ?? null;
-            $fieldValue = match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
+            $fieldValue = $value === '' ? null : match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
                 'bool'     => $value === 'yes',
                 'int'      => (int) $value,
                 'uri'      => new URI($value),
@@ -192,7 +199,7 @@ class PluginController extends BaseController
                     $this->request->getPost('client_timezone')
                 )->setTimezone(app_timezone()),
                 'markdown' => new Markdown($value),
-                default    => $value === '' ? null : $value,
+                default    => $value,
             };
             $plugins->setOption($plugin, $field->key, $fieldValue, $context);
         }
diff --git a/modules/Plugins/Core/BasePlugin.php b/modules/Plugins/Core/BasePlugin.php
index b9fe8674d77a2bbb17303d3e64e7bcbfe920cef2..7eafb3ed005ac5dc5ca490926caffb074cc49c42 100644
--- a/modules/Plugins/Core/BasePlugin.php
+++ b/modules/Plugins/Core/BasePlugin.php
@@ -108,11 +108,6 @@ abstract class BasePlugin implements PluginInterface
         return in_array($name, $this->manifest->hooks, true);
     }
 
-    final public function getSettings(): ?Settings
-    {
-        return $this->manifest->settings;
-    }
-
     final public function getVersion(): string
     {
         return $this->manifest->version;
@@ -167,7 +162,7 @@ abstract class BasePlugin implements PluginInterface
      */
     final public function getSettingsFields(string $type): array
     {
-        $settings = $this->getSettings();
+        $settings = $this->manifest->settings;
         if (! $settings instanceof Settings) {
             return [];
         }
@@ -175,6 +170,14 @@ abstract class BasePlugin implements PluginInterface
         return $settings->{$type};
     }
 
+    /**
+     * @return list<string>
+     */
+    final public function getHooks(): array
+    {
+        return $this->manifest->hooks;
+    }
+
     final public function getKey(): string
     {
         return $this->key;
diff --git a/modules/Plugins/Language/en/Plugins.php b/modules/Plugins/Language/en/Plugins.php
index a41c91db2d00b92e41b52132d9751ea367432191..b5e4dbd1bbff4981bbeb88db44814e1a9ad7fcf3 100644
--- a/modules/Plugins/Language/en/Plugins.php
+++ b/modules/Plugins/Language/en/Plugins.php
@@ -16,7 +16,13 @@ return [
     'authors' => 'Authors',
     'author_email' => 'Email {authorName}',
     'author_homepage' => '{authorName} homepage',
+    'declaredHooks' => 'Declared hooks',
     'settings' => 'Settings',
+    'settingsTitle' => '{type, select,
+        podcast {{pluginName} podcast settings}
+        episode {{pluginName} episode settings}
+        other {{pluginName} general settings}
+    }',
     'view' => 'View',
     'activate' => 'Activate',
     'deactivate' => 'Deactivate',
@@ -29,6 +35,8 @@ return [
         'analytics' => 'Analytics',
         'accessibility' => 'Accessibility',
     ],
+    'noDescription' => 'No description',
+    'noReadme' => 'No README file found.',
     'messages' => [
         'saveSettingsSuccess' => '{pluginName} settings were successfully saved!',
     ],
diff --git a/modules/Plugins/Manifest/Person.php b/modules/Plugins/Manifest/Person.php
index 1a5b4289a7437fcf242dbe73c4b8f578303926c2..2c2cd74c158e9dcdb7dd4cb9a6c995663e026852 100644
--- a/modules/Plugins/Manifest/Person.php
+++ b/modules/Plugins/Manifest/Person.php
@@ -46,8 +46,8 @@ class Person extends ManifestObject
 
             $data = [
                 'name'  => $matches['name'],
-                'email' => $matches['email'],
-                'url'   => $matches['url'],
+                'email' => $matches['email'] ?? null,
+                'url'   => $matches['url'] ?? null,
             ];
         }
 
diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php
index a52a54410c4055ab5595263a398af534f00a8332..c68b89891f175b5be9b4af61d8882280ee6693f7 100644
--- a/themes/cp_admin/_layout.php
+++ b/themes/cp_admin/_layout.php
@@ -41,10 +41,10 @@ $isEpisodeArea = isset($podcast) && isset($episode);
                         <div class="inline-flex items-center">
                             <?php // @icon('exchange-dollar-fill')?>
                             <x-IconButton uri="<?= route_to('subscription-list', $podcast->id) ?>" glyph="exchange-dollar-fill" variant="secondary" size="large" class="p-0 mr-2 border-0"><?= ($isEpisodeArea && $episode->is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?></x-IconButton>
-                            <x-Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
+                            <x-Heading tagName="h1" size="large" class="max-w-sm truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
                         </div>
                     <?php else: ?>
-                            <x-Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
+                            <x-Heading tagName="h1" size="large" class="max-w-lg truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
                     <?php endif; ?>
                         <?= $this->renderSection('headerLeft') ?>
                     </div>
diff --git a/themes/cp_admin/_partials/_nav_menu.php b/themes/cp_admin/_partials/_nav_menu.php
index ecf844cb9b77a8926c83b71aa5708e5ade0033c2..6e0f16fab588661f965eff1b9ae92fdb9754fd9e 100644
--- a/themes/cp_admin/_partials/_nav_menu.php
+++ b/themes/cp_admin/_partials/_nav_menu.php
@@ -49,11 +49,11 @@
                 ?>
             <li class="inline-flex">
                 <?php if ($isAllowed): ?> 
-                    <a class="relative w-full py-3 pl-14 pr-2 text-sm hover:opacity-100 before:content-chevronRightIcon before:absolute before:-ml-5 before:opacity-0 before:w-5 before:h-5 hover:bg-navigation-active<?= $isActive
-                        ? ' before:opacity-100 font-semibold inline-flex items-center'
+                    <a class="line-clamp-1 leading-9 relative w-full py-1 pl-14 pr-2 text-sm hover:opacity-100 before:content-chevronRightIcon before:absolute before:top-2 before:-ml-5 before:opacity-0 before:w-5 before:h-5 hover:bg-navigation-active<?= $isActive
+                        ? ' before:opacity-100 font-semibold'
                         : ' hover:before:opacity-60 focus:before:opacity-60' ?>" href="<?= $href ?>"><?= $label ?></a>
                 <?php else: ?>
-                    <span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active"><?= $label ?></span>
+                    <span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed line-clamp-2 before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active"><?= $label ?></span>
                 <?php endif; ?>
             </li>
             <?php endforeach; ?>
diff --git a/themes/cp_admin/plugins/_plugin.php b/themes/cp_admin/plugins/_plugin.php
index 1c48a318c2f65c21aa76a8ade9e98fe801b0f3ec..0fd1f6c2bbc645c1ada710dace085c6ac78874e2 100644
--- a/themes/cp_admin/plugins/_plugin.php
+++ b/themes/cp_admin/plugins/_plugin.php
@@ -7,8 +7,8 @@
     <?php endif; ?>
     </div>
     <img class="rounded-full min-w-16 max-w-16 aspect-square" src="<?= $plugin->getIconSrc() ?>">
-    <div class="flex flex-col items-start mt-2">
-        <h2 class="flex items-center text-xl font-bold font-display gap-x-2"><a href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getName() ?></a></h2>
+    <div class="flex flex-col items-start mt-2 mb-6">
+        <h2 class="flex items-center text-xl font-bold font-display gap-x-2" title="<?= $plugin->getName() ?>"><a class="line-clamp-1" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getName() ?></a></h2>
         <p class="inline-flex font-mono text-xs">
             <span class="inline-flex tracking-wide bg-gray-100">
                 <a href="<?= route_to('plugins-vendor', $plugin->getVendor()) ?>" class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent"><?= $plugin->getVendor() ?></a>
@@ -16,9 +16,9 @@
                 <a class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= $plugin->getPackage() ?></a></span>
             <span class="mx-1">•</span><span class="px-1 font-mono text-xs"><?= $plugin->getVersion() ?></span>
         </p>
-        <p class="mt-2 text-gray-600"><?= $plugin->getDescription() ?></p>
+        <p class="relative w-full max-w-sm mt-2 text-skin-muted line-clamp-3"><?= $plugin->getDescription() ?? '<span class="absolute inset-0 px-2 m-auto text-sm lowercase shadow-sm w-fit h-fit bg-elevated">' . lang('Plugins.noDescription') . '</span><span class="block w-full h-4 mt-1 bg-gray-100"></span><span class="block w-full h-4 mt-1 bg-gray-100"></span><span class="block w-4/5 h-4 mt-1 bg-gray-100"></span>' ?></p>
     </div>
-    <footer class="flex items-center justify-between mt-6">
+    <footer class="flex items-center justify-between mt-auto">
         <div class="flex gap-x-2">
             <?php if ($plugin->getHomepage()): ?>
                 <?php // @icon('earth-fill')?>
@@ -41,9 +41,9 @@
                 <x-Button type="submit" variant="secondary" size="small"><?= lang('Plugins.activate') ?></x-Button>
             </form>
         <?php endif; ?>
-            <?php if ($plugin->getSettings() !== []): ?>
+            <?php if ($plugin->getSettingsFields('general') !== []): ?>
                 <?php // @icon('equalizer-fill')?>
-                <x-IconButton uri="<?= route_to('plugins-general-settings', $plugin->getVendor(), $plugin->getPackage()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings') ?></x-IconButton>
+                <x-IconButton uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings') ?></x-IconButton>
             <?php endif; ?>
             <button class="p-2 rounded-full" id="more-dropdown-<?= $plugin->getKey() ?>" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $plugin->getKey() ?>-menu" aria-haspopup="true" aria-expanded="false" title="<?= lang('Common.more') ?>"><?= icon('more-2-fill') ?></button>
             <?php $items = [
diff --git a/themes/cp_admin/plugins/_settings.php b/themes/cp_admin/plugins/_settings_form.php
similarity index 98%
rename from themes/cp_admin/plugins/_settings.php
rename to themes/cp_admin/plugins/_settings_form.php
index a81579118d7d584e9bc52ad72b22f40d20401e3f..c7797ad30f673612333956c1062a2eb7768c01de 100644
--- a/themes/cp_admin/plugins/_settings.php
+++ b/themes/cp_admin/plugins/_settings_form.php
@@ -1,7 +1,7 @@
 <form method="POST" action="<?= $action ?>" class="flex flex-col max-w-xl gap-4 p-4 sm:p-6 md:p-8 bg-elevated border-3 border-subtle rounded-xl" >
 <?= csrf_field() ?>
 <?php $hasDatetime = false; ?>
-<?php foreach ($plugin->getSettingsFields($type) as $field): ?>
+<?php foreach ($fields as $field): ?>
     <?php switch ($field->type): case 'checkbox': ?>
         <x-Forms.Checkbox
             name="<?= $field->key ?>"
diff --git a/themes/cp_admin/plugins/settings.php b/themes/cp_admin/plugins/settings.php
index afbd1c9eda415c39032b898a054b69bbbfb28e3d..10d0e16ea914aefd0b9ba96808201ce75a437e9c 100644
--- a/themes/cp_admin/plugins/settings.php
+++ b/themes/cp_admin/plugins/settings.php
@@ -1,14 +1,14 @@
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('title') ?>
-<?= lang('Plugins.settings', [
+<?= lang('Plugins.settingsTitle', [
     'pluginName' => $plugin->getName(),
     'type'       => $type,
 ]) ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('pageTitle') ?>
-<?= lang('Plugins.settings', [
+<?= lang('Plugins.settingsTitle', [
     'pluginName' => $plugin->getName(),
     'type'       => $type,
 ]) ?>
@@ -30,10 +30,10 @@ if (isset($episode)) {
 ?>
 
 <?= $this->section('content') ?>
-<?= view('plugins/_settings', [
+<?= view('plugins/_settings_form', [
     'plugin'  => $plugin,
     'action'  => route_to(sprintf('plugins-settings-%s-action', $type), ...$params),
-    'type'    => $type,
+    'fields'  => $fields,
     'context' => $context,
 ]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/plugins/view.php b/themes/cp_admin/plugins/view.php
index 8bb8462b271f1f79121419d40fdb03c30ce2f51d..68e94e411472a4917b574f82ca55110031b307fa 100644
--- a/themes/cp_admin/plugins/view.php
+++ b/themes/cp_admin/plugins/view.php
@@ -18,14 +18,22 @@
 
 <?= $this->section('headerRight') ?>
 <?php if($plugin->isActive()): ?>
-    <form class="flex justify-end" method="POST" action="<?= route_to('plugins-deactivate', $plugin->getVendor(), $plugin->getPackage()) ?>">
+    <form class="flex justify-end gap-x-2" method="POST" action="<?= route_to('plugins-deactivate', $plugin->getVendor(), $plugin->getPackage()) ?>">
         <?= csrf_field() ?>
         <x-Button type="submit" variant="danger"><?= lang('Plugins.deactivate') ?></x-Button>
+        <?php if ($plugin->getSettingsFields('general') !== []): ?>
+        <?php // @icon('equalizer-fill')?>
+        <x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
+        <?php endif; ?>
     </form>
 <?php else: ?>
-    <form class="flex justify-end" method="POST" action="<?= route_to('plugins-activate', $plugin->getVendor(), $plugin->getPackage()) ?>">
+    <form class="flex justify-end gap-x-2" method="POST" action="<?= route_to('plugins-activate', $plugin->getVendor(), $plugin->getPackage()) ?>">
         <?= csrf_field() ?>
         <x-Button type="submit" variant="secondary"><?= lang('Plugins.activate') ?></x-Button>
+        <?php if ($plugin->getSettingsFields('general') !== []): ?>
+        <?php // @icon('equalizer-fill')?>
+        <x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
+        <?php endif; ?>
     </form>
 <?php endif; ?>
 <?= $this->endSection() ?>
@@ -34,8 +42,10 @@
 <div class="flex flex-col items-start justify-center gap-8 mx-auto xl:flex-row-reverse">
     <aside class="w-full pb-8 border-b xl:sticky xl:max-w-xs top-28 border-subtle xl:border-none">
         <h2 class="mb-2 text-2xl font-bold font-display"><?= lang('Plugins.about') ?></h2>
-        <p><?= $plugin->getDescription() ?></p>
-        <a href="<?= $plugin->getHomepage() ?>" class="inline-flex items-center mt-2 font-semibold hover:underline gap-x-2"><?= icon('link') . $plugin->getHomepage() ?></a>
+        <p class="relative max-w-sm text-skin-muted"><?= $plugin->getDescription() ?? '<span class="absolute inset-0 px-2 m-auto text-sm lowercase shadow-sm w-fit h-fit bg-base">' . lang('Plugins.noDescription') . '</span><span class="block w-full h-4 mt-1 bg-subtle"></span><span class="block w-full h-4 mt-1 bg-subtle"></span><span class="block w-4/5 h-4 mt-1 bg-subtle"></span>' ?></p>
+        <?php if ($plugin->getHomepage()): ?>
+            <a href="<?= $plugin->getHomepage() ?>" class="inline-flex items-center mt-2 font-semibold hover:underline gap-x-2"><?= icon('link') . $plugin->getHomepage() ?></a>
+        <?php endif; ?>
         <?php if ($plugin->getKeywords() !== []): ?>
             <div class="mt-2">
                 <?php foreach ($plugin->getKeywords() as $keyword): ?>
@@ -75,9 +85,26 @@
                 <?php endforeach; ?>
             </ul>
         <?php endif; ?>
+        <?php if ($plugin->getHooks() !== []): ?>
+            <h3 class="mt-6 text-lg font-bold font-display"><?= lang('Plugins.declaredHooks') ?></h3>
+            <ul>
+                <?php foreach ($plugin->getHooks() as $hook): ?>
+                    <li><?= $hook ?></li>
+                <?php endforeach; ?>
+            </ul>
+        <?php endif; ?>
     </aside>
-    <section class="max-w-2xl prose prose-headings:font-display">
-        <?= $plugin->getReadmeHTML() ?>
-    </section>
+    <?php if($plugin->getReadmeHTML()): ?>
+        <section class="w-full max-w-3xl p-4 prose border rounded-t-lg rounded-b-lg xl:rounded-t-none xl:-mt-12 md:p-6 xl:p-12 prose-headings:font-display bg-elevated border-subtle">
+            <?= $plugin->getReadmeHTML() ?>
+        </section>
+    <?php else: ?>
+        <section class="flex flex-col items-center justify-center w-full max-w-3xl p-4 border rounded-t-lg rounded-b-lg xl:rounded-t-none xl:-mt-12 md:p-6 xl:p-12 bg-elevated border-subtle min-h-96">
+            <?= icon('article-line', [
+                'class' => 'text-gray-300 text-6xl',
+            ]) ?>
+            <p class="mt-2 font-semibold text-skin-muted"><?= lang('Plugins.noReadme') ?></p>
+        </section>
+    <?php endif; ?>
 </div>
 <?= $this->endSection() ?>
\ No newline at end of file