diff --git a/examples/pdf/pdfviewer/viewer.qml b/examples/pdf/pdfviewer/viewer.qml
index 1cf0b432b26628d5256c1652e313576f9c5f857c..b0cd8985d21afcd6a26b48fbf6b386c8df1cd3f3 100644
--- a/examples/pdf/pdfviewer/viewer.qml
+++ b/examples/pdf/pdfviewer/viewer.qml
@@ -57,8 +57,8 @@ import Qt.labs.platform 1.1 as Platform
 
 ApplicationWindow {
     id: root
-    width: 800
-    height: 640
+    width: 1280
+    height: 1024
     color: "lightgrey"
     title: document.title
     visible: true
@@ -125,12 +125,22 @@ ApplicationWindow {
                     onTriggered: pageView.rotation += 90
                 }
             }
+            ToolButton {
+                action: Action {
+                    icon.source: "resources/go-previous-view-page.svg"
+                    enabled: pageView.backEnabled
+                    onTriggered: pageView.back()
+                }
+                ToolTip.visible: enabled && hovered
+                ToolTip.delay: 2000
+                ToolTip.text: "go back"
+            }
             SpinBox {
                 id: currentPageSB
                 from: 1
                 to: document.pageCount
-                value: 1
                 editable: true
+                onValueChanged: pageView.currentPage = value - 1
                 Shortcut {
                     sequence: StandardKey.MoveToPreviousPage
                     onActivated: currentPageSB.value--
@@ -140,6 +150,16 @@ ApplicationWindow {
                     onActivated: currentPageSB.value++
                 }
             }
+            ToolButton {
+                action: Action {
+                    icon.source: "resources/go-next-view-page.svg"
+                    enabled: pageView.forwardEnabled
+                    onTriggered: pageView.forward()
+                }
+                ToolTip.visible: enabled && hovered
+                ToolTip.delay: 2000
+                ToolTip.text: "go forward"
+            }
             ToolButton {
                 action: Action {
                     shortcut: StandardKey.Copy
@@ -203,7 +223,10 @@ ApplicationWindow {
 
     PdfPageView {
         id: pageView
-        currentPage: currentPageSB.value - 1
+//        currentPage: currentPageSB.value - 1
+        // TODO should work but ends up being NaN in QQuickSpinBoxPrivate::setValue() (?!)
+//        onCurrentPageChanged: currentPageSB.value = pageView.currrentPage + 1
+        onCurrentPageReallyChanged: currentPageSB.value = page + 1
         document: PdfDocument {
             id: document
             onStatusChanged: if (status === PdfDocument.Error) errorDialog.open()
diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp
index 519ea43afb25a243d65b2a6092aad5b6c52375d5..3c8077ff28dcdf3ec77f1aca5684119bb1947eb4 100644
--- a/src/pdf/quick/plugin.cpp
+++ b/src/pdf/quick/plugin.cpp
@@ -40,6 +40,7 @@
 #include <QtQml/qqmlextensionplugin.h>
 #include "qquickpdfdocument_p.h"
 #include "qquickpdflinkmodel_p.h"
+#include "qquickpdfnavigationstack_p.h"
 #include "qquickpdfsearchmodel_p.h"
 #include "qquickpdfselection_p.h"
 
@@ -83,6 +84,7 @@ public:
 
         qmlRegisterType<QQuickPdfDocument>(uri, 5, 15, "PdfDocument");
         qmlRegisterType<QQuickPdfLinkModel>(uri, 5, 15, "PdfLinkModel");
+        qmlRegisterType<QQuickPdfNavigationStack>(uri, 5, 15, "PdfNavigationStack");
         qmlRegisterType<QQuickPdfSearchModel>(uri, 5, 15, "PdfSearchModel");
         qmlRegisterType<QQuickPdfSelection>(uri, 5, 15, "PdfSelection");
 
diff --git a/src/pdf/quick/qml/PdfPageView.qml b/src/pdf/quick/qml/PdfPageView.qml
index e20ebd1b487ca05ff2f0fd208251b37d65a927f0..041054e596557d49a99334092157d61cb82d92da 100644
--- a/src/pdf/quick/qml/PdfPageView.qml
+++ b/src/pdf/quick/qml/PdfPageView.qml
@@ -48,13 +48,18 @@ Rectangle {
     property var document: null
     property real renderScale: 1
     property alias sourceSize: image.sourceSize
-    property alias currentPage: image.currentFrame
+    property alias currentPage: navigationStack.currentPage
     property alias pageCount: image.frameCount
     property alias searchString: searchModel.searchString
     property alias selectedText: selection.text
     property alias status: image.status
+    property alias backEnabled: navigationStack.backAvailable
+    property alias forwardEnabled: navigationStack.forwardAvailable
+    function back() { navigationStack.back() }
+    function forward() { navigationStack.forward() }
+    signal currentPageReallyChanged(page: int)
 
-    property real __pageScale: image.paintedWidth / document.pagePointSize(image.currentFrame).width
+    property real __pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width
 
     function resetScale() {
         image.sourceSize.width = 0
@@ -78,7 +83,7 @@ Rectangle {
     function scaleToPage(width, height) {
         var windowAspect = width / height
         var halfRotation = Math.abs(paper.rotation % 180)
-        var pagePointSize = document.pagePointSize(image.currentFrame)
+        var pagePointSize = document.pagePointSize(navigationStack.currentPage)
         if (halfRotation > 45 && halfRotation < 135) {
             // rotated 90 or 270º
             var pageAspect = pagePointSize.height / pagePointSize.width
@@ -104,7 +109,7 @@ Rectangle {
     PdfSelection {
         id: selection
         document: paper.document
-        page: image.currentFrame
+        page: navigationStack.currentPage
         fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / paper.__pageScale, textSelectionDrag.centroid.pressPosition.y / paper.__pageScale)
         toPoint: Qt.point(textSelectionDrag.centroid.position.x / paper.__pageScale, textSelectionDrag.centroid.position.y / paper.__pageScale)
         hold: !textSelectionDrag.active && !tapHandler.pressed
@@ -116,11 +121,17 @@ Rectangle {
     PdfSearchModel {
         id: searchModel
         document: paper.document
-        page: image.currentFrame
+        page: navigationStack.currentPage
+    }
+
+    PdfNavigationStack {
+        id: navigationStack
+        onCurrentPageChanged: paper.currentPageReallyChanged(navigationStack.currentPage)
     }
 
     Image {
         id: image
+        currentFrame: navigationStack.currentPage
         source: document.status === PdfDocument.Ready ? document.source : ""
         asynchronous: true
         fillMode: Image.PreserveAspectFit
@@ -145,7 +156,7 @@ Rectangle {
         }
     }
     onRenderScaleChanged: {
-        image.sourceSize.width = document.pagePointSize(image.currentFrame).width * renderScale
+        image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale
         image.sourceSize.height = 0
         paper.scale = 1
     }
@@ -178,7 +189,7 @@ Rectangle {
         model: PdfLinkModel {
             id: linkModel
             document: paper.document
-            page: image.currentFrame
+            page: navigationStack.currentPage
         }
         delegate: Rectangle {
             color: "transparent"
@@ -191,7 +202,7 @@ Rectangle {
             TapHandler {
                 onTapped: {
                     if (page >= 0)
-                        image.currentFrame = page
+                        navigationStack.currentPage = page
                     else
                         Qt.openUrlExternally(url)
                 }
diff --git a/src/pdf/quick/qquickpdfnavigationstack.cpp b/src/pdf/quick/qquickpdfnavigationstack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c19fae7357ba47b0735f310d59b000643f575764
--- /dev/null
+++ b/src/pdf/quick/qquickpdfnavigationstack.cpp
@@ -0,0 +1,162 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtPDF module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickpdfnavigationstack_p.h"
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcNav, "qt.pdf.navigationstack")
+
+/*!
+    \qmltype PdfNavigationStack
+    \instantiates QQuickPdfNavigationStack
+    \inqmlmodule QtQuick.Pdf
+    \ingroup pdf
+    \brief History of the pages visited within a PDF Document.
+    \since 5.15
+
+    PdfNavigationStack remembers which pages the user has visited in a PDF
+    document, and provides the ability to traverse backward and forward.
+*/
+
+QQuickPdfNavigationStack::QQuickPdfNavigationStack(QObject *parent)
+    : QObject(parent)
+{
+}
+
+/*!
+    \qmlmethod void PdfNavigationStack::forward()
+
+    Goes back to the page that was being viewed before back() was called, and
+    then emits the \l currentPageJumped() signal.
+
+    If \l currentPage was set by assignment or binding since the last time
+    \l back() was called, the forward() function does nothing, because there is
+    a branch in the timeline which causes the "future" to be lost.
+*/
+void QQuickPdfNavigationStack::forward()
+{
+    if (m_nextHistoryIndex >= m_pageHistory.count())
+        return;
+    bool backAvailableWas = backAvailable();
+    bool forwardAvailableWas = forwardAvailable();
+    m_changing = true;
+    setCurrentPage(m_pageHistory.at(++m_nextHistoryIndex));
+    m_changing = false;
+    emit currentPageJumped(m_currentPage);
+    if (backAvailableWas != backAvailable())
+        emit backAvailableChanged();
+    if (forwardAvailableWas != forwardAvailable())
+        emit forwardAvailableChanged();
+}
+
+/*!
+    \qmlmethod void PdfNavigationStack::back()
+
+    Pops the stack and causes the \l currentPage property to change to the
+    most-recently-viewed page, and then emits the \l currentPageJumped()
+    signal.
+*/
+void QQuickPdfNavigationStack::back()
+{
+    if (m_nextHistoryIndex <= 0)
+        return;
+    bool backAvailableWas = backAvailable();
+    bool forwardAvailableWas = forwardAvailable();
+    m_changing = true;
+    // TODO don't do that when going back after going forward
+    m_pageHistory.append(m_currentPage);
+    setCurrentPage(m_pageHistory.at(--m_nextHistoryIndex));
+    m_changing = false;
+    emit currentPageJumped(m_currentPage);
+    if (backAvailableWas != backAvailable())
+        emit backAvailableChanged();
+    if (forwardAvailableWas != forwardAvailable())
+        emit forwardAvailableChanged();
+}
+
+/*!
+    \qmlproperty int PdfNavigationStack::currentPage
+
+    This property holds the current page that is being viewed.
+
+    It should be set when the viewer's current page changes. Every time this
+    property is set, it pushes the current page number onto the stack, such
+    that the history of pages that have been viewed will grow.
+*/
+void QQuickPdfNavigationStack::setCurrentPage(int currentPage)
+{
+    if (m_currentPage == currentPage)
+        return;
+    bool backAvailableWas = backAvailable();
+    bool forwardAvailableWas = forwardAvailable();
+    if (!m_changing) {
+        if (m_nextHistoryIndex >= 0 && m_nextHistoryIndex < m_pageHistory.count())
+            m_pageHistory.remove(m_nextHistoryIndex, m_pageHistory.count() - m_nextHistoryIndex);
+        m_pageHistory.append(m_currentPage);
+        m_nextHistoryIndex = m_pageHistory.count();
+    }
+    m_currentPage = currentPage;
+    emit currentPageChanged();
+    if (backAvailableWas != backAvailable())
+        emit backAvailableChanged();
+    if (forwardAvailableWas != forwardAvailable())
+        emit forwardAvailableChanged();
+    qCDebug(qLcNav) << "current" << m_currentPage << "history" << m_pageHistory;
+}
+
+bool QQuickPdfNavigationStack::backAvailable() const
+{
+    return m_nextHistoryIndex > 0;
+}
+
+bool QQuickPdfNavigationStack::forwardAvailable() const
+{
+    return m_nextHistoryIndex < m_pageHistory.count();
+}
+
+/*!
+    \qmlsignal PdfNavigationStack::currentPageJumped(int page)
+
+    This signal is emitted when either forward() or back() is called, to
+    distinguish navigational jumps from cases when the \l currentPage property
+    is set by means of a binding or assignment. Contrast with the
+    \c currentPageChanged signal, which is emitted in all cases, and does not
+    include the \c page argument.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/pdf/quick/qquickpdfnavigationstack_p.h b/src/pdf/quick/qquickpdfnavigationstack_p.h
new file mode 100644
index 0000000000000000000000000000000000000000..54713fabb17d6bb09c20dcc3dd3085d94865e5e0
--- /dev/null
+++ b/src/pdf/quick/qquickpdfnavigationstack_p.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtPDF module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKPDFNAVIGATIONSTACK_P_H
+#define QQUICKPDFNAVIGATIONSTACK_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qquickpdfdocument_p.h"
+
+#include <QtQml/qqml.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPdfNavigationStack : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged)
+    Q_PROPERTY(bool backAvailable READ backAvailable NOTIFY backAvailableChanged)
+    Q_PROPERTY(bool forwardAvailable READ forwardAvailable NOTIFY forwardAvailableChanged)
+
+public:
+    explicit QQuickPdfNavigationStack(QObject *parent = nullptr);
+
+    Q_INVOKABLE void forward();
+    Q_INVOKABLE void back();
+
+    int currentPage() const { return m_currentPage; }
+    void setCurrentPage(int currentPage);
+
+    bool backAvailable() const;
+    bool forwardAvailable() const;
+
+Q_SIGNALS:
+    void currentPageChanged();
+    void currentPageJumped(int page);
+    void backAvailableChanged();
+    void forwardAvailableChanged();
+
+private:
+    QVector<int> m_pageHistory;
+    int m_nextHistoryIndex = 0;
+    int m_currentPage = 0;
+    bool m_changing = false;
+
+    Q_DISABLE_COPY(QQuickPdfNavigationStack)
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPdfNavigationStack)
+
+#endif // QQUICKPDFNAVIGATIONSTACK_P_H
diff --git a/src/pdf/quick/quick.pro b/src/pdf/quick/quick.pro
index 7d65091aa3a64c82887276bdcde2f51a72e6e3c0..a3bdfb45e2eb1817dc4c0fd4e9aee1d612ff76b4 100644
--- a/src/pdf/quick/quick.pro
+++ b/src/pdf/quick/quick.pro
@@ -16,12 +16,14 @@ SOURCES += \
     plugin.cpp \
     qquickpdfdocument.cpp \
     qquickpdflinkmodel.cpp \
+    qquickpdfnavigationstack.cpp \
     qquickpdfsearchmodel.cpp \
     qquickpdfselection.cpp \
 
 HEADERS += \
     qquickpdfdocument_p.h \
     qquickpdflinkmodel_p.h \
+    qquickpdfnavigationstack_p.h \
     qquickpdfsearchmodel_p.h \
     qquickpdfselection_p.h \