diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs
--- a/crates/ui-egui/src/app.rs
+++ b/crates/ui-egui/src/app.rs
@@ -93,6 +93,7 @@ impl PdfCraftApp {
             reload_changed_files: true,
             show_secured_notice: true,
             always_default_view: false,
+            show_page_indicator: false,
             #[cfg(not(target_arch = "wasm32"))]
             watch: Default::default(),
             #[cfg(not(target_arch = "wasm32"))]
diff --git a/crates/ui-egui/src/app/control.rs b/crates/ui-egui/src/app/control.rs
--- a/crates/ui-egui/src/app/control.rs
+++ b/crates/ui-egui/src/app/control.rs
@@ -328,6 +328,14 @@ impl PdfCraftApp {
                     _ => return Err("secured-notice must be true or false".into()),
                 };
             }
+            // `--page-indicator true` / `--page_indicator true`: show a persistent `1 / N` indicator pill over the document.
+            ("page_indicator" | "page-indicator" | "show_page_indicator", _) => {
+                self.show_page_indicator = match value {
+                    "true" | "on" => true,
+                    "false" | "off" => false,
+                    _ => return Err(format!("{key} must be true or false")),
+                };
+            }
             ("system-title-bar", _) => {
                 self.system_title_bar = match value {
                     "true" => true,
@@ -389,4 +397,14 @@ impl PdfCraftApp {
         }
         Ok(())
     }
+
+    /// Read the current value of a named application option (`"page_indicator"`, `"language"`, …).
+    pub fn get_option(&self, key: &str) -> Option<String> {
+        match key {
+            "page_indicator" | "page-indicator" | "show_page_indicator" => Some(self.show_page_indicator.to_string()),
+            "secured-notice" | "show_secured_notice" => Some(self.show_secured_notice.to_string()),
+            "language" => Some(self.language.clone()),
+            _ => None,
+        }
+    }
 }
diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs
--- a/crates/ui-egui/src/app/prefs.rs
+++ b/crates/ui-egui/src/app/prefs.rs
@@ -144,6 +144,7 @@ impl PdfCraftApp {
         v["hide_empty_highlight_hover"] = serde_json::Value::Bool(self.comment_prefs.hide_empty_highlight_hover);
         v["comment_tool_pinned"] = serde_json::Value::Bool(self.comment_prefs.pinned);
         v["show_secured_notice"] = serde_json::Value::Bool(self.show_secured_notice);
+        v["show_page_indicator"] = serde_json::Value::Bool(self.show_page_indicator);
         v["organize_zoom"] = serde_json::json!(self.organize_zoom);
         // Canonical names survive changes to the order of PAPERS and do not depend on UI language.
         v["print_paper"] = serde_json::json!(pdfcraft_engine::print::PAPERS.get(self.print_draft.paper).map(|(name, _)| *name));
@@ -195,6 +196,9 @@ impl PdfCraftApp {
         if let Some(on) = v["show_secured_notice"].as_bool() {
             self.show_secured_notice = on;
         }
+        if let Some(on) = v["show_page_indicator"].as_bool() {
+            self.show_page_indicator = on;
+        }
         self.pinned.restore(&v["pinned_folders"]);
         if let Ok(preference) = serde_json::from_value::<ThemePreference>(v["theme"].clone()) {
             self.set_theme_preference(preference);
@@ -334,4 +338,14 @@ impl PdfCraftApp {
             self.apply_trusted_certificates(certs);
         }
     }
+
+    /// Serialize the user's persistent settings as JSON (alias for [`Self::persist`]).
+    pub fn save_settings(&self) -> String {
+        self.persist()
+    }
+
+    /// Restore the user's persistent settings from JSON (alias for [`Self::restore`]).
+    pub fn load_settings(&mut self, json: &str) {
+        self.restore(json);
+    }
 }
diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs
--- a/crates/ui-egui/src/canvas.rs
+++ b/crates/ui-egui/src/canvas.rs
@@ -43,7 +43,7 @@ use area::{
 };
 #[cfg(test)]
 use bars::in_size_hint_zone;
-use bars::{Notice, notices, page_size_hint, quick_bar};
+use bars::{Notice, notices, page_indicator, page_size_hint, quick_bar};
 pub use cache::RasterMemory;
 pub(crate) use cache::{BASE_SIDE, TILE_THRESHOLD, texture_image, thumbnail_rows};
 #[cfg(test)]
@@ -862,6 +862,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
         (app.left_open && app.left == crate::LeftPanel::Tool("edit")) || matches!(app.quick_tool, QuickTool::AddText | QuickTool::EditText);
     let text_style = app.text_style.clone();
     let show_secured_notice = app.show_secured_notice;
+    let show_page_indicator = app.show_page_indicator;
     let view = &mut app.views[index];
     // Opened without the owner password and something is restricted. The notice says so unless
     // the user turned it off (#4035): the restrictions themselves, and Document Properties ▸
@@ -2432,6 +2433,9 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
     }
     find_bar(view, info.pages.len(), avail, ui, &t);
     page_size_hint(view, info, units, avail, unobstructed, ui, &t);
+    if show_page_indicator {
+        page_indicator(view, info.pages.len(), avail, ui, &t);
+    }
     if tool == QuickTool::EnhanceCamera {
         crate::camera_ui::bar(ui.ctx(), view, avail, &t, can_modify);
     }
diff --git a/crates/ui-egui/src/canvas/bars.rs b/crates/ui-egui/src/canvas/bars.rs
--- a/crates/ui-egui/src/canvas/bars.rs
+++ b/crates/ui-egui/src/canvas/bars.rs
@@ -39,6 +39,32 @@ pub(super) fn page_size_hint(
         });
 }
 
+/// Persistent `current / total` page indicator pill at the bottom of the document area (#4810).
+/// Display-only: never modifies the PDF or its content streams.
+pub(super) fn page_indicator(view: &DocView, total: usize, area: Rect, ui: &mut egui::Ui, t: &Tokens) {
+    if total == 0 {
+        return;
+    }
+    let current = view.current.min(total.saturating_sub(1)).saturating_add(1);
+    let text = format!("{current} / {total}");
+    let fill = Color32::from_rgba_unmultiplied(t.card.r(), t.card.g(), t.card.b(), 220);
+    egui::Area::new(egui::Id::unique("page-indicator"))
+        .order(egui::Order::Foreground)
+        .interactable(false)
+        .pivot(egui::Align2::CENTER_BOTTOM)
+        .fixed_pos(pos2(area.center().x, area.bottom() - 14.0))
+        .show(ui.ctx(), |ui| {
+            egui::Frame::NONE
+                .fill(fill)
+                .stroke(Stroke::new(1.0, t.border))
+                .corner_radius(CornerRadius::same(99))
+                .inner_margin(egui::Margin::symmetric(10, 4))
+                .show(ui, |ui| {
+                    ui.add(egui::Label::new(egui::RichText::new(text).font(theme::medium(12.0)).color(t.text)).selectable(false));
+                });
+        });
+}
+
 /// What the notice bar's buttons ask for.
 pub(super) enum Notice {
     /// Reload the file that changed on disk, dropping unsaved changes.
diff --git a/crates/ui-egui/src/control/host.rs b/crates/ui-egui/src/control/host.rs
--- a/crates/ui-egui/src/control/host.rs
+++ b/crates/ui-egui/src/control/host.rs
@@ -55,6 +55,7 @@ impl Host for crate::PdfCraftApp {
         self.request_quit(ctx)
     }
 
+    /// Return the UI-state snapshot reported by `ui.state`.
     fn state(&self) -> Value {
         let active = self.active.and_then(|i| self.views.get(i));
         let docs: Vec<Value> = self
@@ -133,6 +134,7 @@ impl Host for crate::PdfCraftApp {
             "home": self.active.is_none(),
             "mode": format!("{:?}", self.mode),
             "default_mode": self.default_mode,
+            "show_page_indicator": self.show_page_indicator,
             "comment_tool_pinned": self.comment_prefs.pinned,
             "left_panel": if self.left_open { json!(format!("{:?}", self.left)) } else { Value::Null },
             "tools_side": if self.side_panels.tools_on_right() { "right" } else { "left" },
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -344,6 +344,8 @@ pub struct PdfCraftApp {
     /// Preferences: open every document at Default zoom and Default page display, whatever
     /// layout or zoom it asks for (#1618).
     pub always_default_view: bool,
+    /// Preferences: show a persistent `1 / N` page number indicator pill over the document (#4810).
+    pub show_page_indicator: bool,
     /// Open documents' files, watched for changes on disk.
     #[cfg(not(target_arch = "wasm32"))]
     pub(crate) watch: file_watch::FileWatch,
diff --git a/crates/ui-egui/src/workflows/js_ui.rs b/crates/ui-egui/src/workflows/js_ui.rs
--- a/crates/ui-egui/src/workflows/js_ui.rs
+++ b/crates/ui-egui/src/workflows/js_ui.rs
@@ -440,6 +440,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
             .color(t.text_muted),
     );
     ui.add(widgets::checkbox(&mut app.always_default_view, tl!("Always use my default zoom and page layout")));
+    ui.add(widgets::checkbox(&mut app.show_page_indicator, tl!("Always show page number indicator")));
     ui.horizontal(|ui| {
         ui.label(tl!("Units"));
         for u in crate::units::Unit::ALL {
diff --git a/crates/ui-egui/tests/it/viewer.rs b/crates/ui-egui/tests/it/viewer.rs
--- a/crates/ui-egui/tests/it/viewer.rs
+++ b/crates/ui-egui/tests/it/viewer.rs
@@ -2268,3 +2268,63 @@ fn the_accessible_window_has_the_window_title_as_its_name() {
     assert_eq!(name(&h).as_deref(), Some("Quarterly report — PdfCraft"));
     assert_eq!(name(&h), Some(h.state().window_title.clone()));
 }
+
+/// #4810: an optional persistent `1 / N` page indicator pill can be enabled in Preferences or via
+/// `set_option`, updates as the current page changes, survives settings persistence, and never
+/// modifies the PDF bytes.
+#[test]
+fn persistent_page_indicator_shows_updates_and_leaves_document_bytes_untouched() {
+    use egui::accesskit::Role;
+    let initial_bytes = fixture(3);
+    let expected_bytes = initial_bytes.clone();
+    let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
+        let mut app = PdfCraftApp::new();
+        app.set_option("language", "en").unwrap();
+        app.open_bytes("three.pdf", None, initial_bytes.clone()).unwrap();
+        app
+    });
+    crate::support::settle(&mut h);
+    let layer = egui::LayerId::new(egui::Order::Foreground, egui::Id::unique("page-indicator"));
+    assert!(!h.state().show_page_indicator, "off by default");
+    assert_eq!(h.state().get_option("page_indicator").as_deref(), Some("false"));
+    assert!(!h.ctx.memory(|m| m.areas().visible_last_frame(&layer)));
+    assert!(h.query_by_label("1 / 3").is_none());
+
+    // Toggle on via Preferences -> Documents and view -> "Always show page number indicator".
+    h.state_mut().set_option("dialog", "preferences").unwrap();
+    h.run_steps(2);
+    h.get_by_role_and_label(Role::CheckBox, "Always show page number indicator").scroll_to_me();
+    h.run_steps(4);
+    h.get_by_role_and_label(Role::CheckBox, "Always show page number indicator").click();
+    h.run_steps(2);
+    assert!(h.state().show_page_indicator);
+    assert_eq!(h.state().get_option("page_indicator").as_deref(), Some("true"));
+    h.state_mut().dialog = None;
+    h.state_mut().set_option("panel", "none").unwrap();
+    h.run_steps(3);
+
+    // Draws "1 / 3" on page 1.
+    assert!(h.ctx.memory(|m| m.areas().visible_last_frame(&layer)));
+    h.get_by_label("1 / 3");
+
+    // Changing to page 2 updates the indicator to "2 / 3".
+    h.state_mut().set_option("page", "2").unwrap();
+    h.run_steps(3);
+    assert!(h.query_by_label("1 / 3").is_none());
+    h.get_by_label("2 / 3");
+
+    // Persists across settings save/load and supports set_option validation.
+    let saved = h.state().save_settings();
+    let mut restored = PdfCraftApp::new();
+    restored.load_settings(&saved);
+    assert!(restored.show_page_indicator);
+    restored.set_option("page_indicator", "false").unwrap();
+    assert!(!restored.show_page_indicator);
+    assert_eq!(restored.set_option("page_indicator", "invalid"), Err("page_indicator must be true or false".into()));
+
+    // Display-only: document is clean and bytes are untouched.
+    let s = h.state();
+    let doc = s.session.get(s.views[0].id).unwrap();
+    assert!(!doc.dirty, "showing the page indicator must not dirty the document");
+    assert_eq!(doc.bytes.as_slice(), expected_bytes.as_slice(), "document bytes must remain untouched");
+}
diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml
--- a/parity/acrobat-features.toml
+++ b/parity/acrobat-features.toml
@@ -1154,8 +1154,8 @@ milestone = "M3"
 status = "shipped"
 commands = []
 tools = []
-evidence = ["crates/ui-egui/tests/it/control.rs::state_and_view_options", "crates/ui-egui/tests/it/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/it/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/it/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document"]
-notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout, ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat; in single-page view ↓ / ↑ turn the page once there is nothing left to scroll that way, like the wheel"
+evidence = ["crates/ui-egui/tests/it/control.rs::state_and_view_options", "crates/ui-egui/tests/it/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/it/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/it/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document", "crates/ui-egui/tests/it/viewer.rs::persistent_page_indicator_shows_updates_and_leaves_document_bytes_untouched"]
+notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout, ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat; in single-page view ↓ / ↑ turn the page once there is nothing left to scroll that way, like the wheel; Preferences ▸ Documents and view (--page-indicator true) offers an optional persistent 1 / N page indicator pill over the document (#4810)"
 
 [[feature]]
 id = "view.goto-page-label"
