diff --git a/provider/parameter.go b/provider/parameter.go
index 2f7dc662..f476e850 100644
--- a/provider/parameter.go
+++ b/provider/parameter.go
@@ -21,6 +21,25 @@ import (
 	"golang.org/x/xerrors"
 )
 
+type ValidationMode string
+
+const (
+	ValidationModeEnvVar = "CODER_VALIDATION_MODE"
+	// ValidationModeDefault is used for creating a workspace. It validates the final
+	// value used for a parameter. Some allowances for invalid options are tolerated,
+	// as unused options do not affect the final parameter value. The default value
+	// is also ignored, assuming a value is provided.
+	ValidationModeDefault ValidationMode = ""
+	// ValidationModeTemplateImport tolerates empty values, as the value might not be
+	// available at import. It does not tolerate an invalid default or invalid option
+	// values.
+	ValidationModeTemplateImport ValidationMode = "template-import"
+)
+
+var (
+	defaultValuePath = cty.Path{cty.GetAttrStep{Name: "default"}}
+)
+
 type Option struct {
 	Name        string
 	Description string
@@ -46,14 +65,13 @@ const (
 )
 
 type Parameter struct {
-	Value       string
 	Name        string
 	DisplayName string `mapstructure:"display_name"`
 	Description string
 	Type        OptionType
 	FormType    ParameterFormType
 	Mutable     bool
-	Default     string
+	Default     *string
 	Icon        string
 	Option      []Option
 	Validation  []Validation
@@ -82,7 +100,6 @@ func parameterDataSource() *schema.Resource {
 
 			var parameter Parameter
 			err = mapstructure.Decode(struct {
-				Value       interface{}
 				Name        interface{}
 				DisplayName interface{}
 				Description interface{}
@@ -97,17 +114,22 @@ func parameterDataSource() *schema.Resource {
 				Order       interface{}
 				Ephemeral   interface{}
 			}{
-				Value:       rd.Get("value"),
 				Name:        rd.Get("name"),
 				DisplayName: rd.Get("display_name"),
 				Description: rd.Get("description"),
 				Type:        rd.Get("type"),
 				FormType:    rd.Get("form_type"),
 				Mutable:     rd.Get("mutable"),
-				Default:     rd.Get("default"),
-				Icon:        rd.Get("icon"),
-				Option:      rd.Get("option"),
-				Validation:  fixedValidation,
+				Default: func() *string {
+					if rd.GetRawConfig().AsValueMap()["default"].IsNull() {
+						return nil
+					}
+					val, _ := rd.Get("default").(string)
+					return &val
+				}(),
+				Icon:       rd.Get("icon"),
+				Option:     rd.Get("option"),
+				Validation: fixedValidation,
 				Optional: func() bool {
 					// This hack allows for checking if the "default" field is present in the .tf file.
 					// If "default" is missing or is "null", then it means that this field is required,
@@ -122,19 +144,6 @@ func parameterDataSource() *schema.Resource {
 			if err != nil {
 				return diag.Errorf("decode parameter: %s", err)
 			}
-			var value string
-			if parameter.Default != "" {
-				err := valueIsType(parameter.Type, parameter.Default, cty.Path{cty.GetAttrStep{Name: "default"}})
-				if err != nil {
-					return err
-				}
-				value = parameter.Default
-			}
-			envValue, ok := os.LookupEnv(ParameterEnvironmentVariable(parameter.Name))
-			if ok {
-				value = envValue
-			}
-			rd.Set("value", value)
 
 			if !parameter.Mutable && parameter.Ephemeral {
 				return diag.Errorf("parameter can't be immutable and ephemeral")
@@ -144,38 +153,24 @@ func parameterDataSource() *schema.Resource {
 				return diag.Errorf("ephemeral parameter requires the default property")
 			}
 
-			// TODO: Should we move this into the Valid() function on
-			//  Parameter?
-			if len(parameter.Validation) == 1 {
-				validation := &parameter.Validation[0]
-				err = validation.Valid(parameter.Type, value)
-				if err != nil {
-					return diag.FromErr(err)
-				}
-			}
-
-			// Validate options
-			_, parameter.FormType, err = ValidateFormType(parameter.Type, len(parameter.Option), parameter.FormType)
-			if err != nil {
-				return diag.Diagnostics{
-					{
-						Severity:      diag.Error,
-						Summary:       "Invalid form_type for parameter",
-						Detail:        err.Error(),
-						AttributePath: cty.Path{cty.GetAttrStep{Name: "form_type"}},
-					},
-				}
+			var input *string
+			envValue, ok := os.LookupEnv(ParameterEnvironmentVariable(parameter.Name))
+			if ok {
+				input = &envValue
 			}
-			// Set the form_type back in case the value was changed.
-			// Eg via a default. If a user does not specify, a default value
-			// is used and saved.
-			rd.Set("form_type", parameter.FormType)
 
-			diags := parameter.Valid()
+			mode := os.Getenv(ValidationModeEnvVar)
+			value, diags := parameter.Valid(input, ValidationMode(mode))
 			if diags.HasError() {
 				return diags
 			}
 
+			// Always set back the value, as it can be sourced from the default
+			rd.Set("value", value)
+
+			// Set the form_type as it could have changed in the validation.
+			rd.Set("form_type", parameter.FormType)
+
 			return nil
 		},
 		Schema: map[string]*schema.Schema{
@@ -389,37 +384,63 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int
 	return vArr, nil
 }
 
-func valueIsType(typ OptionType, value string, attrPath cty.Path) diag.Diagnostics {
+func valueIsType(typ OptionType, value string) error {
 	switch typ {
 	case OptionTypeNumber:
 		_, err := strconv.ParseFloat(value, 64)
 		if err != nil {
-			return diag.Errorf("%q is not a number", value)
+			return fmt.Errorf("%q is not a number", value)
 		}
 	case OptionTypeBoolean:
 		_, err := strconv.ParseBool(value)
 		if err != nil {
-			return diag.Errorf("%q is not a bool", value)
+			return fmt.Errorf("%q is not a bool", value)
 		}
 	case OptionTypeListString:
-		_, diags := valueIsListString(value, attrPath)
-		if diags.HasError() {
-			return diags
+		_, err := valueIsListString(value)
+		if err != nil {
+			return err
 		}
 	case OptionTypeString:
 		// Anything is a string!
 	default:
-		return diag.Errorf("invalid type %q", typ)
+		return fmt.Errorf("invalid type %q", typ)
 	}
 	return nil
 }
 
-func (v *Parameter) Valid() diag.Diagnostics {
+func (v *Parameter) Valid(input *string, mode ValidationMode) (string, diag.Diagnostics) {
+	if mode != ValidationModeDefault && mode != ValidationModeTemplateImport {
+		return "", diag.Diagnostics{
+			{
+				Severity: diag.Error,
+				Summary:  "Invalid validation mode",
+				Detail:   fmt.Sprintf("validation mode %q is not supported, use %q, or %q", mode, ValidationModeDefault, ValidationModeTemplateImport),
+			},
+		}
+	}
+
+	var err error
+	var optionType OptionType
+
+	value := input
+	if input == nil {
+		value = v.Default
+	}
+
+	// TODO: When empty values want to be rejected, uncomment this.
+	//   coder/coder should update to use template import mode first,
+	//   before this is uncommented.
+	//if value == nil && mode == ValidationModeDefault {
+	//	var empty string
+	//	value = &empty
+	//}
+
 	// optionType might differ from parameter.Type. This is ok, and parameter.Type
 	// should be used for the value type, and optionType for options.
-	optionType, _, err := ValidateFormType(v.Type, len(v.Option), v.FormType)
+	optionType, v.FormType, err = ValidateFormType(v.Type, len(v.Option), v.FormType)
 	if err != nil {
-		return diag.Diagnostics{
+		return "", diag.Diagnostics{
 			{
 				Severity:      diag.Error,
 				Summary:       "Invalid form_type for parameter",
@@ -429,53 +450,151 @@ func (v *Parameter) Valid() diag.Diagnostics {
 		}
 	}
 
-	optionNames := map[string]any{}
-	optionValues := map[string]any{}
-	if len(v.Option) > 0 {
-		for _, option := range v.Option {
-			_, exists := optionNames[option.Name]
-			if exists {
-				return diag.Diagnostics{{
+	optionValues, diags := v.ValidOptions(optionType, mode)
+	if diags.HasError() {
+		return "", diags
+	}
+
+	if mode == ValidationModeTemplateImport && v.Default != nil {
+		// Template import should validate the default value.
+		err := valueIsType(v.Type, *v.Default)
+		if err != nil {
+			return "", diag.Diagnostics{
+				{
+					Severity:      diag.Error,
+					Summary:       fmt.Sprintf("Default value is not of type %q", v.Type),
+					Detail:        err.Error(),
+					AttributePath: defaultValuePath,
+				},
+			}
+		}
+
+		d := v.validValue(*v.Default, optionType, optionValues, defaultValuePath)
+		if d.HasError() {
+			return "", d
+		}
+	}
+
+	// TODO: This is a bit of a hack. The current behavior states if validation
+	//   is given, then apply validation to unset values.
+	//   This should be removed, and all values should be validated. Meaning
+	//   value == nil should not be accepted in the first place.
+	if len(v.Validation) > 0 && value == nil {
+		empty := ""
+		value = &empty
+	}
+
+	// Value is only validated if it is set. If it is unset, validation
+	// is skipped.
+	if value != nil {
+		d := v.validValue(*value, optionType, optionValues, cty.Path{})
+		if d.HasError() {
+			return "", d
+		}
+
+		err = valueIsType(v.Type, *value)
+		if err != nil {
+			return "", diag.Diagnostics{
+				{
 					Severity: diag.Error,
-					Summary:  "Option names must be unique.",
-					Detail:   fmt.Sprintf("multiple options found with the same name %q", option.Name),
+					Summary:  fmt.Sprintf("Parameter value is not of type %q", v.Type),
+					Detail:   err.Error(),
 				},
-				}
 			}
-			_, exists = optionValues[option.Value]
-			if exists {
-				return diag.Diagnostics{
-					{
-						Severity: diag.Error,
-						Summary:  "Option values must be unique.",
-						Detail:   fmt.Sprintf("multiple options found with the same value %q", option.Value),
-					},
+		}
+	}
+
+	if value == nil {
+		// The previous behavior is to always write an empty string
+		return "", nil
+	}
+
+	return *value, nil
+}
+
+func (v *Parameter) ValidOptions(optionType OptionType, mode ValidationMode) (map[string]struct{}, diag.Diagnostics) {
+	optionNames := map[string]struct{}{}
+	optionValues := map[string]struct{}{}
+
+	var diags diag.Diagnostics
+	for _, option := range v.Option {
+		_, exists := optionNames[option.Name]
+		if exists {
+			return nil, diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Option names must be unique.",
+				Detail:   fmt.Sprintf("multiple options found with the same name %q", option.Name),
+			}}
+		}
+
+		_, exists = optionValues[option.Value]
+		if exists {
+			return nil, diag.Diagnostics{{
+				Severity: diag.Error,
+				Summary:  "Option values must be unique.",
+				Detail:   fmt.Sprintf("multiple options found with the same value %q", option.Value),
+			}}
+		}
+
+		err := valueIsType(optionType, option.Value)
+		if err != nil {
+			diags = append(diags, diag.Diagnostic{
+				Severity: diag.Error,
+				Summary:  fmt.Sprintf("Option %q with value=%q is not of type %q", option.Name, option.Value, optionType),
+				Detail:   err.Error(),
+			})
+			continue
+		}
+		optionValues[option.Value] = struct{}{}
+		optionNames[option.Name] = struct{}{}
+
+		if mode == ValidationModeTemplateImport {
+			opDiags := v.validValue(option.Value, optionType, nil, cty.Path{})
+			if opDiags.HasError() {
+				for i := range opDiags {
+					opDiags[i].Summary = fmt.Sprintf("Option %q: %s", option.Name, opDiags[i].Summary)
 				}
+				diags = append(diags, opDiags...)
 			}
-			diags := valueIsType(optionType, option.Value, cty.Path{})
-			if diags.HasError() {
-				return diags
-			}
-			optionValues[option.Value] = nil
-			optionNames[option.Name] = nil
 		}
 	}
 
-	if v.Default != "" && len(v.Option) > 0 {
+	if diags.HasError() {
+		return nil, diags
+	}
+	return optionValues, nil
+}
+
+func (v *Parameter) validValue(value string, optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics {
+	// name is used for constructing more precise error messages.
+	name := "Value"
+	if path.Equals(defaultValuePath) {
+		name = "Default value"
+	}
+
+	// First validate if the value is a valid option
+	if len(optionValues) > 0 {
 		if v.Type == OptionTypeListString && optionType == OptionTypeString {
 			// If the type is list(string) and optionType is string, we have
 			// to ensure all elements of the default exist as options.
-			defaultValues, diags := valueIsListString(v.Default, cty.Path{cty.GetAttrStep{Name: "default"}})
-			if diags.HasError() {
-				return diags
+			listValues, err := valueIsListString(value)
+			if err != nil {
+				return diag.Diagnostics{
+					{
+						Severity:      diag.Error,
+						Summary:       "When using list(string) type, value must be a json encoded list of strings",
+						Detail:        err.Error(),
+						AttributePath: defaultValuePath,
+					},
+				}
 			}
 
 			// missing is used to construct a more helpful error message
 			var missing []string
-			for _, defaultValue := range defaultValues {
-				_, defaultIsValid := optionValues[defaultValue]
-				if !defaultIsValid {
-					missing = append(missing, defaultValue)
+			for _, listValue := range listValues {
+				_, isValid := optionValues[listValue]
+				if !isValid {
+					missing = append(missing, listValue)
 				}
 			}
 
@@ -483,30 +602,49 @@ func (v *Parameter) Valid() diag.Diagnostics {
 				return diag.Diagnostics{
 					{
 						Severity: diag.Error,
-						Summary:  "Default values must be a valid option",
+						Summary:  fmt.Sprintf("%ss must be a valid option", name),
 						Detail: fmt.Sprintf(
-							"default value %q is not a valid option, values %q are missing from the options",
-							v.Default, strings.Join(missing, ", "),
+							"%s %q is not a valid option, values %q are missing from the options",
+							name, value, strings.Join(missing, ", "),
 						),
-						AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}},
+						AttributePath: defaultValuePath,
 					},
 				}
 			}
 		} else {
-			_, defaultIsValid := optionValues[v.Default]
-			if !defaultIsValid {
+			_, isValid := optionValues[value]
+			if !isValid {
+				extra := ""
+				if value == "" {
+					extra = ". The value is empty, did you forget to set it with a default or from user input?"
+				}
 				return diag.Diagnostics{
 					{
 						Severity:      diag.Error,
-						Summary:       "Default value must be a valid option",
-						Detail:        fmt.Sprintf("the value %q must be defined as one of options", v.Default),
-						AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}},
+						Summary:       fmt.Sprintf("%s must be a valid option%s", name, extra),
+						Detail:        fmt.Sprintf("the value %q must be defined as one of options", value),
+						AttributePath: path,
 					},
 				}
 			}
 		}
 	}
 
+	if len(v.Validation) == 1 {
+		validCheck := &v.Validation[0]
+		err := validCheck.Valid(v.Type, value)
+		if err != nil {
+			return diag.Diagnostics{
+				{
+					Severity:      diag.Error,
+					Summary:       fmt.Sprintf("Invalid parameter %s according to 'validation' block", strings.ToLower(name)),
+					Detail:        err.Error(),
+					AttributePath: path,
+				},
+			}
+		}
+	}
+
 	return nil
 }
 
@@ -570,18 +708,11 @@ func (v *Validation) Valid(typ OptionType, value string) error {
 	return nil
 }
 
-func valueIsListString(value string, path cty.Path) ([]string, diag.Diagnostics) {
+func valueIsListString(value string) ([]string, error) {
 	var items []string
 	err := json.Unmarshal([]byte(value), &items)
 	if err != nil {
-		return nil, diag.Diagnostics{
-			{
-				Severity:      diag.Error,
-				Summary:       "When using list(string) type, value must be a json encoded list of strings",
-				Detail:        fmt.Sprintf("value %q is not a valid list of strings", value),
-				AttributePath: path,
-			},
-		}
+		return nil, fmt.Errorf("value %q is not a valid list of strings", value)
 	}
 	return items, nil
 }
diff --git a/provider/parameter_test.go b/provider/parameter_test.go
index 32877c2b..b2558cb5 100644
--- a/provider/parameter_test.go
+++ b/provider/parameter_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/coder/terraform-provider-coder/v2/provider"
@@ -688,6 +689,168 @@ data "coder_parameter" "region" {
 	}
 }
 
+func TestParameterValidation(t *testing.T) {
+	t.Parallel()
+	opts := func(vals ...string) []provider.Option {
+		options := make([]provider.Option, 0, len(vals))
+		for _, val := range vals {
+			options = append(options, provider.Option{
+				Name:  val,
+				Value: val,
+			})
+		}
+		return options
+	}
+
+	for _, tc := range []struct {
+		Name        string
+		Parameter   provider.Parameter
+		Value       string
+		ExpectError *regexp.Regexp
+	}{
+		{
+			Name: "ValidStringParameter",
+			Parameter: provider.Parameter{
+				Type: "string",
+			},
+			Value: "alpha",
+		},
+		// Test invalid states
+		{
+			Name: "InvalidFormType",
+			Parameter: provider.Parameter{
+				Type:     "string",
+				Option:   opts("alpha", "bravo", "charlie"),
+				FormType: provider.ParameterFormTypeSlider,
+			},
+			Value:       "alpha",
+			ExpectError: regexp.MustCompile("Invalid form_type for parameter"),
+		},
+		{
+			Name: "NotInOptions",
+			Parameter: provider.Parameter{
+				Type:   "string",
+				Option: opts("alpha", "bravo", "charlie"),
+			},
+			Value:       "delta", // not in option set
+			ExpectError: regexp.MustCompile("Value must be a valid option"),
+		},
+		{
+			Name: "NumberNotInOptions",
+			Parameter: provider.Parameter{
+				Type:   "number",
+				Option: opts("1", "2", "3"),
+			},
+			Value:       "0", // not in option set
+			ExpectError: regexp.MustCompile("Value must be a valid option"),
+		},
+		{
+			Name: "NonUniqueOptionNames",
+			Parameter: provider.Parameter{
+				Type:   "string",
+				Option: opts("alpha", "alpha"),
+			},
+			Value:       "alpha",
+			ExpectError: regexp.MustCompile("Option names must be unique"),
+		},
+		{
+			Name: "NonUniqueOptionValues",
+			Parameter: provider.Parameter{
+				Type: "string",
+				Option: []provider.Option{
+					{Name: "Alpha", Value: "alpha"},
+					{Name: "AlphaAgain", Value: "alpha"},
+				},
+			},
+			Value:       "alpha",
+			ExpectError: regexp.MustCompile("Option values must be unique"),
+		},
+		{
+			Name: "IncorrectValueTypeOption",
+			Parameter: provider.Parameter{
+				Type:   "number",
+				Option: opts("not-a-number"),
+			},
+			Value:       "5",
+			ExpectError: regexp.MustCompile("is not a number"),
+		},
+		{
+			Name: "IncorrectValueType",
+			Parameter: provider.Parameter{
+				Type: "number",
+			},
+			Value:       "not-a-number",
+			ExpectError: regexp.MustCompile("Parameter value is not of type \"number\""),
+		},
+		{
+			Name: "NotListStringDefault",
+			Parameter: provider.Parameter{
+				Type:    "list(string)",
+				Default: ptr("not-a-list"),
+			},
+			ExpectError: regexp.MustCompile("not a valid list of strings"),
+		},
+		{
+			Name: "NotListStringDefault",
+			Parameter: provider.Parameter{
+				Type: "list(string)",
+			},
+			Value:       "not-a-list",
+			ExpectError: regexp.MustCompile("not a valid list of strings"),
+		},
+		{
+			Name: "DefaultListStringNotInOptions",
+			Parameter: provider.Parameter{
+				Type:     "list(string)",
+				Default:  ptr(`["red", "yellow", "black"]`),
+				Option:   opts("red", "blue", "green"),
+				FormType: provider.ParameterFormTypeMultiSelect,
+			},
+			Value:       `["red", "yellow", "black"]`,
+			ExpectError: regexp.MustCompile("is not a valid option, values \"yellow, black\" are missing from the options"),
+		},
+		{
+			Name: "ListStringNotInOptions",
+			Parameter: provider.Parameter{
+				Type:     "list(string)",
+				Default:  ptr(`["red"]`),
+				Option:   opts("red", "blue", "green"),
+				FormType: provider.ParameterFormTypeMultiSelect,
+			},
+			Value:       `["red", "yellow", "black"]`,
+			ExpectError: regexp.MustCompile("is not a valid option, values \"yellow, black\" are missing from the options"),
+		},
+		{
+			Name: "InvalidMiniumum",
+			Parameter: provider.Parameter{
+				Type:    "number",
+				Default: ptr("5"),
+				Validation: []provider.Validation{{
+					Min:   10,
+					Error: "must be greater than 10",
+				}},
+			},
+			ExpectError: regexp.MustCompile("must be greater than 10"),
+		},
+	} {
+		tc := tc
+		t.Run(tc.Name, func(t *testing.T) {
+			t.Parallel()
+			value := &tc.Value
+			_, diags := tc.Parameter.Valid(value, provider.ValidationModeDefault)
+			if tc.ExpectError != nil {
+				require.True(t, diags.HasError())
+				errMsg := fmt.Sprintf("%+v", diags[0]) // close enough
+				require.Truef(t, tc.ExpectError.MatchString(errMsg), "got: %s", errMsg)
+			} else {
+				if !assert.False(t, diags.HasError()) {
+					t.Logf("got: %+v", diags[0])
+				}
+			}
+		})
+	}
+}
+
 // TestParameterValidationEnforcement tests various parameter states and the
 // validation enforcement that should be applied to them. The table is described
 // by a markdown table. This is done so that the test cases can be more easily
@@ -703,10 +866,6 @@ func TestParameterValidationEnforcement(t *testing.T) {
 	// - Validation logic does not apply to the default if a value is given
 	//	- [NumIns/DefInv] So the default can be invalid if an input value is valid.
 	//	  The value is therefore not really optional, but it is marked as such.
-	// - [NumInsNotOptsVal | NumsInsNotOpts] values do not need to be in the option set?
-	// - [NumInsNotNum] number params do not require the value to be a number
-	// - [LStrInsNotList] list(string) do not require the value to be a list(string)
-	//	- Same with [MulInsNotListOpts]
 	table, err := os.ReadFile("testdata/parameter_table.md")
 	require.NoError(t, err)
 
@@ -719,7 +878,8 @@ func TestParameterValidationEnforcement(t *testing.T) {
 		Validation  *provider.Validation
 		OutputValue string
 		Optional    bool
-		Error       *regexp.Regexp
+		CreateError *regexp.Regexp
+		ImportError *regexp.Regexp
 	}
 
 	rows := make([]row, 0)
@@ -750,6 +910,19 @@ func TestParameterValidationEnforcement(t *testing.T) {
 				t.Fatalf("failed to parse error column %q: %v", columns[9], err)
 			}
 		}
+
+		var imerr *regexp.Regexp
+		if columns[10] != "" {
+			if columns[10] == "=" {
+				imerr = rerr
+			} else {
+				imerr, err = regexp.Compile(columns[10])
+				if err != nil {
+					t.Fatalf("failed to parse error column %q: %v", columns[10], err)
+				}
+			}
+		}
+
 		var options []string
 		if columns[4] != "" {
 			options = strings.Split(columns[4], ",")
@@ -796,7 +969,8 @@ func TestParameterValidationEnforcement(t *testing.T) {
 			Validation:  validation,
 			OutputValue: columns[7],
 			Optional:    optional,
-			Error:       rerr,
+			CreateError: rerr,
+			ImportError: imerr,
 		})
 	}
 
@@ -815,9 +989,9 @@ func TestParameterValidationEnforcement(t *testing.T) {
 					t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue)
 				}
 
-				if row.Error != nil {
+				if row.CreateError != nil && row.ImportError != nil {
 					if row.OutputValue != "" {
-						t.Errorf("output value %q should not be set if error is set", row.OutputValue)
+						t.Errorf("output value %q should not be set if both errors are set", row.OutputValue)
 					}
 				}
 
@@ -861,42 +1035,56 @@ func TestParameterValidationEnforcement(t *testing.T) {
 
 				cfg.WriteString("}\n")
 
-				resource.Test(t, resource.TestCase{
-					ProviderFactories: coderFactory(),
-					IsUnitTest:        true,
-					Steps: []resource.TestStep{{
-						Config:      cfg.String(),
-						ExpectError: row.Error,
-						Check: func(state *terraform.State) error {
-							require.Len(t, state.Modules, 1)
-							require.Len(t, state.Modules[0].Resources, 1)
-							param := state.Modules[0].Resources["data.coder_parameter.parameter"]
-							require.NotNil(t, param)
+				for _, mode := range []provider.ValidationMode{provider.ValidationModeDefault, provider.ValidationModeTemplateImport} {
+					name := string(mode)
+					if mode == provider.ValidationModeDefault {
+						name = "create"
+					}
+					t.Run(name, func(t *testing.T) {
+						t.Setenv("CODER_VALIDATION_MODE", string(mode))
+						rerr := row.CreateError
+						if mode == provider.ValidationModeTemplateImport {
+							rerr = row.ImportError
+						}
+
+						resource.Test(t, resource.TestCase{
+							ProviderFactories: coderFactory(),
+							IsUnitTest:        true,
+							Steps: []resource.TestStep{{
+								Config:      cfg.String(),
+								ExpectError: rerr,
+								Check: func(state *terraform.State) error {
+									require.Len(t, state.Modules, 1)
+									require.Len(t, state.Modules[0].Resources, 1)
+									param := state.Modules[0].Resources["data.coder_parameter.parameter"]
+									require.NotNil(t, param)
 
-							if row.Default == "" {
-								_, ok := param.Primary.Attributes["default"]
-								require.False(t, ok, "default should not be set")
-							} else {
-								require.Equal(t, strings.Trim(row.Default, `"`), param.Primary.Attributes["default"])
-							}
+									if row.Default == "" {
+										_, ok := param.Primary.Attributes["default"]
+										require.False(t, ok, "default should not be set")
+									} else {
+										require.Equal(t, strings.Trim(row.Default, `"`), param.Primary.Attributes["default"])
+									}
 
-							if row.OutputValue == "" {
-								_, ok := param.Primary.Attributes["value"]
-								require.False(t, ok, "output value should not be set")
-							} else {
-								require.Equal(t, strings.Trim(row.OutputValue, `"`), param.Primary.Attributes["value"])
-							}
+									if row.OutputValue == "" {
+										_, ok := param.Primary.Attributes["value"]
+										require.False(t, ok, "output value should not be set")
+									} else {
+										require.Equal(t, strings.Trim(row.OutputValue, `"`), param.Primary.Attributes["value"])
+									}
 
-							for key, expected := range map[string]string{
-								"optional": strconv.FormatBool(row.Optional),
-							} {
-								require.Equal(t, expected, param.Primary.Attributes[key], "optional")
-							}
+									for key, expected := range map[string]string{
+										"optional": strconv.FormatBool(row.Optional),
+									} {
+										require.Equal(t, expected, param.Primary.Attributes[key], "optional")
+									}
 
-							return nil
-						},
-					}},
-				})
+									return nil
+								},
+							}},
+						})
+					})
+				}
 			})
 		}
 	}
@@ -1096,3 +1284,7 @@ func TestParameterWithManyOptions(t *testing.T) {
 		}},
 	})
 }
+
+func ptr[T any](v T) *T {
+	return &v
+}
diff --git a/provider/testdata/parameter_table.md b/provider/testdata/parameter_table.md
index f7645efa..cf51b8cd 100644
--- a/provider/testdata/parameter_table.md
+++ b/provider/testdata/parameter_table.md
@@ -1,79 +1,80 @@
-| Name                 | Type          | Input     | Default | Options           | Validation | -> | Output | Optional | Error        |
-|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|--------------|
-|                      | Empty Vals    |           |         |                   |            |    |        |          |              |
-| Empty                | string,number |           |         |                   |            |    | ""     | false    |              |
-| EmptyDupeOps         | string,number |           |         | 1,1,1             |            |    |        |          | unique       |
-| EmptyList            | list(string)  |           |         |                   |            |    | ""     | false    |              |
-| EmptyListDupeOpts    | list(string)  |           |         | ["a"],["a"]       |            |    |        |          | unique       |
-| EmptyMulti           | tag-select    |           |         |                   |            |    | ""     | false    |              |
-| EmptyOpts            | string,number |           |         | 1,2,3             |            |    | ""     | false    |              |
-| EmptyRegex           | string        |           |         |                   | world      |    |        |          | regex error  |
-| EmptyMin             | number        |           |         |                   | 1-10       |    |        |          | 1 <  < 10    |
-| EmptyMinOpt          | number        |           |         | 1,2,3             | 2-5        |    |        |          | 2 <  < 5     |
-| EmptyRegexOpt        | string        |           |         | "hello","goodbye" | goodbye    |    |        |          | regex error  |
-| EmptyRegexOk         | string        |           |         |                   | .*         |    | ""     | false    |              |
-|                      |               |           |         |                   |            |    |        |          |              |
-|                      | Default Set   | No inputs |         |                   |            |    |        |          |              |
-| NumDef               | number        |           | 5       |                   |            |    | 5      | true     |              |
-| NumDefVal            | number        |           | 5       |                   | 3-7        |    | 5      | true     |              |
-| NumDefInv            | number        |           | 5       |                   | 10-        |    |        |          | 10 < 5 < 0   |
-| NumDefOpts           | number        |           | 5       | 1,3,5,7           | 2-6        |    | 5      | true     |              |
-| NumDefNotOpts        | number        |           | 5       | 1,3,7,9           | 2-6        |    |        |          | valid option |
-| NumDefInvOpt         | number        |           | 5       | 1,3,5,7           | 6-10       |    |        |          | 6 < 5 < 10   |
-| NumDefNotNum         | number        |           | a       |                   |            |    |        |          | a number     |
-| NumDefOptsNotNum     | number        |           | 1       | 1,a,2             |            |    |        |          | a number     |
-|                      |               |           |         |                   |            |    |        |          |              |
-| StrDef               | string        |           | hello   |                   |            |    | hello  | true     |              |
-| StrDefInv            | string        |           | hello   |                   | world      |    |        |          | regex error  |
-| StrDefOpts           | string        |           | a       | a,b,c             |            |    | a      | true     |              |
-| StrDefNotOpts        | string        |           | a       | b,c,d             |            |    |        |          | valid option |
-| StrDefValOpts        | string        |           | a       | a,b,c,d,e,f       | [a-c]      |    | a      | true     |              |
-| StrDefInvOpt         | string        |           | d       | a,b,c,d,e,f       | [a-c]      |    |        |          | regex error  |
-|                      |               |           |         |                   |            |    |        |          |              |
-| LStrDef              | list(string)  |           | ["a"]   |                   |            |    | ["a"]  | true     |              |
-| LStrDefOpts          | list(string)  |           | ["a"]   | ["a"], ["b"]      |            |    | ["a"]  | true     |              |
-| LStrDefNotOpts       | list(string)  |           | ["a"]   | ["b"], ["c"]      |            |    |        |          | valid option |
-|                      |               |           |         |                   |            |    |        |          |              |
-| MulDef               | tag-select    |           | ["a"]   |                   |            |    | ["a"]  | true     |              |
-| MulDefOpts           | multi-select  |           | ["a"]   | a,b               |            |    | ["a"]  | true     |              |
-| MulDefNotOpts        | multi-select  |           | ["a"]   | b,c               |            |    |        |          | valid option |
-|                      |               |           |         |                   |            |    |        |          |              |
-|                      | Input Vals    |           |         |                   |            |    |        |          |              |
-| NumIns               | number        | 3         |         |                   |            |    | 3      | false    |              |
-| NumInsNotNum         | number        | a         |         |                   |            |    | a      | false    |              |
-| NumInsNotNumInv      | number        | a         |         |                   | 1-3        |    |        |          | 1 < a < 3    |
-| NumInsDef            | number        | 3         | 5       |                   |            |    | 3      | true     |              |
-| NumIns/DefInv        | number        | 3         | 5       |                   | 1-3        |    | 3      | true     |              |
-| NumIns=DefInv        | number        | 5         | 5       |                   | 1-3        |    |        |          | 1 < 5 < 3    |
-| NumInsOpts           | number        | 3         | 5       | 1,2,3,4,5         | 1-3        |    | 3      | true     |              |
-| NumInsNotOptsVal     | number        | 3         | 5       | 1,2,4,5           | 1-3        |    | 3      | true     |              |
-| NumInsNotOptsInv     | number        | 3         | 5       | 1,2,4,5           | 1-2        |    |        | true     | 1 < 3 < 2    |
-| NumInsNotOpts        | number        | 3         | 5       | 1,2,4,5           |            |    | 3      | true     |              |
-| NumInsNotOpts/NoDef  | number        | 3         |         | 1,2,4,5           |            |    | 3      | false    |              |
-|                      |               |           |         |                   |            |    |        |          |              |
-| StrIns               | string        | c         |         |                   |            |    | c      | false    |              |
-| StrInsDupeOpts       | string        | c         |         | a,b,c,c           |            |    |        |          | unique       |
-| StrInsDef            | string        | c         | e       |                   |            |    | c      | true     |              |
-| StrIns/DefInv        | string        | c         | e       |                   | [a-c]      |    | c      | true     |              |
-| StrIns=DefInv        | string        | e         | e       |                   | [a-c]      |    |        |          | regex error  |
-| StrInsOpts           | string        | c         | e       | a,b,c,d,e         | [a-c]      |    | c      | true     |              |
-| StrInsNotOptsVal     | string        | c         | e       | a,b,d,e           | [a-c]      |    | c      | true     |              |
-| StrInsNotOptsInv     | string        | c         | e       | a,b,d,e           | [a-b]      |    |        |          | regex error  |
-| StrInsNotOpts        | string        | c         | e       | a,b,d,e           |            |    | c      | true     |              |
-| StrInsNotOpts/NoDef  | string        | c         |         | a,b,d,e           |            |    | c      | false    |              |
-| StrInsBadVal         | string        | c         |         | a,b,c,d,e         | 1-10       |    |        |          | min cannot   |
-|                      |               |           |         |                   |            |    |        |          |              |
-|                      | list(string)  |           |         |                   |            |    |        |          |              |
-| LStrIns              | list(string)  | ["c"]     |         |                   |            |    | ["c"]  | false    |              |
-| LStrInsNotList       | list(string)  | c         |         |                   |            |    | c      | false    |              |
-| LStrInsDef           | list(string)  | ["c"]     | ["e"]   |                   |            |    | ["c"]  | true     |              |
-| LStrIns/DefInv       | list(string)  | ["c"]     | ["e"]   |                   | [a-c]      |    |        |          | regex cannot |
-| LStrInsOpts          | list(string)  | ["c"]     | ["e"]   | ["c"],["d"],["e"] |            |    | ["c"]  | true     |              |
-| LStrInsNotOpts       | list(string)  | ["c"]     | ["e"]   | ["d"],["e"]       |            |    | ["c"]  | true     |              |
-| LStrInsNotOpts/NoDef | list(string)  | ["c"]     |         | ["d"],["e"]       |            |    | ["c"]  | false    |              |
-|                      |               |           |         |                   |            |    |        |          |              |
-| MulInsOpts           | multi-select  | ["c"]     | ["e"]   | c,d,e             |            |    | ["c"]  | true     |              |
-| MulInsNotListOpts    | multi-select  | c         | ["e"]   | c,d,e             |            |    | c      | true     |              |
-| MulInsNotOpts        | multi-select  | ["c"]     | ["e"]   | d,e               |            |    | ["c"]  | true     |              |
-| MulInsNotOpts/NoDef  | multi-select  | ["c"]     |         | d,e               |            |    | ["c"]  | false    |              |
-| MulInsInvOpts        | multi-select  | ["c"]     | ["e"]   | c,d,e             | [a-c]      |    |        |          | regex cannot |
\ No newline at end of file
+| Name                 | Type          | Input     | Default | Options           | Validation | -> | Output | Optional | ErrorCreate  | ErrorImport   |
+|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|--------------|---------------|
+|                      | Empty Vals    |           |         |                   |            |    |        |          |              |               |
+| Empty                | string,number |           |         |                   |            |    | ""     | false    |              |               |
+| EmptyDupeOps         | string,number |           |         | 1,1,1             |            |    |        |          | unique       | =             |
+| EmptyList            | list(string)  |           |         |                   |            |    | ""     | false    |              |               |
+| EmptyListDupeOpts    | list(string)  |           |         | ["a"],["a"]       |            |    |        |          | unique       | =             |
+| EmptyMulti           | tag-select    |           |         |                   |            |    | ""     | false    |              |               |
+| EmptyOpts            | string,number |           |         | 1,2,3             |            |    | ""     | false    |              |               |
+| EmptyRegex           | string        |           |         |                   | world      |    |        |          | regex error  | =             |
+| EmptyMin             | number        |           |         |                   | 1-10       |    |        |          | 1 <  < 10    | =             |
+| EmptyMinOpt          | number        |           |         | 1,2,3             | 2-5        |    |        |          | 2 <  < 5     | 2 < 1 < 5     |
+| EmptyRegexOpt        | string        |           |         | "hello","goodbye" | goodbye    |    |        |          | regex error  | regex error   |
+| EmptyRegexOk         | string        |           |         |                   | .*         |    | ""     | false    |              |               |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+|                      | Default Set   | No inputs |         |                   |            |    |        |          |              |               |
+| NumDef               | number        |           | 5       |                   |            |    | 5      | true     |              |               |
+| NumDefVal            | number        |           | 5       |                   | 3-7        |    | 5      | true     |              |               |
+| NumDefInv            | number        |           | 5       |                   | 10-        |    |        |          | 10 < 5 < 0   | =             |
+| NumDefOpts           | number        |           | 5       | 1,3,5,7           | 2-6        |    | 5      | true     |              | 2 < 1 < 6     |
+| NumDefNotOpts        | number        |           | 5       | 1,3,7,9           | 2-6        |    |        |          | valid option | 2 < 1 < 6     |
+| NumDefInvOpt         | number        |           | 5       | 1,3,5,7           | 6-10       |    |        |          | 6 < 5 < 10   | =             |
+| NumDefNotNum         | number        |           | a       |                   |            |    |        |          | a number     | =             |
+| NumDefOptsNotNum     | number        |           | 1       | 1,a,2             |            |    |        |          | a number     | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+| StrDef               | string        |           | hello   |                   |            |    | hello  | true     |              |               |
+| StrDefInv            | string        |           | hello   |                   | world      |    |        |          | regex error  | =             |
+| StrDefOpts           | string        |           | a       | a,b,c             |            |    | a      | true     |              |               |
+| StrDefNotOpts        | string        |           | a       | b,c,d             |            |    |        |          | valid option | =             |
+| StrDefValOpts        | string        |           | a       | a,b,c,d,e,f       | [a-c]      |    | a      | true     |              | value "d"     |
+| StrDefInvOpt         | string        |           | d       | a,b,c,d,e,f       | [a-c]      |    |        |          | regex error  | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+| LStrDef              | list(string)  |           | ["a"]   |                   |            |    | ["a"]  | true     |              |               |
+| LStrDefOpts          | list(string)  |           | ["a"]   | ["a"], ["b"]      |            |    | ["a"]  | true     |              |               |
+| LStrDefNotOpts       | list(string)  |           | ["a"]   | ["b"], ["c"]      |            |    |        |          | valid option | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+| MulDef               | tag-select    |           | ["a"]   |                   |            |    | ["a"]  | true     |              |               |
+| MulDefOpts           | multi-select  |           | ["a"]   | a,b               |            |    | ["a"]  | true     |              |               |
+| MulDefNotOpts        | multi-select  |           | ["a"]   | b,c               |            |    |        |          | valid option | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+|                      | Input Vals    |           |         |                   |            |    |        |          |              |               |
+| NumIns               | number        | 3         |         |                   |            |    | 3      | false    |              |               |
+| NumInsOptsNaN        | number        | 3         | 5       | a,1,2,3,4,5       | 1-3        |    |        |          | a number     | =             |
+| NumInsNotNum         | number        | a         |         |                   |            |    | a      | false    |              | =             |
+| NumInsNotNumInv      | number        | a         |         |                   | 1-3        |    |        |          | 1 < a < 3    | =             |
+| NumInsDef            | number        | 3         | 5       |                   |            |    | 3      | true     |              |               |
+| NumIns/DefInv        | number        | 3         | 5       |                   | 1-3        |    | 3      | true     |              | 1 < 5 < 3     |
+| NumIns=DefInv        | number        | 5         | 5       |                   | 1-3        |    |        |          | 1 < 5 < 3    | =             |
+| NumInsOpts           | number        | 3         | 5       | 1,2,3,4,5         | 1-3        |    | 3      | true     |              | 1 < 5 < 3     |
+| NumInsNotOptsVal     | number        | 3         | 5       | 1,2,4,5           | 1-3        |    | 3      | true     |              | 1 < 4 < 3     |
+| NumInsNotOptsInv     | number        | 3         | 5       | 1,2,4,5           | 1-2        |    |        | true     | 1 < 3 < 2    | 1 < 4 < 2     |
+| NumInsNotOpts        | number        | 3         | 5       | 1,2,4,5           |            |    | 3      | true     |              | =             |
+| NumInsNotOpts/NoDef  | number        | 3         |         | 1,2,4,5           |            |    | 3      | false    |              | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+| StrIns               | string        | c         |         |                   |            |    | c      | false    |              |               |
+| StrInsDupeOpts       | string        | c         |         | a,b,c,c           |            |    |        |          | unique       | =             |
+| StrInsDef            | string        | c         | e       |                   |            |    | c      | true     |              |               |
+| StrIns/DefInv        | string        | c         | e       |                   | [a-c]      |    | c      | true     |              | default value |
+| StrIns=DefInv        | string        | e         | e       |                   | [a-c]      |    |        |          | regex error  | =             |
+| StrInsOpts           | string        | c         | e       | a,b,c,d,e         | [a-c]      |    | c      | true     |              | value "d"     |
+| StrInsNotOptsVal     | string        | c         | e       | a,b,d,e           | [a-c]      |    | c      | true     |              | value "d"     |
+| StrInsNotOptsInv     | string        | c         | e       | a,b,d,e           | [a-b]      |    |        |          | regex error  | regex error   |
+| StrInsNotOpts        | string        | c         | e       | a,b,d,e           |            |    | c      | true     |              | =             |
+| StrInsNotOpts/NoDef  | string        | c         |         | a,b,d,e           |            |    | c      | false    |              | =             |
+| StrInsBadVal         | string        | c         |         | a,b,c,d,e         | 1-10       |    |        |          | min cannot   | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+|                      | list(string)  |           |         |                   |            |    |        |          |              |               |
+| LStrIns              | list(string)  | ["c"]     |         |                   |            |    | ["c"]  | false    |              |               |
+| LStrInsNotList       | list(string)  | c         |         |                   |            |    | c      | false    |              | =             |
+| LStrInsDef           | list(string)  | ["c"]     | ["e"]   |                   |            |    | ["c"]  | true     |              |               |
+| LStrIns/DefInv       | list(string)  | ["c"]     | ["e"]   |                   | [a-c]      |    |        |          | regex cannot | =             |
+| LStrInsOpts          | list(string)  | ["c"]     | ["e"]   | ["c"],["d"],["e"] |            |    | ["c"]  | true     |              |               |
+| LStrInsNotOpts       | list(string)  | ["c"]     | ["e"]   | ["d"],["e"]       |            |    | ["c"]  | true     |              | =             |
+| LStrInsNotOpts/NoDef | list(string)  | ["c"]     |         | ["d"],["e"]       |            |    | ["c"]  | false    |              | =             |
+|                      |               |           |         |                   |            |    |        |          |              |               |
+| MulInsOpts           | multi-select  | ["c"]     | ["e"]   | c,d,e             |            |    | ["c"]  | true     |              |               |
+| MulInsNotListOpts    | multi-select  | c         | ["e"]   | c,d,e             |            |    | c      | true     |              | =             |
+| MulInsNotOpts        | multi-select  | ["c"]     | ["e"]   | d,e               |            |    | ["c"]  | true     |              | =             |
+| MulInsNotOpts/NoDef  | multi-select  | ["c"]     |         | d,e               |            |    | ["c"]  | false    |              | =             |
+| MulInsInvOpts        | multi-select  | ["c"]     | ["e"]   | c,d,e             | [a-c]      |    |        |          | regex cannot | =             |
\ No newline at end of file