== WHY (KEEP THIS — IT'S WHY THE FORK EXISTS) ==
Vanilla Plane's upload flow uses AWS S3 PostObject (presigned POST +
multipart/form-data + signed-policy-document). Cloudflare R2 AND
Backblaze B2 — the two most common self-host S3-compatible backends —
both return HTTP 501 NotImplemented for PostObject. Empirically verified
2026-04-30 against B2 s3.us-west-004.backblazeb2.com from inside Plane's
own prod api container, replicating Plane's exact boto3 call:
PUT against B2: 200 OK
POST against B2: 501 NotImplemented "This API call is not supported."
POST against R2: 501 NotImplemented (failure that started this thread)
The error code is `NotImplemented` (not `SignatureDoesNotMatch` etc),
meaning the server rejects the verb itself — no boto3 config, addressing-
style flag, or signature variant fixes it. Tested both path-style and
virtual-hosted-style URLs against B2; both fail identically for POST.
This patch rewrites the upload flow to use presigned PUT, which is
universally supported (R2, B2, AWS S3 native, MinIO, Wasabi, etc).
== WHAT (FIVE-FILE BACKEND, FIVE-FILE FRONTEND) ==
Backend:
* apps/api/plane/settings/storage.py — S3Storage.generate_presigned_post
now mints a presigned PUT URL via generate_presigned_url(HttpMethod="PUT").
Method name kept for caller compat. Response shape:
{url, method: "PUT", fields: {Content-Type, key}}.
* apps/api/plane/utils/openapi/responses.py — example response updated.
* apps/api/plane/tests/unit/settings/test_storage.py — 2 tests updated to
assert the new boto3 call.
Frontend:
* packages/types/src/file.ts — TFileSignedURLResponse.upload_data adds
optional method?: "PUT" | "POST"; drops AWS POST-form-data fields.
* packages/services/src/file/helper.ts — generateFileUploadPayload now
returns a TFileUploadRequest descriptor (url+method+body+headers) that
dispatches on method. POST branch kept for upstream parity but the
fork backend never emits POST.
* packages/services/src/file/file-upload.service.ts +
apps/web/core/services/file-upload.service.ts — uploadFile signature
changes from (url, FormData, progress?) to (payload, progress?).
* 5 caller sites updated (apps/web/core/services/file.service.ts x3,
issue_attachment.service.ts x1, sites-file.service.ts x1).
== TRADEOFFS ACCEPTED ==
* Lost: signed `content-length-range` enforcement at the storage layer.
Server-side validation in the API view still rejects oversized requests
with 413 before minting the URL, so a determined client could only
over-upload by misreporting size, capped at the bucket's own size limit.
* Different request shape on the wire (PUT with raw binary body vs POST
with multipart form). Externally invisible to users.
== ROLLBACK ==
If this becomes a maintenance nightmare:
git revert <this-commit-sha>
# rebuild + push images, swap compose tags, redeploy
After revert, uploads will only work against backends that implement
PostObject (MinIO, AWS S3 native). R2 and B2 will return 501 again.
== FULL DECISION RECORD ==
binarybeachio repo: docs/features/storage-upload-flow.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: rename IssueUserProperty to ProjectUserProperty and update related references across the codebase
* migrate: move issue user properties to project user properties and update related fields and constraints
* refactor: rename IssueUserPropertySerializer and IssueUserDisplayPropertyEndpoint to ProjectUserPropertySerializer and ProjectUserDisplayPropertyEndpoint, updating all related references
* fix: enhance ProjectUserDisplayPropertyEndpoint to handle missing properties by creating new entries and improve response handling
* fix: correct formatting in migration for ProjectUserProperty model options
* migrate: add migration to update existing non-service API tokens to remove workspace association
* migrate: refine migration to update existing non-service API tokens by excluding bot users from workspace removal
* chore: changed the project sort order in project user property
* chore: remove allowed_rate_limit from APIToken
* chore: updated user-properties endpoint for frontend
* chore: removed the extra projectuserproperty
* chore: updated the migration file
* chore: code refactor
* fix: type error
---------
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
Co-authored-by: vamsikrishnamathala <matalav55@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
* [WEB-5804] refactor: decouple filter value types from filter configurations
Remove value type constraints from filter configurations to support
operator-specific value types. Different operators can accept different
value types for the same filter property, so value types should be
determined at the operator level rather than the filter level.
- Remove generic value type parameter from TFilterConfig
- Update TOperatorConfigMap to accept union of all value types
- Simplify filter config factory signatures across all filter types
- Add forceUpdate parameter to updateConditionValue method
* refactor: remove filter value type constraints from filter configurations
Eliminate the generic value type parameter from filter configurations to allow for operator-specific value types. This change enhances flexibility by enabling different operators to accept various value types for the same filter property.
- Updated TFilterConfig and related interfaces to remove value type constraints
- Adjusted filter configuration methods and types accordingly
- Refactored date operator support to align with the new structure
* feat: add sync functionality for OAuth providers
- Implemented `check_sync_enabled` method to verify if sync is enabled for Google, GitHub, GitLab, and Gitea.
- Added `sync_user_data` method to update user details, including first name, last name, display name, and avatar.
- Updated configuration variables to include sync options for each provider.
- Integrated sync check into the login/signup process.
* feat: add sync toggle for OAuth providers in configuration forms
* fix: remove default value for sync options in OAuth configuration forms
* chore: delete old avatar and upload a new one
* chore: update class method
* chore: add email nullable
* refactor: streamline sync check for multiple providers and improve avatar deletion logic
* fix: ensure ENABLE_SYNC configurations default to "0" for Gitea, Github, Gitlab, and Google forms
* fix: simplify toggle switch value handling in ControllerSwitch component
---------
Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
* feat: add timezone selection to workspace onboarding, creation and settings
* refactor: remove timezone selection from workspace creation and onboarding forms
* feat: add in common py
* fix: update marketing consent screen based on is self managed flag
* improvement: enhance ImagePickerPopover with dynamic tab options based on Unsplash configuration
* refactor: product updates modal to include changelog
* [WEB-5290] feat: implement fallback for product updates changelog with loading state and error handling
---------
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
* chore: traige state in intake
* chore: triage state changes
* feat: implement intake state dropdown component and integrate into issue properties
* chore: added the triage state validation
* chore: added triage state filter
* chore: added workspace filter
* fix: migration file
* chore: added triage group state check
* chore: updated the filters
* chore: updated the filters
* chore: added variables for intake state
* fix: import error
* refactor: improve project intake state retrieval logic and update TriageGroupIcon component
* chore: changed the intake validation logic
* refactor: update intake state types and clean up unused interfaces
* chore: changed the state color
* chore: changed the update serializer
* chore: updated with current instance
* chore: update TriageGroupIcon color to match new intake state group color
* chore: stringified value
* chore: added validation in serializer
* chore: added logger instead of print
* fix: correct component closing syntax in ActiveProjectItem
* chore: updated the migration file
* chore: added noop in migation
---------
Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
* feat: enhance rich filters with new components and configurations
- Added `AdditionalFilterValueInput` for unsupported filter types.
- Introduced `FilterItem` and related components for better filter item management.
- Updated filter configurations to include new properties and support for multiple values.
- Improved loading states and error handling in filter components.
- Refactored existing filter logic to streamline operations and enhance performance.
* Refactor rich filters component structure and enhance filter item functionality
- Moved AddFilterButton and AddFilterDropdown to a new directory structure for better organization.
- Updated FilterItemProperty to handle filter selection and condition updates more effectively.
- Enhanced the FilterInstance class with methods to update condition properties and operators, improving filter management.
- Added new functionality to handle invalid filter states and improve user feedback.
* [WEB-5111] feat: add 'created_at' and 'updated_at' filters to work item configuration
- Introduced new filter configurations for 'created_at' and 'updated_at' in the work item filters.
- Updated relevant components to utilize these new filters, enhancing filtering capabilities.
- Added corresponding filter configuration functions in the utils for better date handling.
* fix: build