diff --git a/Cargo.lock b/Cargo.lock
index 845f22178557b948d6a73d586c9efe67a32238b4..f5b272d117566c9b0715d8dd8cae5e0adcb6ecd8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7020,6 +7020,7 @@ dependencies = [
  "pallet-nomination-pools-runtime-api",
  "pallet-offences",
  "pallet-offences-benchmarking",
+ "pallet-parameters",
  "pallet-preimage",
  "pallet-proxy",
  "pallet-ranked-collective",
@@ -10430,6 +10431,26 @@ dependencies = [
  "sp-runtime",
 ]
 
+[[package]]
+name = "pallet-parameters"
+version = "0.0.1"
+dependencies = [
+ "docify",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-example-basic",
+ "parity-scale-codec",
+ "paste",
+ "scale-info",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std 14.0.0",
+]
+
 [[package]]
 name = "pallet-preimage"
 version = "28.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index 8cbfc7a23fed6130e2af818ccc931ca58137c1bc..0a70bb03756b053fe3365b9cbcd5d5956b295d78 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -366,6 +366,7 @@ members = [
 	"substrate/frame/offences/benchmarking",
 	"substrate/frame/paged-list",
 	"substrate/frame/paged-list/fuzzer",
+	"substrate/frame/parameters",
 	"substrate/frame/preimage",
 	"substrate/frame/proxy",
 	"substrate/frame/ranked-collective",
diff --git a/prdoc/pr_2061.prdoc b/prdoc/pr_2061.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..07df11ca0543a5c90a60996f73e96b059a7e8fe4
--- /dev/null
+++ b/prdoc/pr_2061.prdoc
@@ -0,0 +1,14 @@
+# 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: Add Parameters Pallet
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Adds `pallet-parameters` that allows to have parameters for pallet configs that dynamically change at runtime. Allows to be permissioned on a per-key basis and is compatible with ORML macros.
+
+crates:
+  - name: "pallet-parameters"
+  - name: "frame-support"
+  - name: "frame-support-procedural"
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index 4bb5fed2b09a210ff4915609b1b825746ed4f514..76e32f02f0a395a7b87ad82bf6d87779078e1ad5 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -142,6 +142,7 @@ pallet-vesting = { path = "../../../frame/vesting", default-features = false }
 pallet-whitelist = { path = "../../../frame/whitelist", default-features = false }
 pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false }
 pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false }
+pallet-parameters = { path = "../../../frame/parameters", default-features = false }
 
 [build-dependencies]
 substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true }
@@ -209,6 +210,7 @@ std = [
 	"pallet-nomination-pools/std",
 	"pallet-offences-benchmarking?/std",
 	"pallet-offences/std",
+	"pallet-parameters/std",
 	"pallet-preimage/std",
 	"pallet-proxy/std",
 	"pallet-ranked-collective/std",
@@ -310,6 +312,7 @@ runtime-benchmarks = [
 	"pallet-nomination-pools/runtime-benchmarks",
 	"pallet-offences-benchmarking/runtime-benchmarks",
 	"pallet-offences/runtime-benchmarks",
+	"pallet-parameters/runtime-benchmarks",
 	"pallet-preimage/runtime-benchmarks",
 	"pallet-proxy/runtime-benchmarks",
 	"pallet-ranked-collective/runtime-benchmarks",
@@ -386,6 +389,7 @@ try-runtime = [
 	"pallet-nis/try-runtime",
 	"pallet-nomination-pools/try-runtime",
 	"pallet-offences/try-runtime",
+	"pallet-parameters/try-runtime",
 	"pallet-preimage/try-runtime",
 	"pallet-proxy/try-runtime",
 	"pallet-ranked-collective/try-runtime",
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 515272cd99ab46e0ae64ecd62c26e053df13cde3..02d5bb55429db40a86c893063f166a9a37c92613 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -30,6 +30,7 @@ use frame_election_provider_support::{
 use frame_support::{
 	construct_runtime, derive_impl,
 	dispatch::DispatchClass,
+	dynamic_params::{dynamic_pallet_params, dynamic_params},
 	genesis_builder_helper::{build_config, create_default_config},
 	instances::{Instance1, Instance2},
 	ord_parameter_types,
@@ -44,9 +45,9 @@ use frame_support::{
 			GetSalary, PayFromAccount,
 		},
 		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency,
-		EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter,
-		KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced,
-		WithdrawReasons,
+		EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth,
+		InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing,
+		OnUnbalanced, WithdrawReasons,
 	},
 	weights::{
 		constants::{
@@ -457,9 +458,6 @@ impl pallet_glutton::Config for Runtime {
 }
 
 parameter_types! {
-	pub const PreimageBaseDeposit: Balance = 1 * DOLLARS;
-	// One cent: $10,000 / MB
-	pub const PreimageByteDeposit: Balance = 1 * CENTS;
 	pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage);
 }
 
@@ -472,7 +470,11 @@ impl pallet_preimage::Config for Runtime {
 		AccountId,
 		Balances,
 		PreimageHoldReason,
-		LinearStoragePrice<PreimageBaseDeposit, PreimageByteDeposit, Balance>,
+		LinearStoragePrice<
+			dynamic_params::storage::BaseDeposit,
+			dynamic_params::storage::ByteDeposit,
+			Balance,
+		>,
 	>;
 }
 
@@ -1326,9 +1328,6 @@ impl pallet_tips::Config for Runtime {
 }
 
 parameter_types! {
-	pub const DepositPerItem: Balance = deposit(1, 0);
-	pub const DepositPerByte: Balance = deposit(0, 1);
-	pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
 	pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
 	pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30);
 }
@@ -1346,9 +1345,9 @@ impl pallet_contracts::Config for Runtime {
 	/// change because that would break already deployed contracts. The `Call` structure itself
 	/// is not allowed to change the indices of existing pallets, too.
 	type CallFilter = Nothing;
-	type DepositPerItem = DepositPerItem;
-	type DepositPerByte = DepositPerByte;
-	type DefaultDepositLimit = DefaultDepositLimit;
+	type DepositPerItem = dynamic_params::contracts::DepositPerItem;
+	type DepositPerByte = dynamic_params::contracts::DepositPerByte;
+	type DefaultDepositLimit = dynamic_params::contracts::DefaultDepositLimit;
 	type CallStack = [pallet_contracts::Frame<Self>; 5];
 	type WeightPrice = pallet_transaction_payment::Pallet<Self>;
 	type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
@@ -2088,6 +2087,81 @@ impl pallet_mixnet::Config for Runtime {
 	type MinMixnodes = ConstU32<7>; // Low to allow small testing networks
 }
 
+/// Dynamic parameters that can be changed at runtime through the
+/// `pallet_parameters::set_parameter`.
+#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
+pub mod dynamic_params {
+	use super::*;
+
+	#[dynamic_pallet_params]
+	#[codec(index = 0)]
+	pub mod storage {
+		/// Configures the base deposit of storing some data.
+		#[codec(index = 0)]
+		pub static BaseDeposit: Balance = 1 * DOLLARS;
+
+		/// Configures the per-byte deposit of storing some data.
+		#[codec(index = 1)]
+		pub static ByteDeposit: Balance = 1 * CENTS;
+	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 1)]
+	pub mod contracts {
+		#[codec(index = 0)]
+		pub static DepositPerItem: Balance = deposit(1, 0);
+
+		#[codec(index = 1)]
+		pub static DepositPerByte: Balance = deposit(0, 1);
+
+		#[codec(index = 2)]
+		pub static DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+impl Default for RuntimeParameters {
+	fn default() -> Self {
+		RuntimeParameters::Storage(dynamic_params::storage::Parameters::BaseDeposit(
+			dynamic_params::storage::BaseDeposit,
+			Some(1 * DOLLARS),
+		))
+	}
+}
+
+pub struct DynamicParametersManagerOrigin;
+impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for DynamicParametersManagerOrigin {
+	type Success = ();
+
+	fn try_origin(
+		origin: RuntimeOrigin,
+		key: &RuntimeParametersKey,
+	) -> Result<Self::Success, RuntimeOrigin> {
+		match key {
+			RuntimeParametersKey::Storage(_) => {
+				frame_system::ensure_root(origin.clone()).map_err(|_| origin)?;
+				return Ok(())
+			},
+			RuntimeParametersKey::Contract(_) => {
+				frame_system::ensure_root(origin.clone()).map_err(|_| origin)?;
+				return Ok(())
+			},
+		}
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn try_successful_origin(_key: &RuntimeParametersKey) -> Result<RuntimeOrigin, ()> {
+		Ok(RuntimeOrigin::root())
+	}
+}
+
+impl pallet_parameters::Config for Runtime {
+	type RuntimeParameters = RuntimeParameters;
+	type RuntimeEvent = RuntimeEvent;
+	type AdminOrigin = DynamicParametersManagerOrigin;
+	type WeightInfo = ();
+}
+
 construct_runtime!(
 	pub enum Runtime {
 		System: frame_system,
@@ -2169,6 +2243,7 @@ construct_runtime!(
 		Broker: pallet_broker,
 		TasksExample: pallet_example_tasks,
 		Mixnet: pallet_mixnet,
+		Parameters: pallet_parameters,
 		SkipFeelessPayment: pallet_skip_feeless_payment,
 	}
 );
@@ -2288,6 +2363,7 @@ mod benches {
 		[pallet_elections_phragmen, Elections]
 		[pallet_fast_unstake, FastUnstake]
 		[pallet_nis, Nis]
+		[pallet_parameters, Parameters]
 		[pallet_grandpa, Grandpa]
 		[pallet_identity, Identity]
 		[pallet_im_online, ImOnline]
diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml
index 4255ebb66b650efb77264b250396c0a05ba0763a..f866024b8e900cc094b488c49920e14aa9394b36 100644
--- a/substrate/frame/examples/kitchensink/Cargo.toml
+++ b/substrate/frame/examples/kitchensink/Cargo.toml
@@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
 log = { version = "0.4.17", default-features = false }
 scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
 
-frame-support = { path = "../../support", default-features = false }
+frame-support = { path = "../../support", default-features = false, features = ["experimental"] }
 frame-system = { path = "../../system", default-features = false }
 
 sp-io = { path = "../../../primitives/io", default-features = false }
diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c95e23b1a028f39b0a7ae3048500024e70a8f470
--- /dev/null
+++ b/substrate/frame/parameters/Cargo.toml
@@ -0,0 +1,57 @@
+[package]
+name = "pallet-parameters"
+description = "Pallet to store and configure parameters."
+repository.workspace = true
+license = "Apache-2.0"
+version = "0.0.1"
+authors = ["Acala Developers", "Parity Technologies <admin@parity.io>"]
+edition.workspace = true
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] }
+scale-info = { version = "2.1.2", default-features = false, features = ["derive"] }
+paste = { version = "1.0.14", default-features = false }
+serde = { version = "1.0.188", features = ["derive"], optional = true }
+docify = "0.2.5"
+
+frame-support = { path = "../support", default-features = false, features = ["experimental"] }
+frame-system = { path = "../system", default-features = false }
+sp-core = { path = "../../primitives/core", default-features = false }
+sp-runtime = { path = "../../primitives/runtime", default-features = false }
+sp-std = { path = "../../primitives/std", default-features = false }
+frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true }
+
+[dev-dependencies]
+sp-core = { path = "../../primitives/core", features = ["std"] }
+sp-io = { path = "../../primitives/io", features = ["std"] }
+pallet-example-basic = { path = "../examples/basic", features = ["std"] }
+pallet-balances = { path = "../balances", features = ["std"] }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-support/std",
+	"frame-system/std",
+	"scale-info/std",
+	"serde",
+	"sp-core/std",
+	"sp-runtime/std",
+	"sp-std/std",
+]
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"pallet-balances/runtime-benchmarks",
+	"pallet-example-basic/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+]
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"pallet-balances/try-runtime",
+	"pallet-example-basic/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/parameters/src/benchmarking.rs b/substrate/frame/parameters/src/benchmarking.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1f22e026cbca07b236c4680570a3312d8c4b85e5
--- /dev/null
+++ b/substrate/frame/parameters/src/benchmarking.rs
@@ -0,0 +1,51 @@
+// 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.
+
+//! Parameters pallet benchmarking.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+#[cfg(test)]
+use crate::Pallet as Parameters;
+
+use frame_benchmarking::v2::*;
+
+#[benchmarks(where T::RuntimeParameters: Default)]
+mod benchmarks {
+	use super::*;
+
+	#[benchmark]
+	fn set_parameter() -> Result<(), BenchmarkError> {
+		let kv = T::RuntimeParameters::default();
+		let k = kv.clone().into_parts().0;
+
+		let origin =
+			T::AdminOrigin::try_successful_origin(&k).map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, kv);
+
+		Ok(())
+	}
+
+	impl_benchmark_test_suite! {
+		Parameters,
+		crate::tests::mock::new_test_ext(),
+		crate::tests::mock::Runtime,
+	}
+}
diff --git a/substrate/frame/parameters/src/lib.rs b/substrate/frame/parameters/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91cf10ba93f7f9ebf03363cfe3bf8a9b4ff89172
--- /dev/null
+++ b/substrate/frame/parameters/src/lib.rs
@@ -0,0 +1,270 @@
+// 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.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(missing_docs)]
+// Need to enable this one since we document feature-gated stuff.
+#![allow(rustdoc::broken_intra_doc_links)]
+
+//! # **⚠️ WARNING ⚠️**
+//!  
+//! <br>  
+//! <b>THIS CRATE IS NOT AUDITED AND SHOULD NOT BE USED IN PRODUCTION.</b>  
+//! <br>  
+//!
+//! # Parameters
+//!
+//! Allows to update configuration parameters at runtime.
+//!
+//! ## Pallet API
+//!
+//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side
+//! to access said parameters. Parameters themselves are defined in the runtime config and will be
+//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value.
+//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`]
+//! macro or alternatives.
+//!
+//! Note that this is incurring one storage read per access. This should not be a problem in most
+//! cases but must be considered in weight-restrained code.
+//!
+//! ### Inbound
+//!
+//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value
+//! of a parameter. Each parameter can have their own admin origin as given by the
+//! [`Config::AdminOrigin`].
+//!
+//! ### Outbound
+//!
+//! The outbound side is runtime facing for the most part. More general, it provides a `Get`
+//! implementation and can be used in every spot where that is accepted. Two macros are in place:
+//! [`frame_support::dynamic_params::define_parameters` and
+//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a
+//! typed manner.
+//!
+//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
+//! including its configuration trait, dispatchables, storage items, events and errors.
+//!
+//! ## Overview
+//!
+//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to
+//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot
+//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple
+//! parameter change. This pallet allows for fine-grained control over who can update what.
+//! The only down-side is that it trades off performance with convenience and should therefore only
+//! be used in places where that is proven to be uncritical. Values that are rarely accessed but
+//! change often would be a perfect fit.
+//!
+//! ### Example Configuration
+//!
+//! Here is an example of how to define some parameters, including their default values:
+#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)]
+//!
+//! A permissioned origin can be define on a per-key basis like this:
+#![doc = docify::embed!("src/tests/mock.rs", custom_origin)]
+//!
+//! The pallet will also require a default value for benchmarking. Ideally this is the variant with
+//! the longest encoded length. Although in either case the PoV benchmarking will take the worst
+//! case over the whole enum.
+#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)]
+//!
+//! Now the aggregated parameter needs to be injected into the pallet config:
+#![doc = docify::embed!("src/tests/mock.rs", impl_config)]
+//!
+//! As last step, the parameters can now be used in other pallets 🙌
+#![doc = docify::embed!("src/tests/mock.rs", usage)]
+//!
+//! ### Examples Usage
+//!
+//! Now to demonstrate how the values can be updated:
+#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)]
+//!
+//! ## Low Level / Implementation Details
+//!
+//! The pallet stores the parameters in a storage map and implements the matching `Get<Value>` for
+//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is
+//! emitted every time that a value was updated. It is even emitted when the value is changed to the
+//! same.
+//!
+//! The key and value types themselves are defined by macros and aggregated into a runtime wide
+//! enum. This enum is then injected into the pallet. This allows it to be used without any changes
+//! to the pallet that the parameter will be utilized by.
+//!
+//! ### Design Goals
+//!
+//! 1. Easy to update without runtime upgrade.
+//! 2. Exposes metadata and docs for user convenience.
+//! 3. Can be permissioned on a per-key base.
+//!
+//! ### Design
+//!
+//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this -
+//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which
+//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg`
+//! trait, that allows to pass data along to the origin check. It gets passed in the key. The
+//! implementor can then match on the key and the origin to decide whether the origin is
+//! permissioned to set the value.
+
+use frame_support::pallet_prelude::*;
+use frame_system::pallet_prelude::*;
+
+use frame_support::traits::{
+	dynamic_params::{AggregratedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey},
+	EnsureOriginWithArg,
+};
+
+mod benchmarking;
+#[cfg(test)]
+mod tests;
+mod weights;
+
+pub use pallet::*;
+pub use weights::WeightInfo;
+
+/// The key type of a parameter.
+type KeyOf<T> = <<T as Config>::RuntimeParameters as AggregratedKeyValue>::Key;
+
+/// The value type of a parameter.
+type ValueOf<T> = <<T as Config>::RuntimeParameters as AggregratedKeyValue>::Value;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+
+	#[pallet::config(with_default)]
+	pub trait Config: frame_system::Config {
+		/// The overarching event type.
+		#[pallet::no_default_bounds]
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+		/// The overarching KV type of the parameters.
+		///
+		/// Usually created by [`frame_support::dynamic_params`] or equivalent.
+		#[pallet::no_default_bounds]
+		type RuntimeParameters: AggregratedKeyValue;
+
+		/// The origin which may update a parameter.
+		///
+		/// The key of the parameter is passed in as second argument to allow for fine grained
+		/// control.
+		#[pallet::no_default_bounds]
+		type AdminOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, KeyOf<Self>>;
+
+		/// Weight information for extrinsics in this module.
+		type WeightInfo: WeightInfo;
+	}
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// A Parameter was set.
+		///
+		/// Is also emitted when the value was not changed.
+		Updated {
+			/// The key that was updated.
+			key: <T::RuntimeParameters as AggregratedKeyValue>::Key,
+			/// The old value before this call.
+			old_value: Option<<T::RuntimeParameters as AggregratedKeyValue>::Value>,
+			/// The new value after this call.
+			new_value: Option<<T::RuntimeParameters as AggregratedKeyValue>::Value>,
+		},
+	}
+
+	/// Stored parameters.
+	#[pallet::storage]
+	pub type Parameters<T: Config> =
+		StorageMap<_, Blake2_128Concat, KeyOf<T>, ValueOf<T>, OptionQuery>;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		/// Set the value of a parameter.
+		///
+		/// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be
+		/// deleted by setting them to `None`.
+		#[pallet::call_index(0)]
+		#[pallet::weight(T::WeightInfo::set_parameter())]
+		pub fn set_parameter(
+			origin: OriginFor<T>,
+			key_value: T::RuntimeParameters,
+		) -> DispatchResult {
+			let (key, new) = key_value.into_parts();
+			T::AdminOrigin::ensure_origin(origin, &key)?;
+
+			let mut old = None;
+			Parameters::<T>::mutate(&key, |v| {
+				old = v.clone();
+				*v = new.clone();
+			});
+
+			Self::deposit_event(Event::Updated { key, old_value: old, new_value: new });
+
+			Ok(())
+		}
+	}
+	/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
+	pub mod config_preludes {
+		use super::*;
+		use frame_support::derive_impl;
+
+		/// A configuration for testing.
+		pub struct TestDefaultConfig;
+
+		#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)]
+		impl frame_system::DefaultConfig for TestDefaultConfig {}
+
+		#[frame_support::register_default_impl(TestDefaultConfig)]
+		impl DefaultConfig for TestDefaultConfig {
+			#[inject_runtime_type]
+			type RuntimeEvent = ();
+			#[inject_runtime_type]
+			type RuntimeParameters = ();
+
+			type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg<
+				frame_system::EnsureRoot<Self::AccountId>,
+			>;
+
+			type WeightInfo = ();
+		}
+	}
+}
+
+impl<T: Config> RuntimeParameterStore for Pallet<T> {
+	type AggregratedKeyValue = T::RuntimeParameters;
+
+	fn get<KV, K>(key: K) -> Option<K::Value>
+	where
+		KV: AggregratedKeyValue,
+		K: Key + Into<<KV as AggregratedKeyValue>::Key>,
+		<KV as AggregratedKeyValue>::Key: IntoKey<
+			<<Self as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Key,
+		>,
+		<<Self as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Value:
+			TryIntoKey<<KV as AggregratedKeyValue>::Value>,
+		<KV as AggregratedKeyValue>::Value: TryInto<K::WrappedValue>,
+	{
+		let key: <KV as AggregratedKeyValue>::Key = key.into();
+		let val = Parameters::<T>::get(key.into_key());
+		val.and_then(|v| {
+			let val: <KV as AggregratedKeyValue>::Value = v.try_into_key().ok()?;
+			let val: K::WrappedValue = val.try_into().ok()?;
+			let val = val.into();
+			Some(val)
+		})
+	}
+}
diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..98612dc6a6d959c91b78570f562bce9074c43f61
--- /dev/null
+++ b/substrate/frame/parameters/src/tests/mock.rs
@@ -0,0 +1,152 @@
+// 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.
+
+#![cfg(any(test, feature = "runtime-benchmarks"))]
+
+//! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing.
+
+use frame_support::{
+	construct_runtime, derive_impl,
+	dynamic_params::{dynamic_pallet_params, dynamic_params},
+	traits::EnsureOriginWithArg,
+};
+
+use crate as pallet_parameters;
+use crate::*;
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Runtime {
+	type Block = frame_system::mocking::MockBlock<Runtime>;
+	type AccountData = pallet_balances::AccountData<<Self as pallet_balances::Config>::Balance>;
+}
+
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+impl pallet_balances::Config for Runtime {
+	type ReserveIdentifier = [u8; 8];
+	type AccountStore = System;
+}
+
+#[docify::export]
+#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
+pub mod dynamic_params {
+	use super::*;
+
+	#[dynamic_pallet_params]
+	#[codec(index = 3)]
+	pub mod pallet1 {
+		#[codec(index = 0)]
+		pub static Key1: u64 = 0;
+		#[codec(index = 1)]
+		pub static Key2: u32 = 1;
+		#[codec(index = 2)]
+		pub static Key3: u128 = 2;
+	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 1)]
+	pub mod pallet2 {
+		#[codec(index = 2)]
+		pub static Key1: u64 = 0;
+		#[codec(index = 1)]
+		pub static Key2: u32 = 2;
+		#[codec(index = 0)]
+		pub static Key3: u128 = 4;
+	}
+}
+
+#[docify::export(benchmarking_default)]
+#[cfg(feature = "runtime-benchmarks")]
+impl Default for RuntimeParameters {
+	fn default() -> Self {
+		RuntimeParameters::Pallet1(dynamic_params::pallet1::Parameters::Key1(
+			dynamic_params::pallet1::Key1,
+			Some(123),
+		))
+	}
+}
+
+#[docify::export]
+mod custom_origin {
+	use super::*;
+	pub struct ParamsManager;
+
+	impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for ParamsManager {
+		type Success = ();
+
+		fn try_origin(
+			origin: RuntimeOrigin,
+			key: &RuntimeParametersKey,
+		) -> Result<Self::Success, RuntimeOrigin> {
+			// Account 123 is allowed to set parameters in benchmarking only:
+			#[cfg(feature = "runtime-benchmarks")]
+			if ensure_signed(origin.clone()).map_or(false, |acc| acc == 123) {
+				return Ok(());
+			}
+
+			match key {
+				RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()),
+				RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()),
+			}
+			.map_err(|_| origin)
+		}
+
+		#[cfg(feature = "runtime-benchmarks")]
+		fn try_successful_origin(_key: &RuntimeParametersKey) -> Result<RuntimeOrigin, ()> {
+			Ok(RuntimeOrigin::signed(123))
+		}
+	}
+}
+
+#[docify::export(impl_config)]
+#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)]
+impl Config for Runtime {
+	type AdminOrigin = custom_origin::ParamsManager;
+	// RuntimeParameters is injected by the `derive_impl` macro.
+	// RuntimeEvent is injected by the `derive_impl` macro.
+	// WeightInfo is injected by the `derive_impl` macro.
+}
+
+#[docify::export(usage)]
+impl pallet_example_basic::Config for Runtime {
+	// Use the dynamic key in the pallet config:
+	type MagicNumber = dynamic_params::pallet1::Key1;
+
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = ();
+}
+
+construct_runtime!(
+	pub enum Runtime {
+		System: frame_system,
+		PalletParameters: crate,
+		Example: pallet_example_basic,
+		Balances: pallet_balances,
+	}
+);
+
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	let mut ext = sp_io::TestExternalities::new(Default::default());
+	ext.execute_with(|| System::set_block_number(1));
+	ext
+}
+
+pub(crate) fn assert_last_event(generic_event: RuntimeEvent) {
+	let events = frame_system::Pallet::<Runtime>::events();
+	// compare to the last event record
+	let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected");
+	assert_eq!(event, &generic_event);
+}
diff --git a/substrate/frame/parameters/src/tests/mod.rs b/substrate/frame/parameters/src/tests/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0a2d16906aadd394c5facb8c2bdbf45195574246
--- /dev/null
+++ b/substrate/frame/parameters/src/tests/mod.rs
@@ -0,0 +1,20 @@
+// 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.
+
+pub(crate) mod mock;
+mod test_renamed;
+mod unit;
diff --git a/substrate/frame/parameters/src/tests/test_renamed.rs b/substrate/frame/parameters/src/tests/test_renamed.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2e0c1fd9661b4122663a4633e1d4500ac6fad1c
--- /dev/null
+++ b/substrate/frame/parameters/src/tests/test_renamed.rs
@@ -0,0 +1,168 @@
+// 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.
+
+#![cfg(any(test, feature = "runtime-benchmarks"))]
+
+//! Tests that the runtime params can be renamed.
+
+use frame_support::{
+	assert_noop, assert_ok, construct_runtime, derive_impl,
+	dynamic_params::{dynamic_pallet_params, dynamic_params},
+	traits::AsEnsureOriginWithArg,
+};
+use frame_system::EnsureRoot;
+
+use crate as pallet_parameters;
+use crate::*;
+use dynamic_params::*;
+use RuntimeParametersRenamed::*;
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Runtime {
+	type Block = frame_system::mocking::MockBlock<Runtime>;
+	type AccountData = pallet_balances::AccountData<<Self as pallet_balances::Config>::Balance>;
+}
+
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+impl pallet_balances::Config for Runtime {
+	type ReserveIdentifier = [u8; 8];
+	type AccountStore = System;
+}
+
+#[dynamic_params(RuntimeParametersRenamed, pallet_parameters::Parameters::<Runtime>)]
+pub mod dynamic_params {
+	use super::*;
+
+	#[dynamic_pallet_params]
+	#[codec(index = 3)]
+	pub mod pallet1 {
+		#[codec(index = 0)]
+		pub static Key1: u64 = 0;
+		#[codec(index = 1)]
+		pub static Key2: u32 = 1;
+		#[codec(index = 2)]
+		pub static Key3: u128 = 2;
+	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 1)]
+	pub mod pallet2 {
+		#[codec(index = 2)]
+		pub static Key1: u64 = 0;
+		#[codec(index = 1)]
+		pub static Key2: u32 = 2;
+		#[codec(index = 0)]
+		pub static Key3: u128 = 4;
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+impl Default for RuntimeParametersRenamed {
+	fn default() -> Self {
+		RuntimeParametersRenamed::Pallet1(dynamic_params::pallet1::Parameters::Key1(
+			dynamic_params::pallet1::Key1,
+			Some(123),
+		))
+	}
+}
+
+#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)]
+impl Config for Runtime {
+	type AdminOrigin = AsEnsureOriginWithArg<EnsureRoot<Self::AccountId>>;
+	type RuntimeParameters = RuntimeParametersRenamed;
+	// RuntimeEvent is injected by the `derive_impl` macro.
+	// WeightInfo is injected by the `derive_impl` macro.
+}
+
+impl pallet_example_basic::Config for Runtime {
+	// Use the dynamic key in the pallet config:
+	type MagicNumber = dynamic_params::pallet1::Key1;
+
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = ();
+}
+
+construct_runtime!(
+	pub enum Runtime {
+		System: frame_system,
+		PalletParameters: crate,
+		Example: pallet_example_basic,
+		Balances: pallet_balances,
+	}
+);
+
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	let mut ext = sp_io::TestExternalities::new(Default::default());
+	ext.execute_with(|| System::set_block_number(1));
+	ext
+}
+
+pub(crate) fn assert_last_event(generic_event: RuntimeEvent) {
+	let events = frame_system::Pallet::<Runtime>::events();
+	// compare to the last event record
+	let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected");
+	assert_eq!(event, &generic_event);
+}
+
+#[test]
+fn set_parameters_example() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(pallet1::Key3::get(), 2, "Default works");
+
+		// This gets rejected since the origin is not root.
+		assert_noop!(
+			PalletParameters::set_parameter(
+				RuntimeOrigin::signed(1),
+				Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+			),
+			DispatchError::BadOrigin
+		);
+
+		assert_ok!(PalletParameters::set_parameter(
+			RuntimeOrigin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 123, "Update works");
+		assert_last_event(
+			crate::Event::Updated {
+				key: RuntimeParametersRenamedKey::Pallet1(pallet1::ParametersKey::Key3(
+					pallet1::Key3,
+				)),
+				old_value: None,
+				new_value: Some(RuntimeParametersRenamedValue::Pallet1(
+					pallet1::ParametersValue::Key3(123),
+				)),
+			}
+			.into(),
+		);
+	});
+}
+
+#[test]
+fn get_through_external_pallet_works() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(<Runtime as pallet_example_basic::Config>::MagicNumber::get(), 0);
+
+		assert_ok!(PalletParameters::set_parameter(
+			RuntimeOrigin::root(),
+			Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))),
+		));
+
+		assert_eq!(<Runtime as pallet_example_basic::Config>::MagicNumber::get(), 123);
+	});
+}
diff --git a/substrate/frame/parameters/src/tests/unit.rs b/substrate/frame/parameters/src/tests/unit.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d3f11ba96403514fbe77ba89f57872e85cbf655a
--- /dev/null
+++ b/substrate/frame/parameters/src/tests/unit.rs
@@ -0,0 +1,311 @@
+// 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.
+
+//! Unit tests for the parameters pallet.
+
+#![cfg(test)]
+
+use crate::tests::mock::{
+	assert_last_event, dynamic_params::*, new_test_ext, PalletParameters, Runtime,
+	RuntimeOrigin as Origin, RuntimeParameters, RuntimeParameters::*, RuntimeParametersKey,
+	RuntimeParametersValue,
+};
+use codec::Encode;
+use frame_support::{assert_noop, assert_ok, traits::dynamic_params::AggregratedKeyValue};
+use sp_core::Get;
+use sp_runtime::DispatchError;
+
+#[docify::export]
+#[test]
+fn set_parameters_example() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(pallet1::Key3::get(), 2, "Default works");
+
+		// This gets rejected since the origin is not root.
+		assert_noop!(
+			PalletParameters::set_parameter(
+				Origin::signed(1),
+				Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+			),
+			DispatchError::BadOrigin
+		);
+
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 123, "Update works");
+		assert_last_event(
+			crate::Event::Updated {
+				key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)),
+				old_value: None,
+				new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3(
+					123,
+				))),
+			}
+			.into(),
+		);
+	});
+}
+
+#[test]
+fn set_parameters_same_is_noop() {
+	new_test_ext().execute_with(|| {
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 123, "Update works");
+	});
+}
+
+#[test]
+fn set_parameters_twice_works() {
+	new_test_ext().execute_with(|| {
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 432, "Update works");
+	});
+}
+
+#[test]
+fn set_parameters_removing_restores_default_works() {
+	new_test_ext().execute_with(|| {
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 123, "Update works");
+		assert!(
+			crate::Parameters::<Runtime>::contains_key(RuntimeParametersKey::Pallet1(
+				pallet1::ParametersKey::Key3(pallet1::Key3)
+			)),
+			"Key inserted"
+		);
+
+		// Removing the value restores the default.
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, None)),
+		));
+
+		assert_eq!(pallet1::Key3::get(), 2, "Default restored");
+		assert!(
+			!crate::Parameters::<Runtime>::contains_key(RuntimeParametersKey::Pallet1(
+				pallet1::ParametersKey::Key3(pallet1::Key3)
+			)),
+			"Key removed"
+		);
+	});
+}
+
+#[test]
+fn set_parameters_to_default_emits_events_works() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(pallet1::Key3::get(), 2);
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))),
+		));
+		assert_eq!(pallet1::Key3::get(), 2);
+
+		assert!(
+			crate::Parameters::<Runtime>::contains_key(RuntimeParametersKey::Pallet1(
+				pallet1::ParametersKey::Key3(pallet1::Key3)
+			)),
+			"Key inserted"
+		);
+		assert_last_event(
+			crate::Event::Updated {
+				key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)),
+				old_value: None,
+				new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3(2))),
+			}
+			.into(),
+		);
+
+		// It will also emit a second event:
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))),
+		));
+		assert_eq!(frame_system::Pallet::<Runtime>::events().len(), 2);
+	});
+}
+
+#[test]
+fn set_parameters_wrong_origin_errors() {
+	new_test_ext().execute_with(|| {
+		// Pallet1 is root origin only:
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))),
+		));
+
+		assert_noop!(
+			PalletParameters::set_parameter(
+				Origin::signed(1),
+				Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))),
+			),
+			DispatchError::BadOrigin
+		);
+
+		// Pallet2 is signed origin only:
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::signed(1),
+			Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(123))),
+		));
+
+		assert_noop!(
+			PalletParameters::set_parameter(
+				Origin::root(),
+				Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(432))),
+			),
+			DispatchError::BadOrigin
+		);
+	});
+}
+
+#[test]
+fn get_through_external_pallet_works() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(<Runtime as pallet_example_basic::Config>::MagicNumber::get(), 0);
+
+		assert_ok!(PalletParameters::set_parameter(
+			Origin::root(),
+			Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))),
+		));
+
+		assert_eq!(<Runtime as pallet_example_basic::Config>::MagicNumber::get(), 123);
+	});
+}
+
+#[test]
+fn test_define_parameters_key_convert() {
+	let key1 = pallet1::Key1;
+	let parameter_key: pallet1::ParametersKey = key1.clone().into();
+	let key1_2: pallet1::Key1 = parameter_key.clone().try_into().unwrap();
+
+	assert_eq!(key1, key1_2);
+	assert_eq!(parameter_key, pallet1::ParametersKey::Key1(key1));
+
+	let key2 = pallet1::Key2;
+	let parameter_key: pallet1::ParametersKey = key2.clone().into();
+	let key2_2: pallet1::Key2 = parameter_key.clone().try_into().unwrap();
+
+	assert_eq!(key2, key2_2);
+	assert_eq!(parameter_key, pallet1::ParametersKey::Key2(key2));
+}
+
+#[test]
+fn test_define_parameters_value_convert() {
+	let value1 = pallet1::Key1Value(1);
+	let parameter_value: pallet1::ParametersValue = value1.clone().into();
+	let value1_2: pallet1::Key1Value = parameter_value.clone().try_into().unwrap();
+
+	assert_eq!(value1, value1_2);
+	assert_eq!(parameter_value, pallet1::ParametersValue::Key1(1));
+
+	let value2 = pallet1::Key2Value(2);
+	let parameter_value: pallet1::ParametersValue = value2.clone().into();
+	let value2_2: pallet1::Key2Value = parameter_value.clone().try_into().unwrap();
+
+	assert_eq!(value2, value2_2);
+	assert_eq!(parameter_value, pallet1::ParametersValue::Key2(2));
+}
+
+#[test]
+fn test_define_parameters_aggregrated_key_value() {
+	let kv1 = pallet1::Parameters::Key1(pallet1::Key1, None);
+	let (key1, value1) = kv1.clone().into_parts();
+
+	assert_eq!(key1, pallet1::ParametersKey::Key1(pallet1::Key1));
+	assert_eq!(value1, None);
+
+	let kv2 = pallet1::Parameters::Key2(pallet1::Key2, Some(2));
+	let (key2, value2) = kv2.clone().into_parts();
+
+	assert_eq!(key2, pallet1::ParametersKey::Key2(pallet1::Key2));
+	assert_eq!(value2, Some(pallet1::ParametersValue::Key2(2)));
+}
+
+#[test]
+fn test_define_aggregrated_parameters_key_convert() {
+	use codec::Encode;
+
+	let key1 = pallet1::Key1;
+	let parameter_key: pallet1::ParametersKey = key1.clone().into();
+	let runtime_key: RuntimeParametersKey = parameter_key.clone().into();
+
+	assert_eq!(runtime_key, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(key1)));
+	assert_eq!(runtime_key.encode(), vec![3, 0]);
+
+	let key2 = pallet2::Key2;
+	let parameter_key: pallet2::ParametersKey = key2.clone().into();
+	let runtime_key: RuntimeParametersKey = parameter_key.clone().into();
+
+	assert_eq!(runtime_key, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(key2)));
+	assert_eq!(runtime_key.encode(), vec![1, 1]);
+}
+
+#[test]
+fn test_define_aggregrated_parameters_aggregrated_key_value() {
+	let kv1 = RuntimeParameters::Pallet1(pallet1::Parameters::Key1(pallet1::Key1, None));
+	let (key1, value1) = kv1.clone().into_parts();
+
+	assert_eq!(key1, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1)));
+	assert_eq!(value1, None);
+
+	let kv2 = RuntimeParameters::Pallet2(pallet2::Parameters::Key2(pallet2::Key2, Some(2)));
+	let (key2, value2) = kv2.clone().into_parts();
+
+	assert_eq!(key2, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2)));
+	assert_eq!(value2, Some(RuntimeParametersValue::Pallet2(pallet2::ParametersValue::Key2(2))));
+}
+
+#[test]
+fn codec_index_works() {
+	let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1)).encode();
+	assert_eq!(enc, vec![3, 0]);
+	let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key2(pallet1::Key2)).encode();
+	assert_eq!(enc, vec![3, 1]);
+	let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)).encode();
+	assert_eq!(enc, vec![3, 2]);
+
+	let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key1(pallet2::Key1)).encode();
+	assert_eq!(enc, vec![1, 2]);
+	let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2)).encode();
+	assert_eq!(enc, vec![1, 1]);
+	let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key3(pallet2::Key3)).encode();
+	assert_eq!(enc, vec![1, 0]);
+}
diff --git a/substrate/frame/parameters/src/weights.rs b/substrate/frame/parameters/src/weights.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6746960b1b711995b952f6b5784e71debc41a688
--- /dev/null
+++ b/substrate/frame/parameters/src/weights.rs
@@ -0,0 +1,76 @@
+// 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.
+
+//! Autogenerated weights for `pallet_parameters`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2024-02-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
+
+// Executed Command:
+// target/production/substrate-node
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_parameters
+// --chain=dev
+// --header=./substrate/HEADER-APACHE2
+// --output=./substrate/frame/parameters/src/weights.rs
+// --template=./substrate/.maintain/frame-weight-template.hbs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use core::marker::PhantomData;
+
+/// Weight functions needed for `pallet_parameters`.
+pub trait WeightInfo {
+	fn set_parameter() -> Weight;
+}
+
+/// Weights for `pallet_parameters` using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	fn set_parameter() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 0_000 picoseconds.
+		Weight::from_parts(0, 0)
+	}
+}
+
+// For backwards compatibility and tests.
+impl WeightInfo for () {
+	fn set_parameter() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 0_000 picoseconds.
+		Weight::from_parts(0, 0)
+	}
+}
diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml
index d77f6595db3360fa4f60362cd9bff49feb9727e7..5b7fbb3fcaaca22cc453c0fe217e31ce129ac2b5 100644
--- a/substrate/frame/support/procedural/Cargo.toml
+++ b/substrate/frame/support/procedural/Cargo.toml
@@ -24,7 +24,7 @@ cfg-expr = "0.15.5"
 itertools = "0.10.3"
 proc-macro2 = "1.0.56"
 quote = "1.0.28"
-syn = { version = "2.0.48", features = ["full"] }
+syn = { version = "2.0.48", features = ["full", "visit-mut"] }
 frame-support-procedural-tools = { path = "tools" }
 macro_magic = { version = "0.5.0", features = ["proc_support"] }
 proc-macro-warning = { version = "1.0.0", default-features = false }
diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b718ccbc9558480587a1f50f4e85a992e5c750da
--- /dev/null
+++ b/substrate/frame/support/procedural/src/dynamic_params.rs
@@ -0,0 +1,563 @@
+// 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.
+
+//! Code for the `#[dynamic_params]`, `#[dynamic_pallet_params]` and
+//! `#[dynamic_aggregated_params_internal]` macros.
+
+use frame_support_procedural_tools::generate_access_from_frame_or_crate;
+use inflector::Inflector;
+use proc_macro2::{Span, TokenStream};
+use quote::{format_ident, quote, ToTokens};
+use syn::{parse2, spanned::Spanned, visit_mut, visit_mut::VisitMut, Result, Token};
+
+/// Parse and expand a `#[dynamic_params(..)]` module.
+pub fn dynamic_params(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
+	DynamicParamModAttr::parse(attr, item).map(ToTokens::into_token_stream)
+}
+
+/// Parse and expand `#[dynamic_pallet_params(..)]` attribute.
+pub fn dynamic_pallet_params(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
+	DynamicPalletParamAttr::parse(attr, item).map(ToTokens::into_token_stream)
+}
+
+/// Parse and expand `#[dynamic_aggregated_params_internal]` attribute.
+pub fn dynamic_aggregated_params_internal(
+	_attr: TokenStream,
+	item: TokenStream,
+) -> Result<TokenStream> {
+	parse2::<DynamicParamAggregatedEnum>(item).map(ToTokens::into_token_stream)
+}
+
+/// A top `#[dynamic_params(..)]` attribute together with a mod.
+#[derive(derive_syn_parse::Parse)]
+pub struct DynamicParamModAttr {
+	params_mod: syn::ItemMod,
+	meta: DynamicParamModAttrMeta,
+}
+
+/// The inner meta of a `#[dynamic_params(..)]` attribute.
+#[derive(derive_syn_parse::Parse)]
+pub struct DynamicParamModAttrMeta {
+	name: syn::Ident,
+	_comma: Option<Token![,]>,
+	#[parse_if(_comma.is_some())]
+	params_pallet: Option<syn::Type>,
+}
+
+impl DynamicParamModAttr {
+	pub fn parse(attr: TokenStream, item: TokenStream) -> Result<Self> {
+		let params_mod = parse2(item)?;
+		let meta = parse2(attr)?;
+		Ok(Self { params_mod, meta })
+	}
+
+	pub fn inner_mods(&self) -> Vec<syn::ItemMod> {
+		self.params_mod.content.as_ref().map_or(Vec::new(), |(_, items)| {
+			items
+				.iter()
+				.filter_map(|i| match i {
+					syn::Item::Mod(m) => Some(m),
+					_ => None,
+				})
+				.cloned()
+				.collect()
+		})
+	}
+}
+
+impl ToTokens for DynamicParamModAttr {
+	fn to_tokens(&self, tokens: &mut TokenStream) {
+		let scrate = match crate_access() {
+			Ok(path) => path,
+			Err(err) => return tokens.extend(err),
+		};
+		let (mut params_mod, name) = (self.params_mod.clone(), &self.meta.name);
+		let dynam_params_ident = &params_mod.ident;
+
+		let mut quoted_enum = quote! {};
+		for m in self.inner_mods() {
+			let aggregate_name =
+				syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span());
+			let mod_name = &m.ident;
+
+			let mut attrs = m.attrs.clone();
+			attrs.retain(|attr| !attr.path().is_ident("dynamic_pallet_params"));
+			if let Err(err) = ensure_codec_index(&attrs, m.span()) {
+				tokens.extend(err.into_compile_error());
+				return
+			}
+
+			quoted_enum.extend(quote! {
+				#(#attrs)*
+				#aggregate_name(#dynam_params_ident::#mod_name::Parameters),
+			});
+		}
+
+		// Inject the outer args into the inner `#[dynamic_pallet_params(..)]` attribute.
+		if let Some(params_pallet) = &self.meta.params_pallet {
+			MacroInjectArgs { runtime_params: name.clone(), params_pallet: params_pallet.clone() }
+				.visit_item_mod_mut(&mut params_mod);
+		}
+
+		tokens.extend(quote! {
+			#params_mod
+
+			#[#scrate::dynamic_params::dynamic_aggregated_params_internal]
+			pub enum #name {
+				#quoted_enum
+			}
+		});
+	}
+}
+
+/// Ensure there is a `#[codec(index = ..)]` attribute.
+fn ensure_codec_index(attrs: &Vec<syn::Attribute>, span: Span) -> Result<()> {
+	let mut found = false;
+
+	for attr in attrs.iter() {
+		if attr.path().is_ident("codec") {
+			let meta: syn::ExprAssign = attr.parse_args()?;
+			if meta.left.to_token_stream().to_string() == "index" {
+				found = true;
+				break
+			}
+		}
+	}
+
+	if !found {
+		Err(syn::Error::new(span, "Missing explicit `#[codec(index = ..)]` attribute"))
+	} else {
+		Ok(())
+	}
+}
+
+/// Used to inject arguments into the inner `#[dynamic_pallet_params(..)]` attribute.
+///
+/// This allows the outer `#[dynamic_params(..)]` attribute to specify some arguments that dont need
+/// to be repeated every time.
+struct MacroInjectArgs {
+	runtime_params: syn::Ident,
+	params_pallet: syn::Type,
+}
+impl VisitMut for MacroInjectArgs {
+	fn visit_item_mod_mut(&mut self, item: &mut syn::ItemMod) {
+		// Check if the mod has a `#[dynamic_pallet_params(..)]` attribute.
+		let attr = item.attrs.iter_mut().find(|attr| attr.path().is_ident("dynamic_pallet_params"));
+
+		if let Some(attr) = attr {
+			match &attr.meta {
+				syn::Meta::Path(path) =>
+					assert_eq!(path.to_token_stream().to_string(), "dynamic_pallet_params"),
+				_ => (),
+			}
+
+			let runtime_params = &self.runtime_params;
+			let params_pallet = &self.params_pallet;
+
+			attr.meta = syn::parse2::<syn::Meta>(quote! {
+				dynamic_pallet_params(#runtime_params, #params_pallet)
+			})
+			.unwrap()
+			.into();
+		}
+
+		visit_mut::visit_item_mod_mut(self, item);
+	}
+}
+/// The helper attribute of a `#[dynamic_pallet_params(runtime_params, params_pallet)]`
+/// attribute.
+#[derive(derive_syn_parse::Parse)]
+pub struct DynamicPalletParamAttr {
+	inner_mod: syn::ItemMod,
+	meta: DynamicPalletParamAttrMeta,
+}
+
+/// The inner meta of a `#[dynamic_pallet_params(..)]` attribute.
+#[derive(derive_syn_parse::Parse)]
+pub struct DynamicPalletParamAttrMeta {
+	runtime_params: syn::Ident,
+	_comma: Token![,],
+	parameter_pallet: syn::Type,
+}
+
+impl DynamicPalletParamAttr {
+	pub fn parse(attr: TokenStream, item: TokenStream) -> Result<Self> {
+		Ok(Self { inner_mod: parse2(item)?, meta: parse2(attr)? })
+	}
+
+	pub fn statics(&self) -> Vec<syn::ItemStatic> {
+		self.inner_mod.content.as_ref().map_or(Vec::new(), |(_, items)| {
+			items
+				.iter()
+				.filter_map(|i| match i {
+					syn::Item::Static(s) => Some(s),
+					_ => None,
+				})
+				.cloned()
+				.collect()
+		})
+	}
+}
+
+impl ToTokens for DynamicPalletParamAttr {
+	fn to_tokens(&self, tokens: &mut TokenStream) {
+		let scrate = match crate_access() {
+			Ok(path) => path,
+			Err(err) => return tokens.extend(err),
+		};
+		let (params_mod, parameter_pallet, runtime_params) =
+			(&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params);
+
+		let aggregate_name =
+			syn::Ident::new(&params_mod.ident.to_string().to_class_case(), params_mod.ident.span());
+		let (mod_name, vis) = (&params_mod.ident, &params_mod.vis);
+		let statics = self.statics();
+
+		let (mut key_names, mut key_values, mut defaults, mut attrs, mut value_types): (
+			Vec<_>,
+			Vec<_>,
+			Vec<_>,
+			Vec<_>,
+			Vec<_>,
+		) = Default::default();
+
+		for s in statics.iter() {
+			if let Err(err) = ensure_codec_index(&s.attrs, s.span()) {
+				tokens.extend(err.into_compile_error());
+				return
+			}
+
+			key_names.push(&s.ident);
+			key_values.push(format_ident!("{}Value", &s.ident));
+			defaults.push(&s.expr);
+			attrs.push(&s.attrs);
+			value_types.push(&s.ty);
+		}
+
+		let key_ident = syn::Ident::new("ParametersKey", params_mod.ident.span());
+		let value_ident = syn::Ident::new("ParametersValue", params_mod.ident.span());
+		let runtime_key_ident = format_ident!("{}Key", runtime_params);
+		let runtime_value_ident = format_ident!("{}Value", runtime_params);
+
+		tokens.extend(quote! {
+			pub mod #mod_name {
+				use super::*;
+
+				#[doc(hidden)]
+				#[derive(
+					Clone,
+					PartialEq,
+					Eq,
+					#scrate::__private::codec::Encode,
+					#scrate::__private::codec::Decode,
+					#scrate::__private::codec::MaxEncodedLen,
+					#scrate::__private::RuntimeDebug,
+					#scrate::__private::scale_info::TypeInfo
+				)]
+				#vis enum Parameters {
+					#(
+						#(#attrs)*
+						#key_names(#key_names, Option<#value_types>),
+					)*
+				}
+
+				#[doc(hidden)]
+				#[derive(
+					Clone,
+					PartialEq,
+					Eq,
+					#scrate::__private::codec::Encode,
+					#scrate::__private::codec::Decode,
+					#scrate::__private::codec::MaxEncodedLen,
+					#scrate::__private::RuntimeDebug,
+					#scrate::__private::scale_info::TypeInfo
+				)]
+				#vis enum #key_ident {
+					#(
+						#(#attrs)*
+						#key_names(#key_names),
+					)*
+				}
+
+				#[doc(hidden)]
+				#[derive(
+					Clone,
+					PartialEq,
+					Eq,
+					#scrate::__private::codec::Encode,
+					#scrate::__private::codec::Decode,
+					#scrate::__private::codec::MaxEncodedLen,
+					#scrate::__private::RuntimeDebug,
+					#scrate::__private::scale_info::TypeInfo
+				)]
+				#vis enum #value_ident {
+					#(
+						#(#attrs)*
+						#key_names(#value_types),
+					)*
+				}
+
+				impl #scrate::traits::dynamic_params::AggregratedKeyValue for Parameters {
+					type Key = #key_ident;
+					type Value = #value_ident;
+
+					fn into_parts(self) -> (Self::Key, Option<Self::Value>) {
+						match self {
+							#(
+								Parameters::#key_names(key, value) => {
+									(#key_ident::#key_names(key), value.map(#value_ident::#key_names))
+								},
+							)*
+						}
+					}
+				}
+
+				#(
+					#[doc(hidden)]
+					#[derive(
+						Clone,
+						PartialEq,
+						Eq,
+						#scrate::__private::codec::Encode,
+						#scrate::__private::codec::Decode,
+						#scrate::__private::codec::MaxEncodedLen,
+						#scrate::__private::RuntimeDebug,
+						#scrate::__private::scale_info::TypeInfo
+					)]
+					#vis struct #key_names;
+
+					impl #scrate::__private::Get<#value_types> for #key_names {
+						fn get() -> #value_types {
+							match
+								<#parameter_pallet as
+									#scrate::storage::StorageMap<#runtime_key_ident, #runtime_value_ident>
+								>::get(#runtime_key_ident::#aggregate_name(#key_ident::#key_names(#key_names)))
+							{
+								Some(#runtime_value_ident::#aggregate_name(
+									#value_ident::#key_names(inner))) => inner,
+								Some(_) => {
+									#scrate::defensive!("Unexpected value type at key - returning default");
+									#defaults
+								},
+								None => #defaults,
+							}
+						}
+					}
+
+					impl #scrate::traits::dynamic_params::Key for #key_names {
+						type Value = #value_types;
+						type WrappedValue = #key_values;
+					}
+
+					impl From<#key_names> for #key_ident {
+						fn from(key: #key_names) -> Self {
+							#key_ident::#key_names(key)
+						}
+					}
+
+					impl TryFrom<#key_ident> for #key_names {
+						type Error = ();
+
+						fn try_from(key: #key_ident) -> Result<Self, Self::Error> {
+							match key {
+								#key_ident::#key_names(key) => Ok(key),
+								_ => Err(()),
+							}
+						}
+					}
+
+					#[doc(hidden)]
+					#[derive(
+						Clone,
+						PartialEq,
+						Eq,
+						#scrate::sp_runtime::RuntimeDebug,
+					)]
+					#vis struct #key_values(pub #value_types);
+
+					impl From<#key_values> for #value_ident {
+						fn from(value: #key_values) -> Self {
+							#value_ident::#key_names(value.0)
+						}
+					}
+
+					impl From<(#key_names, #value_types)> for Parameters {
+						fn from((key, value): (#key_names, #value_types)) -> Self {
+							Parameters::#key_names(key, Some(value))
+						}
+					}
+
+					impl From<#key_names> for Parameters {
+						fn from(key: #key_names) -> Self {
+							Parameters::#key_names(key, None)
+						}
+					}
+
+					impl TryFrom<#value_ident> for #key_values {
+						type Error = ();
+
+						fn try_from(value: #value_ident) -> Result<Self, Self::Error> {
+							match value {
+								#value_ident::#key_names(value) => Ok(#key_values(value)),
+								_ => Err(()),
+							}
+						}
+					}
+
+					impl From<#key_values> for #value_types {
+						fn from(value: #key_values) -> Self {
+							value.0
+						}
+					}
+				)*
+			}
+		});
+	}
+}
+
+#[derive(derive_syn_parse::Parse)]
+pub struct DynamicParamAggregatedEnum {
+	aggregated_enum: syn::ItemEnum,
+}
+
+impl ToTokens for DynamicParamAggregatedEnum {
+	fn to_tokens(&self, tokens: &mut TokenStream) {
+		let scrate = match crate_access() {
+			Ok(path) => path,
+			Err(err) => return tokens.extend(err),
+		};
+		let params_enum = &self.aggregated_enum;
+		let (name, vis) = (&params_enum.ident, &params_enum.vis);
+
+		let (mut indices, mut param_names, mut param_types): (Vec<_>, Vec<_>, Vec<_>) =
+			Default::default();
+		let mut attributes = Vec::new();
+		for (i, variant) in params_enum.variants.iter().enumerate() {
+			indices.push(i);
+			param_names.push(&variant.ident);
+			attributes.push(&variant.attrs);
+
+			param_types.push(match &variant.fields {
+				syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
+				_ => {
+					*tokens = quote! { compile_error!("Only unnamed enum variants with one inner item are supported") };
+					return
+				},
+			});
+		}
+
+		let params_key_ident = format_ident!("{}Key", params_enum.ident);
+		let params_value_ident = format_ident!("{}Value", params_enum.ident);
+
+		tokens.extend(quote! {
+			#[doc(hidden)]
+			#[derive(
+				Clone,
+				PartialEq,
+				Eq,
+				#scrate::__private::codec::Encode,
+				#scrate::__private::codec::Decode,
+				#scrate::__private::codec::MaxEncodedLen,
+				#scrate::sp_runtime::RuntimeDebug,
+				#scrate::__private::scale_info::TypeInfo
+			)]
+			#vis enum #name {
+				#(
+					//#[codec(index = #indices)]
+					#(#attributes)*
+					#param_names(#param_types),
+				)*
+			}
+
+			#[doc(hidden)]
+			#[derive(
+				Clone,
+				PartialEq,
+				Eq,
+				#scrate::__private::codec::Encode,
+				#scrate::__private::codec::Decode,
+				#scrate::__private::codec::MaxEncodedLen,
+				#scrate::sp_runtime::RuntimeDebug,
+				#scrate::__private::scale_info::TypeInfo
+			)]
+			#vis enum #params_key_ident {
+				#(
+					#(#attributes)*
+					#param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key),
+				)*
+			}
+
+			#[doc(hidden)]
+			#[derive(
+				Clone,
+				PartialEq,
+				Eq,
+				#scrate::__private::codec::Encode,
+				#scrate::__private::codec::Decode,
+				#scrate::__private::codec::MaxEncodedLen,
+				#scrate::sp_runtime::RuntimeDebug,
+				#scrate::__private::scale_info::TypeInfo
+			)]
+			#vis enum #params_value_ident {
+				#(
+					#(#attributes)*
+					#param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value),
+				)*
+			}
+
+			impl #scrate::traits::dynamic_params::AggregratedKeyValue for #name {
+				type Key = #params_key_ident;
+				type Value = #params_value_ident;
+
+				fn into_parts(self) -> (Self::Key, Option<Self::Value>) {
+					match self {
+						#(
+							#name::#param_names(parameter) => {
+								let (key, value) = parameter.into_parts();
+								(#params_key_ident::#param_names(key), value.map(#params_value_ident::#param_names))
+							},
+						)*
+					}
+				}
+			}
+
+			#(
+				impl ::core::convert::From<<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key> for #params_key_ident {
+					fn from(key: <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key) -> Self {
+						#params_key_ident::#param_names(key)
+					}
+				}
+
+				impl ::core::convert::TryFrom<#params_value_ident> for <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value {
+					type Error = ();
+
+					fn try_from(value: #params_value_ident) -> Result<Self, Self::Error> {
+						match value {
+							#params_value_ident::#param_names(value) => Ok(value),
+							_ => Err(()),
+						}
+					}
+				}
+			)*
+		});
+	}
+}
+
+/// Get access to the current crate and convert the error to a compile error.
+fn crate_access() -> core::result::Result<syn::Path, TokenStream> {
+	generate_access_from_frame_or_crate("frame-support").map_err(|e| e.to_compile_error())
+}
diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index 34e68966b82333f56c91c0c32b7573f2fd8284e8..20b8d74310f3e4aca7a5cae61fe3e5d72ec0626c 100644
--- a/substrate/frame/support/procedural/src/lib.rs
+++ b/substrate/frame/support/procedural/src/lib.rs
@@ -18,12 +18,14 @@
 //! Proc macro of Support code for the runtime.
 
 #![recursion_limit = "512"]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 mod benchmark;
 mod construct_runtime;
 mod crate_version;
 mod derive_impl;
 mod dummy_part_checker;
+mod dynamic_params;
 mod key_prefix;
 mod match_and_insert;
 mod no_bound;
@@ -890,12 +892,13 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream {
 		item.ident != "RuntimeOrigin" &&
 		item.ident != "RuntimeHoldReason" &&
 		item.ident != "RuntimeFreezeReason" &&
+		item.ident != "RuntimeParameters" &&
 		item.ident != "PalletInfo"
 	{
 		return syn::Error::new_spanned(
 			item,
 			"`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \
-			`RuntimeTask`, `RuntimeOrigin` or `PalletInfo`",
+			`RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`",
 		)
 		.to_compile_error()
 		.into()
@@ -1685,3 +1688,52 @@ pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream {
 	}
 	.into()
 }
+
+/// Mark a module that contains dynamic parameters.
+///
+/// See the `pallet_parameters` for a full example.
+///
+/// # Arguments
+///
+/// The macro accepts two positional arguments, of which the second is optional.
+///
+/// ## Aggregated Enum Name
+///
+/// This sets the name that the aggregated Key-Value enum will be named after. Common names would be
+/// `RuntimeParameters`, akin to `RuntimeCall`, `RuntimeOrigin` etc. There is no default value for
+/// this argument.
+///
+/// ## Parameter Storage Backend
+///
+/// The second argument provides access to the storage of the parameters. It can either be set on
+/// on this attribute, or on the inner ones. If set on both, the inner one takes precedence.
+#[proc_macro_attribute]
+pub fn dynamic_params(attrs: TokenStream, input: TokenStream) -> TokenStream {
+	dynamic_params::dynamic_params(attrs.into(), input.into())
+		.unwrap_or_else(|r| r.into_compile_error())
+		.into()
+}
+
+/// Define a module inside a [`macro@dynamic_params`] module that contains dynamic parameters.
+///
+/// See the `pallet_parameters` for a full example.
+///
+/// # Argument
+///
+/// This attribute takes one optional argument. The argument can either be put here or on the
+/// surrounding `#[dynamic_params]` attribute. If set on both, the inner one takes precedence.
+#[proc_macro_attribute]
+pub fn dynamic_pallet_params(attrs: TokenStream, input: TokenStream) -> TokenStream {
+	dynamic_params::dynamic_pallet_params(attrs.into(), input.into())
+		.unwrap_or_else(|r| r.into_compile_error())
+		.into()
+}
+
+/// Used internally by [`dynamic_params`].
+#[doc(hidden)]
+#[proc_macro_attribute]
+pub fn dynamic_aggregated_params_internal(attrs: TokenStream, input: TokenStream) -> TokenStream {
+	dynamic_params::dynamic_aggregated_params_internal(attrs.into(), input.into())
+		.unwrap_or_else(|r| r.into_compile_error())
+		.into()
+}
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index 0f6cf05959f5113d28a250b53deec1cc7930c67d..8935acf4e2bb64dc0087c492e2174741cbb40d80 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -44,7 +44,7 @@ pub mod __private {
 	pub use paste;
 	pub use scale_info;
 	pub use serde;
-	pub use sp_core::{OpaqueMetadata, Void};
+	pub use sp_core::{Get, OpaqueMetadata, Void};
 	pub use sp_crypto_hashing_proc_macro;
 	pub use sp_inherents;
 	#[cfg(feature = "std")]
@@ -175,6 +175,14 @@ pub use frame_support_procedural::storage_alias;
 
 pub use frame_support_procedural::derive_impl;
 
+/// Experimental macros for defining dynamic params that can be used in pallet configs.
+#[cfg(feature = "experimental")]
+pub mod dynamic_params {
+	pub use frame_support_procedural::{
+		dynamic_aggregated_params_internal, dynamic_pallet_params, dynamic_params,
+	};
+}
+
 /// Create new implementations of the [`Get`](crate::traits::Get) trait.
 ///
 /// The so-called parameter type can be created in four different ways:
diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs
index 2a42fca76b3c57623c74e3724b342eadf57737cd..3d0429f71b11d6c74f87ef33521634f46b0d591c 100644
--- a/substrate/frame/support/src/traits.rs
+++ b/substrate/frame/support/src/traits.rs
@@ -124,6 +124,8 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify};
 mod tx_pause;
 pub use tx_pause::{TransactionPause, TransactionPauseError};
 
+pub mod dynamic_params;
+
 pub mod tasks;
 pub use tasks::Task;
 
diff --git a/substrate/frame/support/src/traits/dynamic_params.rs b/substrate/frame/support/src/traits/dynamic_params.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8881df04141cc13a97f79bc6df3ed41b8db0323d
--- /dev/null
+++ b/substrate/frame/support/src/traits/dynamic_params.rs
@@ -0,0 +1,153 @@
+// 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.
+
+//! Types and traits for dynamic parameters.
+//!
+//! Can be used by 3rd party macros to define dynamic parameters that are compatible with the the
+//! `parameters` pallet.
+
+use codec::MaxEncodedLen;
+use frame_support::Parameter;
+
+/// A dynamic parameter store across an aggregated KV type.
+pub trait RuntimeParameterStore {
+	type AggregratedKeyValue: AggregratedKeyValue;
+
+	/// Get the value of a parametrized key.
+	///
+	/// Should return `None` if no explicit value was set instead of a default.
+	fn get<KV, K>(key: K) -> Option<K::Value>
+	where
+		KV: AggregratedKeyValue,
+		K: Key + Into<<KV as AggregratedKeyValue>::Key>,
+		<KV as AggregratedKeyValue>::Key: IntoKey<
+			<<Self as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Key,
+		>,
+		<<Self as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Value:
+			TryIntoKey<<KV as AggregratedKeyValue>::Value>,
+		<KV as AggregratedKeyValue>::Value: TryInto<K::WrappedValue>;
+}
+
+/// A dynamic parameter store across a concrete KV type.
+pub trait ParameterStore<KV: AggregratedKeyValue> {
+	/// Get the value of a parametrized key.
+	fn get<K>(key: K) -> Option<K::Value>
+	where
+		K: Key + Into<<KV as AggregratedKeyValue>::Key>,
+		<KV as AggregratedKeyValue>::Value: TryInto<K::WrappedValue>;
+}
+
+/// Key of a dynamic parameter.
+pub trait Key {
+	/// The value that the key is parametrized with.
+	type Value;
+
+	/// An opaque representation of `Self::Value`.
+	type WrappedValue: Into<Self::Value>;
+}
+
+/// The aggregated key-value type of a dynamic parameter store.
+pub trait AggregratedKeyValue: Parameter {
+	/// The aggregated key type.
+	type Key: Parameter + MaxEncodedLen;
+
+	/// The aggregated value type.
+	type Value: Parameter + MaxEncodedLen;
+
+	/// Split the aggregated key-value type into its parts.
+	fn into_parts(self) -> (Self::Key, Option<Self::Value>);
+}
+
+impl AggregratedKeyValue for () {
+	type Key = ();
+	type Value = ();
+
+	fn into_parts(self) -> (Self::Key, Option<Self::Value>) {
+		((), None)
+	}
+}
+
+/// Allows to create a `ParameterStore` from a `RuntimeParameterStore`.
+///
+/// This concretization is useful when configuring pallets, since a pallet will require a parameter
+/// store for its own KV type and not the aggregated runtime-wide KV type.
+pub struct ParameterStoreAdapter<PS, KV>(sp_std::marker::PhantomData<(PS, KV)>);
+
+impl<PS, KV> ParameterStore<KV> for ParameterStoreAdapter<PS, KV>
+where
+	PS: RuntimeParameterStore,
+	KV: AggregratedKeyValue,
+	<KV as AggregratedKeyValue>::Key:
+		IntoKey<<<PS as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Key>,
+	<KV as AggregratedKeyValue>::Value: TryFromKey<
+		<<PS as RuntimeParameterStore>::AggregratedKeyValue as AggregratedKeyValue>::Value,
+	>,
+{
+	fn get<K>(key: K) -> Option<K::Value>
+	where
+		K: Key + Into<<KV as AggregratedKeyValue>::Key>,
+		<KV as AggregratedKeyValue>::Value: TryInto<K::WrappedValue>,
+	{
+		PS::get::<KV, K>(key)
+	}
+}
+
+// workaround for rust bug https://github.com/rust-lang/rust/issues/51445
+mod workaround {
+	pub trait FromKey<T>: Sized {
+		#[must_use]
+		fn from_key(value: T) -> Self;
+	}
+
+	pub trait IntoKey<T>: Sized {
+		#[must_use]
+		fn into_key(self) -> T;
+	}
+
+	impl<T, U> IntoKey<U> for T
+	where
+		U: FromKey<T>,
+	{
+		fn into_key(self) -> U {
+			U::from_key(self)
+		}
+	}
+
+	pub trait TryIntoKey<T>: Sized {
+		type Error;
+
+		fn try_into_key(self) -> Result<T, Self::Error>;
+	}
+
+	pub trait TryFromKey<T>: Sized {
+		type Error;
+
+		fn try_from_key(value: T) -> Result<Self, Self::Error>;
+	}
+
+	impl<T, U> TryIntoKey<U> for T
+	where
+		U: TryFromKey<T>,
+	{
+		type Error = U::Error;
+
+		fn try_into_key(self) -> Result<U, U::Error> {
+			U::try_from_key(self)
+		}
+	}
+}
+pub use workaround::*;
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 cda20288984ae535c0755fd1e6814384d124c515..c7159b34afb3d22737fb5d8ed6662027f04a3bd6 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` or `PalletInfo`
+error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`
   --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5
    |
 32 |     type RuntimeInfo = ();