# Prefect IntervalSchedule Migration: From Legacy Schedules to Prefect 3

> Migrate from deprecated prefect.server.schemas.schedules IntervalSchedule to Prefect 3 scheduling patterns — with side-by-side code comparisons, common errors, and a step-by-step upgrade path.

- Author: Fawad Hussain Syed, software engineer, Geneva (https://fawadhs.dev/about)
- Published: 2026-05-13
- Category: Automation
- Canonical: https://fawadhs.dev/blog/prefect-intervalschedule-migration-guide

---
If you have landed here from an import error, you are not alone. `from prefect.server.schemas.schedules import IntervalSchedule` stopped working and nobody told you.

Prefect 3 changed how schedules work. The old `prefect.server.schemas.schedules` module is gone. The scheduling model moved from server-side schema objects to deployment-level configuration. The concepts are the same but the code is different.

This guide covers the complete migration path: what changed, why, and exactly how to update your code with working examples.

## Who This Is For

- **Data engineers** with Prefect 2 pipelines that broke after upgrading to Prefect 3
- **Developers** getting `ImportError: cannot import name 'IntervalSchedule'` and needing a fix
- **Vibe coders** setting up new Prefect workflows and finding outdated tutorials referencing old imports
- **Teams** planning a Prefect 2 to 3 migration who need to understand what changed

Familiarity with Prefect basics (flows and tasks) is assumed. If you are new to Prefect, see [Schedule and Orchestrate Workflows with Prefect](/blog/schedule-orchestrate-workflows-prefect) first.

## What Changed

```mermaid
flowchart LR
  subgraph Prefect2["Prefect 2 (Legacy)"]
    IS["IntervalSchedule\n(server schema)"]
    CS["CronSchedule\n(server schema)"]
    RS["RRuleSchedule\n(server schema)"]
  end
  subgraph Prefect3["Prefect 3 (Current)"]
    D["Deployment\nschedules=[\n  {...}\n]"]
  end
  IS -.->|"migrated to"| D
  CS -.->|"migrated to"| D
  RS -.->|"migrated to"| D
```

### The Core Change

| Aspect | Prefect 2 | Prefect 3 |
|--------|-----------|-----------|
| **Import** | `from prefect.server.schemas.schedules import IntervalSchedule` | No separate import needed |
| **Schedule object** | `IntervalSchedule(interval=timedelta(hours=1))` | `{"interval": 3600}` (dict) |
| **Attachment** | `Deployment.build_from_flow(..., schedule=schedule)` | `flow.serve(schedules=[...])` or `flow.deploy(schedules=[...])` |
| **Schedule types** | `IntervalSchedule`, `CronSchedule`, `RRuleSchedule` | All expressed as dicts with `interval`, `cron`, or `rrule` key |
| **Server dependency** | Schedule enforced by Prefect server | Schedule managed by deployment |

## The Common Error

When you upgrade Prefect and run existing code, you get:

```text
ImportError: cannot import name 'IntervalSchedule' from 'prefect.server.schemas.schedules'
```

Or sometimes:

```text
ModuleNotFoundError: No module named 'prefect.server.schemas.schedules'
```

This is not a bug. The module was intentionally removed. Here is how to fix it.

## Step 1: Replace IntervalSchedule Imports

### Before (Prefect 2)

```python
from datetime import timedelta
from prefect import flow
from prefect.server.schemas.schedules import IntervalSchedule

@flow
def daily_pipeline():
    """Run the daily data pipeline."""
    print("Pipeline running")

# Create schedule
schedule = IntervalSchedule(interval=timedelta(hours=24))

# Deploy with schedule
daily_pipeline.serve(name="daily-pipeline", schedule=schedule)
```

### After (Prefect 3)

```python
from prefect import flow

@flow
def daily_pipeline():
    """Run the daily data pipeline."""
    print("Pipeline running")

# Deploy with inline schedule
daily_pipeline.serve(
    name="daily-pipeline",
    schedules=[
        {"interval": 86400}  # 24 hours in seconds
    ],
)
```

No import needed. The schedule is a plain dictionary.

## Step 2: Migrate CronSchedule

Cron schedules follow the same pattern.

### Before (Prefect 2)

```python
from prefect.server.schemas.schedules import CronSchedule

schedule = CronSchedule(cron="0 8 * * *", timezone="Europe/London")
my_flow.serve(name="morning-job", schedule=schedule)
```

### After (Prefect 3)

```python
my_flow.serve(
    name="morning-job",
    schedules=[
        {"cron": "0 8 * * *", "timezone": "Europe/London"}
    ],
)
```

### Cron Quick Reference

| Pattern | Meaning |
|---------|---------|
| `0 8 * * *` | Daily at 8am |
| `0 */6 * * *` | Every 6 hours |
| `0 8 * * 1-5` | Weekdays at 8am |
| `0 0 1 * *` | First day of each month |
| `*/15 * * * *` | Every 15 minutes |

## Step 3: Migrate RRuleSchedule

For complex recurrence rules, RRule schedules become dict-based too.

### Before (Prefect 2)

```python
from prefect.server.schemas.schedules import RRuleSchedule

schedule = RRuleSchedule(
    rrule="FREQ=WEEKLY;BYDAY=MO,WE,FR;BYHOUR=9;BYMINUTE=0",
    timezone="Europe/London",
)
my_flow.serve(name="mwf-job", schedule=schedule)
```

### After (Prefect 3)

```python
my_flow.serve(
    name="mwf-job",
    schedules=[
        {
            "rrule": "FREQ=WEEKLY;BYDAY=MO,WE,FR;BYHOUR=9;BYMINUTE=0",
            "timezone": "Europe/London",
        }
    ],
)
```

## Step 4: Multiple Schedules on One Deployment

Prefect 3 supports multiple schedules per deployment — something that was awkward in Prefect 2.

```python
@flow
def reporting_pipeline():
    """Generate reports on multiple schedules."""
    print("Report generated")

reporting_pipeline.serve(
    name="multi-schedule-reports",
    schedules=[
        {"cron": "0 8 * * 1-5", "timezone": "Europe/London"},   # Weekday morning
        {"cron": "0 18 * * 5", "timezone": "Europe/London"},     # Friday evening summary
        {"cron": "0 9 1 * *", "timezone": "Europe/London"},      # Monthly on the 1st
    ],
)
```

## Step 5: Migrate Deployment Files

If you used `deployment.yaml` or `prefect.yaml`, the schedule syntax also changed.

### Before (prefect.yaml — Prefect 2)

```yaml
deployments:
  - name: daily-pipeline
    flow_name: daily_pipeline
    schedule:
      interval: 86400
```

### After (prefect.yaml — Prefect 3)

```yaml
deployments:
  - name: daily-pipeline
    entrypoint: pipeline.py:daily_pipeline
    schedules:
      - interval: 86400
      # Or cron:
      # - cron: "0 8 * * *"
      #   timezone: "Europe/London"
```

Note the key changes: `schedule` (singular) became `schedules` (plural, list), and `flow_name` became `entrypoint`.

## Step 6: Automated Migration Script

If you have many files to update, this script finds and flags all legacy imports.

```python
import os
import re

def find_legacy_schedules(directory: str) -> list[dict]:
    """Scan Python files for deprecated Prefect schedule imports."""
    legacy_patterns = [
        r"from\s+prefect\.server\.schemas\.schedules\s+import",
        r"IntervalSchedule\(",
        r"CronSchedule\(",
        r"RRuleSchedule\(",
    ]

    findings = []
    for root, dirs, files in os.walk(directory):
        dirs[:] = [d for d in dirs if d not in {"node_modules", ".venv", "__pycache__"}]
        for filename in files:
            if not filename.endswith(".py"):
                continue
            filepath = os.path.join(root, filename)
            with open(filepath) as f:
                lines = f.readlines()
            for i, line in enumerate(lines, 1):
                for pattern in legacy_patterns:
                    if re.search(pattern, line):
                        findings.append({
                            "file": filepath,
                            "line": i,
                            "content": line.strip(),
                            "pattern": pattern,
                        })

    print(f"Found {len(findings)} legacy schedule references")
    for f in findings:
        print(f"  {f['file']}:{f['line']} → {f['content']}")
    return findings

# Usage
find_legacy_schedules("./src")
```

## Migration Checklist

```text
Prefect Schedule Migration Checklist
─────────────────────────────────────
✓ Remove all imports from prefect.server.schemas.schedules
✓ Replace IntervalSchedule(interval=timedelta(...)) with {"interval": seconds}
✓ Replace CronSchedule(cron="...") with {"cron": "..."}
✓ Replace RRuleSchedule(rrule="...") with {"rrule": "..."}
✓ Change schedule= to schedules=[] (singular to plural list)
✓ Update prefect.yaml: schedule → schedules (list)
✓ Update flow_name → entrypoint in YAML
✓ Test each deployment: prefect deployment run <name>
✓ Verify schedules in Prefect UI dashboard
```

## What This Replaces

| Prefect 2 Pattern | Prefect 3 Equivalent |
|--------------------|----------------------|
| `from prefect.server.schemas.schedules import IntervalSchedule` | No import needed |
| `IntervalSchedule(interval=timedelta(hours=1))` | `{"interval": 3600}` |
| `CronSchedule(cron="0 8 * * *")` | `{"cron": "0 8 * * *"}` |
| `schedule=schedule_object` | `schedules=[{...}]` |
| One schedule per deployment | Multiple schedules per deployment |
| Server-side schema validation | Deployment-level configuration |

## Next Steps

For a complete introduction to Prefect workflow orchestration, see [Schedule and Orchestrate Workflows with Prefect](/blog/schedule-orchestrate-workflows-prefect). For building reusable task patterns in Prefect, see [Prefect Reusable Task Blocks for Workflows](/blog/prefect-reusable-task-blocks-workflows).

For managing the secrets used in your Prefect deployments, see [Secure Python Automation: Managing Secrets and Keys](/blog/secure-python-automation-secrets-keys). For CI/CD integration with your Prefect pipelines, see [CI/CD Pipeline for Data Workflows](/blog/ci-cd-pipeline-data-workflows).

[Automation services](/services/automation) include Prefect migration, workflow design, and pipeline orchestration.

[Get in touch](/contact) to discuss migrating your Prefect workflows.
