diff --git a/Cargo.lock b/Cargo.lock
index 02bf066e0d92ec8b3b1e985e44ba1dfa9576ac1b..7763e96f01ff5a71f76d7cb2c99517d812198779 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -795,26 +795,35 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.0.6"
+version = "3.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1957aa4a5fb388f0a0a73ce7556c5b42025b874e5cdc2c670775e346e97adec0"
+checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
 dependencies = [
  "atty",
  "bitflags",
  "clap_derive",
+ "clap_lex",
  "indexmap",
  "lazy_static",
- "os_str_bytes",
  "strsim",
  "termcolor",
- "textwrap 0.14.2",
+ "textwrap 0.15.0",
+]
+
+[[package]]
+name = "clap_complete"
+version = "3.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4"
+dependencies = [
+ "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "3.0.6"
+version = "3.1.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153"
+checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
 dependencies = [
  "heck 0.4.0",
  "proc-macro-error",
@@ -823,6 +832,15 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "clap_lex"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
+dependencies = [
+ "os_str_bytes",
+]
+
 [[package]]
 name = "common-runtime"
 version = "0.8.0-dev"
@@ -1271,6 +1289,7 @@ version = "3.0.0"
 dependencies = [
  "async-io",
  "clap",
+ "clap_complete",
  "common-runtime",
  "frame-benchmarking",
  "frame-benchmarking-cli",
@@ -4319,9 +4338,6 @@ name = "os_str_bytes"
 version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
-dependencies = [
- "memchr",
-]
 
 [[package]]
 name = "owning_ref"
@@ -8093,9 +8109,9 @@ dependencies = [
 
 [[package]]
 name = "textwrap"
-version = "0.14.2"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
 
 [[package]]
 name = "thiserror"
diff --git a/Cargo.toml b/Cargo.toml
index a7d8042684aaaeb75e0ef3957083c91d22057792..8b4b392ab2984e8d44d25f4a37bbae240df983d7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,6 +52,7 @@ sp-membership = { path = 'primitives/membership' }
 # crates.io dependencies
 async-io = "1.3"
 clap = { version = "3.0", features = ["derive"] }
+clap_complete = { version = "3" }
 futures = { version = "0.3.1", features = ["compat"] }
 hex = "0.4.3"
 jsonrpc-core = '18.0.0'
diff --git a/README.md b/README.md
index 11a84eff08d697f10797011cc0d5a2158fe242b6..d4b8a89ce4fbf85737b0f9b662e17bdeaf8a8375 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,39 @@ subcommands:
 ./target/release/duniter -h
 ```
 
+### Autocompletion
+
+One can generate autocompletion for its favorite shell using the following option:
+
+```sh
+cargo run --release -- completion --generator <GENERATOR>
+```
+
+Where `GENERATOR` can be any of `bash`, `elvish`, `fish`, `powershell` and `zsh`.
+
+#### Bash
+
+First, get the completion file in a known place:
+
+```sh
+mkdir -p ~/.local/share/duniter
+cargo run --release -- completion --generator bash > ~/.local/share/duniter/completion.bash
+```
+
+You can now manually source the file when needed:
+
+```sh
+source ~/.local/share/duniter/completion.bash
+```
+
+Or you can automatically source it at `bash` startup by adding this to your `~/.bashrc` file:
+
+```sh
+[[ -f $HOME/.local/share/duniter/completion.bash ]] && source $HOME/.local/share/duniter/completion.bash
+```
+
+You can now enjoy semantic completion of the `./target/release/duniter` command using `<Tab>` key.
+
 ## Single-Node Development Chain
 
 This command will start the single-node development chain with persistent state:
diff --git a/node/src/cli.rs b/node/src/cli.rs
index 5ddfb644c7830cca816d3d8b28c87b8d423abf54..46b235dce3dfdfed0b59e87dd542cde2017a86ac 100644
--- a/node/src/cli.rs
+++ b/node/src/cli.rs
@@ -72,6 +72,9 @@ pub enum Subcommand {
     /// The custom benchmark subcommmand benchmarking runtime pallets.
     #[clap(name = "benchmark", about = "Benchmark runtime pallets.")]
     Benchmark(frame_benchmarking_cli::BenchmarkCmd),
+
+    /// Generate completion for various shell interpreters
+    Completion(Completion),
 }
 
 /// Block authoring scheme to be used by the node
@@ -84,6 +87,10 @@ pub enum Sealing {
     /// Author a block upon receiving an RPC command
     Manual,
     /// Author blocks at a regular interval specified in milliseconds
+    // Clap limitiation with non-unit variant.
+    // While it compiles just fine with clap alone, clap_complete emits a compile-time error.
+    // See https://github.com/clap-rs/clap/issues/3543
+    #[clap(skip)]
     Interval(u64),
 }
 
@@ -110,3 +117,9 @@ impl std::str::FromStr for Sealing {
         })
     }
 }
+
+#[derive(Debug, clap::Args)]
+pub struct Completion {
+    #[clap(short, long, arg_enum)]
+    pub generator: clap_complete::Shell,
+}
diff --git a/node/src/command.rs b/node/src/command.rs
index e8923c77571d646b3b0ecc6e22b18eaa8c7b9992..3b5897b8bcad6a69185f8586446ef0ecd558290c 100644
--- a/node/src/command.rs
+++ b/node/src/command.rs
@@ -27,6 +27,7 @@ use crate::service::GDevExecutor;
 use crate::service::GTestExecutor;
 use crate::service::{IdentifyRuntimeType, RuntimeType};
 use crate::{chain_spec, service};
+use clap::CommandFactory;
 use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
 
 impl SubstrateCli for Cli {
@@ -259,6 +260,16 @@ pub fn run() -> sc_cli::Result<()> {
                     .into())
             }
         }
+        Some(Subcommand::Completion(cmd)) => {
+            let command = &mut Cli::command();
+            clap_complete::generate(
+                cmd.generator,
+                command,
+                command.get_name().to_string(),
+                &mut std::io::stdout(),
+            );
+            Ok(())
+        }
         None => {
             let runner = cli.create_runner(&cli.run)?;
             runner.run_node_until_exit(|mut config| async move {