diff --git a/Classes/Controller/Ajax/JoblistController.php b/Classes/Controller/Ajax/JoblistController.php
index ce9b84a17807046b0c5ecdd6fa131953233dece4..b238999d5fc280b5164dcf445f66064a069f945b 100644
--- a/Classes/Controller/Ajax/JoblistController.php
+++ b/Classes/Controller/Ajax/JoblistController.php
@@ -70,7 +70,10 @@ class JoblistController extends AbstractAjaxController {
 		$this->assignFilterValues($recordPageId);
 
 		// set all filtered jobs
-		$this->view->assign('jobs', FrontendFilterService::getJobs($filters, $recordPageId));
+		$jobs = FrontendFilterService::getJobs($filters, $recordPageId);
+		$this->view->assign('jobs', $jobs);
+		$this->view->assign('numberOfPages', \count($jobs));
+
 
 		// set default selected values
 		$this->view->assign('selectedCountry', $filters['country']);
diff --git a/Classes/Controller/JoblistController.php b/Classes/Controller/JoblistController.php
index 3928a4fe391c4ec15e68cb1ce2b5390fb79e0f58..f3994a1b78368806aa029c5b2efefab891345a6b 100644
--- a/Classes/Controller/JoblistController.php
+++ b/Classes/Controller/JoblistController.php
@@ -70,6 +70,7 @@ class JoblistController extends ActionController {
 		// get all jobs for the next root page
 		$jobs = $this->jobRepository->findJobs($storagePid);
 		$this->view->assign('jobs', $jobs);
+		$this->view->assign('numberOfPages', \count($jobs));
 	}
 
 	/**
diff --git a/Classes/Controller/PageBrowserController.php b/Classes/Controller/PageBrowserController.php
new file mode 100644
index 0000000000000000000000000000000000000000..5dd2a3968cfcd9ed3c5822d0b75b1d8ddd72eb98
--- /dev/null
+++ b/Classes/Controller/PageBrowserController.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace SGalinski\SgJobs\Controller;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) sgalinski Internet Services (https://www.sgalinski.de)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+
+/**
+ * Controller that handles the page browser.
+ */
+class PageBrowserController extends ActionController {
+	/**
+	 * @var string
+	 */
+	protected $pageParameterName = '';
+
+	/**
+	 * @var int
+	 */
+	protected $numberOfPages = 0;
+
+	/**
+	 * @var int
+	 */
+	protected $currentPage = 0;
+
+	/**
+	 * @var int
+	 */
+	protected $pagesBefore = 0;
+
+	/**
+	 * @var int
+	 */
+	protected $pagesAfter = 0;
+
+	/**
+	 * @var bool
+	 */
+	protected $enableMorePages = FALSE;
+
+	/**
+	 * @var bool
+	 */
+	protected $enableLessPages = FALSE;
+
+	/**
+	 * Renders the index view.
+	 *
+	 * @param int $currentPage
+	 * @return void
+	 */
+	public function indexAction($currentPage = 0) {
+		$this->currentPage = (int) $currentPage;
+
+		$this->initializeConfiguration();
+		$this->addDataToView();
+	}
+
+	/**
+	 * Initializes the configuration.
+	 *
+	 * @return    void
+	 */
+	protected function initializeConfiguration() {
+		$this->pageParameterName = 'tx_sgjobs_pagebrowser[currentPage]';
+		$this->numberOfPages = (int) $this->settings['numberOfPages'];
+		$this->pagesBefore = (int) $this->settings['pagesBefore'];
+		$this->pagesAfter = (int) $this->settings['pagesAfter'];
+		$this->enableMorePages = (bool) $this->settings['enableMorePages'];
+		$this->enableLessPages = (bool) $this->settings['enableLessPages'];
+	}
+
+	/**
+	 * Adds the necessary data to the view.
+	 *
+	 * @return void
+	 */
+	protected function addDataToView() {
+		if ($this->numberOfPages <= 1) {
+			$this->view = NULL;
+			return;
+		}
+
+		$pageLinks = [];
+		$start = max($this->currentPage - $this->pagesBefore, 0);
+		$end = min($this->numberOfPages, $this->currentPage + $this->pagesAfter + 1);
+		for ($i = $start; $i < $end; $i++) {
+			$pageLinks[] = [
+				'number' => $i + 1,
+				'link' => $this->getPageLink($i),
+				'isCurrentPage' => $i === $this->currentPage,
+			];
+		}
+
+		$this->view->assignMultiple(
+			[
+				'enableLessPages' => $this->enableLessPages,
+				'enableMorePages' => $this->enableMorePages,
+				'previousLink' => $this->getPageLink($this->currentPage - 1),
+				'nextLink' => $this->getPageLink($this->currentPage + 1),
+				'enableLessPagesLink' => $this->getPageLink($this->currentPage - $this->pagesBefore - 1),
+				'enableMorePagesLink' => $this->getPageLink($this->currentPage + $this->pagesAfter + 1),
+				'pageLinks' => $pageLinks,
+				'prevPageExist' => $this->currentPage > 0,
+				'showLessPages' => ($this->currentPage - $this->pagesBefore) > 0,
+				'showNextPages' => ($this->currentPage + $this->pagesAfter + 1) < $this->numberOfPages,
+				'nextPageExist' => $this->currentPage < ($this->numberOfPages - 1),
+			]
+		);
+	}
+
+	/**
+	 * Generates page link. Keeps all current URL parameters except for cHash and tx_pagebrowse_pi1[page].
+	 *
+	 * @param int $page Page number starting from 1
+	 * @return string
+	 */
+	protected function getPageLink($page): string {
+		return $this->uriBuilder->reset()->setAddQueryString(TRUE)->setUseCacheHash(TRUE)
+			->uriFor('index', ['currentPage' => $page,]);
+	}
+}
diff --git a/Classes/ViewHelpers/ExtendedIfViewHelper.php b/Classes/ViewHelpers/ExtendedIfViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..331b3eef24b9a0719445fa73bd579438d1550f58
--- /dev/null
+++ b/Classes/ViewHelpers/ExtendedIfViewHelper.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace SGalinski\SgJobs\ViewHelpers;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) sgalinski Internet Services (https://www.sgalinski.de)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\VersionNumberUtility;
+use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
+
+/**
+ * ExtendedIfViewHelper
+ */
+class ExtendedIfViewHelper extends AbstractConditionViewHelper {
+	/**
+	 * Initializes the "then" and "else" arguments
+	 *
+	 * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception
+	 */
+	public function initializeArguments() {
+		parent::initializeArguments();
+		if (VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version) < 8000000) {
+			$this->registerArgument(
+				'condition', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+			);
+		}
+		$this->registerArgument(
+			'or', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+		);
+		$this->registerArgument(
+			'or2', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+		);
+		$this->registerArgument(
+			'or3', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+		);
+		$this->registerArgument(
+			'or4', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+		);
+		$this->registerArgument(
+			'and', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, TRUE
+		);
+		$this->registerArgument(
+			'and2', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, TRUE
+		);
+		$this->registerArgument(
+			'and3', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, TRUE
+		);
+		$this->registerArgument(
+			'and4', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, TRUE
+		);
+		$this->registerArgument(
+			'negate', 'boolean', 'Condition expression conforming to Fluid boolean rules', FALSE, FALSE
+		);
+	}
+
+	/**
+	 * This method decides if the condition is TRUE or FALSE. It can be overriden in extending viewhelpers to adjust functionality.
+	 *
+	 * @param array $arguments ViewHelper arguments to evaluate the condition for this ViewHelper, allows for flexiblity in overriding this method.
+	 * @return bool
+	 */
+	protected static function evaluateCondition($arguments = NULL): bool {
+		$conditionResult = (
+				(isset($arguments['condition']) && $arguments['condition']) ||
+				(isset($arguments['or']) && $arguments['or']) ||
+				(isset($arguments['or2']) && $arguments['or2']) ||
+				(isset($arguments['or3']) && $arguments['or3']) ||
+				(isset($arguments['or4']) && $arguments['or4'])
+			) && isset($arguments['and']) && $arguments['and'] &&
+			isset($arguments['and2']) && $arguments['and2'] &&
+			isset($arguments['and3']) && $arguments['and3'] &&
+			isset($arguments['and4']) && $arguments['and4'];
+
+		return isset($arguments['negate']) && $arguments['negate'] ? !$conditionResult : $conditionResult;
+	}
+}
diff --git a/Classes/ViewHelpers/PageBrowserViewHelper.php b/Classes/ViewHelpers/PageBrowserViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..a380a2965b3c803ca92b2863364d7853d0dd89e9
--- /dev/null
+++ b/Classes/ViewHelpers/PageBrowserViewHelper.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace SGalinski\SgJobs\ViewHelpers;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) sgalinski Internet Services (https://www.sgalinski.de)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+
+/**
+ * View helper that renders a page browser based upon the pagebrowse extension.
+ *
+ * Example:
+ * {namespace sg=SGalinski\SgJobs\ViewHelpers}
+ * <sg:pageBrowser numberOfPages="" />
+ */
+class PageBrowserViewHelper extends AbstractViewHelper {
+	/**
+	 * Render method of the view helper.
+	 *
+	 * @param integer $numberOfPages
+	 * @return string
+	 * @throws \UnexpectedValueException
+	 */
+	public function render($numberOfPages): string {
+		$configuration = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_sgjobs.']['pagebrowser.'];
+		$configuration['settings.']['numberOfPages'] = (int) $numberOfPages;
+
+		/** @var ContentObjectRenderer $contentObject */
+		$contentObject = $this->objectManager->get(ContentObjectRenderer::class);
+		$contentObject->start([]);
+		return $contentObject->cObjGetSingle('USER', $configuration);
+	}
+}
diff --git a/Configuration/TypoScript/Frontend/constants.ts b/Configuration/TypoScript/Frontend/constants.ts
index f5b525272f662c3ccd6d97b34c3bdd3ab3a7af6f..f991c3f562ebec8cc18cf5d3697d9e0810bd4a98 100644
--- a/Configuration/TypoScript/Frontend/constants.ts
+++ b/Configuration/TypoScript/Frontend/constants.ts
@@ -18,4 +18,18 @@ plugin.tx_sgjobs {
 		# cat=plugin.tx_sgjobs/other; type=string; label=Allowed mime types for uploads in the Fluid template (comma separated)
 		allowedMimeTypes = application/pdf
 	}
+
+	pagebrowser.settings {
+		# Number of page links to show before the current page
+		pagesBefore = 1
+
+		# Number of page links to show before the current page
+		pagesAfter = 1
+
+		# Enables section for "more" pages. This section is shown after links to next pages, normally like three dots (1 2 3 ...). Notice that you can also hide it by emptying corresponding template section.
+		enableMorePages = 1
+
+		# Enables section for "less" pages. This section is shown after links to next pages, normally like three dots (... 1 2 3) Notice that you can also hide it by emptying corresponding template section.
+		enableLessPages = 1
+	}
 }
diff --git a/Configuration/TypoScript/Frontend/setup.ts b/Configuration/TypoScript/Frontend/setup.ts
index 7837b6071b48c251490cc14e85e5a8c318cdea26..c2aee221595406248bc72e918e2b0119907fd9d2 100644
--- a/Configuration/TypoScript/Frontend/setup.ts
+++ b/Configuration/TypoScript/Frontend/setup.ts
@@ -34,4 +34,39 @@ plugin.tx_sgjobs {
 	legacy {
 		enableLegacyFlashMessageHandling = 0
 	}
+
+	pagebrowser = USER
+	pagebrowser {
+		userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
+		extensionName = SgJobs
+		pluginName = PageBrowser
+		vendorName = SGalinski
+		controller = PageBrowser
+		action = index
+		view < plugin.tx_sgjobs.view
+		persistence < plugin.tx_sgjobs.persistence
+		features < plugin.tx_sgjobs.features
+		# cHash can't be generated for pageBrowser call; will cause a 404 error if not set to 0
+		features.requireCHashArgumentForActionArguments = 0
+		legacy < plugin.tx_sgjobs.legacy
+		settings {
+			# Number of page links to show before the current page
+			pagesBefore = {$plugin.tx_sgjobs.pagebrowser.settings.pagesBefore}
+
+			# Number of page links to show before the current page
+			pagesAfter = {$plugin.tx_sgjobs.pagebrowser.settings.pagesAfter}
+
+			# Enables section for "more" pages. This section is shown after links to next pages, normally like three dots (1 2 3 ...). Notice that you can also hide it by emptying corresponding template section.
+			enableMorePages = {$plugin.tx_sgjobs.pagebrowser.settings.enableMorePages}
+
+			# Enables section for "less" pages. This section is shown after links to next pages, normally like three dots (... 1 2 3) Notice that you can also hide it by emptying corresponding template section.
+			enableLessPages = {$plugin.tx_sgjobs.pagebrowser.settings.enableLessPages}
+
+			# The number of the pages
+			numberOfPages = 0
+
+			# The current page
+			currentPage = 0
+		}
+	}
 }
diff --git a/Resources/Private/Layouts/PageBrowser.html b/Resources/Private/Layouts/PageBrowser.html
new file mode 100644
index 0000000000000000000000000000000000000000..1297e8bed3cac53993ce21a270b61f599097a92a
--- /dev/null
+++ b/Resources/Private/Layouts/PageBrowser.html
@@ -0,0 +1,3 @@
+<div class="tx-sgjobs-pagebrowser">
+	<f:render section="main" />
+</div>
diff --git a/Resources/Private/Templates/Joblist/Index.html b/Resources/Private/Templates/Joblist/Index.html
index 810d70fe855533691f66ba2c9ef1938583828e8b..a452fea7c43741d53342be6e07af7e29e3d137b2 100644
--- a/Resources/Private/Templates/Joblist/Index.html
+++ b/Resources/Private/Templates/Joblist/Index.html
@@ -1,9 +1,8 @@
+{namespace sg=SGalinski\SgJobs\ViewHelpers}
 <f:layout name="Default" />
-
 <f:section name="main">
 
 	<div id="sgjobs-joblist">
-
 		<f:render
 			partial="Filter"
 			arguments="{filters: filters, countries: countries, cities: cities, companies: companies, areas: areas, functions: functions}"
@@ -13,6 +12,10 @@
 			<f:render partial="Job" arguments="{job: job}" />
 			<hr>
 		</f:for>
+
+		<f:format.raw>
+			<sg:pageBrowser numberOfPages="{numberOfPages}" />
+		</f:format.raw>
 	</div>
 	<div id="sgjobs-apply-nojob">
 		<f:form action="applyForm" controller="Joblist" pluginName="JobApplication" pageUid="{settings.applyPage}" objectName="jobData">
diff --git a/Resources/Private/Templates/PageBrowser/Index.html b/Resources/Private/Templates/PageBrowser/Index.html
new file mode 100644
index 0000000000000000000000000000000000000000..fa04a25f535f7d4e153b019f383d33e9b852326c
--- /dev/null
+++ b/Resources/Private/Templates/PageBrowser/Index.html
@@ -0,0 +1,85 @@
+{namespace sg=SGalinski\SgJobs\ViewHelpers}
+
+<f:layout name="PageBrowser" />
+
+<f:section name="main">
+	<nav>
+		<ul class="pagination">
+			<f:if condition="{prevPageExist}">
+				<f:then>
+					<li class="tx-pagebrowse-prev">
+						<a href="{previousLink}" aria-label="Previous">
+							&laquo;
+						</a>
+					</li>
+				</f:then>
+				<f:else>
+					<li class="tx-pagebrowse-prev disabled">
+						<a aria-label="Previous">
+							<span aria-hidden="true">
+								&laquo;
+							</span>
+						</a>
+					</li>
+				</f:else>
+			</f:if>
+
+			<sg:extendedIf condition="{enableLessPages}" and="{showLessPages}">
+				<li>
+					<a href="{enableLessPagesLink}">
+						...
+					</a>
+				</li>
+			</sg:extendedIf>
+
+			<f:for each="{pageLinks}" as="pageLink">
+				<f:if condition="{pageLink.isCurrentPage}">
+					<f:then>
+						<li class="tx-pagebrowse-current active">
+							<a href="#">
+								{pageLink.number}
+								<span class="sr-only">
+									(current)
+								</span>
+							</a>
+						</li>
+					</f:then>
+					<f:else>
+						<li class="tx-pagebrowse-page">
+							<a href="{pageLink.link}">
+								{pageLink.number}
+							</a>
+						</li>
+					</f:else>
+				</f:if>
+			</f:for>
+
+			<sg:extendedIf condition="{enableMorePages}" and="{showNextPages}">
+				<li>
+					<a href="{enableMorePagesLink}">
+						...
+					</a>
+				</li>
+			</sg:extendedIf>
+
+			<f:if condition="{nextPageExist}">
+				<f:then>
+					<li class="tx-pagebrowse-next">
+						<a href="{nextLink}" aria-label="Next">
+							&raquo;
+						</a>
+					</li>
+				</f:then>
+				<f:else>
+					<li class="tx-pagebrowse-next disabled">
+						<a aria-label="Next">
+							<span aria-hidden="true">
+								&raquo;
+							</span>
+						</a>
+					</li>
+				</f:else>
+			</f:if>
+		</ul>
+	</nav>
+</f:section>
diff --git a/ext_localconf.php b/ext_localconf.php
index b15755ae847f14c47e5234ab05cc343236a48f52..170399efcb60fdf3b1b05fa1c676eafb81d79b93 100644
--- a/ext_localconf.php
+++ b/ext_localconf.php
@@ -26,14 +26,21 @@
 	]
 );
 
+\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
+	'SGalinski.sg_jobs',
+	'PageBrowser',
+	['PageBrowser' => 'index',],
+	['PageBrowser' => '',]
+);
+
 // Configure ajax controller
-\SGalinski\SgAjax\Service\AjaxRegistration::configureAjaxFrontendPlugin('sg_jobs', [
+\SGalinski\SgAjax\Service\AjaxRegistration::configureAjaxFrontendPlugin(
+	'sg_jobs', [
 		'Ajax\Joblist' => 'filter',
 		'Ajax\JobApplication' => 'deleteTempFile',
 	]
 );
 
-
 if (TYPO3_MODE === 'BE') {
 	$extPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('sg_jobs');
 	$tsPath = $extPath . 'Configuration/TypoScript/Backend/';