diff --git a/gulpfile.esm.js b/gulpfile.esm.js
index c03d6053bd427b71caf0339f90bf9e106bc02e30..c2f73efbc9b0ca0714e4d4fa52d40334bf0cca21 100644
--- a/gulpfile.esm.js
+++ b/gulpfile.esm.js
@@ -17,12 +17,12 @@ const PREZ_GLOB = `${SRC_DIR}/**/index.md`;
 const GRAPH_GLOB = `${SRC_DIR}/**/*.drawio`;
 const PDF_GLOB = `${DEST_DIR}/**/index.html`;
 
-export const clean = () => del(`${DEST_DIR}/**`);
+export const clean = () => del(`${DEST_DIR}/**`, { force: true });
 
 export const assets = () => src(ASSET_GLOB).pipe(dest(DEST_DIR));
-export const graphs = () => src(GRAPH_GLOB).pipe(drawio()).pipe(dest(DEST_DIR));
 export const prez = () =>
   src(PREZ_GLOB).pipe(index()).pipe(pandoc()).pipe(dest(DEST_DIR));
+export const graphs = () => src(GRAPH_GLOB).pipe(drawio()).pipe(dest(DEST_DIR));
 export const pdf = () => src(PDF_GLOB).pipe(wkhtmltopdf()).pipe(dest(DEST_DIR));
 
 export const build = series(clean, parallel(assets, prez, graphs), pdf);
diff --git a/lib/drawio.js b/lib/drawio.js
index 5851a282d9af0f2110ab7f7c12bcdee7ed37f781..873c43f85189a2cc74172565a8d7574cdcba9a0b 100644
--- a/lib/drawio.js
+++ b/lib/drawio.js
@@ -5,47 +5,55 @@ import logger from "gulplog";
 import { mkTempFile } from "./tempdir";
 import { obj } from "through2";
 import PluginError from "plugin-error";
+import { withBinary } from "./optional";
 
 const PLUGIN_NAME = "drawio";
 
-const DRAWIO_BINARY = process.env.DRAWIO_BINARY || "drawio";
-
 const execFile = promisify(child_process.execFile);
 
-export default function drawio() {
-  return obj(
-    callbackify(async function (input) {
-      try {
-        const output = await mkTempFile(input);
-        output.extname = ".svg";
-
-        const args = [
-          "--export",
-          "--format",
-          "svg",
-          "--width",
-          "1024",
-          "--output",
-          output.path,
-          input.path,
-        ];
-
-        const { stdout, stderr } = await execFile(DRAWIO_BINARY, args);
-        logger.info("%s: generated %s", PLUGIN_NAME, output.relative);
-        if (stdout.length > 0) {
-          logger.debug(PLUGIN_NAME, "stdout:", stdout);
-        }
-        if (stderr.length > 0) {
-          logger.info(PLUGIN_NAME, "stderr:", stderr);
-        }
-
-        output.contents = fs.createReadStream(output.path, {
-          encoding: "UTF-8",
-        });
-        this.push(output);
-      } catch (error) {
-        throw new PluginError(PLUGIN_NAME, error);
-      }
-    })
-  );
-}
+const drawio = withBinary(
+  "DRAWIO_BINARY",
+  "drawio",
+  (DRAWIO_BINARY) =>
+    function drawio() {
+      return obj(
+        callbackify(async function (input) {
+          try {
+            const output = await mkTempFile(input);
+            output.extname = ".svg";
+
+            const args = [
+              "--export",
+              "--format",
+              "svg",
+              "--width",
+              "1024",
+              "--output",
+              output.path,
+              input.path,
+            ];
+
+            const { stdout, stderr } = await execFile(DRAWIO_BINARY, args);
+            if (stdout.length > 0) {
+              logger.info(PLUGIN_NAME, "stdout:", stdout);
+            }
+            if (stderr.length > 0) {
+              logger.info(PLUGIN_NAME, "command:", DRAWIO_BINARY, args);
+              logger.info(PLUGIN_NAME, "stderr:", stderr);
+            }
+
+            output.contents = fs.createReadStream(output.path, {
+              encoding: "UTF-8",
+            });
+            logger.info("%s: generated %s", PLUGIN_NAME, output.relative);
+
+            this.push(output);
+          } catch (error) {
+            throw new PluginError(PLUGIN_NAME, error);
+          }
+        })
+      );
+    }
+);
+
+export default drawio;
diff --git a/lib/index.js b/lib/index.js
index 3447e07567892b181b22c16c4ba7dde9aeaf9e52..23ac1717536a486f552c21b4cd0323dbcec86737 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,5 +1,6 @@
 import { callbackify } from "util";
 import File from "vinyl";
+import { getPDFOutput } from "./wkhtmltopdf";
 import logger from "gulplog";
 import { obj } from "through2";
 import PluginError from "plugin-error";
@@ -75,18 +76,6 @@ function getPrezFilename(input) {
   return prez.relative;
 }
 
-/**
- * @param {File} input
- * @return {string}
- */
-function getPdfFilename(input) {
-  const parent = new File({ path: input.dirname });
-  const pdf = input.clone({ deep: false, contents: false });
-  pdf.stem = parent.stem;
-  pdf.extname = ".pdf";
-  return pdf.relative;
-}
-
 function renderIndex(inputs) {
   return `% Présentations
 % Dev@Science
@@ -108,7 +97,9 @@ ${inputs
   .map(
     (input) =>
       `- [${input.titleBlock.title}](${getPrezFilename(input)})` +
-      `[<img class="plain icon" src="file_pdf.png"/>](${getPdfFilename(input)})`
+      `[<img class="plain icon" src="file_pdf.png"/>](${
+        getPDFOutput(input).relative
+      })`
   )
   .join("\n")}`;
 }
diff --git a/lib/optional.js b/lib/optional.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c8e33ddf665c8218eac1b72f7460df9c301c9da
--- /dev/null
+++ b/lib/optional.js
@@ -0,0 +1,29 @@
+import logger from "gulplog";
+import { obj } from "through2";
+import which from "which";
+
+function noop() {
+  return obj((_file, _enc, done) => done());
+}
+
+/**
+ *
+ * @param {string} envName
+ * @param {any}
+ * @param {*} resolve
+ */
+export function withBinary(envName, execName, resolve) {
+  const setting = process.env[envName] || execName;
+  const binary = which.sync(setting, { nothrow: true });
+  if (!binary) {
+    logger.info("%s not found (configure with %s)", execName, envName);
+    return noop;
+  }
+  logger.info(
+    "Using `%s` for %s (configure with %s)",
+    binary,
+    execName,
+    envName
+  );
+  return resolve(binary);
+}
diff --git a/lib/wkhtmltopdf.js b/lib/wkhtmltopdf.js
index b0f520a8b2b060888b5a8bb97895ec71701db075..33febcdae0873d9fcf7e3c70e3e57c9e5287e717 100644
--- a/lib/wkhtmltopdf.js
+++ b/lib/wkhtmltopdf.js
@@ -1,11 +1,12 @@
 import { callbackify, promisify } from "util";
 import child_process from "child_process";
 import { createReadStream } from "fs";
-import File from "vinyl";
 import logger from "gulplog";
 import { mkTempFile } from "./tempdir";
 import { obj } from "through2";
+import path from "path";
 import PluginError from "plugin-error";
+import { withBinary } from "./optional";
 
 const execFile = promisify(child_process.execFile);
 
@@ -32,37 +33,52 @@ const DEFAULT_OPTIONS = {
   ],
 };
 
-const WKHTMLTOPDF_BINARY = process.env.WKHTMLTOPDF_BINARY || "wkhtmltopdf";
+/**
+ * @param {File} input
+ * @return {string}
+ */
+export function getPDFOutput(input) {
+  const output = input.clone({ deep: false, contents: false });
+  output.basename = path.basename(input.dirname, ".md") + ".pdf";
+  return output;
+}
 
-export default function wkhtmltopdf(options = {}) {
-  const { args } = Object.assign({}, DEFAULT_OPTIONS, options);
-  return obj(
-    callbackify(async function (input) {
-      try {
-        const output = await mkTempFile(input);
-        const parent = new File({ path: input.dirname });
-        output.stem = parent.stem;
-        output.extname = ".pdf";
-        const execArgs = [
-          ...args,
-          `file://${input.path}?print-pdf`,
-          output.path,
-        ];
+export const wkhtmltopdf = withBinary(
+  "WKHTMLTOPDF_BINARY",
+  "wkhtmltopdf",
+  (WKHTMLTOPDF_BINARY) =>
+    function wkhtmltopdf(options = {}) {
+      const { args } = Object.assign({}, DEFAULT_OPTIONS, options);
+      return obj(
+        callbackify(async function (input) {
+          try {
+            const output = await mkTempFile(getPDFOutput(input));
+            const execArgs = [
+              ...args,
+              `file://${input.path}?print-pdf`,
+              output.path,
+            ];
 
-        const { stdout, stderr } = await execFile(WKHTMLTOPDF_BINARY, execArgs);
-        logger.info("%s: generated %s", PLUGIN_NAME, output.relative);
-        if (stdout.length > 0) {
-          logger.debug(PLUGIN_NAME, "stdout:", stdout);
-        }
-        if (stderr.length > 0) {
-          logger.debug(PLUGIN_NAME, "stderr:", stderr);
-        }
+            const { stdout, stderr } = await execFile(
+              WKHTMLTOPDF_BINARY,
+              execArgs
+            );
+            logger.info("%s: generated %s", PLUGIN_NAME, output.relative);
+            if (stdout.length > 0) {
+              logger.debug(PLUGIN_NAME, "stdout:", stdout);
+            }
+            if (stderr.length > 0) {
+              logger.debug(PLUGIN_NAME, "stderr:", stderr);
+            }
 
-        output.contents = createReadStream(output.path);
-        this.push(output);
-      } catch (err) {
-        throw new PluginError(PLUGIN_NAME, err, { filename: input.path });
-      }
-    })
-  );
-}
+            output.contents = createReadStream(output.path);
+            this.push(output);
+          } catch (err) {
+            throw new PluginError(PLUGIN_NAME, err, { filename: input.path });
+          }
+        })
+      );
+    }
+);
+
+export default wkhtmltopdf;
diff --git a/package-lock.json b/package-lock.json
index f9483d3501bd0a10e137dfe078f5a00c81d56287..3ccac86c5868281f871c46eb81660f3de4b7bf80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1033,6 +1033,17 @@
         "semver": "^5.5.0",
         "shebang-command": "^1.2.0",
         "which": "^1.2.9"
+      },
+      "dependencies": {
+        "which": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
       }
     },
     "currently-unhandled": {
@@ -2836,6 +2847,16 @@
         "ini": "^1.3.4",
         "is-windows": "^1.0.1",
         "which": "^1.2.14"
+      },
+      "dependencies": {
+        "which": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
       }
     },
     "globals": {
@@ -6870,9 +6891,9 @@
       }
     },
     "which": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
       "requires": {
         "isexe": "^2.0.0"
       }
diff --git a/package.json b/package.json
index ec1f010f205b28096d89f2e9787a2dfd3d403811..474dcd1d8c0d8603fe054dca29e96ccce2ad19ce 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
     "gulplog": "^1.0.0",
     "plugin-error": "^1.0.1",
     "through2": "^3.0.1",
-    "vinyl": "^2.2.0"
+    "vinyl": "^2.2.0",
+    "which": "^2.0.2"
   },
   "devDependencies": {
     "eslint": "^6.8.0",