diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index 4b605223872447ad90fe8d25f2eeb2a07c11e6de..38b2dc1c62e7c02a58f868c14e5574865efafe46 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -696,7 +696,7 @@ pub fn migration_down(ident: PluginIdentifier, timeout: Duration) -> traft::Resu
 /// 2) set `_pico_plugin.enable` to `true`
 /// 3) update routes in `_pico_service_route`
 pub fn enable_plugin(
-    ident: &PluginIdentifier,
+    plugin: &PluginIdentifier,
     on_start_timeout: Duration,
     timeout: Duration,
 ) -> traft::Result<()> {
@@ -704,15 +704,32 @@ pub fn enable_plugin(
 
     let node = node::global()?;
 
-    let op = PluginRaftOp::EnablePlugin {
-        ident: ident.clone(),
-        on_start_timeout,
+    // FIXME: this must be done in a retry loop within reenterable_plugin_change_request
+    let services = node.storage.service.get_by_plugin(&plugin)?;
+    let op = PluginOp::EnablePlugin {
+        plugin: plugin.clone(),
+        // FIXME: we shouldn't need to send this list, it's already available on
+        // the governor, what is going on?
+        services,
+        timeout: on_start_timeout,
     };
+    let dml = Dml::replace(
+        ClusterwideTable::Property,
+        &(&PropertyName::PendingPluginOperation, &op),
+        effective_user_id(),
+    )?;
+
+    let ranges = vec![
+        // Fail if someone proposes another plugin operation
+        Range::new(ClusterwideTable::Property).eq([PropertyName::PendingPluginOperation]),
+        // Fail if someone updates this plugin record
+        Range::new(ClusterwideTable::Plugin).eq([&plugin.name]),
+    ];
 
     let mut index = do_plugin_cas(
         node,
-        Op::Plugin(op),
-        vec![Range::new(ClusterwideTable::Property).eq([PropertyName::PendingPluginOperation])],
+        Op::Dml(dml),
+        ranges,
         Some(|node| Ok(node.storage.properties.pending_plugin_op()?.is_some())),
         deadline,
     )?;
@@ -724,7 +741,7 @@ pub fn enable_plugin(
     let plugin = node
         .storage
         .plugin
-        .get(ident)?
+        .get(plugin)?
         .ok_or(PluginError::EnablingAborted)?;
 
     if !plugin.enabled {
diff --git a/src/traft/node.rs b/src/traft/node.rs
index 83a609c08f7324393d5fda475d1cbdeff69c4bd7..ab284d6e4a43250d7363cff9d153e73c3644128a 100644
--- a/src/traft/node.rs
+++ b/src/traft/node.rs
@@ -732,7 +732,6 @@ impl NodeImpl {
             Op::Dml(op) => dml_is_governor_wakeup_worthy(op),
             Op::BatchDml { ops } => ops.iter().any(dml_is_governor_wakeup_worthy),
             Op::DdlPrepare { .. } => true,
-            Op::Plugin(PluginRaftOp::EnablePlugin { .. }) => true,
             // TODO: remove this once PluginOp::DisablePlugin is removed
             Op::Plugin(PluginRaftOp::DisablePlugin { .. }) => true,
             Op::Plugin(PluginRaftOp::UpdateServiceTopology { .. }) => true,
@@ -1228,28 +1227,6 @@ impl NodeImpl {
                     .expect("storage should not fail");
             }
 
-            // TODO: remove this, just propose a DML into _pico_property directly
-            Op::Plugin(PluginRaftOp::EnablePlugin {
-                ident,
-                on_start_timeout,
-            }) => {
-                let services = self
-                    .storage
-                    .service
-                    .get_by_plugin(&ident)
-                    .expect("storage should not fail");
-
-                let op = PluginOp::EnablePlugin {
-                    plugin: ident,
-                    services,
-                    timeout: on_start_timeout,
-                };
-                self.storage
-                    .properties
-                    .put(PropertyName::PendingPluginOperation, &op)
-                    .expect("storage should not fail");
-            }
-
             Op::Plugin(PluginRaftOp::UpdatePluginConfig {
                 ident,
                 service_name,
diff --git a/src/traft/op.rs b/src/traft/op.rs
index 77970c0ac3e201183bf17f2ca7072f0ffce5a456..f540948df0cee325d9e969107f4f9b7c7c565e71 100644
--- a/src/traft/op.rs
+++ b/src/traft/op.rs
@@ -12,7 +12,6 @@ use ::tarantool::space::{Field, SpaceId};
 use ::tarantool::tlua;
 use ::tarantool::tuple::{ToTupleBuffer, TupleBuffer};
 use serde::{Deserialize, Serialize};
-use std::time::Duration;
 use tarantool::index::IndexType;
 use tarantool::session::UserId;
 use tarantool::space::SpaceEngineType;
@@ -219,9 +218,6 @@ impl std::fmt::Display for Op {
                     object_type = priv_def.object_type(),
                     privilege = priv_def.privilege(), )
             }
-            Op::Plugin(PluginRaftOp::EnablePlugin { ident, .. }) => {
-                write!(f, "EnablePlugin({ident})")
-            }
             Op::Plugin(PluginRaftOp::UpdatePluginConfig {
                 ident,
                 service_name,
@@ -815,14 +811,6 @@ impl Acl {
 #[serde(rename_all = "snake_case")]
 #[serde(tag = "op_kind")]
 pub enum PluginRaftOp {
-    /// Enable a plugin.
-    ///
-    /// Provided operation will be set as pending.
-    /// Only one operation can exist at the same time.
-    EnablePlugin {
-        ident: PluginIdentifier,
-        on_start_timeout: Duration,
-    },
     /// Update plugin service configuration.
     UpdatePluginConfig {
         ident: PluginIdentifier,