diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php
index c8292a16b..aaf0cb9b2 100644
--- a/app/Http/Controllers/TagController.php
+++ b/app/Http/Controllers/TagController.php
@@ -26,7 +26,11 @@ class TagController extends Controller
         $nameFilter = $request->get('name', '');
         $tags = $this->tagRepo
             ->queryWithTotals($search, $nameFilter)
-            ->paginate(20);
+            ->paginate(50)
+            ->appends(array_filter([
+                'search' => $search,
+                'name' => $nameFilter
+            ]));
 
         return view('tags.index', [
             'tags'   => $tags,
diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php
index 1244fe82a..5cf47629a 100644
--- a/resources/lang/en/entities.php
+++ b/resources/lang/en/entities.php
@@ -267,6 +267,7 @@ return [
     'tags_all_values' => 'All values',
     'tags_view_tags' => 'View Tags',
     'tags_view_existing_tags' => 'View existing tags',
+    'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
     'attachments' => 'Attachments',
     'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
     'attachments_explain_instant_save' => 'Changes here are saved instantly.',
diff --git a/resources/views/tags/index.blade.php b/resources/views/tags/index.blade.php
index de231493e..c88449ce7 100644
--- a/resources/views/tags/index.blade.php
+++ b/resources/views/tags/index.blade.php
@@ -11,7 +11,7 @@
                 <div>
                     <div class="block inline mr-xs">
                         <form method="get" action="{{ url("/tags") }}">
-                            @include('form.request-query-inputs', ['params' => ['page', 'name']])
+                            @include('form.request-query-inputs', ['params' => ['name']])
                             <input type="text"
                                    name="search"
                                    placeholder="{{ trans('common.search') }}"
@@ -32,52 +32,23 @@
                 </div>
             @endif
 
+            @if(count($tags) > 0)
+                <table class="table expand-to-padding mt-m">
+                    @foreach($tags as $tag)
+                        @include('tags.parts.table-row', ['tag' => $tag, 'nameFilter' => $nameFilter])
+                    @endforeach
+                </table>
 
-            <table class="table expand-to-padding mt-m">
-                @foreach($tags as $tag)
-                    <tr>
-                        <td>
-                            <span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
-                        </td>
-                        <td width="60" class="px-xs">
-                            <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
-                               title="{{ trans('entities.tags_usages') }}"
-                               class="pill text-muted">@icon('leaderboard'){{ $tag->usages }}</a>
-                        </td>
-                        <td width="60" class="px-xs">
-                            <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
-                               title="{{ trans('entities.tags_assigned_pages') }}"
-                               class="pill text-page">@icon('page'){{ $tag->page_count }}</a>
-                        </td>
-                        <td width="60" class="px-xs">
-                            <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
-                               title="{{ trans('entities.tags_assigned_chapters') }}"
-                               class="pill text-chapter">@icon('chapter'){{ $tag->chapter_count }}</a>
-                        </td>
-                        <td width="60" class="px-xs">
-                            <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
-                               title="{{ trans('entities.tags_assigned_books') }}"
-                               class="pill text-book">@icon('book'){{ $tag->book_count }}</a>
-                        </td>
-                        <td width="60" class="px-xs">
-                            <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
-                               title="{{ trans('entities.tags_assigned_shelves') }}"
-                               class="pill text-bookshelf">@icon('bookshelf'){{ $tag->shelf_count }}</a>
-                        </td>
-                        <td class="text-right text-muted">
-                            @if($tag->values ?? false)
-                                <a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
-                            @elseif(empty($nameFilter))
-                                <a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
-                            @endif
-                        </td>
-                    </tr>
-                @endforeach
-            </table>
-
-            <div>
-                {{ $tags->links() }}
-            </div>
+                <div>
+                    {{ $tags->links() }}
+                </div>
+            @else
+                <p class="text-muted italic my-xl">
+                    {{ trans('common.no_items') }}.
+                    <br>
+                    {{ trans('entities.tags_list_empty_hint') }}
+                </p>
+            @endif
         </main>
 
     </div>
diff --git a/resources/views/tags/parts/table-row.blade.php b/resources/views/tags/parts/table-row.blade.php
new file mode 100644
index 000000000..aa04959a9
--- /dev/null
+++ b/resources/views/tags/parts/table-row.blade.php
@@ -0,0 +1,37 @@
+<tr>
+    <td>
+        <span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
+    </td>
+    <td width="70" class="px-xs">
+        <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
+           title="{{ trans('entities.tags_usages') }}"
+           class="pill text-muted">@icon('leaderboard'){{ $tag->usages }}</a>
+    </td>
+    <td width="70" class="px-xs">
+        <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
+           title="{{ trans('entities.tags_assigned_pages') }}"
+           class="pill text-page">@icon('page'){{ $tag->page_count }}</a>
+    </td>
+    <td width="70" class="px-xs">
+        <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
+           title="{{ trans('entities.tags_assigned_chapters') }}"
+           class="pill text-chapter">@icon('chapter'){{ $tag->chapter_count }}</a>
+    </td>
+    <td width="70" class="px-xs">
+        <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
+           title="{{ trans('entities.tags_assigned_books') }}"
+           class="pill text-book">@icon('book'){{ $tag->book_count }}</a>
+    </td>
+    <td width="70" class="px-xs">
+        <a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
+           title="{{ trans('entities.tags_assigned_shelves') }}"
+           class="pill text-bookshelf">@icon('bookshelf'){{ $tag->shelf_count }}</a>
+    </td>
+    <td class="text-right text-muted">
+        @if($tag->values ?? false)
+            <a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
+        @elseif(empty($nameFilter))
+            <a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
+        @endif
+    </td>
+</tr>
\ No newline at end of file
diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php
index 9b3fb1532..db76cae5f 100644
--- a/tests/Entity/TagTest.php
+++ b/tests/Entity/TagTest.php
@@ -3,6 +3,7 @@
 namespace Tests\Entity;
 
 use BookStack\Actions\Tag;
+use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Entity;
 use BookStack\Entities\Models\Page;
 use Tests\TestCase;
@@ -98,4 +99,95 @@ class TagTest extends TestCase
         $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'color');
         $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'red');
     }
+
+    public function test_tags_index_shows_tag_name_as_expected_with_right_counts()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
+        $page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']);
+
+        $resp = $this->asEditor()->get('/tags');
+        $resp->assertSee('Category');
+        $resp->assertElementCount('.tag-item', 1);
+        $resp->assertDontSee('GreatTestContent');
+        $resp->assertDontSee('OtherTestContent');
+        $resp->assertElementContains('a[title="Total tag usages"]', '2');
+        $resp->assertElementContains('a[title="Assigned to Pages"]', '2');
+        $resp->assertElementContains('a[title="Assigned to Books"]', '0');
+        $resp->assertElementContains('a[title="Assigned to Chapters"]', '0');
+        $resp->assertElementContains('a[title="Assigned to Shelves"]', '0');
+        $resp->assertElementContains('a[href$="/tags?name=Category"]', '2 unique values');
+
+        /** @var Book $book */
+        $book = Book::query()->first();
+        $book->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
+        $resp = $this->asEditor()->get('/tags');
+        $resp->assertElementContains('a[title="Total tag usages"]', '3');
+        $resp->assertElementContains('a[title="Assigned to Books"]', '1');
+        $resp->assertElementContains('a[href$="/tags?name=Category"]', '2 unique values');
+    }
+
+    public function test_tag_index_can_be_searched()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
+
+        $resp = $this->asEditor()->get('/tags?search=cat');
+        $resp->assertElementContains('.tag-item .tag-name', 'Category');
+
+        $resp = $this->asEditor()->get('/tags?search=content');
+        $resp->assertElementContains('.tag-item .tag-name', 'Category');
+        $resp->assertElementContains('.tag-item .tag-value', 'GreatTestContent');
+
+        $resp = $this->asEditor()->get('/tags?search=other');
+        $resp->assertElementNotExists('.tag-item .tag-name');
+    }
+
+    public function test_tag_index_can_be_scoped_to_specific_tag_name()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->tags()->create(['name' => 'Category', 'value' => 'GreatTestContent']);
+        $page->tags()->create(['name' => 'Category', 'value' => 'OtherTestContent']);
+        $page->tags()->create(['name' => 'OtherTagName', 'value' => 'OtherValue']);
+
+        $resp = $this->asEditor()->get('/tags?name=Category');
+        $resp->assertSee('Category');
+        $resp->assertSee('GreatTestContent');
+        $resp->assertSee('OtherTestContent');
+        $resp->assertDontSee('OtherTagName');
+        $resp->assertElementCount('table .tag-item', 2);
+        $resp->assertSee('Active Filter:');
+        $resp->assertElementContains('form[action$="/tags"]', 'Clear Filter');
+    }
+
+    public function test_tags_index_adheres_to_page_permissions()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->tags()->create(['name' => 'SuperCategory', 'value' => 'GreatTestContent']);
+
+        $resp = $this->asEditor()->get('/tags');
+        $resp->assertSee('SuperCategory');
+        $resp = $this->get('/tags?name=SuperCategory');
+        $resp->assertSee('GreatTestContent');
+
+        $page->restricted = true;
+        $this->regenEntityPermissions($page);
+
+        $resp = $this->asEditor()->get('/tags');
+        $resp->assertDontSee('SuperCategory');
+        $resp = $this->get('/tags?name=SuperCategory');
+        $resp->assertDontSee('GreatTestContent');
+    }
+
+    public function test_tag_index_shows_message_on_no_results()
+    {
+        /** @var Page $page */
+        $resp = $this->asEditor()->get('/tags?search=testingval');
+        $resp->assertSee('No items available');
+        $resp->assertSee('Tags can be assigned via the page editor sidebar');
+    }
 }
diff --git a/tests/TestResponse.php b/tests/TestResponse.php
index 5e2be3ac3..4e53aa020 100644
--- a/tests/TestResponse.php
+++ b/tests/TestResponse.php
@@ -53,6 +53,26 @@ class TestResponse extends BaseTestResponse
         return $this;
     }
 
+    /**
+     * Assert the response contains the given count of elements
+     * that match the given css selector.
+     *
+     * @return $this
+     */
+    public function assertElementCount(string $selector, int $count)
+    {
+        $elements = $this->crawler()->filter($selector);
+        PHPUnit::assertTrue(
+            $elements->count() === $count,
+            'Unable to ' . $count . ' element(s) matching the selector: ' . PHP_EOL . PHP_EOL .
+            "[{$selector}]" . PHP_EOL . PHP_EOL .
+            'found ' . $elements->count() . ' within' . PHP_EOL . PHP_EOL .
+            "[{$this->getContent()}]."
+        );
+
+        return $this;
+    }
+
     /**
      * Assert the response does not contain the specified element.
      *