diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index f348f5103531f0785594cbcba483efc4b375fadb..30f4d87df2ce110be29fdd999c84c3e893d64e29 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -3723,6 +3723,9 @@ Definition tables
 
 ### Biome definition (`register_biome`)
 
+**Note**
+The biome API is still in an experimental phase and subject to change.
+
     {
         name = "tundra",
         node_dust = "default:snow",
@@ -3742,6 +3745,9 @@ Definition tables
     --  ^ Node that replaces all seawater nodes not in the defined surface layer.
         node_river_water = "default:ice",
     --  ^ Node that replaces river water in mapgens that use default:river_water.
+        node_riverbed = "default:gravel",
+        depth_riverbed = 2,
+    --  ^ Node placed under river water and thickness of this layer.
         y_min = 1,
         y_max = 31000,
     --  ^ Lower and upper limits for biome.
diff --git a/src/mapgen.cpp b/src/mapgen.cpp
index b5c48a471586058a4d6475f557428fdcd0449d16..66892a5742e6f5e329cb6a4bc2c9c36db9fc17ad 100644
--- a/src/mapgen.cpp
+++ b/src/mapgen.cpp
@@ -536,13 +536,15 @@ MgStoneType MapgenBasic::generateBiomes()
 		u16 depth_top = 0;
 		u16 base_filler = 0;
 		u16 depth_water_top = 0;
+		u16 depth_riverbed = 0;
 		u32 vi = vm->m_area.index(x, node_max.Y, z);
 
 		// Check node at base of mapchunk above, either a node of a previously
 		// generated mapchunk or if not, a node of overgenerated base terrain.
 		content_t c_above = vm->m_data[vi + em.X].getContent();
 		bool air_above = c_above == CONTENT_AIR;
-		bool water_above = (c_above == c_water_source || c_above == c_river_water_source);
+		bool river_water_above = c_above == c_river_water_source;
+		bool water_above = c_above == c_water_source || river_water_above;
 
 		// If there is air or water above enable top/filler placement, otherwise force
 		// nplaced to stone level by setting a number exceeding any possible filler depth.
@@ -564,10 +566,11 @@ MgStoneType MapgenBasic::generateBiomes()
 				biome = biomegen->getBiomeAtIndex(index, y);
 
 				depth_top = biome->depth_top;
-				base_filler = MYMAX(depth_top
-						+ biome->depth_filler
-						+ noise_filler_depth->result[index], 0.f);
+				base_filler = MYMAX(depth_top +
+					biome->depth_filler +
+					noise_filler_depth->result[index], 0.f);
 				depth_water_top = biome->depth_water_top;
+				depth_riverbed = biome->depth_riverbed;
 
 				// Detect stone type for dungeons during every biome calculation.
 				// This is more efficient than detecting per-node and will not
@@ -590,7 +593,15 @@ MgStoneType MapgenBasic::generateBiomes()
 						|| c_below == c_river_water_source)
 					nplaced = U16_MAX;
 
-				if (nplaced < depth_top) {
+				if (river_water_above) {
+					if (nplaced < depth_riverbed) {
+						vm->m_data[vi] = MapNode(biome->c_riverbed);
+						nplaced++;
+					} else {
+						nplaced = U16_MAX;  // Disable top/filler placement
+						river_water_above = false;
+					}
+				} else if (nplaced < depth_top) {
 					vm->m_data[vi] = MapNode(biome->c_top);
 					nplaced++;
 				} else if (nplaced < base_filler) {
@@ -610,9 +621,10 @@ MgStoneType MapgenBasic::generateBiomes()
 				water_above = true;
 			} else if (c == c_river_water_source) {
 				vm->m_data[vi] = MapNode(biome->c_river_water);
-				nplaced = depth_top;  // Enable filler placement for next surface
+				nplaced = 0;  // Enable riverbed placement for next surface
 				air_above = false;
 				water_above = true;
+				river_water_above = true;
 			} else if (c == CONTENT_AIR) {
 				nplaced = 0;  // Enable top/filler placement for next surface
 				air_above = true;
diff --git a/src/mapgen_valleys.cpp b/src/mapgen_valleys.cpp
index 2cd733d36585995f0db88d7c7fcecc93ccdd27e0..6581b792ff0d4762cb2ecc7bb4ef89d7eb421b3a 100644
--- a/src/mapgen_valleys.cpp
+++ b/src/mapgen_valleys.cpp
@@ -114,11 +114,6 @@ MapgenValleys::MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager *
 
 	// Resolve content to be used
 	c_lava_source = ndef->getId("mapgen_lava_source");
-	c_sand        = ndef->getId("mapgen_sand");
-
-	// Fall back to more basic content if not defined
-	if (c_sand == CONTENT_IGNORE)
-		c_sand = c_stone;
 }
 
 
@@ -493,7 +488,6 @@ int MapgenValleys::generateTerrain()
 
 	MapNode n_air(CONTENT_AIR);
 	MapNode n_river_water(c_river_water_source);
-	MapNode n_sand(c_sand);
 	MapNode n_stone(c_stone);
 	MapNode n_water(c_water_source);
 
@@ -537,10 +531,7 @@ int MapgenValleys::generateTerrain()
 				float surface_delta = (float)y - surface_y;
 				bool river = y + 1 < river_y;
 
-				if (fabs(surface_delta) <= 0.5f && y > water_level && river) {
-					// river bottom
-					vm->m_data[index_data] = n_sand;
-				} else if (slope * fill > surface_delta) {
+				if (slope * fill > surface_delta) {
 					// ground
 					vm->m_data[index_data] = n_stone;
 					if (y > heightmap[index_2d])
@@ -553,7 +544,7 @@ int MapgenValleys::generateTerrain()
 				} else if (river) {
 					// river
 					vm->m_data[index_data] = n_river_water;
-				} else {
+				} else {  // air
 					vm->m_data[index_data] = n_air;
 				}
 			}
diff --git a/src/mg_biome.cpp b/src/mg_biome.cpp
index df728af337eb804a94754b05124673e169fd3364..78034bf6ca8b0bc9237d61d5c15671f957ecfb65 100644
--- a/src/mg_biome.cpp
+++ b/src/mg_biome.cpp
@@ -46,6 +46,7 @@ BiomeManager::BiomeManager(IGameDef *gamedef) :
 	b->depth_top       = 0;
 	b->depth_filler    = -MAX_MAP_GENERATION_LIMIT;
 	b->depth_water_top = 0;
+	b->depth_riverbed  = 0;
 	b->y_min           = -MAX_MAP_GENERATION_LIMIT;
 	b->y_max           = MAX_MAP_GENERATION_LIMIT;
 	b->heat_point      = 0.0;
@@ -57,6 +58,7 @@ BiomeManager::BiomeManager(IGameDef *gamedef) :
 	b->m_nodenames.push_back("mapgen_water_source");
 	b->m_nodenames.push_back("mapgen_water_source");
 	b->m_nodenames.push_back("mapgen_river_water_source");
+	b->m_nodenames.push_back("mapgen_stone");
 	b->m_nodenames.push_back("ignore");
 	m_ndef->pendNodeResolve(b);
 
@@ -237,5 +239,6 @@ void Biome::resolveNodeNames()
 	getIdFromNrBacklog(&c_water_top,   "mapgen_water_source",       CONTENT_AIR);
 	getIdFromNrBacklog(&c_water,       "mapgen_water_source",       CONTENT_AIR);
 	getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR);
+	getIdFromNrBacklog(&c_riverbed,    "mapgen_stone",              CONTENT_AIR);
 	getIdFromNrBacklog(&c_dust,        "ignore",                    CONTENT_IGNORE);
 }
diff --git a/src/mg_biome.h b/src/mg_biome.h
index 568d0b1acbb45aecfd0861d68cb55e47eafc0b45..eb0a18a2fae0f87527d0c7c889391592d0ef8f36 100644
--- a/src/mg_biome.h
+++ b/src/mg_biome.h
@@ -54,11 +54,13 @@ class Biome : public ObjDef, public NodeResolver {
 	content_t c_water_top;
 	content_t c_water;
 	content_t c_river_water;
+	content_t c_riverbed;
 	content_t c_dust;
 
 	s16 depth_top;
 	s16 depth_filler;
 	s16 depth_water_top;
+	s16 depth_riverbed;
 
 	s16 y_min;
 	s16 y_max;
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index 6baf217af34ebe74bcdc0f2af652294c7f6bae48..dc188f8a4a2cc3db9207aea81aff1f1fb5152d9f 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -378,6 +378,7 @@ Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef)
 	b->depth_top       = getintfield_default(L,    index, "depth_top",       0);
 	b->depth_filler    = getintfield_default(L,    index, "depth_filler",    -31000);
 	b->depth_water_top = getintfield_default(L,    index, "depth_water_top", 0);
+	b->depth_riverbed  = getintfield_default(L,    index, "depth_riverbed",  0);
 	b->y_min           = getintfield_default(L,    index, "y_min",           -31000);
 	b->y_max           = getintfield_default(L,    index, "y_max",           31000);
 	b->heat_point      = getfloatfield_default(L,  index, "heat_point",      0.f);
@@ -391,6 +392,7 @@ Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef)
 	nn.push_back(getstringfield_default(L, index, "node_water_top",   ""));
 	nn.push_back(getstringfield_default(L, index, "node_water",       ""));
 	nn.push_back(getstringfield_default(L, index, "node_river_water", ""));
+	nn.push_back(getstringfield_default(L, index, "node_riverbed",    ""));
 	nn.push_back(getstringfield_default(L, index, "node_dust",        ""));
 	ndef->pendNodeResolve(b);