diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml
index 43c70d6abc78b0aca6fe0738617632a9b33b809a..df1a1c8be6038c3cbd996d1fa0dceb46aeb87b07 100644
--- a/.github/workflows/check-semver.yml
+++ b/.github/workflows/check-semver.yml
@@ -76,6 +76,7 @@ jobs:
         if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }}
         run: |
           rustup default $TOOLCHAIN
+          rustup target add wasm32-unknown-unknown --toolchain $TOOLCHAIN
           rustup component add rust-src --toolchain $TOOLCHAIN
 
       - name: install parity-publish
diff --git a/Cargo.lock b/Cargo.lock
index d04808c0f0896db0fc3c1575fe5a4219f9b047ff..f12dff812452c8da7d4c58a1a33ed4704681a6a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13635,6 +13635,24 @@ dependencies = [
  "sp-runtime 31.0.1",
 ]
 
+[[package]]
+name = "pallet-example-view-functions"
+version = "1.0.0"
+dependencies = [
+ "frame-benchmarking 28.0.0",
+ "frame-metadata 18.0.0",
+ "frame-support 28.0.0",
+ "frame-system 28.0.0",
+ "log",
+ "parity-scale-codec",
+ "pretty_assertions",
+ "scale-info",
+ "sp-core 28.0.0",
+ "sp-io 30.0.0",
+ "sp-metadata-ir 0.6.0",
+ "sp-runtime 31.0.1",
+]
+
 [[package]]
 name = "pallet-examples"
 version = "4.0.0-dev"
@@ -13649,6 +13667,7 @@ dependencies = [
  "pallet-example-single-block-migrations",
  "pallet-example-split",
  "pallet-example-tasks",
+ "pallet-example-view-functions",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 7a906e7c0d64f1181d8227296c98862a9e4debec..36c048d77c8ec0b62e95bf7ac02277df5086738f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -363,6 +363,7 @@ members = [
 	"substrate/frame/examples/single-block-migrations",
 	"substrate/frame/examples/split",
 	"substrate/frame/examples/tasks",
+	"substrate/frame/examples/view-functions",
 	"substrate/frame/executive",
 	"substrate/frame/fast-unstake",
 	"substrate/frame/glutton",
@@ -941,6 +942,7 @@ pallet-example-offchain-worker = { path = "substrate/frame/examples/offchain-wor
 pallet-example-single-block-migrations = { path = "substrate/frame/examples/single-block-migrations", default-features = false }
 pallet-example-split = { path = "substrate/frame/examples/split", default-features = false }
 pallet-example-tasks = { path = "substrate/frame/examples/tasks", default-features = false }
+pallet-example-view-functions = { path = "substrate/frame/examples/view-functions", default-features = false }
 pallet-examples = { path = "substrate/frame/examples" }
 pallet-fast-unstake = { path = "substrate/frame/fast-unstake", default-features = false }
 pallet-glutton = { path = "substrate/frame/glutton", default-features = false }
diff --git a/cumulus/pallets/weight-reclaim/src/tests.rs b/cumulus/pallets/weight-reclaim/src/tests.rs
index b87c107c7ec71ce8dea8b04b702f673373dfd16f..ce647445b33272b71929ad1ac51042f079e75c4e 100644
--- a/cumulus/pallets/weight-reclaim/src/tests.rs
+++ b/cumulus/pallets/weight-reclaim/src/tests.rs
@@ -89,7 +89,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Test;
 
diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs
index 282fc1ff489c0195973d712c085688bdaf77a30a..3ffa3d61263f2cc6c9204ec208b3c68cbfdc236a 100644
--- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs
+++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs
@@ -57,7 +57,14 @@ type SignedExtra = ();
 mod runtime {
 	/// The main runtime type.
 	#[runtime::runtime]
-	#[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin, RuntimeTask)]
+	#[runtime::derive(
+		RuntimeCall,
+		RuntimeEvent,
+		RuntimeError,
+		RuntimeOrigin,
+		RuntimeTask,
+		RuntimeViewFunction
+	)]
 	pub struct Runtime;
 
 	/// Mandatory system pallet that should always be included in a FRAME runtime.
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 935b62c23388ee1c9dd47ccad4e70e488e074b53..4d5b56bcd911ad67e9d986f7567b5a119ce72c82 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -1613,7 +1613,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
@@ -1975,6 +1976,12 @@ sp_api::impl_runtime_apis! {
 		}
 	}
 
+	impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime {
+		fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> {
+			Runtime::execute_view_function(id, input)
+		}
+	}
+
 	impl sp_block_builder::BlockBuilder<Block> for Runtime {
 		fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
 			Executive::apply_extrinsic(extrinsic)
diff --git a/prdoc/pr_4722.prdoc b/prdoc/pr_4722.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..a5bdbbeb3df9aa45868aa8cdd0156d40ae23e683
--- /dev/null
+++ b/prdoc/pr_4722.prdoc
@@ -0,0 +1,33 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Implement pallet view functions
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Read-only view functions can now be defined on pallets. These functions provide an interface for querying state,
+      from both outside and inside the runtime. Common queries can be defined on pallets, without users having to
+      access the storage directly.
+      
+  - audience: Runtime User
+    description: |
+      Querying the runtime state is now easier with the introduction of pallet view functions. Clients can call commonly
+      defined view functions rather than accessing the storage directly. These are similar to the Runtime APIs, but
+      are defined within the runtime itself.
+
+crates:
+  - name: frame-support
+    bump: minor
+  - name: sp-metadata-ir
+    bump: major
+  - name: frame-support-procedural
+    bump: patch
+  - name: pallet-example-view-functions
+    bump: patch
+  - name: cumulus-pov-validator
+    bump: none
+  - name: cumulus-pallet-weight-reclaim
+    bump: patch
+  - name: westend-runtime
+    bump: minor
\ No newline at end of file
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 220929fdfd838532a84ef5d2256c562666583672..202aee37074db7013e33a1d2e8b7e3179fdc9026 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -2459,7 +2459,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
@@ -3013,6 +3014,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime {
+		fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> {
+			Runtime::execute_view_function(id, input)
+		}
+	}
+
 	impl sp_block_builder::BlockBuilder<Block> for Runtime {
 		fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
 			Executive::apply_extrinsic(extrinsic)
diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml
index 9eac53f0d98b064c3b271357db6c0bf6b4f3a005..40d6959378b8799e3b1e6e655373bf4e5409652e 100644
--- a/substrate/frame/examples/Cargo.toml
+++ b/substrate/frame/examples/Cargo.toml
@@ -26,6 +26,7 @@ pallet-example-offchain-worker = { workspace = true }
 pallet-example-single-block-migrations = { workspace = true }
 pallet-example-split = { workspace = true }
 pallet-example-tasks = { workspace = true }
+pallet-example-view-functions = { workspace = true }
 
 [features]
 default = ["std"]
@@ -40,6 +41,7 @@ std = [
 	"pallet-example-single-block-migrations/std",
 	"pallet-example-split/std",
 	"pallet-example-tasks/std",
+	"pallet-example-view-functions/std",
 ]
 try-runtime = [
 	"pallet-default-config-example/try-runtime",
@@ -51,4 +53,5 @@ try-runtime = [
 	"pallet-example-single-block-migrations/try-runtime",
 	"pallet-example-split/try-runtime",
 	"pallet-example-tasks/try-runtime",
+	"pallet-example-view-functions/try-runtime",
 ]
diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs
index d0d30830f2f04d1bfd4c4afa5a39be91ee549a54..200e92112a3f3b4ba9552a8de68401fc290469e4 100644
--- a/substrate/frame/examples/src/lib.rs
+++ b/substrate/frame/examples/src/lib.rs
@@ -48,6 +48,9 @@
 //!
 //! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work.
 //!
+//! - [`pallet_example_view_functions`]: This pallet demonstrates the use of view functions to query
+//!   pallet state.
+//!
 //! - [`pallet_example_authorization_tx_extension`]: An example `TransactionExtension` that
 //!   authorizes a custom origin through signature validation, along with two support pallets to
 //!   showcase the usage.
diff --git a/substrate/frame/examples/view-functions/Cargo.toml b/substrate/frame/examples/view-functions/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..54d8322643917151ec8cf4be9aced7b758afadf2
--- /dev/null
+++ b/substrate/frame/examples/view-functions/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+name = "pallet-example-view-functions"
+version = "1.0.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+repository.workspace = true
+description = "Pallet to demonstrate the usage of view functions to query pallet state"
+
+[lints]
+workspace = true
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, workspace = true }
+frame-metadata = { features = ["current"], workspace = true }
+log = { workspace = true }
+scale-info = { version = "2.11.1", default-features = false, features = ["derive"], workspace = true }
+
+frame-support = { path = "../../support", default-features = false, workspace = true }
+frame-system = { path = "../../system", default-features = false, workspace = true }
+
+sp-core = { default-features = false, path = "../../../primitives/core", workspace = true }
+sp-io = { path = "../../../primitives/io", default-features = false, workspace = true }
+sp-metadata-ir = { path = "../../../primitives/metadata-ir", default-features = false, workspace = true }
+sp-runtime = { path = "../../../primitives/runtime", default-features = false, workspace = true }
+
+frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true, workspace = true }
+
+[dev-dependencies]
+pretty_assertions = { version = "1.3.0" }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-metadata/std",
+	"frame-support/std",
+	"frame-system/std",
+	"log/std",
+	"scale-info/std",
+	"sp-core/std",
+	"sp-io/std",
+	"sp-metadata-ir/std",
+	"sp-runtime/std",
+]
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+]
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/examples/view-functions/src/lib.rs b/substrate/frame/examples/view-functions/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e842a718ad33442b1f83a88963d637b2e6ca77bf
--- /dev/null
+++ b/substrate/frame/examples/view-functions/src/lib.rs
@@ -0,0 +1,114 @@
+// 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.
+
+//! This pallet demonstrates the use of the `pallet::view_functions_experimental` api for service
+//! work.
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod tests;
+
+use frame_support::Parameter;
+use scale_info::TypeInfo;
+
+pub struct SomeType1;
+impl From<SomeType1> for u64 {
+	fn from(_t: SomeType1) -> Self {
+		0u64
+	}
+}
+
+pub trait SomeAssociation1 {
+	type _1: Parameter + codec::MaxEncodedLen + TypeInfo;
+}
+impl SomeAssociation1 for u64 {
+	type _1 = u64;
+}
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::error]
+	pub enum Error<T> {}
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub type SomeValue<T: Config> = StorageValue<_, u32>;
+
+	#[pallet::storage]
+	pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T>
+	where
+		T::AccountId: From<SomeType1> + SomeAssociation1,
+	{
+		/// Query value no args.
+		pub fn get_value() -> Option<u32> {
+			SomeValue::<T>::get()
+		}
+
+		/// Query value with args.
+		pub fn get_value_with_arg(key: u32) -> Option<u32> {
+			SomeMap::<T>::get(key)
+		}
+	}
+}
+
+#[frame_support::pallet]
+pub mod pallet2 {
+	use super::*;
+	use frame_support::pallet_prelude::*;
+
+	#[pallet::error]
+	pub enum Error<T, I = ()> {}
+
+	#[pallet::config]
+	pub trait Config<I: 'static = ()>: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
+
+	#[pallet::storage]
+	pub type SomeValue<T: Config<I>, I: 'static = ()> = StorageValue<_, u32>;
+
+	#[pallet::storage]
+	pub type SomeMap<T: Config<I>, I: 'static = ()> =
+		StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
+
+	#[pallet::view_functions_experimental]
+	impl<T: Config<I>, I: 'static> Pallet<T, I>
+	where
+		T::AccountId: From<SomeType1> + SomeAssociation1,
+	{
+		/// Query value no args.
+		pub fn get_value() -> Option<u32> {
+			SomeValue::<T, I>::get()
+		}
+
+		/// Query value with 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
new file mode 100644
index 0000000000000000000000000000000000000000..25f5f094651d6a085c041702118a8e69a5f7e4c1
--- /dev/null
+++ b/substrate/frame/examples/view-functions/src/tests.rs
@@ -0,0 +1,188 @@
+// 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.
+
+//! Tests for `pallet-example-view-functions`.
+#![cfg(test)]
+
+use crate::{
+	pallet::{self, Pallet},
+	pallet2,
+};
+use codec::{Decode, Encode};
+use scale_info::{form::PortableForm, 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_runtime::testing::TestXt;
+
+pub type AccountId = u32;
+pub type Balance = u32;
+
+type Block = frame_system::mocking::MockBlock<Runtime>;
+frame_support::construct_runtime!(
+	pub enum Runtime {
+		System: frame_system,
+		ViewFunctionsExample: pallet,
+		ViewFunctionsInstance: pallet2,
+		ViewFunctionsInstance1: pallet2::<Instance1>,
+	}
+);
+
+pub type Extrinsic = TestXt<RuntimeCall, ()>;
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
+impl frame_system::Config for Runtime {
+	type Block = Block;
+}
+
+impl pallet::Config for Runtime {}
+impl pallet2::Config<pallet2::Instance1> for Runtime {}
+
+impl pallet2::Config for Runtime {}
+
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	use sp_runtime::BuildStorage;
+
+	let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap();
+	t.into()
+}
+
+#[test]
+fn pallet_get_value_query() {
+	new_test_ext().execute_with(|| {
+		let some_value = Some(99);
+		pallet::SomeValue::<Runtime>::set(some_value);
+		assert_eq!(some_value, Pallet::<Runtime>::get_value());
+
+		let query = pallet::GetValueViewFunction::<Runtime>::new();
+		test_dispatch_view_function(&query, some_value);
+	});
+}
+
+#[test]
+fn pallet_get_value_with_arg_query() {
+	new_test_ext().execute_with(|| {
+		let some_key = 1u32;
+		let some_value = Some(123);
+		pallet::SomeMap::<Runtime>::set(some_key, some_value);
+		assert_eq!(some_value, Pallet::<Runtime>::get_value_with_arg(some_key));
+
+		let query = pallet::GetValueWithArgViewFunction::<Runtime>::new(some_key);
+		test_dispatch_view_function(&query, some_value);
+	});
+}
+
+#[test]
+fn pallet_multiple_instances() {
+	use pallet2::Instance1;
+
+	new_test_ext().execute_with(|| {
+		let instance_value = Some(123);
+		let instance1_value = Some(456);
+
+		pallet2::SomeValue::<Runtime>::set(instance_value);
+		pallet2::SomeValue::<Runtime, Instance1>::set(instance1_value);
+
+		let query = pallet2::GetValueViewFunction::<Runtime>::new();
+		test_dispatch_view_function(&query, instance_value);
+
+		let query_instance1 = pallet2::GetValueViewFunction::<Runtime, Instance1>::new();
+		test_dispatch_view_function(&query_instance1, instance1_value);
+	});
+}
+
+#[test]
+fn metadata_ir_definitions() {
+	new_test_ext().execute_with(|| {
+		let metadata_ir = Runtime::metadata_ir();
+		let pallet1 = metadata_ir
+			.view_functions
+			.groups
+			.iter()
+			.find(|pallet| pallet.name == "ViewFunctionsExample")
+			.unwrap();
+
+		fn view_fn_id(preifx_hash: [u8; 16], view_fn_signature: &str) -> [u8; 32] {
+			let mut id = [0u8; 32];
+			id[..16].copy_from_slice(&preifx_hash);
+			id[16..].copy_from_slice(&twox_128(view_fn_signature.as_bytes()));
+			id
+		}
+
+		let get_value_id = view_fn_id(
+			<ViewFunctionsExample as PalletInfoAccess>::name_hash(),
+			"get_value() -> Option<u32>",
+		);
+
+		let get_value_with_arg_id = view_fn_id(
+			<ViewFunctionsExample as PalletInfoAccess>::name_hash(),
+			"get_value_with_arg(u32) -> Option<u32>",
+		);
+
+		pretty_assertions::assert_eq!(
+			pallet1.view_functions,
+			vec![
+				ViewFunctionMetadataIR {
+					name: "get_value",
+					id: get_value_id,
+					args: vec![],
+					output: meta_type::<Option<u32>>(),
+					docs: vec![" Query value no args."],
+				},
+				ViewFunctionMetadataIR {
+					name: "get_value_with_arg",
+					id: get_value_with_arg_id,
+					args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::<u32>() },],
+					output: meta_type::<Option<u32>>(),
+					docs: vec![" Query value with args."],
+				},
+			]
+		);
+	});
+}
+
+#[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,
+	V: Decode + Eq + PartialEq + std::fmt::Debug,
+{
+	let input = query.encode();
+	let output = Runtime::execute_view_function(Q::id(), input).unwrap();
+	let query_result = V::decode(&mut &output[..]).unwrap();
+
+	assert_eq!(expected, query_result,);
+}
diff --git a/substrate/frame/support/procedural/examples/proc_main/main.rs b/substrate/frame/support/procedural/examples/proc_main/main.rs
index 4bdfc76dd92f0821865fbe7a74fbf0bea77f35e3..946bd5ff03ed298824ec7edd0ba31c8885d5df0f 100644
--- a/substrate/frame/support/procedural/examples/proc_main/main.rs
+++ b/substrate/frame/support/procedural/examples/proc_main/main.rs
@@ -234,7 +234,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/procedural/examples/proc_main/runtime.rs b/substrate/frame/support/procedural/examples/proc_main/runtime.rs
index 109ca4f6dc488228cc596c2d3ff2447aa4cacff4..8de560555895bf64b3e21d8c0e892b77d6e77d4a 100644
--- a/substrate/frame/support/procedural/examples/proc_main/runtime.rs
+++ b/substrate/frame/support/procedural/examples/proc_main/runtime.rs
@@ -99,7 +99,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs
index f055e8ce28e904639dba11f9f7639bf64c934d95..411d74ecbb3d23c84e5b62c14e10a1b047e49248 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::Pallet;
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::str::FromStr;
 use syn::Ident;
 
 pub fn expand_outer_dispatch(
@@ -40,15 +39,7 @@ pub fn expand_outer_dispatch(
 		let name = &pallet_declaration.name;
 		let path = &pallet_declaration.path;
 		let index = pallet_declaration.index;
-		let attr =
-			pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-				let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			});
+		let attr = pallet_declaration.get_attributes();
 
 		variant_defs.extend(quote! {
 			#attr
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs
index dbbe6ba6e6c32ec09a8514033108617883075243..7a51ba6ecf1da9c9f9af5abe83f85fee59323995 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs
@@ -19,7 +19,6 @@ use crate::construct_runtime::Pallet;
 use inflector::Inflector;
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote, ToTokens};
-use std::str::FromStr;
 use syn::Ident;
 
 pub fn expand_outer_config(
@@ -41,14 +40,7 @@ pub fn expand_outer_config(
 			let field_name =
 				&Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span());
 			let part_is_generic = !pallet_entry.generics.params.is_empty();
-			let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-				let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			});
+			let attr = &decl.get_attributes();
 
 			types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic));
 			fields.extend(quote!(#attr pub #field_name: #config,));
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
index e34c6ac5016a9f8597fae03f6012f1e7d2f79659..e25492802c3293d02321b2b1fcc0f26aabe8dce4 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::Pallet;
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::str::FromStr;
 use syn::Ident;
 
 pub fn expand_outer_inherent(
@@ -36,14 +35,7 @@ pub fn expand_outer_inherent(
 		if pallet_decl.exists_part("Inherent") {
 			let name = &pallet_decl.name;
 			let path = &pallet_decl.path;
-			let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-				let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			});
+			let attr = pallet_decl.get_attributes();
 
 			pallet_names.push(name);
 			pallet_attrs.push(attr);
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 0b3bd516886513faee90ea367aaf0ed86593e9a3..d246c00628640d8dfd27a133a2c06637b3953eb8 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::{parse::PalletPath, Pallet};
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::str::FromStr;
 use syn::Ident;
 
 pub fn expand_runtime_metadata(
@@ -51,14 +50,7 @@ pub fn expand_runtime_metadata(
 			let errors = expand_pallet_metadata_errors(runtime, decl);
 			let associated_types = expand_pallet_metadata_associated_types(runtime, decl);
 			let docs = expand_pallet_metadata_docs(runtime, decl);
-			let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-				let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			});
+			let attr = decl.get_attributes();
 			let deprecation_info = expand_pallet_metadata_deprecation(runtime, decl);
 			quote! {
 				#attr
@@ -78,6 +70,20 @@ 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 {
@@ -149,6 +155,10 @@ 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),* ],
 					}
 				}
 			}
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
index 88f9a3c6e33fd3fc99b2f4e511d5a6c0afd9263a..823aa69dbdf2b1a4d725f8b33c28a231a1699977 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs
@@ -28,6 +28,7 @@ mod outer_enums;
 mod slash_reason;
 mod task;
 mod unsigned;
+mod view_function;
 
 pub use call::expand_outer_dispatch;
 pub use config::expand_outer_config;
@@ -41,3 +42,4 @@ pub use outer_enums::{expand_outer_enum, OuterEnumType};
 pub use slash_reason::expand_outer_slash_reason;
 pub use task::expand_outer_task;
 pub use unsigned::expand_outer_validate_unsigned;
+pub use view_function::expand_outer_query;
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs
index 1c4ab436ad92aaee824a7f2916c32483fe443f6f..4742e68e2e2a8d86c14a27e038868e25892557e0 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME};
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::str::FromStr;
 use syn::{Generics, Ident};
 
 pub fn expand_outer_origin(
@@ -335,14 +334,7 @@ fn expand_origin_caller_variant(
 	let part_is_generic = !generics.params.is_empty();
 	let variant_name = &pallet.name;
 	let path = &pallet.path;
-	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-			.expect("was successfully parsed before; qed");
-		quote! {
-			#acc
-			#attr
-		}
-	});
+	let attr = pallet.get_attributes();
 
 	match instance {
 		Some(inst) if part_is_generic => quote! {
@@ -387,14 +379,7 @@ fn expand_origin_pallet_conversions(
 	};
 
 	let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name());
-	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-			.expect("was successfully parsed before; qed");
-		quote! {
-			#acc
-			#attr
-		}
-	});
+	let attr = pallet.get_attributes();
 
 	quote! {
 		#attr
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs
index 80b242ccbe493607a59e672b7f0958fc9d0a213c..80d3a5af26627f9cdcdb58cd1ef6645c523dbf86 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::Pallet;
 use proc_macro2::{Span, TokenStream};
 use quote::{quote, ToTokens};
-use std::str::FromStr;
 use syn::{Generics, Ident};
 
 /// Represents the types supported for creating an outer enum.
@@ -185,14 +184,7 @@ fn expand_enum_variant(
 	let path = &pallet.path;
 	let variant_name = &pallet.name;
 	let part_is_generic = !generics.params.is_empty();
-	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-			.expect("was successfully parsed before; qed");
-		quote! {
-			#acc
-			#attr
-		}
-	});
+	let attr = pallet.get_attributes();
 
 	match instance {
 		Some(inst) if part_is_generic => quote! {
@@ -224,14 +216,7 @@ fn expand_enum_conversion(
 	enum_name_ident: &Ident,
 ) -> TokenStream {
 	let variant_name = &pallet.name;
-	let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-		let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-			.expect("was successfully parsed before; qed");
-		quote! {
-			#acc
-			#attr
-		}
-	});
+	let attr = pallet.get_attributes();
 
 	quote! {
 		#attr
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
index 1302f86455f2ceeb7af58e46e30b05afccbc44cf..b9b8efb8c006364dc48e25926c158664066a9d65 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs
@@ -16,7 +16,6 @@
 // limitations under the License
 
 use crate::construct_runtime::Pallet;
-use core::str::FromStr;
 use proc_macro2::{Ident, TokenStream as TokenStream2};
 use quote::quote;
 
@@ -42,14 +41,7 @@ pub fn expand_outer_task(
 		let instance = decl.instance.as_ref().map(|instance| quote!(, #path::#instance));
 		let task_type = quote!(#path::Task<#runtime_name #instance>);
 
-		let attr = decl.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| {
-			let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original()))
-				.expect("was successfully parsed before; qed");
-			quote! {
-				#acc
-				#attr
-			}
-		});
+		let attr = decl.get_attributes();
 
 		from_impls.push(quote! {
 			#attr
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs
index 33aadba0d1f1c522aae13fb647b9fe8a918d8e24..737a39ea681e0d1c09c458c4b59229f5159136d4 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs
@@ -18,7 +18,6 @@
 use crate::construct_runtime::Pallet;
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::str::FromStr;
 use syn::Ident;
 
 pub fn expand_outer_validate_unsigned(
@@ -34,14 +33,7 @@ pub fn expand_outer_validate_unsigned(
 		if pallet_decl.exists_part("ValidateUnsigned") {
 			let name = &pallet_decl.name;
 			let path = &pallet_decl.path;
-			let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
-				let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			});
+			let attr = pallet_decl.get_attributes();
 
 			pallet_names.push(name);
 			pallet_attrs.push(attr);
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
new file mode 100644
index 0000000000000000000000000000000000000000..094dcca4a5b5249cfe3026790d9938a84f5649fe
--- /dev/null
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs
@@ -0,0 +1,78 @@
+// 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
+
+use crate::construct_runtime::Pallet;
+use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
+
+/// Expands implementation of runtime level `DispatchViewFunction`.
+pub fn expand_outer_query(
+	runtime_name: &Ident,
+	pallet_decls: &[Pallet],
+	scrate: &TokenStream2,
+) -> TokenStream2 {
+	let runtime_view_function = syn::Ident::new("RuntimeViewFunction", Span::call_site());
+
+	let prefix_conditionals = pallet_decls.iter().map(|pallet| {
+		let pallet_name = &pallet.name;
+		let attr = pallet.get_attributes();
+		quote::quote! {
+			#attr
+			if id.prefix == <#pallet_name as #scrate::view_functions::ViewFunctionIdPrefix>::prefix() {
+				return <#pallet_name as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(id, input, output)
+			}
+		}
+	});
+
+	quote::quote! {
+		/// Runtime query type.
+		#[derive(
+			Clone, PartialEq, Eq,
+			#scrate::__private::codec::Encode,
+			#scrate::__private::codec::Decode,
+			#scrate::__private::scale_info::TypeInfo,
+			#scrate::__private::RuntimeDebug,
+		)]
+		pub enum #runtime_view_function {}
+
+		const _: () = {
+			impl #scrate::view_functions::DispatchViewFunction for #runtime_view_function {
+				fn dispatch_view_function<O: #scrate::__private::codec::Output>(
+					id: & #scrate::view_functions::ViewFunctionId,
+					input: &mut &[u8],
+					output: &mut O
+				) -> Result<(), #scrate::view_functions::ViewFunctionDispatchError>
+				{
+					#( #prefix_conditionals )*
+					Err(#scrate::view_functions::ViewFunctionDispatchError::NotFound(id.clone()))
+				}
+			}
+
+			impl #runtime_name {
+				/// Convenience function for query execution from the runtime API.
+				pub fn execute_view_function(
+					id: #scrate::view_functions::ViewFunctionId,
+					input: #scrate::__private::Vec<::core::primitive::u8>,
+				) -> Result<#scrate::__private::Vec<::core::primitive::u8>, #scrate::view_functions::ViewFunctionDispatchError>
+				{
+					let mut output = #scrate::__private::vec![];
+					<#runtime_view_function as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(&id, &mut &input[..], &mut output)?;
+					Ok(output)
+				}
+			}
+		};
+	}
+}
diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs
index 087faf37252de9fc32f8b1ca691c32d52e612982..c6018e048f2f8c15acdc5264a52c35df52c6aa7a 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs
@@ -400,6 +400,7 @@ fn construct_runtime_final_expansion(
 
 	let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
 	let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
+	let query = expand::expand_outer_query(&name, &pallets, &scrate);
 	let metadata = expand::expand_runtime_metadata(
 		&name,
 		&pallets,
@@ -492,6 +493,8 @@ fn construct_runtime_final_expansion(
 
 		#tasks
 
+		#query
+
 		#metadata
 
 		#outer_config
@@ -650,16 +653,7 @@ pub(crate) fn decl_pallet_runtime_setup(
 		.collect::<Vec<_>>();
 	let pallet_attrs = pallet_declarations
 		.iter()
-		.map(|pallet| {
-			pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| {
-				let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original()))
-					.expect("was successfully parsed before; qed");
-				quote! {
-					#acc
-					#attr
-				}
-			})
-		})
+		.map(|pallet| pallet.get_attributes())
 		.collect::<Vec<_>>();
 
 	quote!(
diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs
index 729a803a302ed7451f890ab6a81b666379d68d07..2df08123821a3972d5053aeb0ce9e6a7e3477a79 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs
@@ -15,6 +15,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use core::str::FromStr;
 use frame_support_procedural_tools::syn_ext as ext;
 use proc_macro2::{Span, TokenStream};
 use quote::ToTokens;
@@ -609,6 +610,18 @@ impl Pallet {
 	pub fn exists_part(&self, name: &str) -> bool {
 		self.find_part(name).is_some()
 	}
+
+	// Get runtime attributes for the pallet, mostly used for macros
+	pub fn get_attributes(&self) -> TokenStream {
+		self.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
+			let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
+				.expect("was successfully parsed before; qed");
+			quote::quote! {
+				#acc
+				#attr
+			}
+		})
+	}
 }
 
 /// Result of a conversion of a declaration of pallets.
diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index c2f546d92048ac2bc259138b27ac5820227fefd6..26703a2438ef9ad368c3718bc497c9563bb407fe 100644
--- a/substrate/frame/support/procedural/src/lib.rs
+++ b/substrate/frame/support/procedural/src/lib.rs
@@ -817,6 +817,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
 	if item.ident != "RuntimeCall" &&
 		item.ident != "RuntimeEvent" &&
 		item.ident != "RuntimeTask" &&
+		item.ident != "RuntimeViewFunction" &&
 		item.ident != "RuntimeOrigin" &&
 		item.ident != "RuntimeHoldReason" &&
 		item.ident != "RuntimeFreezeReason" &&
@@ -826,7 +827,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
 		return syn::Error::new_spanned(
 			item,
 			"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \
-			`RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`",
+			`RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`",
 		)
 		.to_compile_error()
 		.into();
diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs
index 3f9b50f79c0ccf8ea37e23cd79d1bd1aead2064f..439ec55e269d43aaab8ec91296c48df970ca6985 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs
@@ -35,6 +35,7 @@ mod tasks;
 mod tt_default_parts;
 mod type_value;
 mod validate_unsigned;
+mod view_functions;
 mod warnings;
 
 use crate::pallet::Def;
@@ -66,6 +67,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
 	let error = error::expand_error(&mut def);
 	let event = event::expand_event(&mut def);
 	let storages = storage::expand_storages(&mut def);
+	let view_functions = view_functions::expand_view_functions(&def);
 	let inherents = inherent::expand_inherents(&mut def);
 	let instances = instances::expand_instances(&mut def);
 	let hooks = hooks::expand_hooks(&mut def);
@@ -108,6 +110,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*]
 		#error
 		#event
 		#storages
+		#view_functions
 		#inherents
 		#instances
 		#hooks
diff --git a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs
new file mode 100644
index 0000000000000000000000000000000000000000..587e74a2ac182f2dc817db9bb6058e2c93611bc2
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs
@@ -0,0 +1,263 @@
+// 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.
+
+use crate::pallet::{parse::view_functions::ViewFunctionDef, Def};
+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 view_function_prefix_impl =
+		expand_view_function_prefix_impl(def, span, where_clause.as_ref());
+
+	let view_fn_impls = view_fns
+		.iter()
+		.map(|view_fn| expand_view_function(def, span, where_clause.as_ref(), view_fn));
+	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);
+
+	quote::quote! {
+		#view_function_prefix_impl
+		#( #view_fn_impls )*
+		#impl_dispatch_view_function
+		#impl_view_function_metadata
+	}
+}
+
+fn expand_view_function_prefix_impl(
+	def: &Def,
+	span: Span,
+	where_clause: Option<&syn::WhereClause>,
+) -> TokenStream {
+	let pallet_ident = &def.pallet_struct.pallet;
+	let frame_support = &def.frame_support;
+	let frame_system = &def.frame_system;
+	let type_impl_gen = &def.type_impl_generics(span);
+	let type_use_gen = &def.type_use_generics(span);
+
+	quote::quote! {
+		impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdPrefix for #pallet_ident<#type_use_gen> #where_clause {
+			fn prefix() -> [::core::primitive::u8; 16usize] {
+				<
+					<T as #frame_system::Config>::PalletInfo
+					as #frame_support::traits::PalletInfo
+				>::name_hash::<Pallet<#type_use_gen>>()
+					.expect("No name_hash found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.")
+			}
+		}
+	}
+}
+
+fn expand_view_function(
+	def: &Def,
+	span: Span,
+	where_clause: Option<&syn::WhereClause>,
+	view_fn: &ViewFunctionDef,
+) -> TokenStream {
+	let frame_support = &def.frame_support;
+	let pallet_ident = &def.pallet_struct.pallet;
+	let type_impl_gen = &def.type_impl_generics(span);
+	let type_decl_bounded_gen = &def.type_decl_bounded_generics(span);
+	let type_use_gen = &def.type_use_generics(span);
+	let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" };
+
+	let view_function_struct_ident = view_fn.view_function_struct_ident();
+	let view_fn_name = &view_fn.name;
+	let (arg_names, arg_types) = match view_fn.args_names_types() {
+		Ok((arg_names, arg_types)) => (arg_names, arg_types),
+		Err(e) => return e.into_compile_error(),
+	};
+	let return_type = &view_fn.return_type;
+	let docs = &view_fn.docs;
+
+	let view_function_id_suffix_bytes_raw = match view_fn.view_function_id_suffix_bytes() {
+		Ok(view_function_id_suffix_bytes_raw) => view_function_id_suffix_bytes_raw,
+		Err(e) => return e.into_compile_error(),
+	};
+	let view_function_id_suffix_bytes = view_function_id_suffix_bytes_raw
+		.map(|byte| syn::LitInt::new(&format!("0x{:X}_u8", byte), Span::call_site()));
+
+	quote::quote! {
+		#( #[doc = #docs] )*
+		#[allow(missing_docs)]
+		#[derive(
+			#frame_support::RuntimeDebugNoBound,
+			#frame_support::CloneNoBound,
+			#frame_support::EqNoBound,
+			#frame_support::PartialEqNoBound,
+			#frame_support::__private::codec::Encode,
+			#frame_support::__private::codec::Decode,
+			#frame_support::__private::scale_info::TypeInfo,
+		)]
+		#[codec(encode_bound())]
+		#[codec(decode_bound())]
+		#[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)]
+		pub struct #view_function_struct_ident<#type_decl_bounded_gen> #where_clause {
+			#(
+				pub #arg_names: #arg_types,
+			)*
+			_marker: ::core::marker::PhantomData<(#type_use_gen,)>,
+		}
+
+		impl<#type_impl_gen> #view_function_struct_ident<#type_use_gen> #where_clause {
+			/// Create a new [`#view_function_struct_ident`] instance.
+			pub fn new(#( #arg_names: #arg_types, )*) -> Self {
+				Self {
+					#( #arg_names, )*
+					_marker: ::core::default::Default::default()
+				}
+			}
+		}
+
+		impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdSuffix for #view_function_struct_ident<#type_use_gen> #where_clause {
+			const SUFFIX: [::core::primitive::u8; 16usize] = [ #( #view_function_id_suffix_bytes ),* ];
+		}
+
+		impl<#type_impl_gen> #frame_support::view_functions::ViewFunction for #view_function_struct_ident<#type_use_gen> #where_clause {
+			fn id() -> #frame_support::view_functions::ViewFunctionId {
+				#frame_support::view_functions::ViewFunctionId {
+					prefix: <#pallet_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdPrefix>::prefix(),
+					suffix: <Self as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX,
+				}
+			}
+
+			type ReturnType = #return_type;
+
+			fn invoke(self) -> Self::ReturnType {
+				let Self { #( #arg_names, )* _marker } = self;
+				#pallet_ident::<#type_use_gen> :: #view_fn_name( #( #arg_names, )* )
+			}
+		}
+	}
+}
+
+fn impl_dispatch_view_function(
+	def: &Def,
+	span: Span,
+	where_clause: Option<&syn::WhereClause>,
+	view_fns: &[ViewFunctionDef],
+) -> TokenStream {
+	let frame_support = &def.frame_support;
+	let pallet_ident = &def.pallet_struct.pallet;
+	let type_impl_gen = &def.type_impl_generics(span);
+	let type_use_gen = &def.type_use_generics(span);
+
+	let query_match_arms = view_fns.iter().map(|view_fn| {
+		let view_function_struct_ident = view_fn.view_function_struct_ident();
+		quote::quote! {
+			<#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX => {
+				<#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::execute(input, output)
+			}
+		}
+	});
+
+	quote::quote! {
+		impl<#type_impl_gen> #frame_support::view_functions::DispatchViewFunction
+			for #pallet_ident<#type_use_gen> #where_clause
+		{
+			#[deny(unreachable_patterns)]
+			fn dispatch_view_function<O: #frame_support::__private::codec::Output>(
+				id: & #frame_support::view_functions::ViewFunctionId,
+				input: &mut &[u8],
+				output: &mut O
+			) -> Result<(), #frame_support::view_functions::ViewFunctionDispatchError>
+			{
+				match id.suffix {
+					#( #query_match_arms )*
+					_ => Err(#frame_support::view_functions::ViewFunctionDispatchError::NotFound(id.clone())),
+				}
+			}
+		}
+	}
+}
+
+fn impl_view_function_metadata(
+	def: &Def,
+	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;
+	let type_impl_gen = &def.type_impl_generics(span);
+	let type_use_gen = &def.type_use_generics(span);
+
+	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| {
+			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 {
+							name: ::core::stringify!(#pat),
+							ty: #frame_support::__private::scale_info::meta_type::<#ty>(),
+						}
+					})
+				}
+			}
+		});
+
+		let no_docs = vec![];
+		let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs };
+
+		quote::quote! {
+			#frame_support::__private::metadata_ir::ViewFunctionMetadataIR {
+				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 ),* ],
+				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 ),* ],
+			}
+		}
+	});
+
+	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 ),* ],
+				}
+			}
+		}
+	}
+}
diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs
index c9a150effccbee6ecf7ebb308837556828c1c071..89875974b8b5d023c161600a96518ad65cb855fd 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs
@@ -36,6 +36,7 @@ pub mod storage;
 pub mod tasks;
 pub mod type_value;
 pub mod validate_unsigned;
+pub mod view_functions;
 
 #[cfg(test)]
 pub mod tests;
@@ -70,6 +71,7 @@ pub struct Def {
 	pub frame_system: syn::Path,
 	pub frame_support: syn::Path,
 	pub dev_mode: bool,
+	pub view_functions: Option<view_functions::ViewFunctionsImplDef>,
 }
 
 impl Def {
@@ -103,6 +105,7 @@ impl Def {
 		let mut storages = vec![];
 		let mut type_values = vec![];
 		let mut composites: Vec<CompositeDef> = vec![];
+		let mut view_functions = None;
 
 		for (index, item) in items.iter_mut().enumerate() {
 			let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?;
@@ -205,6 +208,9 @@ impl Def {
 					}
 					composites.push(composite);
 				},
+				Some(PalletAttr::ViewFunctions(span)) => {
+					view_functions = Some(view_functions::ViewFunctionsImplDef::try_from(span, item)?);
+				}
 				Some(attr) => {
 					let msg = "Invalid duplicated attribute";
 					return Err(syn::Error::new(attr.span(), msg))
@@ -250,6 +256,7 @@ impl Def {
 			frame_system,
 			frame_support,
 			dev_mode,
+			view_functions,
 		};
 
 		def.check_instance_usage()?;
@@ -563,6 +570,7 @@ mod keyword {
 	syn::custom_keyword!(pallet);
 	syn::custom_keyword!(extra_constants);
 	syn::custom_keyword!(composite_enum);
+	syn::custom_keyword!(view_functions_experimental);
 }
 
 /// The possible values for the `#[pallet::config]` attribute.
@@ -652,6 +660,7 @@ enum PalletAttr {
 	TypeValue(proc_macro2::Span),
 	ExtraConstants(proc_macro2::Span),
 	Composite(proc_macro2::Span),
+	ViewFunctions(proc_macro2::Span),
 }
 
 impl PalletAttr {
@@ -677,6 +686,7 @@ impl PalletAttr {
 			Self::TypeValue(span) => *span,
 			Self::ExtraConstants(span) => *span,
 			Self::Composite(span) => *span,
+			Self::ViewFunctions(span) => *span,
 		}
 	}
 }
@@ -778,6 +788,10 @@ impl syn::parse::Parse for PalletAttr {
 			Ok(PalletAttr::ExtraConstants(content.parse::<keyword::extra_constants>()?.span()))
 		} else if lookahead.peek(keyword::composite_enum) {
 			Ok(PalletAttr::Composite(content.parse::<keyword::composite_enum>()?.span()))
+		} else if lookahead.peek(keyword::view_functions_experimental) {
+			Ok(PalletAttr::ViewFunctions(
+				content.parse::<keyword::view_functions_experimental>()?.span(),
+			))
 		} else {
 			Err(lookahead.error())
 		}
diff --git a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs
new file mode 100644
index 0000000000000000000000000000000000000000..766bcb13da8b3cbddc164a8fdd2a2ecab066f6d4
--- /dev/null
+++ b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs
@@ -0,0 +1,155 @@
+// 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 governsing permissions and
+// limitations under the License.
+
+use frame_support_procedural_tools::get_doc_literals;
+use inflector::Inflector;
+use syn::spanned::Spanned;
+
+/// Parsed representation of an impl block annotated with `pallet::view_functions_experimental`.
+pub struct ViewFunctionsImplDef {
+	/// The where_clause used.
+	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>,
+}
+
+impl ViewFunctionsImplDef {
+	pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> {
+		let syn::Item::Impl(item_impl) = item else {
+			return Err(syn::Error::new(
+				item.span(),
+				"Invalid pallet::view_functions_experimental, expected item impl",
+			))
+		};
+		let mut view_functions = Vec::new();
+		for item in &mut item_impl.items {
+			if let syn::ImplItem::Fn(method) = item {
+				if !matches!(method.vis, syn::Visibility::Public(_)) {
+					let msg = "Invalid pallet::view_functions_experimental, view function must be public: \
+						`pub fn`";
+
+					let span = match method.vis {
+						syn::Visibility::Inherited => method.sig.span(),
+						_ => method.vis.span(),
+					};
+
+					return Err(syn::Error::new(span, msg))
+				}
+
+				let view_fn_def = ViewFunctionDef::try_from(method.clone())?;
+				view_functions.push(view_fn_def)
+			} else {
+				return Err(syn::Error::new(
+					item.span(),
+					"Invalid pallet::view_functions_experimental, expected a function",
+				))
+			}
+		}
+		Ok(Self {
+			view_functions,
+			attr_span,
+			where_clause: item_impl.generics.where_clause.clone(),
+			docs: get_doc_literals(&item_impl.attrs),
+		})
+	}
+}
+
+/// Parsed representation of a view function definition.
+#[derive(Clone)]
+pub struct ViewFunctionDef {
+	pub name: syn::Ident,
+	pub docs: Vec<syn::Expr>,
+	pub args: Vec<syn::FnArg>,
+	pub return_type: syn::Type,
+}
+
+impl TryFrom<syn::ImplItemFn> for ViewFunctionDef {
+	type Error = syn::Error;
+	fn try_from(method: syn::ImplItemFn) -> Result<Self, Self::Error> {
+		let syn::ReturnType::Type(_, type_) = method.sig.output else {
+			return Err(syn::Error::new(method.sig.span(), "view functions must return a value"))
+		};
+
+		Ok(Self {
+			name: method.sig.ident.clone(),
+			docs: get_doc_literals(&method.attrs),
+			args: method.sig.inputs.iter().cloned().collect::<Vec<_>>(),
+			return_type: *type_.clone(),
+		})
+	}
+}
+
+impl ViewFunctionDef {
+	pub fn view_function_struct_ident(&self) -> syn::Ident {
+		syn::Ident::new(
+			&format!("{}ViewFunction", self.name.to_string().to_pascal_case()),
+			self.name.span(),
+		)
+	}
+
+	pub fn view_function_id_suffix_bytes(&self) -> Result<[u8; 16], syn::Error> {
+		let mut output = [0u8; 16];
+
+		// concatenate the signature string
+		let arg_types = self
+			.args_names_types()?
+			.1
+			.iter()
+			.map(|ty| quote::quote!(#ty).to_string().replace(" ", ""))
+			.collect::<Vec<_>>()
+			.join(",");
+		let return_type = &self.return_type;
+		let return_type = quote::quote!(#return_type).to_string().replace(" ", "");
+		let view_fn_signature = format!(
+			"{view_function_name}({arg_types}) -> {return_type}",
+			view_function_name = &self.name,
+		);
+
+		// hash the signature string
+		let hash = sp_crypto_hashing::twox_128(view_fn_signature.as_bytes());
+		output.copy_from_slice(&hash[..]);
+		Ok(output)
+	}
+
+	pub fn args_names_types(&self) -> Result<(Vec<syn::Ident>, Vec<syn::Type>), syn::Error> {
+		Ok(self
+			.args
+			.iter()
+			.map(|arg| {
+				let syn::FnArg::Typed(pat_type) = arg else {
+					return Err(syn::Error::new(
+						arg.span(),
+						"Unsupported argument in view function",
+					));
+				};
+				let syn::Pat::Ident(ident) = &*pat_type.pat else {
+					return Err(syn::Error::new(
+						pat_type.pat.span(),
+						"Unsupported pattern in view function argument",
+					));
+				};
+				Ok((ident.ident.clone(), *pat_type.ty.clone()))
+			})
+			.collect::<Result<Vec<(syn::Ident, syn::Type)>, syn::Error>>()?
+			.into_iter()
+			.unzip())
+	}
+}
diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs
index 666bc03aa415df6b495cd479e62cb929f8767cd8..005b109c0eb5fe2a1c79f44f664d2dad47a5bf84 100644
--- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs
+++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs
@@ -182,6 +182,7 @@ fn construct_runtime_final_expansion(
 	let mut slash_reason = None;
 	let mut lock_id = None;
 	let mut task = None;
+	let mut query = None;
 
 	for runtime_type in runtime_types.iter() {
 		match runtime_type {
@@ -224,6 +225,9 @@ fn construct_runtime_final_expansion(
 			RuntimeType::RuntimeTask(_) => {
 				task = Some(expand::expand_outer_task(&name, &pallets, &scrate));
 			},
+			RuntimeType::RuntimeViewFunction(_) => {
+				query = Some(expand::expand_outer_query(&name, &pallets, &scrate));
+			},
 		}
 	}
 
@@ -301,6 +305,8 @@ fn construct_runtime_final_expansion(
 
 		#task
 
+		#query
+
 		#metadata
 
 		#outer_config
diff --git a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs
index a4480e2a1fd32622bea3a7f20294b4c2bee88309..9a385146a811e85211aa0bf6791154ac76276687 100644
--- a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs
+++ b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs
@@ -32,6 +32,7 @@ mod keyword {
 	custom_keyword!(RuntimeSlashReason);
 	custom_keyword!(RuntimeLockId);
 	custom_keyword!(RuntimeTask);
+	custom_keyword!(RuntimeViewFunction);
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -45,6 +46,7 @@ pub enum RuntimeType {
 	RuntimeSlashReason(keyword::RuntimeSlashReason),
 	RuntimeLockId(keyword::RuntimeLockId),
 	RuntimeTask(keyword::RuntimeTask),
+	RuntimeViewFunction(keyword::RuntimeViewFunction),
 }
 
 impl Parse for RuntimeType {
@@ -69,6 +71,8 @@ impl Parse for RuntimeType {
 			Ok(Self::RuntimeLockId(input.parse()?))
 		} else if lookahead.peek(keyword::RuntimeTask) {
 			Ok(Self::RuntimeTask(input.parse()?))
+		} else if lookahead.peek(keyword::RuntimeViewFunction) {
+			Ok(Self::RuntimeViewFunction(input.parse()?))
 		} else {
 			Err(lookahead.error())
 		}
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index a6969260e6a26bedef86f55e7b27689978891aa7..97d16e2a06d23349fe4ea653be73428dde976a68 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -87,6 +87,7 @@ pub mod storage;
 #[cfg(test)]
 mod tests;
 pub mod traits;
+pub mod view_functions;
 pub mod weights;
 #[doc(hidden)]
 pub mod unsigned {
diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs
index 7c90a12d4167e376841cb0c5a683d56d36559c5c..b10e719b9ac36caaa7e283de1c9e415d00c9edef 100644
--- a/substrate/frame/support/src/tests/mod.rs
+++ b/substrate/frame/support/src/tests/mod.rs
@@ -237,7 +237,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/src/view_functions.rs b/substrate/frame/support/src/view_functions.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dd23fad94a4fd578bdc3d63f86bcb1a1750fe064
--- /dev/null
+++ b/substrate/frame/support/src/view_functions.rs
@@ -0,0 +1,128 @@
+// 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 fsor the specific language governing permissions and
+// limitations under the License.
+
+//! Traits for querying pallet view functions.
+
+use alloc::vec::Vec;
+use codec::{Decode, DecodeAll, Encode, Output};
+use scale_info::TypeInfo;
+use sp_runtime::RuntimeDebug;
+
+/// The unique identifier for a view function.
+#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
+pub struct ViewFunctionId {
+	/// The part of the id for dispatching view functions from the top level of the runtime.
+	///
+	/// Specifies which view function grouping this view function belongs to. This could be a group
+	/// of view functions associated with a pallet, or a pallet agnostic group of view functions.
+	pub prefix: [u8; 16],
+	/// The part of the id for dispatching to a view function within a group.
+	pub suffix: [u8; 16],
+}
+
+impl From<ViewFunctionId> for [u8; 32] {
+	fn from(value: ViewFunctionId) -> Self {
+		let mut output = [0u8; 32];
+		output[..16].copy_from_slice(&value.prefix);
+		output[16..].copy_from_slice(&value.suffix);
+		output
+	}
+}
+
+/// Error type for view function dispatching.
+#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
+pub enum ViewFunctionDispatchError {
+	/// View functions are not implemented for this runtime.
+	NotImplemented,
+	/// A view function with the given `ViewFunctionId` was not found
+	NotFound(ViewFunctionId),
+	/// Failed to decode the view function input.
+	Codec,
+}
+
+impl From<codec::Error> for ViewFunctionDispatchError {
+	fn from(_: codec::Error) -> Self {
+		ViewFunctionDispatchError::Codec
+	}
+}
+
+/// 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`.
+pub trait DispatchViewFunction {
+	fn dispatch_view_function<O: Output>(
+		id: &ViewFunctionId,
+		input: &mut &[u8],
+		output: &mut O,
+	) -> Result<(), ViewFunctionDispatchError>;
+}
+
+impl DispatchViewFunction for () {
+	fn dispatch_view_function<O: Output>(
+		_id: &ViewFunctionId,
+		_input: &mut &[u8],
+		_output: &mut O,
+	) -> Result<(), ViewFunctionDispatchError> {
+		Err(ViewFunctionDispatchError::NotImplemented)
+	}
+}
+
+/// Automatically implemented for each pallet by the macro [`pallet`](crate::pallet).
+pub trait ViewFunctionIdPrefix {
+	fn prefix() -> [u8; 16];
+}
+
+/// Automatically implemented for each pallet view function method by the macro
+/// [`pallet`](crate::pallet).
+pub trait ViewFunctionIdSuffix {
+	const SUFFIX: [u8; 16];
+}
+
+/// Automatically implemented for each pallet view function method by the macro
+/// [`pallet`](crate::pallet).
+pub trait ViewFunction: DecodeAll {
+	fn id() -> ViewFunctionId;
+	type ReturnType: Encode;
+
+	fn invoke(self) -> Self::ReturnType;
+
+	fn execute<O: Output>(
+		input: &mut &[u8],
+		output: &mut O,
+	) -> Result<(), ViewFunctionDispatchError> {
+		let view_function = Self::decode_all(input)?;
+		let result = view_function.invoke();
+		Encode::encode_to(&result, output);
+		Ok(())
+	}
+}
+
+pub mod runtime_api {
+	use super::*;
+
+	sp_api::decl_runtime_apis! {
+		#[api_version(1)]
+		/// Runtime API for executing view functions
+		pub trait RuntimeViewFunction {
+			/// Execute a view function query.
+			fn execute_view_function(
+				query_id: ViewFunctionId,
+				input: Vec<u8>,
+			) -> Result<Vec<u8>, ViewFunctionDispatchError>;
+		}
+	}
+}
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 726b09cf54c997b04ffa17a6256cae25e4c17886..faa9cb558c262f09111a488a4e2f4bf114a74375 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
@@ -561,6 +561,15 @@ 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[E0277]: the trait bound `Runtime: Config` is not satisfied
+  --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3
+   |
+26 |         System: frame_system::{Pallet, Call, Storage, Config<T>, Event<T>},
+   |         ^^^^^^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet<Runtime>: ViewFunctionIdPrefix`
+   |
+   = help: the trait `ViewFunctionIdPrefix` is implemented for `Pallet<T>`
+   = note: required for `Pallet<Runtime>` to implement `ViewFunctionIdPrefix`
+
 error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
@@ -736,6 +745,31 @@ 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
+  --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
+   |
+20 |    construct_runtime! {
+   |  __^
+   | | _|
+   | ||
+21 | ||     pub struct Runtime where
+   | ||______________________- doesn't satisfy `Runtime: Config`
+22 | |          Block = Block,
+23 | |          NodeBlock = Block,
+...  |
+27 | |      }
+28 | |  }
+   | |__^ 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`
+note: the trait `Config` must be implemented
+  --> $WORKSPACE/substrate/frame/system/src/lib.rs
+   |
+   |     pub trait Config: 'static + Eq + Clone {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
+
 error[E0277]: the trait bound `Runtime: Config` is not satisfied
   --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1
    |
diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
index c7159b34afb3d22737fb5d8ed6662027f04a3bd6..aafc6b5a2c874a7a1ba219fa4b442d5da6114bb3 100644
--- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
+++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr
@@ -1,4 +1,4 @@
-error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`
+error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`
   --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5
    |
 32 |     type RuntimeInfo = ();
diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs
index 9df1f461bba2511538da7ff1f3455c9a36653163..e45ff64e4c26eda45147fb9d65b688914735bfca 100644
--- a/substrate/frame/support/test/tests/pallet.rs
+++ b/substrate/frame/support/test/tests/pallet.rs
@@ -461,6 +461,22 @@ pub mod pallet {
 		_myfield: u32,
 	}
 
+	#[pallet::view_functions_experimental]
+	impl<T: Config> Pallet<T>
+	where
+		T::AccountId: From<SomeType1> + SomeAssociation1,
+	{
+		/// Query value no args.
+		pub fn get_value() -> Option<u32> {
+			Value::<T>::get()
+		}
+
+		/// Query value with args.
+		pub fn get_value_with_arg(key: u16) -> Option<u32> {
+			Map2::<T>::get(key)
+		}
+	}
+
 	#[pallet::genesis_build]
 	impl<T: Config> BuildGenesisConfig for GenesisConfig<T>
 	where
@@ -814,7 +830,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/test/tests/runtime.rs b/substrate/frame/support/test/tests/runtime.rs
index 5335e08837e4adb2735fd3beecd21f8023be7778..cbcdf8d27b39a6e3f48f7cbef5e02c350d56a943 100644
--- a/substrate/frame/support/test/tests/runtime.rs
+++ b/substrate/frame/support/test/tests/runtime.rs
@@ -296,7 +296,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs
index 7b92073a82b1a789ca2b33296893fcb8d62a3a62..1594356ad8fe846b329818634c2a235fd2cae77e 100644
--- a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs
+++ b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs
@@ -296,7 +296,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr
index 0b128c3dd4579ba93f7950f42f0ff97440eadd38..daa6721ff051dac23d66cde5f56c4b1e023b5e97 100644
--- a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr
+++ b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr
@@ -1,4 +1,4 @@
-error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask`
+error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask`, `RuntimeViewFunction`
   --> tests/runtime_ui/invalid_runtime_type_derive.rs:21:23
    |
 21 |     #[runtime::derive(RuntimeInfo)]
diff --git a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs
index 514f150180153692caf55ba9b3ecb171ca4e1a2a..8350211335a5251ec7ef1356dd491dbe7034cc18 100644
--- a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs
+++ b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs
@@ -27,7 +27,7 @@ impl frame_system::Config for Runtime {
 #[frame_support::runtime]
 mod runtime {
     #[runtime::runtime]
-    #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask)]
+    #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask, RuntimeViewFunction)]
     pub struct Runtime;
 
     #[runtime::pallet_index(0)]
diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs
index dc01f7eaadb3337f1a4ff42322417d2e353ba299..e048010a34b75a7facb2cd0abe019eb305734096 100644
--- a/substrate/primitives/metadata-ir/src/lib.rs
+++ b/substrate/primitives/metadata-ir/src/lib.rs
@@ -122,6 +122,7 @@ 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 af217ffe16eeb829cdbbf42f6483a09505709fd0..0617fc7dfb94f7d8c397d4ccbcd4219d400c488d 100644
--- a/substrate/primitives/metadata-ir/src/types.rs
+++ b/substrate/primitives/metadata-ir/src/types.rs
@@ -15,7 +15,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use codec::{Compact, Encode};
+use codec::{Compact, Decode, Encode};
 use scale_info::{
 	form::{Form, MetaForm, PortableForm},
 	prelude::{collections::BTreeMap, vec::Vec},
@@ -41,6 +41,8 @@ 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.
@@ -118,6 +120,89 @@ impl IntoPortable for RuntimeApiMethodParamMetadataIR {
 	}
 }
 
+/// Metadata of the top level runtime view function dispatch.
+#[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 name: T::String,
+	/// Query id.
+	pub id: [u8; 32],
+	/// Query args.
+	pub args: Vec<ViewFunctionArgMetadataIR<T>>,
+	/// Query output.
+	pub output: T::Type,
+	/// Query documentation.
+	pub docs: Vec<T::String>,
+}
+
+impl IntoPortable for ViewFunctionMetadataIR {
+	type Output = ViewFunctionMetadataIR<PortableForm>;
+
+	fn into_portable(self, registry: &mut Registry) -> Self::Output {
+		ViewFunctionMetadataIR {
+			name: self.name.into_portable(registry),
+			id: self.id,
+			args: registry.map_into_portable(self.args),
+			output: registry.register_type(&self.output),
+			docs: registry.map_into_portable(self.docs),
+		}
+	}
+}
+
+/// Metadata of a runtime method argument.
+#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
+pub struct ViewFunctionArgMetadataIR<T: Form = MetaForm> {
+	/// Query argument name.
+	pub name: T::String,
+	/// Query argument type.
+	pub ty: T::Type,
+}
+
+impl IntoPortable for ViewFunctionArgMetadataIR {
+	type Output = ViewFunctionArgMetadataIR<PortableForm>;
+
+	fn into_portable(self, registry: &mut Registry) -> Self::Output {
+		ViewFunctionArgMetadataIR {
+			name: self.name.into_portable(registry),
+			ty: registry.register_type(&self.ty),
+		}
+	}
+}
+
 /// The intermediate representation for a pallet metadata.
 #[derive(Clone, PartialEq, Eq, Encode, Debug)]
 pub struct PalletMetadataIR<T: Form = MetaForm> {
diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs
index ed315a31e6dc9f4f9e1c4af9142818756a64ab56..7bc76f22b58d004507c900bd139c9c58f1e8dbf3 100644
--- a/substrate/primitives/metadata-ir/src/v15.rs
+++ b/substrate/primitives/metadata-ir/src/v15.rs
@@ -17,31 +17,39 @@
 
 //! Convert the IR to V15 metadata.
 
-use crate::OuterEnumsIR;
-
 use super::types::{
-	ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR,
+	ExtrinsicMetadataIR, MetadataIR, OuterEnumsIR, PalletMetadataIR, RuntimeApiMetadataIR,
 	RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR,
 };
 
 use frame_metadata::v15::{
-	CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata,
-	RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15,
-	SignedExtensionMetadata,
+	CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata,
+	RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata,
+	RuntimeMetadataV15, SignedExtensionMetadata,
 };
+use scale_info::{IntoPortable, Registry};
 
 impl From<MetadataIR> for RuntimeMetadataV15 {
 	fn from(ir: MetadataIR) -> Self {
-		RuntimeMetadataV15::new(
-			ir.pallets.into_iter().map(Into::into).collect(),
-			ir.extrinsic.into(),
-			ir.ty,
-			ir.apis.into_iter().map(Into::into).collect(),
-			ir.outer_enums.into(),
-			// Substrate does not collect yet the custom metadata fields.
-			// This allows us to extend the V15 easily.
-			CustomMetadata { map: Default::default() },
-		)
+		let mut registry = Registry::new();
+		let pallets =
+			registry.map_into_portable(ir.pallets.into_iter().map(Into::<PalletMetadata>::into));
+		let extrinsic = Into::<ExtrinsicMetadata>::into(ir.extrinsic).into_portable(&mut registry);
+		let ty = registry.register_type(&ir.ty);
+		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);
+
+		Self { types: registry.into(), pallets, extrinsic, ty, apis, outer_enums, custom }
 	}
 }
 
diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs
index 972c7500f3993f5a362e422e300262025146ce1c..5d549bf1a912d7be414b206d9cdf73ad75c3161c 100644
--- a/templates/minimal/runtime/src/lib.rs
+++ b/templates/minimal/runtime/src/lib.rs
@@ -138,7 +138,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs
index b924428d4145c51ec7de253d59380cdb8dce57f6..3eeb9604f015334cd71c485f648b0dc796d33a9b 100644
--- a/templates/parachain/pallets/template/src/mock.rs
+++ b/templates/parachain/pallets/template/src/mock.rs
@@ -18,7 +18,8 @@ mod test_runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Test;
 
diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs
index 05a508ca655fb1fe6be4db53a839b76c8a9c1cec..d7da43b86af166e991371acb78806c46072cdfc0 100644
--- a/templates/parachain/runtime/src/apis.rs
+++ b/templates/parachain/runtime/src/apis.rs
@@ -114,6 +114,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime {
+		fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> {
+			Runtime::execute_view_function(id, input)
+		}
+	}
+
 	impl sp_block_builder::BlockBuilder<Block> for Runtime {
 		fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
 			Executive::apply_extrinsic(extrinsic)
diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs
index 0be27ecce73945a6fbda409f7d19994493d9be0f..f312e9f80192fd767cdf93288c3331186aabf45a 100644
--- a/templates/parachain/runtime/src/lib.rs
+++ b/templates/parachain/runtime/src/lib.rs
@@ -262,7 +262,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;
 
diff --git a/templates/solochain/pallets/template/src/mock.rs b/templates/solochain/pallets/template/src/mock.rs
index 1b86cd9b7709a43bf7abe45893569b53a614f0e3..44085bc3bff18e90d301c5275944c6e8aa41fd20 100644
--- a/templates/solochain/pallets/template/src/mock.rs
+++ b/templates/solochain/pallets/template/src/mock.rs
@@ -18,7 +18,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Test;
 
diff --git a/templates/solochain/runtime/src/apis.rs b/templates/solochain/runtime/src/apis.rs
index 06c645fa0c53959b209fa4bcfe867eb6c34a5a6d..9dc588c43a2d5eaaa823802972a735dab40d49b0 100644
--- a/templates/solochain/runtime/src/apis.rs
+++ b/templates/solochain/runtime/src/apis.rs
@@ -75,6 +75,12 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime {
+		fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> {
+			Runtime::execute_view_function(id, input)
+		}
+	}
+
 	impl sp_block_builder::BlockBuilder<Block> for Runtime {
 		fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
 			Executive::apply_extrinsic(extrinsic)
diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs
index 6a2149ec8b637c2b253a9327abd81260259cf150..f25b8413721ea2a2ede609fcfaf515f15d163468 100644
--- a/templates/solochain/runtime/src/lib.rs
+++ b/templates/solochain/runtime/src/lib.rs
@@ -196,7 +196,8 @@ mod runtime {
 		RuntimeHoldReason,
 		RuntimeSlashReason,
 		RuntimeLockId,
-		RuntimeTask
+		RuntimeTask,
+		RuntimeViewFunction
 	)]
 	pub struct Runtime;