diff --git a/CHANGELOG.md b/CHANGELOG.md
index a97c7cb4..3082d0d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+
+### Added
+
+- Add `texlab.build.pdfDirectory` setting ([#911](https://github.com/latex-lsp/texlab/issues/911))
+
 ## [5.8.0] - 2023-07-30
 
 ### Added
diff --git a/crates/base-db/src/config.rs b/crates/base-db/src/config.rs
index 36063b47..bffd28b9 100644
--- a/crates/base-db/src/config.rs
+++ b/crates/base-db/src/config.rs
@@ -24,6 +24,7 @@ pub struct BuildConfig {
     pub forward_search_after: bool,
     pub aux_dir: String,
     pub log_dir: String,
+    pub pdf_dir: String,
     pub output_filename: Option<PathBuf>,
 }
 
@@ -115,6 +116,7 @@ impl Default for BuildConfig {
             forward_search_after: false,
             aux_dir: String::from("."),
             log_dir: String::from("."),
+            pdf_dir: String::from("."),
             output_filename: None,
         }
     }
diff --git a/crates/commands/src/clean.rs b/crates/commands/src/clean.rs
index d6c3977f..562d9c49 100644
--- a/crates/commands/src/clean.rs
+++ b/crates/commands/src/clean.rs
@@ -21,20 +21,26 @@ impl CleanCommand {
             anyhow::bail!("document '{}' is not a local file", document.uri)
         };
 
-        let dir = workspace.current_dir(&document.dir);
-        let dir = workspace
-            .output_dir(&dir, workspace.config().build.log_dir.clone())
-            .to_file_path()
-            .unwrap();
+        let base_dir = workspace.current_dir(&document.dir);
 
         let flag = match target {
             CleanTarget::Auxiliary => "-c",
             CleanTarget::Artifacts => "-C",
         };
 
+        let out_dir = match target {
+            CleanTarget::Auxiliary => &workspace.config().build.aux_dir,
+            CleanTarget::Artifacts => &workspace.config().build.pdf_dir,
+        };
+
+        let out_dir = workspace
+            .output_dir(&base_dir, out_dir.clone())
+            .to_file_path()
+            .unwrap();
+
         let executable = String::from("latexmk");
         let args = vec![
-            format!("-outdir={}", dir.display()),
+            format!("-outdir={}", out_dir.display()),
             String::from(flag),
             path.display().to_string(),
         ];
diff --git a/crates/commands/src/fwd_search.rs b/crates/commands/src/fwd_search.rs
index 153e1245..9004ef9b 100644
--- a/crates/commands/src/fwd_search.rs
+++ b/crates/commands/src/fwd_search.rs
@@ -60,7 +60,7 @@ impl ForwardSearch {
 
         let dir = workspace.current_dir(&parent.dir);
         let dir = workspace
-            .output_dir(&dir, workspace.config().build.log_dir.clone())
+            .output_dir(&dir, workspace.config().build.pdf_dir.clone())
             .to_file_path()
             .unwrap();
 
diff --git a/crates/texlab/src/server/options.rs b/crates/texlab/src/server/options.rs
index ddf20f0a..a602e5d7 100644
--- a/crates/texlab/src/server/options.rs
+++ b/crates/texlab/src/server/options.rs
@@ -71,6 +71,7 @@ pub struct BuildOptions {
     pub forward_search_after: bool,
     pub aux_directory: Option<String>,
     pub log_directory: Option<String>,
+    pub pdf_directory: Option<String>,
     pub filename: Option<String>,
 }
 
@@ -164,12 +165,17 @@ impl From<Options> for Config {
             .or_else(|| value.aux_directory.clone())
             .unwrap_or_else(|| String::from("."));
 
-        config.build.log_dir = value
+        config.build.pdf_dir = value
             .build
-            .log_directory
+            .pdf_directory
             .or_else(|| value.aux_directory)
             .unwrap_or_else(|| String::from("."));
 
+        config.build.log_dir = value
+            .build
+            .log_directory
+            .unwrap_or_else(|| config.build.pdf_dir.clone());
+
         config.build.output_filename = value.build.filename.map(PathBuf::from);
 
         config.diagnostics.allowed_patterns = value