diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs
index f3cdcf7fd54a99b82dca138f8b2b567c6f607593..77a9e56eecba5c91e3a36534f8f79da6ead30107 100644
--- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs
+++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs
@@ -96,6 +96,7 @@ fn generate_extern_host_function(
 		method.sig.ident,
 	);
 	let return_value = &method.sig.output;
+	let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
 
 	let ffi_return_value = match method.sig.output {
 		ReturnType::Default => quote!(),
@@ -112,6 +113,7 @@ fn generate_extern_host_function(
 	};
 
 	Ok(quote! {
+		#(#cfg_attrs)*
 		#[doc = #doc_string]
 		pub fn #function ( #( #args ),* ) #return_value {
 			extern "C" {
@@ -143,8 +145,10 @@ fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStre
 	let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident);
 	let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident);
 	let output = &method.sig.output;
+	let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
 
 	Ok(quote! {
+		#(#cfg_attrs)*
 		#[cfg(not(feature = "std"))]
 		#[allow(non_upper_case_globals)]
 		#[doc = #doc_string]
@@ -163,14 +167,15 @@ fn generate_host_functions_struct(
 	let crate_ = generate_crate_access();
 
 	let mut host_function_impls = Vec::new();
-	let mut host_function_names = Vec::new();
 	let mut register_bodies = Vec::new();
+	let mut append_hf_bodies = Vec::new();
+
 	for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
-		let (implementation, name, register_body) =
+		let (implementation, register_body, append_hf_body) =
 			generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
 		host_function_impls.push(implementation);
-		host_function_names.push(name);
 		register_bodies.push(register_body);
+		append_hf_bodies.push(append_hf_body);
 	}
 
 	Ok(quote! {
@@ -183,7 +188,9 @@ fn generate_host_functions_struct(
 		#[cfg(feature = "std")]
 		impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
 			fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
-				vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ]
+				let mut host_functions_list = Vec::new();
+				#(#append_hf_bodies)*
+				host_functions_list
 			}
 
 			#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
@@ -208,7 +215,7 @@ fn generate_host_function_implementation(
 	method: &RuntimeInterfaceFunction,
 	version: u32,
 	is_wasm_only: bool,
-) -> Result<(TokenStream, Ident, TokenStream)> {
+) -> Result<(TokenStream, TokenStream, TokenStream)> {
 	let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
 	let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
 	let crate_ = generate_crate_access();
@@ -323,10 +330,21 @@ fn generate_host_function_implementation(
 		});
 	}
 
+	let cfg_attrs: Vec<_> =
+		method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect();
+	if version > 1 && !cfg_attrs.is_empty() {
+		return Err(Error::new(
+			method.span(),
+			"Conditional compilation is not supported for versioned functions",
+		))
+	}
+
 	let implementation = quote! {
+		#(#cfg_attrs)*
 		#[cfg(feature = "std")]
 		struct #struct_name;
 
+		#(#cfg_attrs)*
 		#[cfg(feature = "std")]
 		impl #struct_name {
 			fn call(
@@ -341,6 +359,7 @@ fn generate_host_function_implementation(
 			}
 		}
 
+		#(#cfg_attrs)*
 		#[cfg(feature = "std")]
 		impl #crate_::sp_wasm_interface::Function for #struct_name {
 			fn name(&self) -> &str {
@@ -368,6 +387,7 @@ fn generate_host_function_implementation(
 	};
 
 	let register_body = quote! {
+		#(#cfg_attrs)*
 		registry.register_static(
 			#crate_::sp_wasm_interface::Function::name(&#struct_name),
 			|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
@@ -399,7 +419,12 @@ fn generate_host_function_implementation(
 		)?;
 	};
 
-	Ok((implementation, struct_name, register_body))
+	let append_hf_body = quote! {
+		#(#cfg_attrs)*
+		host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function);
+	};
+
+	Ok((implementation, register_body, append_hf_body))
 }
 
 /// Generate the `wasm_interface::Signature` for the given host function `sig`.
diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs
index 058801522a4f0d258897359eb6249bbc0d0f19ff..1f1638880bb6c8655573ad260aac9cd40e6a5592 100644
--- a/substrate/primitives/runtime-interface/src/lib.rs
+++ b/substrate/primitives/runtime-interface/src/lib.rs
@@ -180,10 +180,19 @@ pub use sp_std;
 ///             None => self.clear_storage(&[1, 2, 3, 4]),
 ///         }
 ///     }
+///
+///     /// A function can be gated behind a configuration (`cfg`) attribute.
+///     /// To prevent ambiguity and confusion about what will be the final exposed host
+///     /// functions list, conditionally compiled functions can't be versioned.
+///     /// That is, conditionally compiled functions with `version`s greater than 1
+///     /// are not allowed.
+///     #[cfg(feature = "experimental-function")]
+///     fn gated_call(data: &[u8]) -> Vec<u8> {
+///         [42].to_vec()
+///     }
 /// }
 /// ```
 ///
-///
 /// The given example will generate roughly the following code for native:
 ///
 /// ```
@@ -197,6 +206,8 @@ pub use sp_std;
 ///         fn call_version_2(data: &[u8]) -> Vec<u8>;
 ///         fn call_version_3(data: &[u8]) -> Vec<u8>;
 ///         fn set_or_clear_version_1(&mut self, optional: Option<Vec<u8>>);
+///         #[cfg(feature = "experimental-function")]
+///         fn gated_call_version_1(data: &[u8]) -> Vec<u8>;
 ///     }
 ///
 ///     impl Interface for &mut dyn sp_externalities::Externalities {
@@ -209,6 +220,8 @@ pub use sp_std;
 ///                 None => self.clear_storage(&[1, 2, 3, 4]),
 ///             }
 ///         }
+///         #[cfg(feature = "experimental-function")]
+///         fn gated_call_version_1(data: &[u8]) -> Vec<u8> { [42].to_vec() }
 ///     }
 ///
 ///     pub fn call(data: &[u8]) -> Vec<u8> {
@@ -237,6 +250,16 @@ pub use sp_std;
 ///             .expect("`set_or_clear` called outside of an Externalities-provided environment.")
 ///     }
 ///
+///     #[cfg(feature = "experimental-function")]
+///     pub fn gated_call(data: &[u8]) -> Vec<u8> {
+///         gated_call_version_1(data)
+///     }
+///
+///     #[cfg(feature = "experimental-function")]
+///     fn gated_call_version_1(data: &[u8]) -> Vec<u8> {
+///         <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data)
+///     }
+///
 ///     /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and
 ///     /// provides the host implementation for the wasm side. The host implementation converts the
 ///     /// arguments from wasm to native and calls the corresponding native function.
@@ -247,28 +270,43 @@ pub use sp_std;
 /// }
 /// ```
 ///
-///
 /// The given example will generate roughly the following code for wasm:
 ///
 /// ```
 /// mod interface {
 ///     mod extern_host_functions_impls {
-///         extern "C" {
-///             /// Every function is exported as `ext_TRAIT_NAME_FUNCTION_NAME_version_VERSION`.
-///             ///
-///             /// `TRAIT_NAME` is converted into snake case.
-///             ///
-///             /// The type for each argument of the exported function depends on
-///             /// `<ARGUMENT_TYPE as RIType>::FFIType`.
-///             ///
-///             /// `data` holds the pointer and the length to the `[u8]` slice.
-///             pub fn ext_Interface_call_version_1(data: u64) -> u64;
-///             /// `optional` holds the pointer and the length of the encoded value.
-///             pub fn ext_Interface_set_or_clear_version_1(optional: u64);
+///         /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`.
+///         ///
+///         /// The type for each argument of the exported function depends on
+///         /// `<ARGUMENT_TYPE as RIType>::FFIType`.
+///         ///
+///         /// `key` holds the pointer and the length to the `data` slice.
+///         pub fn call(data: &[u8]) -> Vec<u8> {
+///             extern "C" { pub fn ext_call_version_2(key: u64); }
+///             // Should call into extenal `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))`
+///             // But this is too much to replicate in a doc test so here we just return a dummy vector.
+///             // Note that we jump into the latest version not marked as `register_only` (i.e. version 2).
+///             Vec::new()
+///         }
+///
+///         /// `key` holds the pointer and the length of the `option` value.
+///         pub fn set_or_clear(option: Option<Vec<u8>>) {
+///             extern "C" { pub fn ext_set_or_clear_version_1(key: u64); }
+///             // Same as above
+///         }
+///
+///         /// `key` holds the pointer and the length to the `data` slice.
+///         #[cfg(feature = "experimental-function")]
+///         pub fn gated_call(data: &[u8]) -> Vec<u8> {
+///             extern "C" { pub fn ext_gated_call_version_1(key: u64); }
+///             /// Same as above
+///             Vec::new()
 ///         }
 ///     }
 ///
-///     /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`).
+///     /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and
+///     /// by default this is initialized to jump into the corresponding function in
+///     /// `extern_host_functions_impls`.
 ///     ///
 ///     /// This can be used to replace the implementation of the `call` function.
 ///     /// Instead of calling into the host, the callee will automatically call the other
@@ -279,6 +317,8 @@ pub use sp_std;
 ///     /// `host_call.replace_implementation(some_other_impl)`
 ///     pub static host_call: () = ();
 ///     pub static host_set_or_clear: () = ();
+///     #[cfg(feature = "experimental-feature")]
+///     pub static gated_call: () = ();
 ///
 ///     pub fn call(data: &[u8]) -> Vec<u8> {
 ///         // This is the actual call: `host_call.get()(data)`
@@ -291,6 +331,12 @@ pub use sp_std;
 ///     pub fn set_or_clear(optional: Option<Vec<u8>>) {
 ///         // Same as above
 ///     }
+///
+///     #[cfg(feature = "experimental-feature")]
+///     pub fn gated_call(data: &[u8]) -> Vec<u8> {
+///         // Same as above
+///         Vec::new()
+///     }
 /// }
 /// ```
 ///
diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs
new file mode 100644
index 0000000000000000000000000000000000000000..51e45f178f0c5d1163fd6549dacb8f16ce61bd33
--- /dev/null
+++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs
@@ -0,0 +1,18 @@
+use sp_runtime_interface::runtime_interface;
+
+#[runtime_interface]
+trait Test {
+	fn foo() {}
+
+	#[cfg(feature = "bar-feature")]
+	fn bar() {}
+
+	#[cfg(not(feature = "bar-feature"))]
+	fn qux() {}
+}
+
+fn main() {
+	test::foo();
+	test::bar();
+	test::qux();
+}
diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e8accd62fc68dec8680e093e7b6c0f7b0fc21291
--- /dev/null
+++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr
@@ -0,0 +1,5 @@
+error[E0425]: cannot find function `bar` in module `test`
+  --> tests/ui/no_feature_gated_method.rs:16:8
+   |
+16 |     test::bar();
+   |           ^^^ not found in `test`
diff --git a/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a4a8a5804bee342f5c224f7ccf886f1cda53ad72
--- /dev/null
+++ b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs
@@ -0,0 +1,12 @@
+use sp_runtime_interface::runtime_interface;
+
+#[runtime_interface]
+trait Test {
+	fn foo() {}
+
+	#[version(2)]
+	#[cfg(feature = "foo-feature")]
+	fn foo() {}
+}
+
+fn main() {}
diff --git a/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..6f50e14278de56abf824522cc5e1d63d63419d7e
--- /dev/null
+++ b/substrate/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr
@@ -0,0 +1,5 @@
+error: Conditional compilation is not supported for versioned functions
+ --> tests/ui/no_versioned_conditional_build.rs:7:2
+  |
+7 |     #[version(2)]
+  |     ^