diff --git a/prdoc/pr_4075.prdoc b/prdoc/pr_4075.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..05e54073b6c7b5562b6a8880185d3e38b2526ab8
--- /dev/null
+++ b/prdoc/pr_4075.prdoc
@@ -0,0 +1,19 @@
+title: Adds ability to trigger tasks via unsigned transactions
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      This PR updates the `validate_unsigned` hook for `frame_system` to allow valid tasks 
+      to be submitted as unsigned transactions. It also updates the task example to be able to 
+      submit such transactions via an off-chain worker.
+
+      Note that `is_valid` call on a task MUST be cheap with minimal to no storage reads. 
+      Else, it can make the blockchain vulnerable to DoS attacks.
+
+      Further, these tasks will be executed in a random order.
+
+crates:
+  - name: frame-system
+    bump: patch
+  - name: pallet-example-tasks
+    bump: minor
diff --git a/substrate/frame/examples/tasks/src/lib.rs b/substrate/frame/examples/tasks/src/lib.rs
index c65d8095bcf6a2c2295bb87b11e4041fbab88173..1908a235ba15868c41f1fe62c3edd43532e329cf 100644
--- a/substrate/frame/examples/tasks/src/lib.rs
+++ b/substrate/frame/examples/tasks/src/lib.rs
@@ -19,6 +19,9 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use frame_support::dispatch::DispatchResult;
+use frame_system::offchain::SendTransactionTypes;
+#[cfg(feature = "experimental")]
+use frame_system::offchain::SubmitTransaction;
 // Re-export pallet items so that they can be accessed from the crate namespace.
 pub use pallet::*;
 
@@ -31,10 +34,14 @@ mod benchmarking;
 pub mod weights;
 pub use weights::*;
 
+#[cfg(feature = "experimental")]
+const LOG_TARGET: &str = "pallet-example-tasks";
+
 #[frame_support::pallet(dev_mode)]
 pub mod pallet {
 	use super::*;
 	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::*;
 
 	#[pallet::error]
 	pub enum Error<T> {
@@ -59,9 +66,36 @@ pub mod pallet {
 		}
 	}
 
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		#[cfg(feature = "experimental")]
+		fn offchain_worker(_block_number: BlockNumberFor<T>) {
+			if let Some(key) = Numbers::<T>::iter_keys().next() {
+				// Create a valid task
+				let task = Task::<T>::AddNumberIntoTotal { i: key };
+				let runtime_task = <T as Config>::RuntimeTask::from(task);
+				let call = frame_system::Call::<T>::do_task { task: runtime_task.into() };
+
+				// Submit the task as an unsigned transaction
+				let res =
+					SubmitTransaction::<T, frame_system::Call<T>>::submit_unsigned_transaction(
+						call.into(),
+					);
+				match res {
+					Ok(_) => log::info!(target: LOG_TARGET, "Submitted the task."),
+					Err(e) => log::error!(target: LOG_TARGET, "Error submitting task: {:?}", e),
+				}
+			}
+		}
+	}
+
 	#[pallet::config]
-	pub trait Config: frame_system::Config {
-		type RuntimeTask: frame_support::traits::Task;
+	pub trait Config:
+		SendTransactionTypes<frame_system::Call<Self>> + frame_system::Config
+	{
+		type RuntimeTask: frame_support::traits::Task
+			+ IsType<<Self as frame_system::Config>::RuntimeTask>
+			+ From<Task<Self>>;
 		type WeightInfo: WeightInfo;
 	}
 
diff --git a/substrate/frame/examples/tasks/src/mock.rs b/substrate/frame/examples/tasks/src/mock.rs
index 76ac9e76bff8a79dbedfd6f6a4746a452a685eba..33912bb5269c5ba58cb9f5256cceb26d681577e5 100644
--- a/substrate/frame/examples/tasks/src/mock.rs
+++ b/substrate/frame/examples/tasks/src/mock.rs
@@ -20,6 +20,7 @@
 
 use crate::{self as tasks_example};
 use frame_support::derive_impl;
+use sp_runtime::testing::TestXt;
 
 pub type AccountId = u32;
 pub type Balance = u32;
@@ -32,12 +33,32 @@ frame_support::construct_runtime!(
 	}
 );
 
+pub type Extrinsic = TestXt<RuntimeCall, ()>;
+
 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for Runtime {
 	type Block = Block;
 }
 
+impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
+where
+	RuntimeCall: From<LocalCall>,
+{
+	type OverarchingCall = RuntimeCall;
+	type Extrinsic = Extrinsic;
+}
+
 impl tasks_example::Config for Runtime {
 	type RuntimeTask = RuntimeTask;
 	type WeightInfo = ();
 }
+
+pub fn advance_to(b: u64) {
+	#[cfg(feature = "experimental")]
+	use frame_support::traits::Hooks;
+	while System::block_number() < b {
+		System::set_block_number(System::block_number() + 1);
+		#[cfg(feature = "experimental")]
+		TasksExample::offchain_worker(System::block_number());
+	}
+}
diff --git a/substrate/frame/examples/tasks/src/tests.rs b/substrate/frame/examples/tasks/src/tests.rs
index fc3c69f4aef95601c2a96faf1c939c27ee419fd4..6c8acb0194bde6cba27e4e2706f78676468c0057 100644
--- a/substrate/frame/examples/tasks/src/tests.rs
+++ b/substrate/frame/examples/tasks/src/tests.rs
@@ -19,7 +19,11 @@
 #![cfg(test)]
 
 use crate::{mock::*, Numbers};
+#[cfg(feature = "experimental")]
+use codec::Decode;
 use frame_support::traits::Task;
+#[cfg(feature = "experimental")]
+use sp_core::offchain::{testing, OffchainWorkerExt, TransactionPoolExt};
 use sp_runtime::BuildStorage;
 
 #[cfg(feature = "experimental")]
@@ -130,3 +134,29 @@ fn task_execution_fails_for_invalid_task() {
 		);
 	});
 }
+
+#[cfg(feature = "experimental")]
+#[test]
+fn task_with_offchain_worker() {
+	let (offchain, _offchain_state) = testing::TestOffchainExt::new();
+	let (pool, pool_state) = testing::TestTransactionPoolExt::new();
+
+	let mut t = sp_io::TestExternalities::default();
+	t.register_extension(OffchainWorkerExt::new(offchain));
+	t.register_extension(TransactionPoolExt::new(pool));
+
+	t.execute_with(|| {
+		advance_to(1);
+		assert!(pool_state.read().transactions.is_empty());
+
+		Numbers::<Runtime>::insert(0, 10);
+		assert_eq!(crate::Total::<Runtime>::get(), (0, 0));
+
+		advance_to(2);
+
+		let tx = pool_state.write().transactions.pop().unwrap();
+		assert!(pool_state.read().transactions.is_empty());
+		let tx = Extrinsic::decode(&mut &*tx).unwrap();
+		assert_eq!(tx.signature, None);
+	});
+}
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index 984a7f7537fe892b64d2ba864225960cb0dadc58..7eddea1259d7d040e57084232feb53eb2b6b1270 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -2465,6 +2465,9 @@ pub mod pallet_macros {
 	/// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and
 	/// submit such tasks via an extrinsic defined in `frame_system` called `do_task`.
 	///
+	/// When submitted as unsigned transactions (for example via an off-chain workder), note
+	/// that the tasks will be executed in a random order.
+	///
 	/// ## Example
 	#[doc = docify::embed!("src/tests/tasks.rs", tasks_example)]
 	/// Now, this can be executed as follows:
diff --git a/substrate/frame/support/src/traits/tasks.rs b/substrate/frame/support/src/traits/tasks.rs
index 24f3430cf50b5a23175c4c586c1642da580cf862..42b837e55970def3c4d99f3c5f3cc9ca5dacaaf3 100644
--- a/substrate/frame/support/src/traits/tasks.rs
+++ b/substrate/frame/support/src/traits/tasks.rs
@@ -46,6 +46,10 @@ pub trait Task: Sized + FullCodec + TypeInfo + Clone + Debug + PartialEq + Eq {
 	fn iter() -> Self::Enumeration;
 
 	/// Checks if a particular instance of this `Task` variant is a valid piece of work.
+	///
+	/// This is used to validate tasks for unsigned execution. Hence, it MUST be cheap
+	/// with minimal to no storage reads. Else, it can make the blockchain vulnerable
+	/// to DoS attacks.
 	fn is_valid(&self) -> bool;
 
 	/// Performs the work for this particular `Task` variant.
diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs
index 184f27b61ed2a78fa298e4883db6d4eb39d8a184..30df4dcfd43e9058cb3acc1a8f9b1863bf925e11 100644
--- a/substrate/frame/system/src/lib.rs
+++ b/substrate/frame/system/src/lib.rs
@@ -741,9 +741,7 @@ pub mod pallet {
 		#[cfg(feature = "experimental")]
 		#[pallet::call_index(8)]
 		#[pallet::weight(task.weight())]
-		pub fn do_task(origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
-			ensure_signed(origin)?;
-
+		pub fn do_task(_origin: OriginFor<T>, task: T::RuntimeTask) -> DispatchResultWithPostInfo {
 			if !task.is_valid() {
 				return Err(Error::<T>::InvalidTask.into())
 			}
@@ -1032,6 +1030,18 @@ pub mod pallet {
 					})
 				}
 			}
+			#[cfg(feature = "experimental")]
+			if let Call::do_task { ref task } = call {
+				if task.is_valid() {
+					return Ok(ValidTransaction {
+						priority: u64::max_value(),
+						requires: Vec::new(),
+						provides: vec![T::Hashing::hash_of(&task.encode()).as_ref().to_vec()],
+						longevity: TransactionLongevity::max_value(),
+						propagate: true,
+					})
+				}
+			}
 			Err(InvalidTransaction::Call.into())
 		}
 	}