diff --git a/prdoc/pr_7412.prdoc b/prdoc/pr_7412.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..fd6bbea8654137266f634868e2f11f80117b1f99
--- /dev/null
+++ b/prdoc/pr_7412.prdoc
@@ -0,0 +1,16 @@
+title: 'Pallet view functions: improve metadata, API docs and testing'
+doc:
+- audience: Runtime Dev
+  description: |-
+    - refactor view functions metadata according to #6833 in preparation for V16, and move them to pallet-level metadata
+    - add `view_functions_experimental` macro to `pallet_macros` with API docs
+    - improve UI testing for view functions
+crates:
+- name: frame-support-procedural
+  bump: minor
+- name: sp-metadata-ir
+  bump: major
+- name: pallet-example-view-functions
+  bump: patch
+- name: frame-support
+  bump: minor
diff --git a/substrate/frame/examples/view-functions/src/lib.rs b/substrate/frame/examples/view-functions/src/lib.rs
index e842a718ad33442b1f83a88963d637b2e6ca77bf..9cad8ab1bcdb0bd54a0f55263efcb1122a8c5c55 100644
--- a/substrate/frame/examples/view-functions/src/lib.rs
+++ b/substrate/frame/examples/view-functions/src/lib.rs
@@ -63,12 +63,12 @@ pub mod pallet {
 	where
 		T::AccountId: From<SomeType1> + SomeAssociation1,
 	{
-		/// Query value no args.
+		/// Query value with no input args.
 		pub fn get_value() -> Option<u32> {
 			SomeValue::<T>::get()
 		}
 
-		/// Query value with args.
+		/// Query value with input args.
 		pub fn get_value_with_arg(key: u32) -> Option<u32> {
 			SomeMap::<T>::get(key)
 		}
@@ -101,12 +101,12 @@ pub mod pallet2 {
 	where
 		T::AccountId: From<SomeType1> + SomeAssociation1,
 	{
-		/// Query value no args.
+		/// Query value with no input args.
 		pub fn get_value() -> Option<u32> {
 			SomeValue::<T, I>::get()
 		}
 
-		/// Query value with args.
+		/// Query value with input args.
 		pub fn get_value_with_arg(key: u32) -> Option<u32> {
 			SomeMap::<T, I>::get(key)
 		}
diff --git a/substrate/frame/examples/view-functions/src/tests.rs b/substrate/frame/examples/view-functions/src/tests.rs
index 25f5f094651d6a085c041702118a8e69a5f7e4c1..01a2d15dc6cde67378c5ec597d8f31ad9c33fd65 100644
--- a/substrate/frame/examples/view-functions/src/tests.rs
+++ b/substrate/frame/examples/view-functions/src/tests.rs
@@ -23,11 +23,14 @@ use crate::{
 	pallet2,
 };
 use codec::{Decode, Encode};
-use scale_info::{form::PortableForm, meta_type};
+use scale_info::meta_type;
 
 use frame_support::{derive_impl, pallet_prelude::PalletInfoAccess, view_functions::ViewFunction};
 use sp_io::hashing::twox_128;
-use sp_metadata_ir::{ViewFunctionArgMetadataIR, ViewFunctionGroupIR, ViewFunctionMetadataIR};
+use sp_metadata_ir::{
+	DeprecationStatusIR, PalletViewFunctionMethodMetadataIR,
+	PalletViewFunctionMethodParamMetadataIR,
+};
 use sp_runtime::testing::TestXt;
 
 pub type AccountId = u32;
@@ -111,8 +114,7 @@ fn metadata_ir_definitions() {
 	new_test_ext().execute_with(|| {
 		let metadata_ir = Runtime::metadata_ir();
 		let pallet1 = metadata_ir
-			.view_functions
-			.groups
+			.pallets
 			.iter()
 			.find(|pallet| pallet.name == "ViewFunctionsExample")
 			.unwrap();
@@ -137,44 +139,30 @@ fn metadata_ir_definitions() {
 		pretty_assertions::assert_eq!(
 			pallet1.view_functions,
 			vec![
-				ViewFunctionMetadataIR {
+				PalletViewFunctionMethodMetadataIR {
 					name: "get_value",
 					id: get_value_id,
-					args: vec![],
+					inputs: vec![],
 					output: meta_type::<Option<u32>>(),
-					docs: vec![" Query value no args."],
+					docs: vec![" Query value with no input args."],
+					deprecation_info: DeprecationStatusIR::NotDeprecated,
 				},
-				ViewFunctionMetadataIR {
+				PalletViewFunctionMethodMetadataIR {
 					name: "get_value_with_arg",
 					id: get_value_with_arg_id,
-					args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::<u32>() },],
+					inputs: vec![PalletViewFunctionMethodParamMetadataIR {
+						name: "key",
+						ty: meta_type::<u32>()
+					},],
 					output: meta_type::<Option<u32>>(),
-					docs: vec![" Query value with args."],
+					docs: vec![" Query value with input args."],
+					deprecation_info: DeprecationStatusIR::NotDeprecated,
 				},
 			]
 		);
 	});
 }
 
-#[test]
-fn metadata_encoded_to_custom_value() {
-	new_test_ext().execute_with(|| {
-		let metadata = sp_metadata_ir::into_latest(Runtime::metadata_ir());
-		// metadata is currently experimental so lives as a custom value.
-		let frame_metadata::RuntimeMetadata::V15(v15) = metadata.1 else {
-			panic!("Expected metadata v15")
-		};
-		let custom_value = v15
-			.custom
-			.map
-			.get("view_functions_experimental")
-			.expect("Expected custom value");
-		let view_function_groups: Vec<ViewFunctionGroupIR<PortableForm>> =
-			Decode::decode(&mut &custom_value.value[..]).unwrap();
-		assert_eq!(view_function_groups.len(), 4);
-	});
-}
-
 fn test_dispatch_view_function<Q, V>(query: &Q, expected: V)
 where
 	Q: ViewFunction + Encode,
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs
index d246c00628640d8dfd27a133a2c06637b3953eb8..be5678f4e5d291774b69619175e5af2feca75842 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs
@@ -45,6 +45,7 @@ pub fn expand_runtime_metadata(
 			let index = &decl.index;
 			let storage = expand_pallet_metadata_storage(&filtered_names, runtime, decl);
 			let calls = expand_pallet_metadata_calls(&filtered_names, runtime, decl);
+			let view_functions = expand_pallet_metadata_view_functions(runtime, decl);
 			let event = expand_pallet_metadata_events(&filtered_names, runtime, decl);
 			let constants = expand_pallet_metadata_constants(runtime, decl);
 			let errors = expand_pallet_metadata_errors(runtime, decl);
@@ -59,6 +60,7 @@ pub fn expand_runtime_metadata(
 					index: #index,
 					storage: #storage,
 					calls: #calls,
+					view_functions: #view_functions,
 					event: #event,
 					constants: #constants,
 					error: #errors,
@@ -70,20 +72,6 @@ pub fn expand_runtime_metadata(
 		})
 		.collect::<Vec<_>>();
 
-	let view_functions = pallet_declarations.iter().map(|decl| {
-		let name = &decl.name;
-		let path = &decl.path;
-		let instance = decl.instance.as_ref().into_iter();
-		let attr = decl.get_attributes();
-
-		quote! {
-			#attr
-			#path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata(
-				::core::stringify!(#name)
-			)
-		}
-	});
-
 	quote! {
 		impl #runtime {
 			fn metadata_ir() -> #scrate::__private::metadata_ir::MetadataIR {
@@ -156,10 +144,6 @@ pub fn expand_runtime_metadata(
 						event_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeEvent>(),
 						error_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeError>(),
 					},
-					view_functions: #scrate::__private::metadata_ir::RuntimeViewFunctionsIR {
-						ty: #scrate::__private::scale_info::meta_type::<RuntimeViewFunction>(),
-						groups: #scrate::__private::sp_std::vec![ #(#view_functions),* ],
-					}
 				}
 			}
 
@@ -216,6 +200,15 @@ fn expand_pallet_metadata_calls(
 	}
 }
 
+fn expand_pallet_metadata_view_functions(runtime: &Ident, decl: &Pallet) -> TokenStream {
+	let path = &decl.path;
+	let instance = decl.instance.as_ref().into_iter();
+
+	quote! {
+		#path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata()
+	}
+}
+
 fn expand_pallet_metadata_events(
 	filtered_names: &[&'static str],
 	runtime: &Ident,
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs
index 094dcca4a5b5249cfe3026790d9938a84f5649fe..df6b4c42a9b053018cfa90c4de5ec9a6eed5d25f 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs
@@ -62,7 +62,7 @@ pub fn expand_outer_query(
 			}
 
 			impl #runtime_name {
-				/// Convenience function for query execution from the runtime API.
+				/// Convenience function for view functions dispatching and execution from the runtime API.
 				pub fn execute_view_function(
 					id: #scrate::view_functions::ViewFunctionId,
 					input: #scrate::__private::Vec<::core::primitive::u8>,
diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index 26703a2438ef9ad368c3718bc497c9563bb407fe..3a106aa05d51c23303100b59ebd34906cb9e5bb7 100644
--- a/substrate/frame/support/procedural/src/lib.rs
+++ b/substrate/frame/support/procedural/src/lib.rs
@@ -1091,6 +1091,16 @@ pub fn validate_unsigned(_: TokenStream, _: TokenStream) -> TokenStream {
 	pallet_macro_stub()
 }
 
+///
+/// ---
+///
+/// Documentation for this macro can be found at
+/// `frame_support::pallet_macros::view_functions_experimental`.
+#[proc_macro_attribute]
+pub fn view_functions_experimental(_: TokenStream, _: TokenStream) -> TokenStream {
+	pallet_macro_stub()
+}
+
 ///
 /// ---
 ///
diff --git a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs
index 587e74a2ac182f2dc817db9bb6058e2c93611bc2..5838db9d89ddcf5dbae38fa9fcd2d42ad54df99f 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs
@@ -20,14 +20,10 @@ use proc_macro2::{Span, TokenStream};
 use syn::spanned::Spanned;
 
 pub fn expand_view_functions(def: &Def) -> TokenStream {
-	let (span, where_clause, view_fns, docs) = match def.view_functions.as_ref() {
-		Some(view_fns) => (
-			view_fns.attr_span,
-			view_fns.where_clause.clone(),
-			view_fns.view_functions.clone(),
-			view_fns.docs.clone(),
-		),
-		None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()),
+	let (span, where_clause, view_fns) = match def.view_functions.as_ref() {
+		Some(view_fns) =>
+			(view_fns.attr_span, view_fns.where_clause.clone(), view_fns.view_functions.clone()),
+		None => (def.item.span(), def.config.where_clause.clone(), Vec::new()),
 	};
 
 	let view_function_prefix_impl =
@@ -39,7 +35,7 @@ pub fn expand_view_functions(def: &Def) -> TokenStream {
 	let impl_dispatch_view_function =
 		impl_dispatch_view_function(def, span, where_clause.as_ref(), &view_fns);
 	let impl_view_function_metadata =
-		impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns, &docs);
+		impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns);
 
 	quote::quote! {
 		#view_function_prefix_impl
@@ -201,7 +197,6 @@ fn impl_view_function_metadata(
 	span: Span,
 	where_clause: Option<&syn::WhereClause>,
 	view_fns: &[ViewFunctionDef],
-	docs: &[syn::Expr],
 ) -> TokenStream {
 	let frame_support = &def.frame_support;
 	let pallet_ident = &def.pallet_struct.pallet;
@@ -211,14 +206,14 @@ fn impl_view_function_metadata(
 	let view_functions = view_fns.iter().map(|view_fn| {
 		let view_function_struct_ident = view_fn.view_function_struct_ident();
 		let name = &view_fn.name;
-		let args = view_fn.args.iter().filter_map(|fn_arg| {
+		let inputs = view_fn.args.iter().filter_map(|fn_arg| {
 			match fn_arg {
 				syn::FnArg::Receiver(_) => None,
 				syn::FnArg::Typed(typed) => {
 					let pat = &typed.pat;
 					let ty = &typed.ty;
 					Some(quote::quote! {
-						#frame_support::__private::metadata_ir::ViewFunctionArgMetadataIR {
+						#frame_support::__private::metadata_ir::PalletViewFunctionMethodParamMetadataIR {
 							name: ::core::stringify!(#pat),
 							ty: #frame_support::__private::scale_info::meta_type::<#ty>(),
 						}
@@ -230,33 +225,34 @@ fn impl_view_function_metadata(
 		let no_docs = vec![];
 		let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs };
 
+		let deprecation = match crate::deprecation::get_deprecation(
+			&quote::quote! { #frame_support },
+			&def.item.attrs,
+		) {
+			Ok(deprecation) => deprecation,
+			Err(e) => return e.into_compile_error(),
+		};
+
 		quote::quote! {
-			#frame_support::__private::metadata_ir::ViewFunctionMetadataIR {
+			#frame_support::__private::metadata_ir::PalletViewFunctionMethodMetadataIR {
 				name: ::core::stringify!(#name),
 				id: <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::id().into(),
-				args: #frame_support::__private::sp_std::vec![ #( #args ),* ],
+				inputs: #frame_support::__private::sp_std::vec![ #( #inputs ),* ],
 				output: #frame_support::__private::scale_info::meta_type::<
 					<#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::ReturnType
 				>(),
 				docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ],
+				deprecation_info: #deprecation,
 			}
 		}
 	});
 
-	let no_docs = vec![];
-	let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { docs };
-
 	quote::quote! {
 		impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause {
 			#[doc(hidden)]
-			pub fn pallet_view_functions_metadata(name: &'static ::core::primitive::str)
-				-> #frame_support::__private::metadata_ir::ViewFunctionGroupIR
-			{
-				#frame_support::__private::metadata_ir::ViewFunctionGroupIR {
-					name,
-					view_functions: #frame_support::__private::sp_std::vec![ #( #view_functions ),* ],
-					docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ],
-				}
+			pub fn pallet_view_functions_metadata()
+				-> #frame_support::__private::Vec<#frame_support::__private::metadata_ir::PalletViewFunctionMethodMetadataIR> {
+				#frame_support::__private::vec![ #( #view_functions ),* ]
 			}
 		}
 	}
diff --git a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs
index 766bcb13da8b3cbddc164a8fdd2a2ecab066f6d4..469ee0fe5b795953177a668955cf69b04b10e22f 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs
@@ -25,8 +25,6 @@ pub struct ViewFunctionsImplDef {
 	pub where_clause: Option<syn::WhereClause>,
 	/// The span of the pallet::view_functions_experimental attribute.
 	pub attr_span: proc_macro2::Span,
-	/// Docs, specified on the impl Block.
-	pub docs: Vec<syn::Expr>,
 	/// The view function definitions.
 	pub view_functions: Vec<ViewFunctionDef>,
 }
@@ -67,7 +65,6 @@ impl ViewFunctionsImplDef {
 			view_functions,
 			attr_span,
 			where_clause: item_impl.generics.where_clause.clone(),
-			docs: get_doc_literals(&item_impl.attrs),
 		})
 	}
 }
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index 97d16e2a06d23349fe4ea653be73428dde976a68..cd3312ebfd860b03d99e1f1b6fac87bb81af4052 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -1709,6 +1709,61 @@ pub mod pallet_macros {
 	/// in the future to give information directly to [`frame_support::construct_runtime`].
 	pub use frame_support_procedural::validate_unsigned;
 
+	/// Allows defining	view functions on a pallet.
+	///
+	/// A pallet view function is a read-only function providing access to the state of the
+	/// pallet from both outside and inside the runtime. It should provide a _stable_ interface
+	/// for querying the state of the pallet, avoiding direct storage access and upgrading
+	/// along with the runtime.
+	///
+	/// ## Syntax
+	/// View functions methods must be read-only and always return some output. A
+	/// `view_functions_experimental` impl block only allows methods to be defined inside of
+	/// it.
+	///
+	/// ## Example
+	/// ```
+	/// #[frame_support::pallet]
+	/// pub mod pallet {
+	/// 	use frame_support::pallet_prelude::*;
+	///
+	///  	#[pallet::config]
+	///  	pub trait Config: frame_system::Config {}
+	///
+	///  	#[pallet::pallet]
+	///  	pub struct Pallet<T>(_);
+	///
+	///     #[pallet::storage]
+	/// 	pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
+	///
+	///     #[pallet::view_functions_experimental]
+	///     impl<T: Config> Pallet<T> {
+	/// 		/// Retrieve a map storage value by key.
+	///         pub fn get_value_with_arg(key: u32) -> Option<u32> {
+	/// 			SomeMap::<T>::get(key)
+	/// 		}
+	///     }
+	/// }
+	/// ```
+	///
+	///
+	/// ## Usage and implementation details
+	/// To allow outside access to pallet view functions, you need to add a runtime API that
+	/// accepts view function queries and dispatches them to the right pallet. You can do that
+	/// by implementing the
+	/// [`RuntimeViewFunction`](frame_support::view_functions::runtime_api::RuntimeViewFunction)
+	/// trait for the runtime inside an [`impl_runtime_apis!`](sp_api::impl_runtime_apis)
+	/// block.
+	///
+	/// The `RuntimeViewFunction` trait implements a hashing-based dispatching mechanism to
+	/// dispatch view functions to the right method in the right pallet based on their IDs. A
+	/// view function ID depends both on its pallet and on its method signature, so it remains
+	/// stable as long as those two elements are not modified. In general, pallet view
+	/// functions should expose a _stable_ interface and changes to the method signature are
+	/// strongly discouraged. For more details on the dispatching mechanism, see the
+	/// [`DispatchViewFunction`](frame_support::view_functions::DispatchViewFunction) trait.
+	pub use frame_support_procedural::view_functions_experimental;
+
 	/// Allows defining a struct implementing the [`Get`](frame_support::traits::Get) trait to
 	/// ease the use of storage types.
 	///
diff --git a/substrate/frame/support/src/view_functions.rs b/substrate/frame/support/src/view_functions.rs
index dd23fad94a4fd578bdc3d63f86bcb1a1750fe064..cba451b554eb67513a0ae56da1f63af581abadf6 100644
--- a/substrate/frame/support/src/view_functions.rs
+++ b/substrate/frame/support/src/view_functions.rs
@@ -63,6 +63,12 @@ impl From<codec::Error> for ViewFunctionDispatchError {
 /// Implemented by both pallets and the runtime. The runtime is dispatching by prefix using the
 /// pallet implementation of `ViewFunctionIdPrefix` then the pallet is dispatching by suffix using
 /// the methods implementation of `ViewFunctionIdSuffix`.
+///
+/// In more details, `ViewFunctionId` = `ViewFunctionIdPrefix` ++ `ViewFunctionIdSuffix`, where
+/// `ViewFunctionIdPrefix=twox_128(pallet_name)` and
+/// `ViewFunctionIdSuffix=twox_128("fn_name(fnarg_types) -> return_ty")`. The prefix is the same as
+/// the storage prefix for pallets. The suffix is generated from the view function method type
+/// signature, so is guaranteed to be unique for that pallet implementation.
 pub trait DispatchViewFunction {
 	fn dispatch_view_function<O: Output>(
 		id: &ViewFunctionId,
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr
index faa9cb558c262f09111a488a4e2f4bf114a74375..444096bd9a5b895649c1228144525074e063977e 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr
@@ -620,7 +620,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the variant or associated item `event_metadata` exists for enum `Event<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the function or associated item `pallet_view_functions_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
@@ -634,7 +634,7 @@ error[E0599]: the variant or associated item `event_metadata` exists for enum `E
 ...  |
 27 | |      }
 28 | |  }
-   | |__^ variant or associated item cannot be called on `Event<Runtime>` due to unsatisfied trait bounds
+   | |__^ function or associated item cannot be called on `Pallet<Runtime>` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `Runtime: Config`
@@ -645,7 +645,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the function or associated item `pallet_constants_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the variant or associated item `event_metadata` exists for enum `Event<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
@@ -659,7 +659,7 @@ error[E0599]: the function or associated item `pallet_constants_metadata` exists
 ...  |
 27 | |      }
 28 | |  }
-   | |__^ function or associated item cannot be called on `Pallet<Runtime>` due to unsatisfied trait bounds
+   | |__^ variant or associated item cannot be called on `Event<Runtime>` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `Runtime: Config`
@@ -670,7 +670,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the function or associated item `error_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the function or associated item `pallet_constants_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
@@ -695,7 +695,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the function or associated item `pallet_documentation_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the function or associated item `error_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
@@ -720,7 +720,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the function or associated item `pallet_associated_types_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the function or associated item `pallet_documentation_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
@@ -745,7 +745,7 @@ note: the trait `Config` must be implemented
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error[E0599]: the function or associated item `pallet_view_functions_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
+error[E0599]: the function or associated item `pallet_associated_types_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
 20 |    construct_runtime! {
diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/view_function_valid.rs b/substrate/frame/support/test/tests/pallet_ui/pass/view_function_valid.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a4c3a6eb9baf563994c49046c0c3cbd79f429f51
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/pass/view_function_valid.rs
@@ -0,0 +1,39 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config(with_default)]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub type MyStorage<T> = StorageValue<_, u32>;
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T> {
+		pub fn get_value() -> Option<u32> {
+			MyStorage::<T>::get()
+		}
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.rs b/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.rs
new file mode 100644
index 0000000000000000000000000000000000000000..81a794d9b0d6bd9c9bf3efb13c8dc8f8a9743e2b
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.rs
@@ -0,0 +1,34 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config(with_default)]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T> {
+		pub const SECONDS_PER_MINUTE: u32 = 60;
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.stderr b/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..6afdf707fce60c933bcb30c1c0c6c396f56c545f
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_invalid_item.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::view_functions_experimental, expected a function
+  --> tests/pallet_ui/view_function_invalid_item.rs:30:3
+   |
+30 |         pub const SECONDS_PER_MINUTE: u32 = 60;
+   |         ^^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.rs b/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3356e354ca72cad0c0a648b52016c1347ce86d14
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.rs
@@ -0,0 +1,39 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config(with_default)]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub type MyStorage<T> = StorageValue<_, u32>;
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T> {
+		pub fn get_value() {
+			MyStorage::<T>::set(0);
+		}
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.stderr b/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..689253e1c6e7023bc82c7ae1db7afe65830f3700
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_no_return.stderr
@@ -0,0 +1,5 @@
+error: view functions must return a value
+  --> tests/pallet_ui/view_function_no_return.rs:33:7
+   |
+33 |         pub fn get_value() {
+   |             ^^
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.rs b/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e6611013335c628f5e736ad7c3ad9142d5ff7ac0
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.rs
@@ -0,0 +1,39 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[frame_support::pallet]
+mod pallet {
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::config(with_default)]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub type MyStorage<T> = StorageValue<_, u32>;
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T> {
+		fn get_value() -> Option<u32> {
+			MyStorage::<T>::get()
+		}
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.stderr b/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..7b0688af07974d8476f869bb6aa01747886cd1bb
--- /dev/null
+++ b/substrate/frame/support/test/tests/pallet_ui/view_function_not_public.stderr
@@ -0,0 +1,5 @@
+error: Invalid pallet::view_functions_experimental, view function must be public: `pub fn`
+  --> tests/pallet_ui/view_function_not_public.rs:33:3
+   |
+33 |         fn get_value() -> Option<u32> {
+   |         ^^
diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs
index e048010a34b75a7facb2cd0abe019eb305734096..dc01f7eaadb3337f1a4ff42322417d2e353ba299 100644
--- a/substrate/primitives/metadata-ir/src/lib.rs
+++ b/substrate/primitives/metadata-ir/src/lib.rs
@@ -122,7 +122,6 @@ mod test {
 				event_enum_ty: meta_type::<()>(),
 				error_enum_ty: meta_type::<()>(),
 			},
-			view_functions: RuntimeViewFunctionsIR { ty: meta_type::<()>(), groups: vec![] },
 		}
 	}
 
diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs
index 0617fc7dfb94f7d8c397d4ccbcd4219d400c488d..986925e6f516804d5f6028aaeec2fc3965a0f2b1 100644
--- a/substrate/primitives/metadata-ir/src/types.rs
+++ b/substrate/primitives/metadata-ir/src/types.rs
@@ -41,8 +41,6 @@ pub struct MetadataIR<T: Form = MetaForm> {
 	pub apis: Vec<RuntimeApiMetadataIR<T>>,
 	/// The outer enums types as found in the runtime.
 	pub outer_enums: OuterEnumsIR<T>,
-	/// Metadata of view function queries
-	pub view_functions: RuntimeViewFunctionsIR<T>,
 }
 
 /// Metadata of a runtime trait.
@@ -120,83 +118,52 @@ impl IntoPortable for RuntimeApiMethodParamMetadataIR {
 	}
 }
 
-/// Metadata of the top level runtime view function dispatch.
+/// Metadata of a pallet view function method.
 #[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
-pub struct RuntimeViewFunctionsIR<T: Form = MetaForm> {
-	/// The type implementing the runtime query dispatch.
-	pub ty: T::Type,
-	/// The view function groupings metadata.
-	pub groups: Vec<ViewFunctionGroupIR<T>>,
-}
-
-/// Metadata of a runtime view function group.
-///
-/// For example, view functions associated with a pallet would form a view function group.
-#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
-pub struct ViewFunctionGroupIR<T: Form = MetaForm> {
-	/// Name of the view function group.
-	pub name: T::String,
-	/// View functions belonging to the group.
-	pub view_functions: Vec<ViewFunctionMetadataIR<T>>,
-	/// View function group documentation.
-	pub docs: Vec<T::String>,
-}
-
-impl IntoPortable for ViewFunctionGroupIR {
-	type Output = ViewFunctionGroupIR<PortableForm>;
-
-	fn into_portable(self, registry: &mut Registry) -> Self::Output {
-		ViewFunctionGroupIR {
-			name: self.name.into_portable(registry),
-			view_functions: registry.map_into_portable(self.view_functions),
-			docs: registry.map_into_portable(self.docs),
-		}
-	}
-}
-
-/// Metadata of a runtime view function.
-#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
-pub struct ViewFunctionMetadataIR<T: Form = MetaForm> {
-	/// Query name.
+pub struct PalletViewFunctionMethodMetadataIR<T: Form = MetaForm> {
+	/// Method name.
 	pub name: T::String,
-	/// Query id.
+	/// Method id.
 	pub id: [u8; 32],
-	/// Query args.
-	pub args: Vec<ViewFunctionArgMetadataIR<T>>,
-	/// Query output.
+	/// Method parameters.
+	pub inputs: Vec<PalletViewFunctionMethodParamMetadataIR<T>>,
+	/// Method output.
 	pub output: T::Type,
-	/// Query documentation.
+	/// Method documentation.
 	pub docs: Vec<T::String>,
+	/// Deprecation info
+	pub deprecation_info: DeprecationStatusIR<T>,
 }
 
-impl IntoPortable for ViewFunctionMetadataIR {
-	type Output = ViewFunctionMetadataIR<PortableForm>;
+impl IntoPortable for PalletViewFunctionMethodMetadataIR {
+	type Output = PalletViewFunctionMethodMetadataIR<PortableForm>;
 
 	fn into_portable(self, registry: &mut Registry) -> Self::Output {
-		ViewFunctionMetadataIR {
+		PalletViewFunctionMethodMetadataIR {
 			name: self.name.into_portable(registry),
 			id: self.id,
-			args: registry.map_into_portable(self.args),
+			inputs: registry.map_into_portable(self.inputs),
 			output: registry.register_type(&self.output),
 			docs: registry.map_into_portable(self.docs),
+			deprecation_info: self.deprecation_info.into_portable(registry),
 		}
 	}
 }
 
-/// Metadata of a runtime method argument.
+/// Metadata of a pallet view function method argument.
 #[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
-pub struct ViewFunctionArgMetadataIR<T: Form = MetaForm> {
-	/// Query argument name.
+pub struct PalletViewFunctionMethodParamMetadataIR<T: Form = MetaForm> {
+	/// Parameter name.
 	pub name: T::String,
-	/// Query argument type.
+	/// Parameter type.
 	pub ty: T::Type,
 }
 
-impl IntoPortable for ViewFunctionArgMetadataIR {
-	type Output = ViewFunctionArgMetadataIR<PortableForm>;
+impl IntoPortable for PalletViewFunctionMethodParamMetadataIR {
+	type Output = PalletViewFunctionMethodParamMetadataIR<PortableForm>;
 
 	fn into_portable(self, registry: &mut Registry) -> Self::Output {
-		ViewFunctionArgMetadataIR {
+		PalletViewFunctionMethodParamMetadataIR {
 			name: self.name.into_portable(registry),
 			ty: registry.register_type(&self.ty),
 		}
@@ -212,6 +179,8 @@ pub struct PalletMetadataIR<T: Form = MetaForm> {
 	pub storage: Option<PalletStorageMetadataIR<T>>,
 	/// Pallet calls metadata.
 	pub calls: Option<PalletCallMetadataIR<T>>,
+	/// Pallet view functions metadata.
+	pub view_functions: Vec<PalletViewFunctionMethodMetadataIR<T>>,
 	/// Pallet event metadata.
 	pub event: Option<PalletEventMetadataIR<T>>,
 	/// Pallet constants metadata.
@@ -237,6 +206,11 @@ impl IntoPortable for PalletMetadataIR {
 			name: self.name.into_portable(registry),
 			storage: self.storage.map(|storage| storage.into_portable(registry)),
 			calls: self.calls.map(|calls| calls.into_portable(registry)),
+			view_functions: self
+				.view_functions
+				.into_iter()
+				.map(|view_functions| view_functions.into_portable(registry))
+				.collect(),
 			event: self.event.map(|event| event.into_portable(registry)),
 			constants: registry.map_into_portable(self.constants),
 			error: self.error.map(|error| error.into_portable(registry)),
diff --git a/substrate/primitives/metadata-ir/src/unstable.rs b/substrate/primitives/metadata-ir/src/unstable.rs
index d46ce3ec6a7d470abca872501fad3c7d0632ad32..412cb78ce84f14cf7f805446d99e210514daef1a 100644
--- a/substrate/primitives/metadata-ir/src/unstable.rs
+++ b/substrate/primitives/metadata-ir/src/unstable.rs
@@ -85,6 +85,8 @@ impl From<PalletMetadataIR> for PalletMetadata {
 			name: ir.name,
 			storage: ir.storage.map(Into::into),
 			calls: ir.calls.map(Into::into),
+			// TODO: add with the new v16 release of frame-metadata
+			// view_functions: ir.view_functions.into_iter().map(Into::into).collect(),
 			event: ir.event.map(Into::into),
 			constants: ir.constants.into_iter().map(Into::into).collect(),
 			error: ir.error.map(Into::into),
diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs
index 7bc76f22b58d004507c900bd139c9c58f1e8dbf3..64bc3e843ab5898b6fb8b6b16e41944d00c4c035 100644
--- a/substrate/primitives/metadata-ir/src/v15.rs
+++ b/substrate/primitives/metadata-ir/src/v15.rs
@@ -23,9 +23,9 @@ use super::types::{
 };
 
 use frame_metadata::v15::{
-	CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata,
-	RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata,
-	RuntimeMetadataV15, SignedExtensionMetadata,
+	CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata,
+	RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15,
+	SignedExtensionMetadata,
 };
 use scale_info::{IntoPortable, Registry};
 
@@ -39,15 +39,7 @@ impl From<MetadataIR> for RuntimeMetadataV15 {
 		let apis =
 			registry.map_into_portable(ir.apis.into_iter().map(Into::<RuntimeApiMetadata>::into));
 		let outer_enums = Into::<OuterEnums>::into(ir.outer_enums).into_portable(&mut registry);
-
-		let view_function_groups = registry.map_into_portable(ir.view_functions.groups.into_iter());
-		let view_functions_custom_metadata = CustomValueMetadata {
-			ty: ir.view_functions.ty,
-			value: codec::Encode::encode(&view_function_groups),
-		};
-		let mut custom_map = alloc::collections::BTreeMap::new();
-		custom_map.insert("view_functions_experimental", view_functions_custom_metadata);
-		let custom = CustomMetadata { map: custom_map }.into_portable(&mut registry);
+		let custom = CustomMetadata { map: Default::default() };
 
 		Self { types: registry.into(), pallets, extrinsic, ty, apis, outer_enums, custom }
 	}