# How File Attachments Work in Plane: Storage, Size Limits, and API Endpoints

> Explore how Plane handles file attachments. Learn about storage options S3 Azure Blob local disk size limits and API endpoints for efficient file management.

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

---

**Plane handles file attachments through the IssueAttachment model, which wraps a reusable FileAsset stored in configurable backends (S3, Azure Blob, or local disk), enforces a default 25MB size limit, and exposes REST endpoints at `/api/issues/{issue_id}/attachments/` with a modern v2 API supporting chunked uploads and signed URLs.**

Plane is an open-source project management platform that treats file uploads as first-class entities within issue tracking. Understanding how file attachments in Plane are stored, validated, and retrieved is essential for customizing deployments or integrating with external storage systems. This article examines the underlying Django models, storage abstraction, and API surface based on the current source code in the makeplane/plane repository.

## Data Model and Storage Architecture

### The IssueAttachment and FileAsset Relationship

Every file dragged-and-dropped onto an issue creates an **IssueAttachment** instance. This model does not store the binary data directly; instead, it maintains a foreign key to the **FileAsset** model, which serves as the central storage layer for all uploads across the application (including project avatars).

In [`apps/api/plane/db/models/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/issue.py) (lines 398-417), the `IssueAttachment` model is defined with fields linking it to its parent issue, the creator, and the associated `FileAsset`:

```python

# From apps/api/plane/db/models/issue.py

class IssueAttachment(BaseModel):
    issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
    asset = models.ForeignKey("FileAsset", on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
    size = models.BigIntegerField()
    content_type = models.CharField(max_length=255)

```

The actual binary data and storage path reside in the `FileAsset` model ([`apps/api/plane/db/models/file_asset.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/file_asset.py)), which tracks the MIME type, byte size, and storage location regardless of the backend used.

### Storage Backend Configuration

Plane uses Django's storage abstraction layer, allowing operators to switch between local filesystem storage and cloud providers without modifying application code. By default, files are stored on the local disk, but the system supports **Amazon S3**, **Google Cloud Storage**, and **Azure Blob Storage** through the `django-storages` library.

Configuration is handled in [`apps/api/plane/settings/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/base.py). The active backend is determined by the `DEFAULT_FILE_STORAGE` setting (or `STORAGES["default"]["BACKEND"]` in newer Django versions), controlled via environment variables such as `FILE_STORAGE_BACKEND`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY`.

## File Size Limits and Validation

### Default 25MB Limit

Plane enforces a hard file size limit at the serializer level to prevent resource exhaustion. The default maximum is approximately **25 MB** (exactly 26214400 bytes), configurable through the `MAX_ATTACHMENT_SIZE` Django setting.

The validation occurs in [`apps/api/plane/app/serializers/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/serializers/issue.py) (lines 669-680) within the `IssueAttachmentUploadSerializer`:

```python

# From apps/api/plane/app/serializers/issue.py

class IssueAttachmentUploadSerializer(serializers.ModelSerializer):
    asset = serializers.FileField(
        max_length=settings.MAX_ATTACHMENT_SIZE,  # Default: 25MB

        allow_empty_file=False,
    )
    
    def validate_asset(self, value):
        if value.size > settings.MAX_ATTACHMENT_SIZE:
            raise serializers.ValidationError(
                f"File size exceeds {settings.MAX_ATTACHMENT_SIZE} bytes"
            )
        return value

```

### Chunked Upload Support

For files exceeding the standard upload threshold, Plane implements **chunked upload** logic. The frontend splits large files into segments, uploads them individually, and the server reassembles them into a single `FileAsset`. This logic resides in [`apps/api/plane/app/services/asset_v2.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/services/asset_v2.py), which handles multipart form data and temporary storage before final assembly.

## API Endpoints for Attachments

Plane exposes two generations of attachment APIs. Both versions inherit from `BaseAPIView`, which automatically applies authentication, project-level permissions, and rate throttling.

### Version 1 Endpoints

The original API lives in [`apps/api/plane/api/views/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/api/views/issue.py) and provides standard CRUD operations:

1. **List attachments**: `GET /api/issues/{issue_id}/attachments/` (line 1765)
2. **Upload attachment**: `POST /api/issues/{issue_id}/attachments/` using `multipart/form-data`
3. **Retrieve metadata**: `GET /api/issues/attachments/{attachment_id}/` (line 1980)
4. **Update metadata**: `PATCH /api/issues/attachments/{attachment_id}/`
5. **Delete**: `DELETE /api/issues/attachments/{attachment_id}/`

The upload endpoint uses the `IssueAttachmentUploadSerializer` to validate file size and content type before persisting records.

### Version 2 Endpoints and Signed URLs

The modern v2 API ([`apps/api/plane/app/views/issue/attachment.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/issue/attachment.py)) offers enhanced functionality including **presigned URLs** for direct browser-to-storage uploads and improved error handling. The v2 endpoints mirror the v1 structure but under the `/api/v2/issues/` path:

- `GET/POST /api/v2/issues/{issue_id}/attachments/`
- `GET/PATCH/DELETE /api/v2/issues/attachments/{attachment_id}/`

These endpoints support the chunked upload workflow and return signed URLs that allow clients to upload directly to the configured S3 or Azure bucket, bypassing the application server for large file transfers.

## Frontend Integration

The Plane web client consumes these endpoints through the **AttachmentStore** ([`apps/web/core/store/issue/issue-details/attachment.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/issue/issue-details/attachment.store.ts)), a MobX store that manages the attachment list for the currently viewed issue. The `AttachmentUpload` component handles drag-and-drop interactions, while `AttachmentListItem` renders individual files with download and delete actions.

## Summary

- **Architecture**: File attachments use a two-tier model where `IssueAttachment` references `FileAsset`, keeping storage concerns separate from issue logic.
- **Storage**: Configurable via Django settings in [`apps/api/plane/settings/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/base.py), supporting local disk, S3, Azure Blob, and GCS.
- **Limits**: Default 25MB cap enforced in `IssueAttachmentUploadSerializer` at [`apps/api/plane/app/serializers/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/serializers/issue.py), customizable via `MAX_ATTACHMENT_SIZE`.
- **APIs**: v1 endpoints in [`apps/api/plane/api/views/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/api/views/issue.py) provide basic CRUD; v2 endpoints in [`apps/api/plane/app/views/issue/attachment.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/issue/attachment.py) add signed URLs and chunked upload support.
- **Large files**: Chunked upload logic resides in [`apps/api/plane/app/services/asset_v2.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/services/asset_v2.py) for handling multipart uploads.

## Frequently Asked Questions

### What is the maximum file size for attachments in Plane?

By default, Plane limits attachments to approximately 25 MB per file. This value is controlled by the `MAX_ATTACHMENT_SIZE` setting in bytes and is enforced at the API serializer level in [`apps/api/plane/app/serializers/issue.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/serializers/issue.py).

### Can I use Amazon S3 instead of local storage for file attachments?

Yes. Plane uses Django's storage abstraction, allowing you to configure S3, Azure Blob Storage, or Google Cloud Storage by setting the appropriate environment variables (`FILE_STORAGE_BACKEND`, `AWS_ACCESS_KEY_ID`, etc.) in [`apps/api/plane/settings/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/base.py).

### What is the difference between the v1 and v2 attachment APIs?

The v1 API (`/api/issues/...`) provides standard CRUD operations for attachments, while the v2 API (`/api/v2/issues/...`) adds support for signed URLs and chunked uploads, enabling direct-to-storage uploads and better handling of large files through [`apps/api/plane/app/services/asset_v2.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/services/asset_v2.py).

### How does Plane handle large file uploads?

For files exceeding the standard upload size, Plane implements chunked upload logic. The frontend splits the file into chunks, uploads them to the v2 endpoints, and the server reassembles them using the service logic in [`apps/api/plane/app/services/asset_v2.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/services/asset_v2.py).