diff --git a/internal/experiments/experiments.go b/internal/experiments/experiments.go
index 6235a039..ab676022 100644
--- a/internal/experiments/experiments.go
+++ b/internal/experiments/experiments.go
@@ -44,7 +44,7 @@ func init() {
 	GentleForce = New("GENTLE_FORCE", 1)
 	RemoteTaskfiles = New("REMOTE_TASKFILES", 1)
 	AnyVariables = New("ANY_VARIABLES")
-	MapVariables = New("MAP_VARIABLES", 1, 2)
+	MapVariables = New("MAP_VARIABLES")
 	EnvPrecedence = New("ENV_PRECEDENCE", 1)
 }
 
diff --git a/taskfile/ast/var.go b/taskfile/ast/var.go
index abe5157d..0806e423 100644
--- a/taskfile/ast/var.go
+++ b/taskfile/ast/var.go
@@ -1,12 +1,9 @@
 package ast
 
 import (
-	"strings"
-
 	"gopkg.in/yaml.v3"
 
 	"github.com/go-task/task/v3/errors"
-	"github.com/go-task/task/v3/internal/experiments"
 )
 
 // Var represents either a static or dynamic variable.
@@ -19,82 +16,26 @@ type Var struct {
 }
 
 func (v *Var) UnmarshalYAML(node *yaml.Node) error {
-	if experiments.MapVariables.Enabled() {
-
-		// This implementation is not backwards-compatible and replaces the 'sh' key with map variables
-		if experiments.MapVariables.Value == 1 {
-			var value any
-			if err := node.Decode(&value); err != nil {
-				return errors.NewTaskfileDecodeError(err, node)
-			}
-			// If the value is a string and it starts with $, then it's a shell command
-			if str, ok := value.(string); ok {
-				if str, ok = strings.CutPrefix(str, "$"); ok {
-					v.Sh = &str
-					return nil
-				}
-				if str, ok = strings.CutPrefix(str, "#"); ok {
-					v.Ref = str
-					return nil
-				}
-			}
-			v.Value = value
-			return nil
-		}
-
-		// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
-		if experiments.MapVariables.Value == 2 {
-			switch node.Kind {
-			case yaml.MappingNode:
-				key := node.Content[0].Value
-				switch key {
-				case "sh", "ref", "map":
-					var m struct {
-						Sh  *string
-						Ref string
-						Map any
-					}
-					if err := node.Decode(&m); err != nil {
-						return errors.NewTaskfileDecodeError(err, node)
-					}
-					v.Sh = m.Sh
-					v.Ref = m.Ref
-					v.Value = m.Map
-					return nil
-				default:
-					return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
-				}
-			default:
-				var value any
-				if err := node.Decode(&value); err != nil {
-					return errors.NewTaskfileDecodeError(err, node)
-				}
-				v.Value = value
-				return nil
-			}
-		}
-	}
-
 	switch node.Kind {
-
 	case yaml.MappingNode:
 		key := node.Content[0].Value
 		switch key {
-		case "sh", "ref":
+		case "sh", "ref", "map":
 			var m struct {
 				Sh  *string
 				Ref string
+				Map any
 			}
 			if err := node.Decode(&m); err != nil {
 				return errors.NewTaskfileDecodeError(err, node)
 			}
 			v.Sh = m.Sh
 			v.Ref = m.Ref
+			v.Value = m.Map
 			return nil
 		default:
-			return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
+			return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
 		}
-
 	default:
 		var value any
 		if err := node.Decode(&value); err != nil {
diff --git a/website/docs/experiments/map_variables.mdx b/website/docs/experiments/map_variables.mdx
deleted file mode 100644
index 477714d0..00000000
--- a/website/docs/experiments/map_variables.mdx
+++ /dev/null
@@ -1,245 +0,0 @@
----
-slug: /experiments/map-variables/
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-# Map Variables (#1585)
-
-:::caution
-
-All experimental features are subject to breaking changes and/or removal _at any
-time_. We strongly recommend that you do not use these features in a production
-environment. They are intended for testing and feedback only.
-
-:::
-
-Currently, Task supports all variable types except for maps. This experiment
-adds two different proposals for map variables. Click on the tabs below to
-switch between them.
-
-
-
-
-
-:::warning
-
-This experiment proposal breaks the following functionality:
-
-- Dynamically defined variables (using the `sh` keyword)
-
-:::
-
-:::info
-
-To enable this experiment, set the environment variable:
-`TASK_X_MAP_VARIABLES=1`. Check out [our guide to enabling experiments
-][enabling-experiments] for more information.
-
-:::
-
-This proposal removes support for the `sh` and `ref` keywords in favour of a new
-syntax for dynamically defined variables and references. This allows you to
-define a map directly as you would for any other type:
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      FOO: {a: 1, b: 2, c: 3} # <-- Directly defined map on the `FOO` key
-    cmds:
-      - 'echo {{.FOO.a}}'
-```
-
-## Migration
-
-Taskfiles with dynamically defined variables via the `sh` subkey or references
-defined with `ref` will no longer work with this experiment enabled. In order to
-keep using these features, you will need to migrate your Taskfile to use the new
-syntax.
-
-### Dynamic Variables
-
-Previously, you had to define dynamic variables using the `sh` subkey. With this
-experiment enabled, you will need to remove the `sh` subkey and define your
-command as a string that begins with a `$`. This will instruct Task to interpret
-the string as a command instead of a literal value and the variable will be
-populated with the output of the command. For example:
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      CALCULATED_VAR:
-        sh: 'echo hello'
-    cmds:
-      - 'echo {{.CALCULATED_VAR}}'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      CALCULATED_VAR: '$echo hello' # <-- Prefix dynamic variable with a `$`
-    cmds:
-      - 'echo {{.CALCULATED_VAR}}'
-```
-
-
-
-### References
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      VAR: 42
-      VAR_REF:
-        ref: '.FOO'
-    cmds:
-      - 'echo {{.VAR_REF}}'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      VAR: 42
-      VAR_REF: '#.FOO' # <-- Prefix reference with a `#`
-    cmds:
-      - 'echo {{.VAR_REF}}'
-```
-
-
-
-If your current Taskfile contains a string variable that begins with a `$` or a
-`#`, you will now need to escape it with a backslash (`\`) to stop Task from
-interpreting it as a command or reference.
-
-
-
-
-:::info
-
-To enable this experiment, set the environment variable:
-`TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments
-][enabling-experiments] for more information.
-
-:::
-
-This proposal maintains backwards-compatibility and the `sh` subkey and adds
-another new `map` subkey for defining map variables:
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      FOO:
-        map: {a: 1, b: 2, c: 3} # <-- Defined using the `map' subkey instead of directly on 'FOO'
-      BAR: true # <-- Other types of variables are still defined directly on the key
-      BAZ:
-        sh: 'echo Hello Task' # <-- The `sh` subkey is still supported
-      QUX:
-        ref: '.BAZ' # <-- The `ref` subkey is still supported
-    cmds:
-      - 'echo {{.FOO.a}}'
-```
-
-
-
-## Looping over maps
-
-This experiment also adds support for looping over maps using the `for` keyword,
-just like arrays. In addition to the `{{.ITEM}}` variable being populated when
-looping over a map, we also make an additional `{{.KEY}}` variable available
-that holds the string value of the map key.
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      MAP: {a: 1, b: 2, c: 3}
-    cmds:
-      - for:
-          var: MAP
-        cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
-  foo:
-    vars:
-      map:
-        MAP: {a: 1, b: 2, c: 3}
-    cmds:
-      - for:
-          var: MAP
-        cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
-```
-
-:::note
-
-Remember that maps are unordered, so
-the order in which the items are looped over is random.
-
-:::
-
-
-
-{/* prettier-ignore-start */}
-[enabling-experiments]: ./experiments.mdx#enabling-experiments
-{/* prettier-ignore-end */}
diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx
index f9738596..a894df42 100644
--- a/website/docs/usage.mdx
+++ b/website/docs/usage.mdx
@@ -1113,53 +1113,38 @@ variable types are supported:
 - `int`
 - `float`
 - `array`
+- `map`
 
 :::note
 
-Maps are not supported by default, but there is an
-[experiment][map-variables] that can be enabled to add support. If
-you're interested in this functionality, we would appreciate your feedback.
-:pray:
-
-In the meantime, it is technically possible to define a map using a `ref` resolver and a templating function. For example:
-
-```yaml
-version: '3'
-
-tasks:
-  task-with-map:
-    vars:
-      FOO:
-        ref: dict "a" "1" "b" "2" "c" "3"
-    cmds:
-      - echo {{.FOO}}
-```
-
-```txt
-map[a:1 b:2 c:3]
-```
-
-OR by using the same technique with JSON:
-
-```yaml
-version: '3'
-
-tasks:
-  task-with-map:
-    vars:
-      JSON: '{"a": 1, "b": 2, "c": 3}'
-      FOO:
-        ref: "fromJson .JSON"
-    cmds:
-      - echo {{.FOO}}
-```
-
-```txt
-map[a:1 b:2 c:3]
-```
+Defining a map requires that you use a special `map` subkey (see example below).
 
 :::
 
+```yaml
+version: 3
+
+tasks:
+  foo:
+    vars:
+      STRING: 'Hello, World!'
+      BOOL: true
+      INT: 42
+      FLOAT: 3.14
+      ARRAY: [1, 2, 3]
+      MAP:
+        map: {A: 1, B: 2, C: 3}
+    cmds:
+      - 'echo {{.STRING}}'  # Hello, World!
+      - 'echo {{.BOOL}}'    # true
+      - 'echo {{.INT}}'     # 42
+      - 'echo {{.FLOAT}}'   # 3.14
+      - 'echo {{.ARRAY}}'   # [1 2 3]
+      - 'echo {{.ARRAY.0}}' # 1
+      - 'echo {{.MAP}}'     # map[A:1 B:2 C:3]
+      - 'echo {{.MAP.A}}'   # 1
+```
+
 Variables can be set in many places in a Taskfile. When executing
 [templates][templating-reference], Task will look for variables in the order
 listed below (most important first):
@@ -1360,6 +1345,29 @@ tasks:
       - 'echo {{.FOO}}' # <-- FOO is just the letter 'A'
 ```
 
+### Parsing JSON/YAML into map variables
+
+If you have a raw JSON or YAML string that you want to process in Task, you can
+use a combination of the `ref` keyword and the `fromJson` or `fromYaml`
+templating functions to parse the string into a map variable. For example:
+
+```yaml
+version: '3'
+
+tasks:
+  task-with-map:
+    vars:
+      JSON: '{"a": 1, "b": 2, "c": 3}'
+      FOO:
+        ref: "fromJson .JSON"
+    cmds:
+      - echo {{.FOO}}
+```
+
+```txt
+map[a:1 b:2 c:3]
+```
+
 ## Looping over values
 
 Task allows you to loop over certain values and execute a command for each.
@@ -1508,7 +1516,7 @@ tasks:
         cmd: cat {{.ITEM}}
 ```
 
-You can also loop over arrays directly and maps:
+You can also loop over arrays and maps directly:
 
 ```yaml
 version: 3
@@ -2319,6 +2327,5 @@ if called by another task, either directly or as a dependency.
 
 {/* prettier-ignore-start */}
 [gotemplate]: https://golang.org/pkg/text/template/
-[map-variables]: ./experiments/map_variables.mdx
 [templating-reference]: ./reference/templating.mdx
 {/* prettier-ignore-end */}
diff --git a/website/static/schema.json b/website/static/schema.json
index 49af497b..1aaeaa90 100644
--- a/website/static/schema.json
+++ b/website/static/schema.json
@@ -276,7 +276,7 @@
         "^.*$": {
           "anyOf": [
             {
-              "type": ["boolean", "integer", "null", "number", "string", "object", "array"]
+              "type": ["boolean", "integer", "null", "number", "string", "array"]
             },
             {
               "$ref": "#/definitions/var_subkey"