# How Plane Module Management Groups Related Issues Across Cycles and Tracks Dependencies

> Discover how Plane's module management groups related issues and tracks dependencies using a hierarchical data model and smart linking for cross-dimensional filtering.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: how-to-guide
- Published: 2026-06-22

---

**Plane uses a hierarchical data model where Modules serve as first-class grouping containers above individual issues, leveraging a many-to-many link table (`ModuleIssue`) to connect issues to modules while preserving cycle assignments through a nullable `cycle_id` field, allowing cross-dimensional filtering and dependency surfacing via the `IssueLink` table.**

Plane's module management system enables teams to organize work thematically across time-boxed iterations. In the `makeplane/plane` repository, modules function as persistent containers that transcend individual cycles, creating a matrix view where you can track related issues by both thematic grouping and sprint timing. This architecture supports complex dependency mapping while maintaining clean separation between temporal and logical organization.

## The Data Model: Modules, Issues, and Cycles

The relationship structure follows a chain where cycles and modules intersect at the issue level:

```

Cycle ──► Issue ◄──► ModuleIssue ◄──► Module

```

**Modules** are defined 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 stores metadata including name, dates, status, and ordering fields. The `ModuleIssue` class acts as a junction table:

```python

# apps/api/plane/db/models/module.py

class ModuleIssue(ProjectBaseModel):
    module = models.ForeignKey("db.Module", on_delete=models.CASCADE, related_name="issue_module")
    issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="issue_module")

```

**Issues** maintain a nullable `cycle_id` column in their model definition, allowing them to exist independently or within a specific cycle. This dual-linking capability enables the system to surface issues that belong to both a specific module and a specific cycle simultaneously.

## Grouping Issues Across Cycles

### Backend Query Logic

The API retrieves module-grouped issues through the endpoint `GET /api/v1/modules/{module_id}/issues/`, decorated with OpenAPI specifications in [`apps/api/plane/utils/openapi/decorators.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/openapi/decorators.py). The query construction leverages the `filter_module` helper in [`apps/api/plane/utils/issue_filters.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/issue_filters.py):

```python
Issue.objects.filter(
    issue_module__module_id=module_id,          # join via ModuleIssue

    cycle_id=cycle_id,                         # optional filter by cycle

    issue_module__deleted_at__isnull=True,     # exclude soft-deleted links

)

```

This query pattern allows the system to fetch all issues associated with a module while optionally constraining the results to a specific cycle, enabling the "Module X in Cycle Y" view.

### Frontend Grouping

The **ModulesListView** component in [`apps/web/core/components/modules/modules-list-view.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/modules/modules-list-view.tsx) handles the presentation layer. When users toggle "group by cycle," the component aggregates issues using the cycle field:

```tsx
// apps/web/core/components/modules/modules-list-view.tsx
const grouped = groupBy(issues, (issue) => issue.cycle?.name ?? 'No Cycle');
return (
  <Accordion>
    {Object.entries(grouped).map(([cycleName, issues]) => (
      <AccordionItem key={cycleName} title={cycleName}>
        <IssueList items={issues} />
      </AccordionItem>
    ))}
  </Accordion>
);

```

The filter configuration resides in [`packages/utils/src/work-item-filters/configs/filters/module.ts`](https://github.com/makeplane/plane/blob/main/packages/utils/src/work-item-filters/configs/filters/module.ts), which constructs filter objects that the [`module_filter.store.ts`](https://github.com/makeplane/plane/blob/main/module_filter.store.ts) store processes to include `cycle_id` parameters in API requests.

## Dependency Tracking

While dependencies are stored separately in the `IssueLink` table, module-centric views surface these relationships to maintain visibility across thematic boundaries. When serializer logic in [`apps/api/plane/utils/serializers/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/serializers/issue.py) prepares module data, it includes a `dependencies` field listing linked issue IDs.

The **IssueDetail** component in [`apps/web/core/components/issue-detail/issue-detail.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/issue-detail/issue-detail.tsx) renders these dependencies regardless of module membership:

```tsx
// apps/web/core/components/issue-detail/issue-detail.tsx
{issue.dependencies?.length && (
  <Section title="Dependencies">
    {issue.dependencies.map((dep) => (
      <Link key={dep.id} to={`/issues/${dep.id}`}>{dep.title}</Link>
    ))}
  </Section>
)}

```

This implementation allows users to identify cross-module blockers while maintaining their primary module and cycle groupings.

## Implementation Examples

### Linking Issues to Modules

Use the service layer method in [`packages/services/src/module/module.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/module/module.service.ts):

```typescript
import { ModuleService } from '@plane/services/module';

async function addIssueToModule(moduleId: string, issueId: string) {
  await ModuleService.addIssue({ module_id: moduleId, issue_id: issueId });
}

```

### Filtering by Module and Cycle

API requests support combined filtering via query parameters:

```bash
curl "https://api.plane.so/api/v1/modules/550e8400-e29b-41d4-a716-446655440001/issues?cycle=3f1c2d4e"

```

The `orderModules` utility in [`packages/utils/src/module.ts`](https://github.com/makeplane/plane/blob/main/packages/utils/src/module.ts) handles client-side sorting of these results.

## Summary

- **Modules** are first-class containers defined in [`apps/api/plane/db/models/module.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/module.py) that persist across cycles
- **ModuleIssue** serves as a many-to-many junction table linking issues to modules while preserving independent cycle assignments
- **Cross-cycle grouping** is achieved by filtering `Issue` objects through both `issue_module__module_id` and `cycle_id` fields
- **Dependency visibility** spans module boundaries through the `IssueLink` table and serializer-side inclusion of `dependencies` arrays
- **Frontend components** like `ModulesListView` leverage the `groupBy` utility to organize issues by cycle within module views

## Frequently Asked Questions

### How does Plane handle issues that belong to multiple modules?

Plane supports many-to-many relationships between issues and modules through the `ModuleIssue` junction table. An issue can link to multiple modules simultaneously while maintaining its cycle assignment, allowing it to appear in different module views while retaining its sprint context. The `ModuleIssue` model in [`apps/api/plane/db/models/module.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/module.py) manages these associations with soft-delete support via the `deleted_at` field.

### Can I filter module issues by specific cycles in the API?

Yes. The API endpoint `GET /api/v1/modules/{module_id}/issues/` accepts a `cycle` query parameter that filters results to issues belonging to both the specified module and cycle. The `filter_module` function in [`apps/api/plane/utils/issue_filters.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/issue_filters.py) constructs this query by joining the `ModuleIssue` and `Issue` tables and applying the `cycle_id` filter.

### Where does Plane store dependency information between issues?

Dependencies are tracked in the `IssueLink` table (referenced in the serializers), though the exact table definition lives outside the module-specific code. The `IssueDetail` component surfaces these relationships by rendering the `dependencies` array populated by [`apps/api/plane/utils/serializers/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/serializers/issue.py), showing linked issues regardless of their module or cycle assignments.

### How do I programmatically add an issue to a module?

Use the `ModuleService` class in [`packages/services/src/module/module.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/module/module.service.ts). The `addIssue` method accepts an object containing `module_id` and `issue_id`, creating the association in the `ModuleIssue` table. This service layer handles the API communication to `POST /api/v1/modules/{module_id}/issues/`.