From f3277a4f1556c3864bebc707d506c5a38755f976 Mon Sep 17 00:00:00 2001 From: Dylan Dellett-Wion Date: Thu, 30 Apr 2026 21:36:53 -0400 Subject: [PATCH 1/2] Document bucket CORS requirement for external S3 backends Plane's web UI uploads files directly to the S3 endpoint via presigned POST. When the endpoint is on a different hostname from the web UI, the browser issues a CORS preflight that the bucket must answer; the docs only cover the credentials/region/endpoint envs and don't mention this. Bundled MinIO masks the issue by being routed under the same appHost ingress (same-origin), so external-S3 deployments are the only ones affected. Adds a "Bucket CORS configuration" subsection to the Doc Store (Minio/S3) Setup block in both the CE and commercial Kubernetes guides, with a reference CORSRules policy and the put-bucket-cors command. Refs: makeplane/developer-docs#269 Co-Authored-By: Claude --- .../install-methods-commercial/kubernetes.md | 28 +++++++++++++++++++ docs/self-hosting/methods/kubernetes.md | 28 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/docs/self-hosting/methods/install-methods-commercial/kubernetes.md b/docs/self-hosting/methods/install-methods-commercial/kubernetes.md index b2429f05..332c36d2 100644 --- a/docs/self-hosting/methods/install-methods-commercial/kubernetes.md +++ b/docs/self-hosting/methods/install-methods-commercial/kubernetes.md @@ -280,6 +280,34 @@ airgapped: | env.aws_s3_endpoint_url | | | External `S3` (or compatible) storage service providers shares a `endpoint_url` for the integration purpose for the application to connect and do the necessary upload or download operations. To be provided when `services.minio.local_setup=false` | | env.use_storage_proxy | false | | When set to `true`, all S3 (or compatible) file GET requests from the browser are proxied through Plane's API service instead of accessing the S3 endpoint directly. Enable this if your storage endpoint is not accessible publicly or you want to control download access through the API. | +##### Bucket CORS configuration (required for browser uploads to external S3) + +When `services.minio.local_setup=false` you must configure CORS on the target bucket. Plane's web UI uploads files directly to your S3 endpoint via presigned POST. When the S3 endpoint is on a different hostname from the Plane web UI, the browser's same-origin policy requires the bucket to answer a CORS preflight; otherwise uploads fail with `net::ERR_FAILED` even though all server-side configuration is correct. + +The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin); external backends are always cross-origin and need explicit CORS configuration. + +Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL: + +```json +{ + "CORSRules": [ + { + "AllowedOrigins": ["https://plane.example.com"], + "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], + "AllowedHeaders": ["*"], + "ExposeHeaders": ["ETag"], + "MaxAgeSeconds": 3000 + } + ] +} +``` + +```bash +aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \ + --bucket uploads \ + --cors-configuration file://cors.json +``` + #### Web Deployment | Setting | Default | Required | Description | diff --git a/docs/self-hosting/methods/kubernetes.md b/docs/self-hosting/methods/kubernetes.md index d77f99a6..afc0b38c 100644 --- a/docs/self-hosting/methods/kubernetes.md +++ b/docs/self-hosting/methods/kubernetes.md @@ -278,6 +278,34 @@ airgapped: | env.aws_s3_endpoint_url | | | External `S3` (or compatible) storage service providers shares a `endpoint_url` for the integration purpose for the application to connect and do the necessary upload or download operations. To be provided when `services.minio.local_setup=false` | | env.use_storage_proxy | false | | When set to `true`, all S3 (or compatible) file GET requests from the browser are proxied through Plane's API service instead of accessing the S3 endpoint directly. Enable this if your storage endpoint is not accessible publicly or you want to control download access through the API. | +##### Bucket CORS configuration (required for browser uploads to external S3) + +When `services.minio.local_setup=false` you must configure CORS on the target bucket. Plane's web UI uploads files directly to your S3 endpoint via presigned POST. When the S3 endpoint is on a different hostname from the Plane web UI, the browser's same-origin policy requires the bucket to answer a CORS preflight; otherwise uploads fail with `net::ERR_FAILED` even though all server-side configuration is correct. + +The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin); external backends are always cross-origin and need explicit CORS configuration. + +Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL: + +```json +{ + "CORSRules": [ + { + "AllowedOrigins": ["https://plane.example.com"], + "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], + "AllowedHeaders": ["*"], + "ExposeHeaders": ["ETag"], + "MaxAgeSeconds": 3000 + } + ] +} +``` + +```bash +aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \ + --bucket uploads \ + --cors-configuration file://cors.json +``` + #### Web Deployment | Setting | Default | Required | Description | From 0096828bfcf94eae85044e8470e8882345e22108 Mon Sep 17 00:00:00 2001 From: Dylan Dellett-Wion Date: Thu, 30 Apr 2026 21:48:09 -0400 Subject: [PATCH 2/2] Address review: clarify CORS necessity, soften origin claim, generalize bucket placeholder - Soften "external backends are always cross-origin" to note the proxy/DNS-alias workaround. - Add a note that env.use_storage_proxy=true only proxies downloads and that uploads still require bucket CORS. - Replace the literal `--bucket uploads` placeholder with `` and reference env.docstore_bucket. Co-Authored-By: Claude --- .../methods/install-methods-commercial/kubernetes.md | 8 +++++--- docs/self-hosting/methods/kubernetes.md | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/self-hosting/methods/install-methods-commercial/kubernetes.md b/docs/self-hosting/methods/install-methods-commercial/kubernetes.md index 332c36d2..796f31da 100644 --- a/docs/self-hosting/methods/install-methods-commercial/kubernetes.md +++ b/docs/self-hosting/methods/install-methods-commercial/kubernetes.md @@ -284,9 +284,11 @@ airgapped: When `services.minio.local_setup=false` you must configure CORS on the target bucket. Plane's web UI uploads files directly to your S3 endpoint via presigned POST. When the S3 endpoint is on a different hostname from the Plane web UI, the browser's same-origin policy requires the bucket to answer a CORS preflight; otherwise uploads fail with `net::ERR_FAILED` even though all server-side configuration is correct. -The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin); external backends are always cross-origin and need explicit CORS configuration. +The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin). External backends are typically cross-origin and need explicit CORS configuration, unless you front the S3 endpoint with a same-origin reverse proxy or DNS alias. -Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL: +`env.use_storage_proxy=true` only affects browser-initiated GETs (downloads); uploads always go directly to the S3 endpoint via presigned POST and still require bucket CORS. + +Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL and `` with the value of `env.docstore_bucket` (default `uploads`): ```json { @@ -304,7 +306,7 @@ Apply the following CORS configuration to your bucket, replacing `https://plane. ```bash aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \ - --bucket uploads \ + --bucket \ --cors-configuration file://cors.json ``` diff --git a/docs/self-hosting/methods/kubernetes.md b/docs/self-hosting/methods/kubernetes.md index afc0b38c..36e6c9bb 100644 --- a/docs/self-hosting/methods/kubernetes.md +++ b/docs/self-hosting/methods/kubernetes.md @@ -282,9 +282,11 @@ airgapped: When `services.minio.local_setup=false` you must configure CORS on the target bucket. Plane's web UI uploads files directly to your S3 endpoint via presigned POST. When the S3 endpoint is on a different hostname from the Plane web UI, the browser's same-origin policy requires the bucket to answer a CORS preflight; otherwise uploads fail with `net::ERR_FAILED` even though all server-side configuration is correct. -The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin); external backends are always cross-origin and need explicit CORS configuration. +The bundled MinIO setup avoids this by routing the bucket through the same ingress as the web UI (same-origin). External backends are typically cross-origin and need explicit CORS configuration, unless you front the S3 endpoint with a same-origin reverse proxy or DNS alias. -Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL: +`env.use_storage_proxy=true` only affects browser-initiated GETs (downloads); uploads always go directly to the S3 endpoint via presigned POST and still require bucket CORS. + +Apply the following CORS configuration to your bucket, replacing `https://plane.example.com` with your Plane web URL and `` with the value of `env.docstore_bucket` (default `uploads`): ```json { @@ -302,7 +304,7 @@ Apply the following CORS configuration to your bucket, replacing `https://plane. ```bash aws --endpoint-url https://your-s3-endpoint s3api put-bucket-cors \ - --bucket uploads \ + --bucket \ --cors-configuration file://cors.json ```