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

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 (lines 398-417), the IssueAttachment model is defined with fields linking it to its parent issue, the creator, and the associated FileAsset:


# 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), 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. 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 (lines 669-680) within the IssueAttachmentUploadSerializer:


# 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, 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 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) 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), 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

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.

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.

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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →