diff --git a/game_api.txt b/game_api.txt
index fa9d6fc560009a6dc3a4d044746d67d4fe44a924..a3edcfe8a750429b58fa78585f3052ce8b13ac6c 100644
--- a/game_api.txt
+++ b/game_api.txt
@@ -144,6 +144,21 @@ farming.register_plant(name, Plant definition)
 	maxlight = default.LIGHT_MAX -- Maximum light to grow
 }
 
+Screwdriver API
+---------------
+The screwdriver API allows you to control a node's behaviour when a screwdriver is used on it.
+To use it, add the on_screwdriver function to the node definition.
+on_rotate(pos, node, user, mode, new_param2)
+^ pos: position of the node that the screwdriver is being used on
+^ node: that node
+^ user: the player who used the screwdriver
+^ mode: screwdriver.ROTATE_FACE or screwdriver.ROTATE_AXIS
+^ new_param2: the new value of param2 that would have been set if on_rotate wasn't there
+^ return value: false to disallow rotation, nil to keep default behaviour, true to allow
+  it but to indicate that changed have already been made (so the screwdriver will wear out)
+^ use on_rotate = screwdriver.disallow to always disallow rotation
+^ use on_rotate = screwdriver.rotate_simple to allow only face rotation
+
 Stairs API
 ----------
 The stairs API lets you register stairs and slabs and ensures that they are registered the same way as those
diff --git a/mods/beds/api.lua b/mods/beds/api.lua
index 320fcfb35dbaa6d4f4100414f7c51b82531edafc..8f924730897baee5ea949658873d9b646bc33078 100644
--- a/mods/beds/api.lua
+++ b/mods/beds/api.lua
@@ -26,10 +26,10 @@ function beds.register_bed(name, def)
 				return true
 			end
 			local dir = minetest.facedir_to_dir(n.param2)
-			local p = {x=pos.x+dir.x,y=pos.y,z=pos.z+dir.z}
+			local p = vector.add(pos, dir)
 			local n2 = minetest.get_node_or_nil(p)
-			local def = minetest.registered_items[n2.name] or nil
-			if not n2 or not def or not def.buildable_to then
+			local def = n2 and minetest.registered_items[n2.name]
+			if not def or not def.buildable_to then
 				minetest.remove_node(pos)
 				return true
 			end
@@ -40,7 +40,7 @@ function beds.register_bed(name, def)
 			local n = minetest.get_node_or_nil(pos)
 			if not n then return end
 			local dir = minetest.facedir_to_dir(n.param2)
-			local p = {x=pos.x+dir.x,y=pos.y,z=pos.z+dir.z}
+			local p = vector.add(pos, dir)
 			local n2 = minetest.get_node(p)
 			if minetest.get_item_group(n2.name, "bed") == 2 and n.param2 == n2.param2 then
 				minetest.remove_node(p)
@@ -49,6 +49,37 @@ function beds.register_bed(name, def)
 		on_rightclick = function(pos, node, clicker)
 			beds.on_rightclick(pos, clicker)
 		end,
+		on_rotate = function(pos, node, user, mode, new_param2)
+			local dir = minetest.facedir_to_dir(node.param2)
+			local p = vector.add(pos, dir)
+			local node2 = minetest.get_node_or_nil(p)
+			if not node2 or not minetest.get_item_group(node2.name, "bed") == 2 or
+					not node.param2 == node2.param2 then
+				return false
+			end
+			if minetest.is_protected(p, user:get_player_name()) then
+				minetest.record_protection_violation(p, user:get_player_name())
+				return false
+			end
+			if mode ~= screwdriver.ROTATE_FACE then
+				return false
+			end
+			local newp = vector.add(pos, minetest.facedir_to_dir(new_param2))
+			local node3 = minetest.get_node_or_nil(newp)
+			local def = node3 and minetest.registered_nodes[node3.name]
+			if not def or not def.buildable_to then
+				return false
+			end
+			if minetest.is_protected(newp, user:get_player_name()) then
+				minetest.record_protection_violation(newp, user:get_player_name())
+				return false
+			end
+			node.param2 = new_param2
+			minetest.swap_node(pos, node)
+			minetest.remove_node(p)
+			minetest.set_node(newp, {name = node.name:gsub("%_bottom", "_top"), param2 = new_param2})
+			return true
+		end,
 	})
 
 	minetest.register_node(name .. "_top", {
diff --git a/mods/doors/init.lua b/mods/doors/init.lua
index f3dd16959c9a8a8b085278e9c4ad97ab463194ba..b762751affedb44a27b959648d02b64d891d3200 100644
--- a/mods/doors/init.lua
+++ b/mods/doors/init.lua
@@ -163,6 +163,33 @@ function doors.register_door(name, def)
 		return meta:get_string("doors_owner") == pn
 	end
 
+	local function on_rotate(pos, node, dir, user, check_name, mode, new_param2)
+		if not check_player_priv(pos, user) then
+			return false
+		end
+		if mode ~= screwdriver.ROTATE_FACE then
+			return false
+		end
+
+		pos.y = pos.y + dir
+		if not minetest.get_node(pos).name == check_name then
+			return false
+		end
+		if minetest.is_protected(pos, user:get_player_name()) then
+			minetest.record_protection_violation(pos, user:get_player_name())
+			return false
+		end
+
+		local node2 = minetest.get_node(pos)
+		node2.param2 = (node2.param2 + 1) % 4
+		minetest.swap_node(pos, node2)
+
+		pos.y = pos.y - dir
+		node.param2 = (node.param2 + 1) % 4
+		minetest.swap_node(pos, node)
+		return true
+	end
+
 	minetest.register_node(name.."_b_1", {
 		tiles = {tb[2], tb[2], tb[2], tb[2], tb[1], tb[1].."^[transformfx"},
 		paramtype = "light",
@@ -190,6 +217,10 @@ function doors.register_door(name, def)
 			end
 		end,
 		
+		on_rotate = function(pos, node, user, mode, new_param2)
+			return on_rotate(pos, node, 1, user, name.."_t_1", mode)
+		end,
+
 		can_dig = check_player_priv,
 		sounds = def.sounds,
 		sunlight_propagates = def.sunlight,
@@ -223,6 +254,10 @@ function doors.register_door(name, def)
 			end
 		end,
 		
+		on_rotate = function(pos, node, user, mode, new_param2)
+			return on_rotate(pos, node, -1, user, name.."_b_1", mode)
+		end,
+
 		can_dig = check_player_priv,
 		sounds = def.sounds,
 		sunlight_propagates = def.sunlight,
@@ -256,6 +291,10 @@ function doors.register_door(name, def)
 			end
 		end,
 		
+		on_rotate = function(pos, node, user, mode, new_param2)
+			return on_rotate(pos, node, 1, user, name.."_t_2", mode)
+		end,
+
 		can_dig = check_player_priv,
 		sounds = def.sounds,
 		sunlight_propagates = def.sunlight,
@@ -289,6 +328,10 @@ function doors.register_door(name, def)
 			end
 		end,
 		
+		on_rotate = function(pos, node, user, mode, new_param2)
+			return on_rotate(pos, node, -1, user, name.."_b_2", mode)
+		end,
+
 		can_dig = check_player_priv,
 		sounds = def.sounds,
 		sunlight_propagates = def.sunlight,
@@ -392,6 +435,8 @@ function doors.register_trapdoor(name, def)
 		minetest.set_node(pos, {name = newname, param1 = node.param1, param2 = node.param2})
 	end
 
+	def.on_rotate = screwdriver.rotate_simple
+
 	-- Common trapdoor configuration
 	def.drawtype = "nodebox"
 	def.paramtype = "light"
diff --git a/mods/screwdriver/init.lua b/mods/screwdriver/init.lua
index 65e7f004e0d43de250336dad5909895638c94564..bccc9fbca2157703f07caffa406f64bf7e01b941 100644
--- a/mods/screwdriver/init.lua
+++ b/mods/screwdriver/init.lua
@@ -1,3 +1,5 @@
+screwdriver = {}
+
 local function nextrange(x, max)
 	x = x + 1
 	if x > max then
@@ -6,8 +8,16 @@ local function nextrange(x, max)
 	return x
 end
 
-local ROTATE_FACE = 1
-local ROTATE_AXIS = 2
+screwdriver.ROTATE_FACE = 1
+screwdriver.ROTATE_AXIS = 2
+screwdriver.disallow = function(pos, node, user, mode, new_param2)
+	return false
+end
+screwdriver.rotate_simple = function(pos, node, user, mode, new_param2)
+	if mode ~= screwdriver.ROTATE_FACE then
+		return false
+	end
+end
 local USES = 200
 
 -- Handles rotation
@@ -25,31 +35,47 @@ local function screwdriver_handler(itemstack, user, pointed_thing, mode)
 
 	local node = minetest.get_node(pos)
 	local ndef = minetest.registered_nodes[node.name]
-	if not ndef or not ndef.paramtype2 == "facedir" or
-			(ndef.drawtype == "nodebox" and
-			not ndef.node_box.type == "fixed") or
-			node.param2 == nil then
-		return
-	end
-
-	if ndef.can_dig and not ndef.can_dig(pos, user) then
-		return
-	end
-
-	-- Set param2
+	-- Compute param2
 	local rotationPart = node.param2 % 32 -- get first 4 bits
 	local preservePart = node.param2 - rotationPart
-
 	local axisdir = math.floor(rotationPart / 4)
 	local rotation = rotationPart - axisdir * 4
-	if mode == ROTATE_FACE then
+	if mode == screwdriver.ROTATE_FACE then
 		rotationPart = axisdir * 4 + nextrange(rotation, 3)
-	elseif mode == ROTATE_AXIS then
+	elseif mode == screwdriver.ROTATE_AXIS then
 		rotationPart = nextrange(axisdir, 5) * 4
 	end
 
-	node.param2 = preservePart + rotationPart
-	minetest.swap_node(pos, node)
+	local new_param2 = preservePart + rotationPart
+	local should_rotate = true
+
+	if ndef and ndef.on_rotate then -- Node provides a handler, so let the handler decide instead if the node can be rotated
+		-- Copy pos and node because callback can modify it
+		local result = ndef.on_rotate(vector.new(pos),
+				{name = node.name, param1 = node.param1, param2 = node.param2},
+				user, mode)
+		if result == false then -- Disallow rotation
+			return
+		elseif result == true then
+			should_rotate = false
+		end
+	else
+		if not ndef or not ndef.paramtype2 == "facedir" or
+				(ndef.drawtype == "nodebox" and
+				not ndef.node_box.type == "fixed") or
+				node.param2 == nil then
+			return
+		end
+
+		if ndef.can_dig and not ndef.can_dig(pos, user) then
+			return
+		end
+	end
+
+	if should_rotate then
+		node.param2 = new_param2
+		minetest.swap_node(pos, node)
+	end
 
 	if not minetest.setting_getbool("creative_mode") then
 		itemstack:add_wear(65535 / (USES - 1))
@@ -63,11 +89,11 @@ minetest.register_tool("screwdriver:screwdriver", {
 	description = "Screwdriver (left-click rotates face, right-click rotates axis)",
 	inventory_image = "screwdriver.png",
 	on_use = function(itemstack, user, pointed_thing)
-		screwdriver_handler(itemstack, user, pointed_thing, ROTATE_FACE)
+		screwdriver_handler(itemstack, user, pointed_thing, screwdriver.ROTATE_FACE)
 		return itemstack
 	end,
 	on_place = function(itemstack, user, pointed_thing)
-		screwdriver_handler(itemstack, user, pointed_thing, ROTATE_AXIS)
+		screwdriver_handler(itemstack, user, pointed_thing, screwdriver.ROTATE_AXIS)
 		return itemstack
 	end,
 })