# How Cycles and Modules Differ in Plane's Project Management Model

> Understand the key differences between Cycles and Modules in Plane's project management. Learn how time-boxed sprints and feature groupings impact your workflow and issue assignments.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: deep-dive
- Published: 2026-06-23

---

**Cycles are time-boxed sprint containers with fixed start and end dates, whereas Modules are logical feature groupings without temporal constraints; critically, an issue can belong to only one Cycle but can be simultaneously linked to multiple Modules.**

Plane (an open-source project management platform) provides two distinct organizational primitives for managing work within a project. While both Cycles and Modules serve to group issues, they operate on fundamentally different principles—Cycles govern *when* work happens through calendar-bound iterations, while Modules define *what* work belongs together through categorical associations. Understanding these architectural distinctions is essential for effective data modeling and API usage.

## Core Conceptual Differences

Plane treats Cycles and Modules as complementary organizational layers rather than interchangeable tags. Their designs reflect distinct project management needs.

### Cycles: Time-Boxed Iterations

**Cycles** represent calendar-bound execution windows analogous to agile sprints. Each Cycle enforces a strict temporal boundary through mandatory `start_date` and `end_date` fields, creating a burndown-friendly container where progress is measured against time remaining. The model tracks `progress_snapshot` to preserve historical velocity data even after archiving.

### Modules: Logical Work Groupings

**Modules** function as taxonomic buckets for organizing issues by feature area, component, or product domain. Unlike Cycles, Modules carry no temporal constraints; instead, they utilize a `color` attribute for visual categorization and serve as long-term classifications that persist across multiple sprints. An issue can exist in multiple Modules simultaneously, enabling cross-cutting concerns like "Authentication" and "Technical Debt" to span disparate feature sets.

## Data Model and Database Architecture

The underlying database schemas reflect these philosophical differences in storage location and field composition.

### Cycle Schema

In [`apps/api/plane/db/models/cycle.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle.py), the Cycle model inherits from `BaseModel` and stores temporal execution data:

- **Required fields**: `name`, `start_date`, `end_date`
- **Status tracking**: `status` (active, upcoming, completed), `progress_snapshot` (JSON for analytics)
- **Soft deletion**: `archived_at` timestamp

### Module Schema

In [`apps/api/plane/db/models/module.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/module.py), the Module model emphasizes categorical identity over temporal bounds:

- **Identification fields**: `name`, `description`, `color` (hex code for UI theming)
- **Status tracking**: `status` (backlog, in-progress, completed)
- **Soft deletion**: `archived_at` timestamp (retains color and status for historical views)

Both models maintain a foreign key relationship to their parent **Project**, ensuring scoped access control and isolation.

## Issue Relationships and Cardinality

The most significant architectural distinction lies in how issues associate with these containers.

### One-to-One Cycle Assignment

Issues maintain a **foreign key relationship** to Cycles through the `CycleIssue` model (implicit in [`apps/api/plane/db/models/cycle_issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle_issue.py)). An issue can belong to **zero or one** Cycle at any time. When reassigned, the previous Cycle link is destroyed and replaced, ensuring strict sprint ownership.

### Many-to-Many Module Linking

Issues relate to Modules through an explicit junction table defined in [`apps/api/plane/db/models/module_issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/module_issue.py). This **many-to-many** relationship allows an issue to exist in multiple Modules simultaneously—such as belonging to both the "Payments" feature Module and the "Q4 Tech Debt" Module—without temporal restriction.

## API and Service Layer Implementation

Business logic for both entities follows similar patterns but handles distinct validation rules.

### Cycle Services

The Cycle service layer in [`packages/services/src/cycle/cycle.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/cycle/cycle.service.ts) enforces date validation and progress calculation:

- **`createCycle()`**: Validates that `end_date` follows `start_date` and initializes `progress_snapshot`
- **`updateCycle()`**: Prevents modification of dates if issues are already assigned (optional business rule)
- **API endpoints**: `POST /api/v1/cycles/`, `PATCH /api/v1/cycles/{id}/`

### Module Services

The Module service in [`packages/services/src/module/module.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/module/module.service.ts) focuses on categorization and color management:

- **`createModule()`**: Validates hex color codes and enforces unique naming per project
- **`updateModule()`**: Handles status transitions without temporal constraints
- **API endpoints**: `POST /api/v1/modules/`, `PATCH /api/v1/modules/{id}/`

## Frontend State Management

The React frontend utilizes MobX stores to manage local state and API synchronization.

### Cycle Stores

[`apps/web/core/store/cycle.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/cycle.store.ts) manages the selected Cycle, filter states, and CRUD operations. Filter definitions reside in [`packages/types/src/cycle/cycle_filters.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/cycle/cycle_filters.ts), supporting date-range queries and progress-based filtering.

### Module Stores

[`apps/web/core/store/module.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/module.store.ts) handles Module selection and categorical filtering. The corresponding type definitions in [`packages/types/src/module/module_filters.ts`](https://github.com/makeplane/plane/blob/main/packages/types/src/module/module_filters.ts) enable color-based and status-based UI filtering.

## Practical Implementation Examples

### Creating a Sprint Cycle

```tsx
import { useCycle } from '@/core/hooks/store/use-cycle';

function NewSprintForm() {
  const { createCycle } = useCycle();

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createCycle({
      name: 'Sprint 23',
      start_date: '2026-07-01',
      end_date: '2026-07-14',
      project: currentProjectId,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields for name, start/end dates */}
    </form>
  );
}

```

*Implementation path*: [`packages/services/src/cycle/cycle.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/cycle/cycle.service.ts) → API call to `POST /api/v1/cycles/`.

### Creating a Feature Module

```tsx
import { useModule } from '@/core/hooks/store/use-module';

function NewFeatureGroupForm() {
  const { createModule } = useModule();

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createModule({
      name: 'Payments',
      color: '#ff6600',
      project: currentProjectId,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* fields for name, color picker */}
    </form>
  );
}

```

*Implementation path*: [`packages/services/src/module/module.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/module/module.service.ts) → API call to `POST /api/v1/modules/`.

### Assigning Issues to Both Containers

The `IssueSerializer` in [`apps/api/plane/space/serializer/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/space/serializer/issue.py) handles dual assignment:

```python
class IssueSerializer(serializers.ModelSerializer):
    cycle = serializers.PrimaryKeyRelatedField(
        queryset=Cycle.objects.all(),
        required=False,
        allow_null=True,
    )
    modules = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=Module.objects.all(),
        required=False,
    )

```

This configuration allows a single API payload to assign an issue to one Cycle (via foreign key) and multiple Modules (via many-to-many relation) simultaneously.

## Summary

- **Cycles** enforce time-boxed execution with `start_date` and `end_date` constraints, tracking sprint progress through the `CycleIssue` foreign key relationship.
- **Modules** provide flexible categorization without temporal bounds, using many-to-many `ModuleIssue` relationships allowing cross-cutting feature organization.
- **Data models** reside in [`apps/api/plane/db/models/cycle.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle.py) and [`module.py`](https://github.com/makeplane/plane/blob/main/module.py), with associated services in [`packages/services/src/cycle/cycle.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/cycle/cycle.service.ts) and [`module/module.service.ts`](https://github.com/makeplane/plane/blob/main/module/module.service.ts).
- **Frontend state** is managed through dedicated MobX stores ([`cycle.store.ts`](https://github.com/makeplane/plane/blob/main/cycle.store.ts), [`module.store.ts`](https://github.com/makeplane/plane/blob/main/module.store.ts)) with type-safe filter definitions.
- **Archival** preserves historical data for both entities, but Cycles retain progress snapshots while Modules retain color categorization for long-term product mapping.

## Frequently Asked Questions

### Can an issue belong to multiple Cycles simultaneously?

No. According to the Plane source code in [`apps/api/plane/db/models/cycle_issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle_issue.py), issues maintain a **foreign key relationship** to Cycles, restricting an issue to **zero or one** Cycle at any time. If you need to move work between sprints, you must reassign the issue to the new Cycle, effectively transferring it from the previous iteration.

### What happens when you archive a Cycle compared to a Module?

Archived Cycles remain accessible in a dedicated "Archived" tab and retain their `progress_snapshot` data for historical velocity analysis, as implemented in [`apps/api/plane/db/models/cycle.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle.py). Archived Modules also move to an archived view but preserve their `color` and `status` attributes for categorical reference, allowing issues to remain associated with these logical groupings for long-term product mapping even after the Module is no longer active.

### Do Modules support burndown charts like Cycles?

No. Burndown and velocity tracking are **Cycle-specific features** tied to the `start_date` and `end_date` fields. Modules lack temporal constraints and therefore do not generate progress-over-time statistics. For tracking completion rates within a Module, Plane aggregates issue statuses statically rather than calculating time-based burndown, as Modules are designed for categorical organization rather than sprint execution monitoring.

### Can Modules have start and end dates like Cycles?

While the base Module model in [`apps/api/plane/db/models/module.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/module.py) does not include date fields by design, the platform's architecture supports custom fields or workflow adaptations. However, the standard implementation treats Modules as timeless categories. If you need date-bound groupings, you should use **Cycles** specifically, as they are optimized for temporal queries and progress snapshots that Modules intentionally omit.