diff --git a/Cargo.toml b/Cargo.toml
index 149f7cc6098409f663d072dd250d9e2d87aa368b..a141138bcee10a9c213f350aeb83e518931ecdae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,7 +48,6 @@ libp2p = { version = "0.52" }
 subxt = { version = "0.35.3", features = ["substrate-compat"] }
 subxt-signer = { version = "0.35.3", features = ["subxt"] }
 tracing = "0.1.35"
-pjs-rs = "0.1.2"
 kube = "0.87.1"
 k8s-openapi = "0.20.0"
 tar = "0.4"
diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml
index 40fcf727ade5a4379101de875ccb9cf8630bb255..a11ef27468e9ed0a622b9302566c29d7a50420b9 100644
--- a/crates/examples/Cargo.toml
+++ b/crates/examples/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-zombienet-sdk = {workspace = true }
+zombienet-sdk = {workspace = true, features = ["pjs"]}
 tokio = { workspace = true }
 futures = { workspace = true }
 subxt = { workspace = true }
diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml
index c674ab31869091c5e444253565ef8a8d397cff7a..d1bb614f5fdf4357dd724f108917d90d10b7a981 100644
--- a/crates/orchestrator/Cargo.toml
+++ b/crates/orchestrator/Cargo.toml
@@ -28,7 +28,7 @@ subxt = { workspace = true }
 subxt-signer = { workspace = true }
 reqwest = { workspace = true }
 tracing = { workspace = true }
-pjs-rs = { workspace = true }
+pjs-rs = { version = "0.1.2", optional = true }
 uuid = { workspace = true }
 
 # Zombienet deps
@@ -36,3 +36,6 @@ configuration = { workspace = true }
 support = { workspace = true }
 provider = { workspace = true }
 prom-metrics-parser = { workspace = true }
+
+[features]
+pjs = ["dep:pjs-rs"]
diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs
index 03e63d1156c5b85e4a83e24c2f35ba6bca9961fc..999d4ce78c6abc0ac772f8841f36fc1312c92018 100644
--- a/crates/orchestrator/src/lib.rs
+++ b/crates/orchestrator/src/lib.rs
@@ -6,6 +6,8 @@ mod generators;
 pub mod network;
 mod network_helper;
 mod network_spec;
+#[cfg(feature = "pjs")]
+pub mod pjs_helper;
 mod shared;
 mod spawner;
 
@@ -494,7 +496,8 @@ pub enum ZombieRole {
 
 // re-export
 pub use network::{AddCollatorOptions, AddNodeOptions};
-pub use shared::types::PjsResult;
+#[cfg(feature = "pjs")]
+pub use pjs_helper::PjsResult;
 
 #[cfg(test)]
 mod tests {
diff --git a/crates/orchestrator/src/network/node.rs b/crates/orchestrator/src/network/node.rs
index b00666f008665262f67379373ee60975775dcfb2..a38cc11d30afe2eee3f8339fb98a0e94e542b919 100644
--- a/crates/orchestrator/src/network/node.rs
+++ b/crates/orchestrator/src/network/node.rs
@@ -1,15 +1,14 @@
-use std::{path::Path, sync::Arc, time::Duration};
+use std::{sync::Arc, time::Duration};
 
 use anyhow::anyhow;
-use pjs_rs::ReturnValue;
 use prom_metrics_parser::MetricMap;
 use provider::DynNode;
-use serde_json::json;
 use subxt::{backend::rpc::RpcClient, OnlineClient};
 use tokio::sync::RwLock;
-use tracing::trace;
 
-use crate::{network_spec::node::NodeSpec, shared::types::PjsResult};
+use crate::network_spec::node::NodeSpec;
+#[cfg(feature = "pjs")]
+use crate::pjs_helper::{pjs_build_template, pjs_exec, PjsResult, ReturnValue};
 
 #[derive(Clone)]
 pub struct NetworkNode {
@@ -81,50 +80,6 @@ impl NetworkNode {
         }
     }
 
-    /// Execute js/ts code inside [pjs_rs] custom runtime.
-    ///
-    /// The code will be run in a wrapper similar to the `javascript` developer tab
-    /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow
-    /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value].
-    pub async fn pjs(
-        &self,
-        code: impl AsRef<str>,
-        args: Vec<serde_json::Value>,
-        user_types: Option<serde_json::Value>,
-    ) -> Result<PjsResult, anyhow::Error> {
-        let code = pjs_build_template(self.ws_uri(), code.as_ref(), args, user_types);
-        trace!("Code to execute: {code}");
-        let value = match pjs_inner(code)? {
-            ReturnValue::Deserialized(val) => Ok(val),
-            ReturnValue::CantDeserialize(msg) => Err(msg),
-        };
-
-        Ok(value)
-    }
-
-    /// Execute js/ts file  inside [pjs_rs] custom runtime.
-    ///
-    /// The content of the file will be run in a wrapper similar to the `javascript` developer tab
-    /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow
-    /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value].
-    pub async fn pjs_file(
-        &self,
-        file: impl AsRef<Path>,
-        args: Vec<serde_json::Value>,
-        user_types: Option<serde_json::Value>,
-    ) -> Result<PjsResult, anyhow::Error> {
-        let content = std::fs::read_to_string(file)?;
-        let code = pjs_build_template(self.ws_uri(), content.as_ref(), args, user_types);
-        trace!("Code to execute: {code}");
-
-        let value = match pjs_inner(code)? {
-            ReturnValue::Deserialized(val) => Ok(val),
-            ReturnValue::CantDeserialize(msg) => Err(msg),
-        };
-
-        Ok(value)
-    }
-
     /// Resume the node, this is implemented by resuming the
     /// actual process (e.g polkadot) with sending `SIGCONT` signal
     pub async fn resume(&self) -> Result<(), anyhow::Error> {
@@ -192,6 +147,52 @@ impl NetworkNode {
         Ok(self.inner.logs().await?)
     }
 
+    #[cfg(feature = "pjs")]
+    /// Execute js/ts code inside [pjs_rs] custom runtime.
+    ///
+    /// The code will be run in a wrapper similar to the `javascript` developer tab
+    /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow
+    /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value].
+    pub async fn pjs(
+        &self,
+        code: impl AsRef<str>,
+        args: Vec<serde_json::Value>,
+        user_types: Option<serde_json::Value>,
+    ) -> Result<PjsResult, anyhow::Error> {
+        let code = pjs_build_template(self.ws_uri(), code.as_ref(), args, user_types);
+        tracing::trace!("Code to execute: {code}");
+        let value = match pjs_exec(code)? {
+            ReturnValue::Deserialized(val) => Ok(val),
+            ReturnValue::CantDeserialize(msg) => Err(msg),
+        };
+
+        Ok(value)
+    }
+
+    #[cfg(feature = "pjs")]
+    /// Execute js/ts file  inside [pjs_rs] custom runtime.
+    ///
+    /// The content of the file will be run in a wrapper similar to the `javascript` developer tab
+    /// of polkadot.js apps. The returning value is represented as [PjsResult] enum, to allow
+    /// to communicate that the execution was successful but the returning value can be deserialized as [serde_json::Value].
+    pub async fn pjs_file(
+        &self,
+        file: impl AsRef<std::path::Path>,
+        args: Vec<serde_json::Value>,
+        user_types: Option<serde_json::Value>,
+    ) -> Result<PjsResult, anyhow::Error> {
+        let content = std::fs::read_to_string(file)?;
+        let code = pjs_build_template(self.ws_uri(), content.as_ref(), args, user_types);
+        tracing::trace!("Code to execute: {code}");
+
+        let value = match pjs_exec(code)? {
+            ReturnValue::Deserialized(val) => Ok(val),
+            ReturnValue::CantDeserialize(msg) => Err(msg),
+        };
+
+        Ok(value)
+    }
+
     async fn fetch_metrics(&self) -> Result<(), anyhow::Error> {
         let response = reqwest::get(&self.prometheus_uri).await?;
         let metrics = prom_metrics_parser::parse(&response.text().await?)?;
@@ -227,64 +228,3 @@ impl std::fmt::Debug for NetworkNode {
             .finish()
     }
 }
-
-// Helper methods
-
-fn pjs_build_template(
-    ws_uri: &str,
-    content: &str,
-    args: Vec<serde_json::Value>,
-    user_types: Option<serde_json::Value>,
-) -> String {
-    let types = if let Some(user_types) = user_types {
-        if let Some(types) = user_types.pointer("/types") {
-            // if the user_types includes the `types` key use the inner value
-            types.clone()
-        } else {
-            user_types.clone()
-        }
-    } else {
-        // No custom types, just an emtpy json
-        json!({})
-    };
-
-    let tmpl = format!(
-        r#"
-    const {{ util, utilCrypto, keyring, types }} = pjs;
-    ( async () => {{
-        const api = await pjs.api.ApiPromise.create({{
-            provider: new pjs.api.WsProvider('{}'),
-            types: {}
-         }});
-        const _run = async (api, hashing, keyring, types, util, arguments) => {{
-            {}
-        }};
-        return await _run(api, utilCrypto, keyring, types, util, {});
-    }})()
-    "#,
-        ws_uri,
-        types,
-        content,
-        json!(args),
-    );
-    trace!(tmpl = tmpl, "code to execute");
-    tmpl
-}
-
-// Since pjs-rs run a custom javascript runtime (using deno_core) we need to
-// execute in an isolated thread.
-fn pjs_inner(code: String) -> Result<ReturnValue, anyhow::Error> {
-    let rt = tokio::runtime::Builder::new_current_thread()
-        .enable_all()
-        .build()?;
-
-    std::thread::spawn(move || {
-        rt.block_on(async move {
-            let value = pjs_rs::run_ts_code(code, None).await;
-            trace!("ts_code return: {:?}", value);
-            value
-        })
-    })
-    .join()
-    .map_err(|_| anyhow!("[pjs] Thread panicked"))?
-}
diff --git a/crates/orchestrator/src/pjs_helper.rs b/crates/orchestrator/src/pjs_helper.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ffe7d82db89ae3aa60e13dd1f09a32f4cad1e56a
--- /dev/null
+++ b/crates/orchestrator/src/pjs_helper.rs
@@ -0,0 +1,72 @@
+use anyhow::anyhow;
+pub use pjs_rs::ReturnValue;
+use serde_json::json;
+use tracing::trace;
+
+pub fn pjs_build_template(
+    ws_uri: &str,
+    content: &str,
+    args: Vec<serde_json::Value>,
+    user_types: Option<serde_json::Value>,
+) -> String {
+    let types = if let Some(user_types) = user_types {
+        if let Some(types) = user_types.pointer("/types") {
+            // if the user_types includes the `types` key use the inner value
+            types.clone()
+        } else {
+            user_types.clone()
+        }
+    } else {
+        // No custom types, just an emtpy json
+        json!({})
+    };
+
+    let tmpl = format!(
+        r#"
+    const {{ util, utilCrypto, keyring, types }} = pjs;
+    ( async () => {{
+        const api = await pjs.api.ApiPromise.create({{
+            provider: new pjs.api.WsProvider('{}'),
+            types: {}
+         }});
+        const _run = async (api, hashing, keyring, types, util, arguments) => {{
+            {}
+        }};
+        return await _run(api, utilCrypto, keyring, types, util, {});
+    }})()
+    "#,
+        ws_uri,
+        types,
+        content,
+        json!(args),
+    );
+    trace!(tmpl = tmpl, "code to execute");
+    tmpl
+}
+
+// Since pjs-rs run a custom javascript runtime (using deno_core) we need to
+// execute in an isolated thread.
+pub fn pjs_exec(code: String) -> Result<ReturnValue, anyhow::Error> {
+    let rt = tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?;
+
+    std::thread::spawn(move || {
+        rt.block_on(async move {
+            let value = pjs_rs::run_ts_code(code, None).await;
+            trace!("ts_code return: {:?}", value);
+            value
+        })
+    })
+    .join()
+    .map_err(|_| anyhow!("[pjs] Thread panicked"))?
+}
+
+/// pjs-rs success [Result] type
+///
+/// Represent the possible states returned from a succefully call to pjs-rs
+///
+/// Ok(value) -> Deserialized return value into a [serde_json::Value]
+/// Err(msg) -> Execution of the script finish Ok, but the returned value
+/// can't be deserialize into a [serde_json::Value]
+pub type PjsResult = Result<serde_json::Value, String>;
diff --git a/crates/orchestrator/src/shared/types.rs b/crates/orchestrator/src/shared/types.rs
index 015170f90b4584b4686d13b8d38f725002a6b24f..a0bc2b367175a05fa4b7bd1a7b3c57b6ebf24d93 100644
--- a/crates/orchestrator/src/shared/types.rs
+++ b/crates/orchestrator/src/shared/types.rs
@@ -75,12 +75,3 @@ pub struct ParachainGenesisArgs {
     pub validation_code: String,
     pub parachain: bool,
 }
-
-/// pjs-rs success [Result] type
-///
-/// Represent the possible states returned from a succefully call to pjs-rs
-///
-/// Ok(value) -> Deserialized return value into a [serde_json::Value]
-/// Err(msg) -> Execution of the script finish Ok, but the returned value
-/// can't be deserialize into a [serde_json::Value]
-pub type PjsResult = Result<serde_json::Value, String>;
diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml
index 1cf6901db20e65f7c262cb0469ac0cc134ea1b78..414a46188ad59de04ca2c7b054c3b0e62c4f919d 100644
--- a/crates/sdk/Cargo.toml
+++ b/crates/sdk/Cargo.toml
@@ -30,3 +30,6 @@ tracing-subscriber = "0.3"
 kube = { workspace = true, features = ["ws", "runtime"] }
 k8s-openapi = { workspace = true, features = ["v1_27"] }
 serde_json = {workspace = true }
+
+[features]
+pjs = ["orchestrator/pjs"]
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
index 0e4afe92f988d6750daaaab8d46a8e5f19580743..fd61db5ce7dba20a1b2061a53c8905b1dbaefb05 100644
--- a/crates/sdk/src/lib.rs
+++ b/crates/sdk/src/lib.rs
@@ -1,9 +1,11 @@
 use async_trait::async_trait;
 pub use configuration::{NetworkConfig, NetworkConfigBuilder, RegistrationStrategy};
+#[cfg(feature = "pjs")]
+pub use orchestrator::pjs_helper::PjsResult;
 pub use orchestrator::{
     errors::OrchestratorError,
     network::{node::NetworkNode, Network},
-    AddCollatorOptions, AddNodeOptions, Orchestrator, PjsResult,
+    AddCollatorOptions, AddNodeOptions, Orchestrator,
 };
 use provider::{DockerProvider, KubernetesProvider, NativeProvider};
 pub use support::fs::local::LocalFileSystem;