diff --git a/docs/data-sources/workspace_preset.md b/docs/data-sources/workspace_preset.md
index cd4908c2..26e597e2 100644
--- a/docs/data-sources/workspace_preset.md
+++ b/docs/data-sources/workspace_preset.md
@@ -55,6 +55,7 @@ Required:
 Optional:
 
 - `expiration_policy` (Block Set, Max: 1) Configuration block that defines TTL (time-to-live) behavior for prebuilds. Use this to automatically invalidate and delete prebuilds after a certain period, ensuring they stay up-to-date. (see [below for nested schema](#nestedblock--prebuilds--expiration_policy))
+- `scheduling` (Block List, Max: 1) Configuration block that defines scheduling behavior for prebuilds. Use this to automatically adjust the number of prebuild instances based on a schedule. (see [below for nested schema](#nestedblock--prebuilds--scheduling))
 
 <a id="nestedblock--prebuilds--expiration_policy"></a>
 ### Nested Schema for `prebuilds.expiration_policy`
@@ -62,3 +63,22 @@ Optional:
 Required:
 
 - `ttl` (Number) Time in seconds after which an unclaimed prebuild is considered expired and eligible for cleanup.
+
+
+<a id="nestedblock--prebuilds--scheduling"></a>
+### Nested Schema for `prebuilds.scheduling`
+
+Required:
+
+- `schedule` (Block List, Min: 1) One or more schedule blocks that define when to scale the number of prebuild instances. (see [below for nested schema](#nestedblock--prebuilds--scheduling--schedule))
+- `timezone` (String) The timezone to use for the prebuild schedules (e.g., "UTC", "America/New_York"). 
+Timezone must be a valid timezone in the IANA timezone database. 
+See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a complete list of valid timezone identifiers and https://www.iana.org/time-zones for the official IANA timezone database.
+
+<a id="nestedblock--prebuilds--scheduling--schedule"></a>
+### Nested Schema for `prebuilds.scheduling.schedule`
+
+Required:
+
+- `cron` (String) A cron expression that defines when this schedule should be active. The cron expression must be in the format "* HOUR DOM MONTH DAY-OF-WEEK" where HOUR is 0-23, DOM (day-of-month) is 1-31, MONTH is 1-12, and DAY-OF-WEEK is 0-6 (Sunday-Saturday). The minute field must be "*" to ensure the schedule covers entire hours rather than specific minute intervals.
+- `instances` (Number) The number of prebuild instances to maintain during this schedule period.
diff --git a/integration/integration_test.go b/integration/integration_test.go
index 36612904..b075aebd 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -90,12 +90,17 @@ func TestIntegration(t *testing.T) {
 				// TODO (sasswart): the cli doesn't support presets yet.
 				// once it does, the value for workspace_parameter.value
 				// will be the preset value.
-				"workspace_parameter.value":                        `param value`,
-				"workspace_parameter.icon":                         `param icon`,
-				"workspace_preset.name":                            `preset`,
-				"workspace_preset.parameters.param":                `preset param value`,
-				"workspace_preset.prebuilds.instances":             `1`,
-				"workspace_preset.prebuilds.expiration_policy.ttl": `86400`,
+				"workspace_parameter.value":                                 `param value`,
+				"workspace_parameter.icon":                                  `param icon`,
+				"workspace_preset.name":                                     `preset`,
+				"workspace_preset.parameters.param":                         `preset param value`,
+				"workspace_preset.prebuilds.instances":                      `1`,
+				"workspace_preset.prebuilds.expiration_policy.ttl":          `86400`,
+				"workspace_preset.prebuilds.scheduling.timezone":            `UTC`,
+				"workspace_preset.prebuilds.scheduling.schedule0.cron":      `\* 8-18 \* \* 1-5`,
+				"workspace_preset.prebuilds.scheduling.schedule0.instances": `3`,
+				"workspace_preset.prebuilds.scheduling.schedule1.cron":      `\* 8-14 \* \* 6`,
+				"workspace_preset.prebuilds.scheduling.schedule1.instances": `1`,
 			},
 		},
 		{
diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf
index 50274fff..12344546 100644
--- a/integration/test-data-source/main.tf
+++ b/integration/test-data-source/main.tf
@@ -30,6 +30,17 @@ data "coder_workspace_preset" "preset" {
     expiration_policy {
       ttl = 86400
     }
+    scheduling {
+      timezone = "UTC"
+      schedule {
+        cron      = "* 8-18 * * 1-5"
+        instances = 3
+      }
+      schedule {
+        cron      = "* 8-14 * * 6"
+        instances = 1
+      }
+    }
   }
 }
 
@@ -56,6 +67,11 @@ locals {
     "workspace_preset.parameters.param" : data.coder_workspace_preset.preset.parameters.param,
     "workspace_preset.prebuilds.instances" : tostring(one(data.coder_workspace_preset.preset.prebuilds).instances),
     "workspace_preset.prebuilds.expiration_policy.ttl" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).expiration_policy).ttl),
+    "workspace_preset.prebuilds.scheduling.timezone" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).timezone),
+    "workspace_preset.prebuilds.scheduling.schedule0.cron" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[0].cron),
+    "workspace_preset.prebuilds.scheduling.schedule0.instances" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[0].instances),
+    "workspace_preset.prebuilds.scheduling.schedule1.cron" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[1].cron),
+    "workspace_preset.prebuilds.scheduling.schedule1.instances" : tostring(one(one(data.coder_workspace_preset.preset.prebuilds).scheduling).schedule[1].instances),
   }
 }
 
diff --git a/provider/helpers/schedule_validation.go b/provider/helpers/schedule_validation.go
new file mode 100644
index 00000000..c5a6972f
--- /dev/null
+++ b/provider/helpers/schedule_validation.go
@@ -0,0 +1,187 @@
+package helpers
+
+import (
+	"strconv"
+	"strings"
+
+	"golang.org/x/xerrors"
+)
+
+// ValidateSchedules checks if any schedules overlap
+func ValidateSchedules(schedules []string) error {
+	for i := 0; i < len(schedules); i++ {
+		for j := i + 1; j < len(schedules); j++ {
+			overlap, err := SchedulesOverlap(schedules[i], schedules[j])
+			if err != nil {
+				return xerrors.Errorf("invalid schedule: %w", err)
+			}
+			if overlap {
+				return xerrors.Errorf("schedules overlap: %s and %s",
+					schedules[i], schedules[j])
+			}
+		}
+	}
+	return nil
+}
+
+// SchedulesOverlap checks if two schedules overlap by checking
+// all cron fields separately
+func SchedulesOverlap(schedule1, schedule2 string) (bool, error) {
+	// Get cron fields
+	fields1 := strings.Fields(schedule1)
+	fields2 := strings.Fields(schedule2)
+
+	if len(fields1) != 5 {
+		return false, xerrors.Errorf("schedule %q has %d fields, expected 5 fields (minute hour day-of-month month day-of-week)", schedule1, len(fields1))
+	}
+	if len(fields2) != 5 {
+		return false, xerrors.Errorf("schedule %q has %d fields, expected 5 fields (minute hour day-of-month month day-of-week)", schedule2, len(fields2))
+	}
+
+	// Check if months overlap
+	monthsOverlap, err := MonthsOverlap(fields1[3], fields2[3])
+	if err != nil {
+		return false, xerrors.Errorf("invalid month range: %w", err)
+	}
+	if !monthsOverlap {
+		return false, nil
+	}
+
+	// Check if days overlap (DOM OR DOW)
+	daysOverlap, err := DaysOverlap(fields1[2], fields1[4], fields2[2], fields2[4])
+	if err != nil {
+		return false, xerrors.Errorf("invalid day range: %w", err)
+	}
+	if !daysOverlap {
+		return false, nil
+	}
+
+	// Check if hours overlap
+	hoursOverlap, err := HoursOverlap(fields1[1], fields2[1])
+	if err != nil {
+		return false, xerrors.Errorf("invalid hour range: %w", err)
+	}
+
+	return hoursOverlap, nil
+}
+
+// MonthsOverlap checks if two month ranges overlap
+func MonthsOverlap(months1, months2 string) (bool, error) {
+	return CheckOverlap(months1, months2, 12)
+}
+
+// HoursOverlap checks if two hour ranges overlap
+func HoursOverlap(hours1, hours2 string) (bool, error) {
+	return CheckOverlap(hours1, hours2, 23)
+}
+
+// DomOverlap checks if two day-of-month ranges overlap
+func DomOverlap(dom1, dom2 string) (bool, error) {
+	return CheckOverlap(dom1, dom2, 31)
+}
+
+// DowOverlap checks if two day-of-week ranges overlap
+func DowOverlap(dow1, dow2 string) (bool, error) {
+	return CheckOverlap(dow1, dow2, 6)
+}
+
+// DaysOverlap checks if two day ranges overlap, considering both DOM and DOW.
+// Returns true if both DOM and DOW overlap, or if one is * and the other overlaps.
+func DaysOverlap(dom1, dow1, dom2, dow2 string) (bool, error) {
+	// If either DOM is *, we only need to check DOW overlap
+	if dom1 == "*" || dom2 == "*" {
+		return DowOverlap(dow1, dow2)
+	}
+
+	// If either DOW is *, we only need to check DOM overlap
+	if dow1 == "*" || dow2 == "*" {
+		return DomOverlap(dom1, dom2)
+	}
+
+	// If both DOM and DOW are specified, we need to check both
+	// because the schedule runs when either matches
+	domOverlap, err := DomOverlap(dom1, dom2)
+	if err != nil {
+		return false, err
+	}
+	dowOverlap, err := DowOverlap(dow1, dow2)
+	if err != nil {
+		return false, err
+	}
+
+	// If either DOM or DOW overlaps, the schedules overlap
+	return domOverlap || dowOverlap, nil
+}
+
+// CheckOverlap is a function to check if two ranges overlap
+func CheckOverlap(range1, range2 string, maxValue int) (bool, error) {
+	set1, err := ParseRange(range1, maxValue)
+	if err != nil {
+		return false, err
+	}
+	set2, err := ParseRange(range2, maxValue)
+	if err != nil {
+		return false, err
+	}
+
+	for value := range set1 {
+		if set2[value] {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+// ParseRange converts a cron range to a set of integers
+// maxValue is the maximum allowed value (e.g., 23 for hours, 6 for DOW, 12 for months, 31 for DOM)
+func ParseRange(input string, maxValue int) (map[int]bool, error) {
+	result := make(map[int]bool)
+
+	// Handle "*" case
+	if input == "*" {
+		for i := 0; i <= maxValue; i++ {
+			result[i] = true
+		}
+		return result, nil
+	}
+
+	// Parse ranges like "1-3,5,7-9"
+	parts := strings.Split(input, ",")
+	for _, part := range parts {
+		if strings.Contains(part, "-") {
+			// Handle range like "1-3"
+			rangeParts := strings.Split(part, "-")
+			start, err := strconv.Atoi(rangeParts[0])
+			if err != nil {
+				return nil, xerrors.Errorf("invalid start value in range: %w", err)
+			}
+			end, err := strconv.Atoi(rangeParts[1])
+			if err != nil {
+				return nil, xerrors.Errorf("invalid end value in range: %w", err)
+			}
+
+			// Validate range
+			if start < 0 || end > maxValue || start > end {
+				return nil, xerrors.Errorf("invalid range %d-%d: values must be between 0 and %d", start, end, maxValue)
+			}
+
+			for i := start; i <= end; i++ {
+				result[i] = true
+			}
+		} else {
+			// Handle single value
+			value, err := strconv.Atoi(part)
+			if err != nil {
+				return nil, xerrors.Errorf("invalid value: %w", err)
+			}
+
+			// Validate value
+			if value < 0 || value > maxValue {
+				return nil, xerrors.Errorf("invalid value %d: must be between 0 and %d", value, maxValue)
+			}
+
+			result[value] = true
+		}
+	}
+	return result, nil
+}
diff --git a/provider/helpers/schedule_validation_test.go b/provider/helpers/schedule_validation_test.go
new file mode 100644
index 00000000..2971fd07
--- /dev/null
+++ b/provider/helpers/schedule_validation_test.go
@@ -0,0 +1,585 @@
+// schedule_validation_test.go
+
+package helpers_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/coder/terraform-provider-coder/v2/provider/helpers"
+)
+
+func TestParseRange(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name      string
+		input     string
+		maxValue  int
+		expected  map[int]bool
+		expectErr bool
+	}{
+		{
+			name:     "Wildcard",
+			input:    "*",
+			maxValue: 5,
+			expected: map[int]bool{
+				0: true, 1: true, 2: true, 3: true, 4: true, 5: true,
+			},
+		},
+		{
+			name:     "Single value",
+			input:    "3",
+			maxValue: 5,
+			expected: map[int]bool{
+				3: true,
+			},
+		},
+		{
+			name:     "Range",
+			input:    "1-3",
+			maxValue: 5,
+			expected: map[int]bool{
+				1: true, 2: true, 3: true,
+			},
+		},
+		{
+			name:     "Complex range",
+			input:    "1-3,5,7-9",
+			maxValue: 9,
+			expected: map[int]bool{
+				1: true, 2: true, 3: true, 5: true, 7: true, 8: true, 9: true,
+			},
+		},
+		{
+			name:      "Value too high",
+			input:     "6",
+			maxValue:  5,
+			expectErr: true,
+		},
+		{
+			name:      "Range too high",
+			input:     "4-6",
+			maxValue:  5,
+			expectErr: true,
+		},
+		{
+			name:      "Invalid range",
+			input:     "3-1",
+			maxValue:  5,
+			expectErr: true,
+		},
+		{
+			name:      "Invalid value",
+			input:     "abc",
+			maxValue:  5,
+			expectErr: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			result, err := helpers.ParseRange(testCase.input, testCase.maxValue)
+			if testCase.expectErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, testCase.expected, result)
+		})
+	}
+}
+
+func TestCheckOverlap(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name      string
+		range1    string
+		range2    string
+		maxValue  int
+		overlap   bool
+		expectErr bool
+	}{
+		{
+			name:     "Same range",
+			range1:   "1-5",
+			range2:   "1-5",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:     "Different ranges",
+			range1:   "1-3",
+			range2:   "4-6",
+			maxValue: 10,
+			overlap:  false,
+		},
+		{
+			name:     "Overlapping ranges",
+			range1:   "1-5",
+			range2:   "4-8",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:     "Wildcard overlap",
+			range1:   "*",
+			range2:   "3-5",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:     "Complex ranges",
+			range1:   "1-3,5,7-9",
+			range2:   "2-4,6,8-10",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:     "Single values",
+			range1:   "1",
+			range2:   "1",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:     "Single value vs range",
+			range1:   "1",
+			range2:   "1-3",
+			maxValue: 10,
+			overlap:  true,
+		},
+		{
+			name:      "Invalid range - value too high",
+			range1:    "11",
+			range2:    "1-3",
+			maxValue:  10,
+			expectErr: true,
+		},
+		{
+			name:      "Invalid range - negative value",
+			range1:    "-1",
+			range2:    "1-3",
+			maxValue:  10,
+			expectErr: true,
+		},
+		{
+			name:      "Invalid range - malformed",
+			range1:    "1-",
+			range2:    "1-3",
+			maxValue:  10,
+			expectErr: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			overlap, err := helpers.CheckOverlap(testCase.range1, testCase.range2, testCase.maxValue)
+			if testCase.expectErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, testCase.overlap, overlap)
+		})
+	}
+}
+
+func TestOverlapWrappers(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name        string
+		range1      string
+		range2      string
+		overlap     bool
+		expectErr   bool
+		overlapFunc func(string, string) (bool, error)
+	}{
+		// HoursOverlap tests (max 23)
+		{
+			name:        "Valid hour range",
+			range1:      "23",
+			range2:      "23",
+			overlap:     true,
+			overlapFunc: helpers.HoursOverlap,
+		},
+		{
+			name:        "Invalid hour range",
+			range1:      "24",
+			range2:      "24",
+			expectErr:   true,
+			overlapFunc: helpers.HoursOverlap,
+		},
+
+		// MonthsOverlap tests (max 12)
+		{
+			name:        "Valid month range",
+			range1:      "12",
+			range2:      "12",
+			overlap:     true,
+			overlapFunc: helpers.MonthsOverlap,
+		},
+		{
+			name:        "Invalid month range",
+			range1:      "13",
+			range2:      "13",
+			expectErr:   true,
+			overlapFunc: helpers.MonthsOverlap,
+		},
+
+		// DomOverlap tests (max 31)
+		{
+			name:        "Valid day of month range",
+			range1:      "31",
+			range2:      "31",
+			overlap:     true,
+			overlapFunc: helpers.DomOverlap,
+		},
+		{
+			name:        "Invalid day of month range",
+			range1:      "32",
+			range2:      "32",
+			expectErr:   true,
+			overlapFunc: helpers.DomOverlap,
+		},
+
+		// DowOverlap tests (max 6)
+		{
+			name:        "Valid day of week range",
+			range1:      "6",
+			range2:      "6",
+			overlap:     true,
+			overlapFunc: helpers.DowOverlap,
+		},
+		{
+			name:        "Invalid day of week range",
+			range1:      "7",
+			range2:      "7",
+			expectErr:   true,
+			overlapFunc: helpers.DowOverlap,
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			overlap, err := testCase.overlapFunc(testCase.range1, testCase.range2)
+			if testCase.expectErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, testCase.overlap, overlap)
+		})
+	}
+}
+
+func TestDaysOverlap(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name      string
+		dom1      string
+		dow1      string
+		dom2      string
+		dow2      string
+		overlap   bool
+		expectErr bool
+	}{
+		{
+			name:    "DOM overlap only",
+			dom1:    "1-15",
+			dow1:    "1-3",
+			dom2:    "10-20",
+			dow2:    "4-6",
+			overlap: true, // true because DOM overlaps (10-15)
+		},
+		{
+			name:    "DOW overlap only",
+			dom1:    "1-15",
+			dow1:    "1-3",
+			dom2:    "16-31",
+			dow2:    "3-5",
+			overlap: true, // true because DOW overlaps (3)
+		},
+		{
+			name:    "Both DOM and DOW overlap",
+			dom1:    "1-15",
+			dow1:    "1-3",
+			dom2:    "10-20",
+			dow2:    "3-5",
+			overlap: true, // true because both overlap
+		},
+		{
+			name:    "No overlap",
+			dom1:    "1-15",
+			dow1:    "1-3",
+			dom2:    "16-31",
+			dow2:    "4-6",
+			overlap: false, // false because neither overlaps
+		},
+		{
+			name:    "Both DOW wildcard - DOM overlaps",
+			dom1:    "1-15",
+			dow1:    "*",
+			dom2:    "10-20",
+			dow2:    "*",
+			overlap: true, // true because DOM overlaps (10-15)
+		},
+		{
+			name:    "Both DOW wildcard - DOM doesn't overlap",
+			dom1:    "1-15",
+			dow1:    "*",
+			dom2:    "16-31",
+			dow2:    "*",
+			overlap: false, // false because DOM doesn't overlap
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			overlap, err := helpers.DaysOverlap(testCase.dom1, testCase.dow1, testCase.dom2, testCase.dow2)
+			if testCase.expectErr {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, testCase.overlap, overlap)
+		})
+	}
+}
+
+func TestSchedulesOverlap(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name           string
+		s1             string
+		s2             string
+		overlap        bool
+		expectedErrMsg string
+	}{
+		// Basic overlap cases
+		{
+			name:    "Same schedule",
+			s1:      "* 9-18 * * 1-5",
+			s2:      "* 9-18 * * 1-5",
+			overlap: true,
+		},
+		{
+			name:    "Different hours - no overlap",
+			s1:      "* 9-12 * * 1-5",
+			s2:      "* 13-18 * * 1-5",
+			overlap: false,
+		},
+		{
+			name:    "Different hours - partial overlap",
+			s1:      "* 9-14 * * 1-5",
+			s2:      "* 12-18 * * 1-5",
+			overlap: true,
+		},
+		{
+			name:    "Different hours - one contained in another",
+			s1:      "* 9-18 * * 1-5",
+			s2:      "* 12-14 * * 1-5",
+			overlap: true,
+		},
+
+		// Day of week overlap cases (with wildcard DOM)
+		{
+			name:    "Different DOW with wildcard DOM",
+			s1:      "* 9-18 * * 1,3,5", // Mon,Wed,Fri
+			s2:      "* 9-18 * * 2,4,6", // Tue,Thu,Sat
+			overlap: false,              // No overlap because DOW ranges don't overlap
+		},
+		{
+			name:    "Different DOW with wildcard DOM - complex ranges",
+			s1:      "* 9-18 * * 1-3", // Mon-Wed
+			s2:      "* 9-18 * * 4-5", // Thu-Fri
+			overlap: false,            // No overlap because DOW ranges don't overlap
+		},
+
+		// Day of week overlap cases (with specific DOM)
+		{
+			name:    "Different DOW with specific DOM - no overlap",
+			s1:      "* 9-18 1 * 1-3",
+			s2:      "* 9-18 2 * 4-5",
+			overlap: false, // No overlap because different DOM and DOW
+		},
+		{
+			name:    "Different DOW with specific DOM - partial overlap",
+			s1:      "* 9-18 1 * 1-4",
+			s2:      "* 9-18 1 * 3-5",
+			overlap: true, // Overlaps because same DOM
+		},
+		{
+			name:    "Different DOW with specific DOM - complex ranges",
+			s1:      "* 9-18 1 * 1,3,5",
+			s2:      "* 9-18 1 * 2,4,6",
+			overlap: true, // Overlaps because same DOM
+		},
+
+		// Wildcard cases
+		{
+			name:    "Wildcard hours vs specific hours",
+			s1:      "* * * * 1-5",
+			s2:      "* 9-18 * * 1-5",
+			overlap: true,
+		},
+		{
+			name:    "Wildcard DOW vs specific DOW",
+			s1:      "* 9-18 * * *",
+			s2:      "* 9-18 * * 1-5",
+			overlap: true,
+		},
+		{
+			name:    "Both wildcard DOW",
+			s1:      "* 9-18 * * *",
+			s2:      "* 9-18 * * *",
+			overlap: true,
+		},
+
+		// Complex time ranges
+		{
+			name:    "Complex hour ranges - no overlap",
+			s1:      "* 9-11,13-15 * * 1-5",
+			s2:      "* 12,16-18 * * 1-5",
+			overlap: false,
+		},
+		{
+			name:    "Complex hour ranges - partial overlap",
+			s1:      "* 9-11,13-15 * * 1-5",
+			s2:      "* 10-12,14-16 * * 1-5",
+			overlap: true,
+		},
+		{
+			name:    "Complex hour ranges - contained",
+			s1:      "* 9-18 * * 1-5",
+			s2:      "* 10-11,13-14 * * 1-5",
+			overlap: true,
+		},
+
+		// Error cases (keeping minimal)
+		{
+			name:           "Invalid hour range",
+			s1:             "* 25-26 * * 1-5",
+			s2:             "* 9-18 * * 1-5",
+			expectedErrMsg: "invalid hour range",
+		},
+		{
+			name:           "Invalid month range",
+			s1:             "* 9-18 * 13 1-5",
+			s2:             "* 9-18 * * 1-5",
+			expectedErrMsg: "invalid month range",
+		},
+		{
+			name:           "Invalid field count - too few fields",
+			s1:             "* 9-18 * *",
+			s2:             "* 9-18 * * 1-5",
+			expectedErrMsg: "has 4 fields, expected 5 fields",
+		},
+		{
+			name:           "Invalid field count - too many fields",
+			s1:             "* 9-18 * * 1-5 *",
+			s2:             "* 9-18 * * 1-5",
+			expectedErrMsg: "has 6 fields, expected 5 fields",
+		},
+		{
+			name:           "Invalid field count - s2 has too few fields",
+			s1:             "* 9-18 * * 1-5",
+			s2:             "* 9-18 * *",
+			expectedErrMsg: "has 4 fields, expected 5 fields",
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+
+			overlap, err := helpers.SchedulesOverlap(testCase.s1, testCase.s2)
+			if testCase.expectedErrMsg != "" {
+				require.Error(t, err)
+				require.Contains(t, err.Error(), testCase.expectedErrMsg)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, testCase.overlap, overlap)
+			}
+		})
+	}
+}
+
+func TestValidateSchedules(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name           string
+		schedules      []string
+		expectedErrMsg string
+	}{
+		// Basic validation
+		{
+			name:      "Empty schedules",
+			schedules: []string{},
+		},
+		{
+			name: "Single valid schedule",
+			schedules: []string{
+				"* 9-18 * * 1-5",
+			},
+		},
+
+		// Non-overlapping schedules
+		{
+			name: "Multiple valid non-overlapping schedules",
+			schedules: []string{
+				"* 9-12 * * 1-5",
+				"* 13-18 * * 1-5",
+			},
+		},
+		{
+			name: "Multiple valid non-overlapping schedules",
+			schedules: []string{
+				"* 9-18 * * 1-5",
+				"* 9-13 * * 6,0",
+			},
+		},
+
+		// Overlapping schedules
+		{
+			name: "Two overlapping schedules",
+			schedules: []string{
+				"* 9-14 * * 1-5",
+				"* 12-18 * * 1-5",
+			},
+			expectedErrMsg: "schedules overlap: * 9-14 * * 1-5 and * 12-18 * * 1-5",
+		},
+		{
+			name: "Three schedules with only second and third overlapping",
+			schedules: []string{
+				"* 9-11 * * 1-5",  // 9AM-11AM (no overlap)
+				"* 12-18 * * 1-5", // 12PM-6PM
+				"* 15-20 * * 1-5", // 3PM-8PM (overlaps with second)
+			},
+			expectedErrMsg: "schedules overlap: * 12-18 * * 1-5 and * 15-20 * * 1-5",
+		},
+	}
+
+	for _, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			err := helpers.ValidateSchedules(testCase.schedules)
+			if testCase.expectedErrMsg != "" {
+				require.Error(t, err)
+				require.Contains(t, err.Error(), testCase.expectedErrMsg)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/provider/workspace_preset.go b/provider/workspace_preset.go
index e0f2276c..0a44b1eb 100644
--- a/provider/workspace_preset.go
+++ b/provider/workspace_preset.go
@@ -3,13 +3,20 @@ package provider
 import (
 	"context"
 	"fmt"
+	"strings"
+	"time"
+
+	"github.com/coder/terraform-provider-coder/v2/provider/helpers"
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
 	"github.com/mitchellh/mapstructure"
+	rbcron "github.com/robfig/cron/v3"
 )
 
+var PrebuildsCRONParser = rbcron.NewParser(rbcron.Minute | rbcron.Hour | rbcron.Dom | rbcron.Month | rbcron.Dow)
+
 type WorkspacePreset struct {
 	Name       string            `mapstructure:"name"`
 	Parameters map[string]string `mapstructure:"parameters"`
@@ -29,12 +36,23 @@ type WorkspacePrebuild struct {
 	// for utilities that parse our terraform output using this type. To remain compatible
 	// with those cases, we use a slice here.
 	ExpirationPolicy []ExpirationPolicy `mapstructure:"expiration_policy"`
+	Scheduling       []Scheduling       `mapstructure:"scheduling"`
 }
 
 type ExpirationPolicy struct {
 	TTL int `mapstructure:"ttl"`
 }
 
+type Scheduling struct {
+	Timezone string     `mapstructure:"timezone"`
+	Schedule []Schedule `mapstructure:"schedule"`
+}
+
+type Schedule struct {
+	Cron      string `mapstructure:"cron"`
+	Instances int    `mapstructure:"instances"`
+}
+
 func workspacePresetDataSource() *schema.Resource {
 	return &schema.Resource{
 		SchemaVersion: 1,
@@ -52,6 +70,12 @@ func workspacePresetDataSource() *schema.Resource {
 				return diag.Errorf("decode workspace preset: %s", err)
 			}
 
+			// Validate schedule overlaps if scheduling is configured
+			err = validateSchedules(rd)
+			if err != nil {
+				return diag.Errorf("schedules overlap with each other: %s", err)
+			}
+
 			rd.SetId(preset.Name)
 
 			return nil
@@ -119,9 +143,147 @@ func workspacePresetDataSource() *schema.Resource {
 								},
 							},
 						},
+						"scheduling": {
+							Type:        schema.TypeList,
+							Description: "Configuration block that defines scheduling behavior for prebuilds. Use this to automatically adjust the number of prebuild instances based on a schedule.",
+							Optional:    true,
+							MaxItems:    1,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"timezone": {
+										Type: schema.TypeString,
+										Description: `The timezone to use for the prebuild schedules (e.g., "UTC", "America/New_York"). 
+Timezone must be a valid timezone in the IANA timezone database. 
+See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a complete list of valid timezone identifiers and https://www.iana.org/time-zones for the official IANA timezone database.`,
+										Required: true,
+										ValidateFunc: func(val interface{}, key string) ([]string, []error) {
+											timezone := val.(string)
+
+											_, err := time.LoadLocation(timezone)
+											if err != nil {
+												return nil, []error{fmt.Errorf("failed to load timezone %q: %w", timezone, err)}
+											}
+
+											return nil, nil
+										},
+									},
+									"schedule": {
+										Type:        schema.TypeList,
+										Description: "One or more schedule blocks that define when to scale the number of prebuild instances.",
+										Required:    true,
+										MinItems:    1,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"cron": {
+													Type:        schema.TypeString,
+													Description: "A cron expression that defines when this schedule should be active. The cron expression must be in the format \"* HOUR DOM MONTH DAY-OF-WEEK\" where HOUR is 0-23, DOM (day-of-month) is 1-31, MONTH is 1-12, and DAY-OF-WEEK is 0-6 (Sunday-Saturday). The minute field must be \"*\" to ensure the schedule covers entire hours rather than specific minute intervals.",
+													Required:    true,
+													ValidateFunc: func(val interface{}, key string) ([]string, []error) {
+														cronSpec := val.(string)
+
+														err := validatePrebuildsCronSpec(cronSpec)
+														if err != nil {
+															return nil, []error{fmt.Errorf("cron spec failed validation: %w", err)}
+														}
+
+														_, err = PrebuildsCRONParser.Parse(cronSpec)
+														if err != nil {
+															return nil, []error{fmt.Errorf("failed to parse cron spec: %w", err)}
+														}
+
+														return nil, nil
+													},
+												},
+												"instances": {
+													Type:        schema.TypeInt,
+													Description: "The number of prebuild instances to maintain during this schedule period.",
+													Required:    true,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
 					},
 				},
 			},
 		},
 	}
 }
+
+// validatePrebuildsCronSpec ensures that the minute field is set to *.
+// This is required because prebuild schedules represent continuous time ranges,
+// and we want the schedule to cover entire hours rather than specific minute intervals.
+func validatePrebuildsCronSpec(spec string) error {
+	parts := strings.Fields(spec)
+	if len(parts) != 5 {
+		return fmt.Errorf("cron specification should consist of 5 fields")
+	}
+	if parts[0] != "*" {
+		return fmt.Errorf("minute field should be *")
+	}
+
+	return nil
+}
+
+// validateSchedules checks if any of the configured prebuild schedules overlap with each other.
+// It returns an error if overlaps are found, nil otherwise.
+func validateSchedules(rd *schema.ResourceData) error {
+	// TypeSet from schema definition
+	prebuilds := rd.Get("prebuilds").(*schema.Set)
+	if prebuilds.Len() == 0 {
+		return nil
+	}
+
+	// Each element of TypeSet with Elem: &schema.Resource{} should be map[string]interface{}
+	prebuild, ok := prebuilds.List()[0].(map[string]interface{})
+	if !ok {
+		return fmt.Errorf("invalid prebuild configuration: expected map[string]interface{}")
+	}
+
+	// TypeList from schema definition
+	schedulingBlocks, ok := prebuild["scheduling"].([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid scheduling configuration: expected []interface{}")
+	}
+	if len(schedulingBlocks) == 0 {
+		return nil
+	}
+
+	// Each element of TypeList with Elem: &schema.Resource{} should be map[string]interface{}
+	schedulingBlock, ok := schedulingBlocks[0].(map[string]interface{})
+	if !ok {
+		return fmt.Errorf("invalid scheduling configuration: expected map[string]interface{}")
+	}
+
+	// TypeList from schema definition
+	scheduleBlocks, ok := schedulingBlock["schedule"].([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid schedule configuration: expected []interface{}")
+	}
+	if len(scheduleBlocks) == 0 {
+		return nil
+	}
+
+	cronSpecs := make([]string, len(scheduleBlocks))
+	for i, scheduleBlock := range scheduleBlocks {
+		// Each element of TypeList with Elem: &schema.Resource{} should be map[string]interface{}
+		schedule, ok := scheduleBlock.(map[string]interface{})
+		if !ok {
+			return fmt.Errorf("invalid schedule configuration: expected map[string]interface{}")
+		}
+
+		// TypeString from schema definition
+		cronSpec := schedule["cron"].(string)
+
+		cronSpecs[i] = cronSpec
+	}
+
+	err := helpers.ValidateSchedules(cronSpecs)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/provider/workspace_preset_test.go b/provider/workspace_preset_test.go
index b8e752ae..84dfec17 100644
--- a/provider/workspace_preset_test.go
+++ b/provider/workspace_preset_test.go
@@ -265,6 +265,271 @@ func TestWorkspacePreset(t *testing.T) {
 			}`,
 			ExpectError: regexp.MustCompile("An argument named \"invalid_argument\" is not expected here."),
 		},
+		{
+			Name: "Prebuilds is set with an empty scheduling field",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`The argument "[^"]+" is required, but no definition was found.`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling field, but without timezone",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+					  	schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`The argument "timezone" is required, but no definition was found.`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling field, but without schedule",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`At least 1 "schedule" blocks are required.`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling.schedule field, but without cron",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+						schedule {
+							instances = 3
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`The argument "cron" is required, but no definition was found.`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling.schedule field, but without instances",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+						schedule {
+							cron = "* 8-18 * * 1-5"
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`The argument "instances" is required, but no definition was found.`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling.schedule field, but with invalid type for instances",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+						schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = "not_a_number"
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`Inappropriate value for attribute "instances": a number is required`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling field with 1 schedule",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+					  	schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+					  	}
+					}
+				}
+			}`,
+			ExpectError: nil,
+			Check: func(state *terraform.State) error {
+				require.Len(t, state.Modules, 1)
+				require.Len(t, state.Modules[0].Resources, 1)
+				resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"]
+				require.NotNil(t, resource)
+				attrs := resource.Primary.Attributes
+				require.Equal(t, attrs["name"], "preset_1")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.timezone"], "UTC")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.0.cron"], "* 8-18 * * 1-5")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.0.instances"], "3")
+				return nil
+			},
+		},
+		{
+			Name: "Prebuilds is set with an scheduling field with 2 schedules",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+					  	schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+					  	}
+						schedule {
+							cron = "* 8-14 * * 6"
+							instances = 1
+						}
+					}
+				}
+			}`,
+			ExpectError: nil,
+			Check: func(state *terraform.State) error {
+				require.Len(t, state.Modules, 1)
+				require.Len(t, state.Modules[0].Resources, 1)
+				resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"]
+				require.NotNil(t, resource)
+				attrs := resource.Primary.Attributes
+				require.Equal(t, attrs["name"], "preset_1")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.timezone"], "UTC")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.0.cron"], "* 8-18 * * 1-5")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.0.instances"], "3")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.1.cron"], "* 8-14 * * 6")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.schedule.1.instances"], "1")
+				return nil
+			},
+		},
+		{
+			Name: "Prebuilds is set with an scheduling.schedule field, but the cron includes a disallowed minute field",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+						schedule {
+							cron = "30 8-18 * * 1-5"
+							instances = "1"
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`cron spec failed validation: minute field should be *`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling.schedule field, but the cron hour field is invalid",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+						schedule {
+							cron = "* 25-26 * * 1-5"
+							instances = "1"
+					  	}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`failed to parse cron spec: end of range \(26\) above maximum \(23\): 25-26`),
+		},
+		{
+			Name: "Prebuilds is set with a valid scheduling.timezone field",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "America/Los_Angeles"
+						schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+						}
+					}
+				}
+			}`,
+			ExpectError: nil,
+			Check: func(state *terraform.State) error {
+				require.Len(t, state.Modules, 1)
+				require.Len(t, state.Modules[0].Resources, 1)
+				resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"]
+				require.NotNil(t, resource)
+				attrs := resource.Primary.Attributes
+				require.Equal(t, attrs["name"], "preset_1")
+				require.Equal(t, attrs["prebuilds.0.scheduling.0.timezone"], "America/Los_Angeles")
+				return nil
+			},
+		},
+		{
+			Name: "Prebuilds is set with an invalid scheduling.timezone field",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "InvalidLocation"
+						schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+						}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`failed to load timezone "InvalidLocation": unknown time zone InvalidLocation`),
+		},
+		{
+			Name: "Prebuilds is set with an scheduling field, with 2 overlapping schedules",
+			Config: `
+			data "coder_workspace_preset" "preset_1" {
+				name = "preset_1"
+				prebuilds {
+					instances = 1
+					scheduling {
+						timezone = "UTC"
+					  	schedule {
+							cron = "* 8-18 * * 1-5"
+							instances = 3
+					  	}
+						schedule {
+							cron = "* 18-19 * * 5-6"
+							instances = 1
+						}
+					}
+				}
+			}`,
+			ExpectError: regexp.MustCompile(`schedules overlap with each other: schedules overlap: \* 8-18 \* \* 1-5 and \* 18-19 \* \* 5-6`),
+		},
 	}
 
 	for _, testcase := range testcases {