diff --git a/DESCRIPTION b/DESCRIPTION
index 5b4c546f..33d49ba8 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
 Package: tinyplot
 Type: Package
 Title: Lightweight Extension of the Base R Graphics System
-Version: 0.4.1
+Version: 0.4.1.99
 Date: 2025-06-02
 Authors@R: 
   c(
diff --git a/NEWS.md b/NEWS.md
index 89667a7d..9c1c01ed 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -4,6 +4,15 @@ _If you are viewing this file on CRAN, please check the
 [latest NEWS](https://grantmcdermott.com/tinyplot/NEWS.html) on our website
 where the formatting is also better._
 
+## Development
+
+### Bug fixes
+
+- Safer handling of pre-plot hooks. Resolves an issue affecting how `tinyplot`
+  behaves inside loops, particularly for themed plots where only the final plot
+  was being drawn in Quarto/RMarkdown contexts. Special thanks to @hadley and @cderv
+  for helping us to debug. (@vincentarelbundock #425)
+
 ## 0.4.1
 
 ### Bug fixes
diff --git a/R/environment.R b/R/environment.R
new file mode 100644
index 00000000..6febbb7a
--- /dev/null
+++ b/R/environment.R
@@ -0,0 +1,14 @@
+init_environment = function() {
+  tnypltptns = parent.env(environment())
+  assign(".tinyplot_env", new.env(), envir = tnypltptns)
+  .tpar = new.env()
+  assign(".tpar", .tpar, envir = tnypltptns)
+}
+
+get_environment_variable = function(name) {
+  get(name, envir = get(".tinyplot_env", envir = parent.env(environment())))
+}
+
+set_environment_variable = function(name, value) {
+  assign(name, value, envir = get(".tinyplot_env", envir = parent.env(environment())))
+}
diff --git a/R/hooks.R b/R/hooks.R
new file mode 100644
index 00000000..1c4af278
--- /dev/null
+++ b/R/hooks.R
@@ -0,0 +1,36 @@
+# Copied from https://raw.githubusercontent.com/r-lib/evaluate/refs/heads/main/R/hooks.R
+# license: MIT + file LICENSE
+
+
+
+#' Set and remove hooks
+#'
+#' This interface wraps the base [setHook()] function to provide a return
+#' value that makes it easy to undo.
+#'
+#' @param hooks a named list of hooks - each hook can either be a function or
+#'   a list of functions.
+#' @param action `"replace"`, `"append"` or `"prepend"`
+#' @keywords internal
+set_hooks <- function(hooks, action = "append") {
+  old <- list()
+  for (hook_name in names(hooks)) {
+    old[[hook_name]] <- getHook(hook_name)
+    setHook(hook_name, hooks[[hook_name]], action = action)
+  }
+  invisible(old)
+}
+
+#' @rdname set_hooks
+#' @keywords internal
+remove_hooks <- function(hooks) {
+  for (hook_name in names(hooks)) {
+    hook <- getHook(hook_name)
+    if (length(hook) > 0) {
+      for (fun in unlist(hooks[hook_name])) {
+        hook[sapply(hook, identical, fun)] <- NULL
+      }
+    }
+    setHook(hook_name, hook, "replace")
+  }
+}
diff --git a/R/tinytheme.R b/R/tinytheme.R
index 23d2f90d..ab1e9f12 100644
--- a/R/tinytheme.R
+++ b/R/tinytheme.R
@@ -157,7 +157,8 @@ tinytheme = function(
       # for default theme, we want to revert the original pars and turn off the
       # before.new.plot hook (otherwise manual par(x = y) changes won't work) 
       tpar(settings, hook = FALSE)
-      setHook("before.new.plot", NULL, "replace")
+      old_hooks = get_environment_variable(".tpar_hooks")
+      remove_hooks(old_hooks)
     } else {
       tpar(settings, hook = TRUE)
     }
diff --git a/R/tpar.R b/R/tpar.R
index d1e662df..ab587d36 100644
--- a/R/tpar.R
+++ b/R/tpar.R
@@ -139,7 +139,12 @@ tpar = function(..., hook = FALSE) {
     base_par = opts[base_par]
     if (length(base_par) > 0) {
       if (isTRUE(hook)) {
-        setHook("before.plot.new", function() par(base_par), action = "replace")
+        # append new hook to existing ones
+        new_hooks = list("before.plot.new" = function() par(base_par))
+        set_hooks(new_hooks, action = "append")
+        # save new hook to tinyplot environment for later removal
+        old_hooks = get_environment_variable(".tpar_hooks")
+        set_environment_variable(".tpar_hooks", c(old_hooks, new_hooks))
       } else {
         par_names = names(par(no.readonly = TRUE))
         base_par = base_par[names(base_par) %in% par_names]
@@ -310,11 +315,10 @@ init_tpar = function(rm_hook = FALSE) {
   rm(list = names(.tpar), envir = .tpar)
 
   if (isTRUE(rm_hook)) {
-    hook = getHook("before.plot.new")
-    if (length(hook) > 0) {
-      # need weird function because of Quarto evaluate::evaluate failure
-      # setHook("before.plot.new", NULL, action = "replace")
-      setHook("before.plot.new", function() NULL, action = "replace")
+    old_hooks = get_environment_variable(".tpar_hooks")
+    if (length(old_hooks) > 0) {
+      remove_hooks(old_hooks)
+      set_environment_variable(".tpar_hooks", NULL)
     }
   }
 
diff --git a/R/zzz.R b/R/zzz.R
index f75af8fa..08f3f4ee 100644
--- a/R/zzz.R
+++ b/R/zzz.R
@@ -7,18 +7,13 @@
 .onLoad = function(libname, pkgname) {
   # https://stackoverflow.com/questions/12598242/global-variables-in-packages-in-r
   # https://stackoverflow.com/questions/49056642/r-how-to-make-variable-available-to-namespace-at-loading-time?noredirect=1&lq=1
-  tnypltptns = parent.env(environment())
-  assign(".tinyplot_env", new.env(), envir = tnypltptns)
-  .tpar = new.env()
-
-  assign(".tpar", .tpar, envir = tnypltptns)
-
+  init_environment()
   init_tpar()
-
-  assign(".saved_par_before", NULL, envir = get(".tinyplot_env", envir = parent.env(environment())))
-  assign(".saved_par_after", NULL, envir = get(".tinyplot_env", envir = parent.env(environment())))
-  assign(".saved_par_first", NULL, envir = get(".tinyplot_env", envir = parent.env(environment())))
-  assign(".last_call", NULL, envir = get(".tinyplot_env", envir = parent.env(environment())))
+  set_environment_variable(".saved_par_before", NULL)
+  set_environment_variable(".saved_par_after", NULL)
+  set_environment_variable(".saved_par_first", NULL)
+  set_environment_variable(".last_call", NULL)
+  set_environment_variable(".tpar_hooks", NULL)
 
   globalVariables(c(
     "add",
diff --git a/man/set_hooks.Rd b/man/set_hooks.Rd
new file mode 100644
index 00000000..b1821b41
--- /dev/null
+++ b/man/set_hooks.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/hooks.R
+\name{set_hooks}
+\alias{set_hooks}
+\alias{remove_hooks}
+\title{Set and remove hooks}
+\usage{
+set_hooks(hooks, action = "append")
+
+remove_hooks(hooks)
+}
+\arguments{
+\item{hooks}{a named list of hooks - each hook can either be a function or
+a list of functions.}
+
+\item{action}{\code{"replace"}, \code{"append"} or \code{"prepend"}}
+}
+\description{
+This interface wraps the base \code{\link[=setHook]{setHook()}} function to provide a return
+value that makes it easy to undo.
+}
+\keyword{internal}
