--- url: 'https://developers.plane.so/self-hosting/overview.html' description: >- Deploy Plane on your own infrastructure with Docker, Kubernetes, or Podman. Complete self-hosting guides for open-source project management with full control and customization. --- # Deploy Plane on your infrastructure Take complete control of your project management infrastructure by deploying Plane on your own servers. Self-hosting Plane gives you full ownership of your data and the flexibility to deploy wherever you need - from a single Docker container to enterprise Kubernetes clusters. ## Why self-host Plane? **Data sovereignty and privacy**\ Keep all your project data within your own infrastructure. Perfect for organizations with strict data residency requirements or privacy regulations. **Complete control**\ Customize every aspect of Plane to match your workflows. Control when and how updates are applied, and integrate with your existing tools and infrastructure. **Compliance and security**\ Meet regulatory requirements like GDPR, HIPAA, SOC 2, or industry-specific standards by maintaining full control over data storage and access. **No vendor lock-in**\ Your data remains accessible in open formats. Migrate, backup, or customize without restrictions. ## Deployment methods Choose the deployment method that best fits your infrastructure and team size: [Other deployment methods](/self-hosting/methods/overview) ## Configuration and governance Once deployed, configure your Plane instance to match your organization's needs: --- --- url: 'https://developers.plane.so/api-reference/introduction.html' description: >- Complete REST API reference for Plane. Learn authentication, HTTP methods, pagination, rate limiting, and how to integrate Plane with your applications programmatically. --- # Plane API Documentation The Plane API is organized around REST. Our API has predictable resource-oriented URLs, accepts application/json request bodies, returns JSON responses, and uses standard HTTP response codes, authentication, and verbs. ## Base URL All requests to the Plane Cloud API must be made to the following base URL: ``` https://api.plane.so/ ``` This URL should be prefixed to all endpoint paths. For example, to retrieve all projects in a workspace: ``` GET https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/ ``` ::: tip If you're using a self-hosted instance of Plane, your API base URL will differ based on your custom domain and setup. ::: ## Authentication Our APIs use a key for authentication. The API key should be included in the header of each request to verify the client's identity and permissions. The key should be passed as the value of the `X-API-Key` header. ::: info You must have a Plane account or be registered to your instance to generate a key. ::: ### Generating an API Key 1. Log into your Plane account and go to **Profile Settings**. 2. Go to **Personal Access Tokens** in the list of tabs available. 3. Click `Add personal access token`. 4. Choose a title and description so you know why you are creating this token and where you will use it. 5. Choose an expiry if you want this to stop working after a point. ### Using the API Key To authenticate an API request, include your API key in the request header: ``` X-API-Key: ``` It is important to keep your API key confidential to prevent unauthorized access to your account. ### Using an OAuth Token If your application uses [OAuth](/dev-tools/build-plane-app/overview) to obtain user authorization (for example, a Plane app you've built), you can authenticate API requests with the OAuth access token. Include the token in the `Authorization` header as a Bearer token: ``` Authorization: Bearer ``` The access token is scoped to the permissions (scopes) the user granted when authorizing your app. See [OAuth scopes](/dev-tools/build-plane-app/oauth-scopes) for the full list of available scopes. ### Example of an Authenticated API Request **Using an API key:** ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/ Headers: X-API-Key: plane_api_ ``` **Using an OAuth token:** ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/ Headers: Authorization: Bearer ``` **Response:** ```json { [ ... ] } ``` ### Error Handling * **Missing API Key**: If the `X-API-Key` header is not included, the API will return an error indicating that authentication is required. * **Invalid API Key**: If the provided API key is invalid or expired, the API will return an error message indicating an authentication failure. ### Security Recommendations * **Keep the API Key Secret**: Treat your API key like a password. Do not share it or expose it in client-side code. * **Regenerate Key If Compromised**: If you suspect that your API key has been compromised, generate a new one immediately and update your applications. *** ## HTTP Methods HTTP defines a set of request methods, also known as HTTP verbs, to indicate the desired action for a given resource. | Verb | Description | Example | | ------ | --------------------------------------------------- | ------------------------------- | | GET | Requests a representation of the specified resource | Fetch all issues from a project | | POST | Submits an entity to the specified resource | Create a project | | DELETE | Deletes the specified resource | Delete a module-issue | | PATCH | Applies partial modifications to a resource | Edit a module | ## Status Codes ### Success Responses | Status Code | Description | | -------------- | --------------------------------------------------------------------------------------------------- | | 200 OK | The request succeeded, and a new resource was created, generally sent in GET or PATCH requests. | | 201 Created | The request is succeeded, and a new resource was created, generally sent in POST or PATCH requests. | | 204 No Content | The request is succeeded, and no body is sent, generally comes from the DELETE request. | ### Error Responses | Status Code | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 Bad Request | The server cannot or will not process the request due to something that is perceived to be a client error. | | 401 Unauthorized | Although the HTTP standard specifies "unauthorized", semantically, this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. | | 404 Not Found | The server cannot find the requested resource. This means the URL is not recognized. | | 429 Throttling Error | The server is processing too many requests at once and is unable to process your request. Retry the request after some time. | | 500 Internal Server Error | The server has encountered a situation it does not know how to handle. | | 502 Bad Gateway | This error response means that the server got an invalid response while working as a gateway to get a response needed to handle the request. | | 503 Service Unavailable | The server is not ready to handle the request. Common causes are a server that is down for maintenance or is overloaded. | | 504 Gateway Timeout | This error response is given when the server acts as a gateway and cannot get a timely response. | *** ## Pagination ### Overview This API implements a cursor-based pagination system, allowing clients to efficiently navigate through large datasets. The system uses a cursor parameter to manage the position and direction of pagination. ### Cursor Format The cursor is a string formatted as `value:offset:is_prev`, where: * `value` represents the page size (number of items per page). * `offset` is the current page number (starting from 0). * `is_prev` indicates whether the cursor is moving to the previous page (`1`) or to the next page (`0`). ### Request Parameters * **`per_page` (optional)**: Number of items to display per page. Defaults to 100. The maximum allowed value specified by the server is 100. * **`cursor` (optional)**: Cursor string to navigate to a specific page. If not provided, pagination starts from the first page. ### Response Fields The paginated response includes the following fields: | Field | Description | | ------------------- | -------------------------------------------------------------------- | | `next_cursor` | Cursor string for the next page. | | `prev_cursor` | Cursor string for the previous page. | | `next_page_results` | Boolean indicating if there are more results after the current page. | | `prev_page_results` | Boolean indicating if there are results before the current page. | | `count` | Total number of items on the current page. | | `total_pages` | Estimated total number of pages. | | `total_results` | Total number of items across all pages. | | `extra_stats` | Additional statistics, if any. | | `results` | Array of items for the current page. | ### Example: Fetching the First Page **Request:** ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?per_page=20 ``` **Response:** ```json { "next_cursor": "20:1:0", "prev_cursor": "", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 50, "total_results": 1000, "extra_stats": {}, "results": [ ... ] } ``` ### Example: Fetching the Next Page **Request:** ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items?per_page=20&cursor=20:1:0 ``` **Response:** ```json { "next_cursor": "20:2:0", "prev_cursor": "20:0:1", "next_page_results": true, "prev_page_results": true, "count": 20, "total_pages": 50, "total_results": 1000, "extra_stats": {}, "results": [ ... ] } ``` *** ## Rate Limiting ### Overview To ensure fair usage and maintain the quality of service for all users, our API implements rate limiting. Rate limiting restricts the number of requests a client can make within a certain time frame. ### Rate Limit Details * **Limit**: Each client is limited to 60 requests per minute. * **Reset Interval**: The rate limit counter resets every minute. * **Scope of Limitation**: The rate limit applies to all requests made with a given API key. ### Identifying Your Rate Limit Status Rate limit status is communicated in the response headers of each API request: * **`X-RateLimit-Remaining`**: The number of requests remaining in the current rate limit window. * **`X-RateLimit-Reset`**: The time at which the current rate limit window resets (in UTC epoch seconds). ``` X-RateLimit-Remaining: 45 X-RateLimit-Reset: 1700327957 ``` *** ## Fields and Expand Query Parameters Our API provides flexible data retrieval capabilities through two powerful query parameters: `fields` and `expand`. These parameters allow clients to tailor the response data to their specific needs, optimizing both the payload size and the clarity of the response. ### Fields Parameter The `fields` parameter enables clients to selectively retrieve only a subset of fields for a given resource. This is particularly useful for minimizing response size and bandwidth consumption, especially when the client requires only specific pieces of data. **Usage:** The `fields` parameter accepts a comma-separated list of field names that the client wants to be included in the response. ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?fields=id,name,description ``` In this example, the API will return only the `id`, `name`, and `description` fields of the resource. ### Expand Parameter The `expand` parameter allows clients to request additional related information to be included in the response. This is useful for retrieving detailed information about nested resources without making separate API calls. **Usage:** The `expand` parameter can be used to include details of related resources or nested objects in the response. ``` GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?expand=assignees,state ``` This request will return the resource data along with expanded information about the `assignees` and `state`. ### Error Handling * Invalid or unrecognized field names passed in the `fields` parameter will result in an error response, indicating which fields are invalid. * Similarly, if `expand` is used on fields that cannot be expanded, an appropriate error message will be returned. --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/overview.html' description: >- Build and integrate an app with Plane using OAuth 2.0 authentication. Covers bot tokens, user tokens, webhooks, and API access for custom integrations. --- # Build a Plane app ::: info Plane apps are currently in **Beta**. Please send any feedback to support@plane.so. ::: ## Overview Plane uses OAuth 2.0 to allow applications to access workspace data on behalf of users or as an autonomous bot. This comprehensive guide covers everything you need to build, integrate, and deploy apps that extend Plane's functionality. ## What you can build Plane apps enable you to: * **AI Agents** - Create intelligent agents that respond to @mentions in work item comments * **Workflow Automation** - Build bots that automate repetitive tasks across your workspace * **Integrations** - Connect Plane with external tools and services * **Custom Dashboards** - Build analytics and reporting tools using Plane's data * **Webhook Handlers** - React to events in real-time as they happen in Plane ## Key concepts ### OAuth 2.0 flows Plane supports two authentication flows: * **Bot Token Flow** (Client Credentials) - For autonomous apps, agents, and webhooks that act independently * **User Token Flow** (Authorization Code) - For apps that need to act on behalf of specific users Most integrations should use the **Bot Token flow**. See [Choose Your Flow](/dev-tools/build-plane-app/choose-token-flow) for detailed implementation guides. ### App components A complete Plane app typically includes: 1. **OAuth Application** - Registered in Plane with Client ID and Secret 2. **Setup URL** - Entry point where users begin the installation process 3. **Redirect URI** - Callback endpoint that receives authorization codes 4. **Webhook URL** - Endpoint for receiving real-time event notifications 5. **API Integration** - Code that interacts with Plane's REST API ## Getting started Follow these steps to build your first Plane app: ### 1. Create an OAuth application Register your app in Plane to get credentials: * Navigate to **Workspace Settings** → **Integrations** * Configure your app's URLs and permissions * Store your **Client ID** and **Client Secret** securely [Learn more →](/dev-tools/build-plane-app/create-oauth-application) ### 2. Choose your authentication flow Decide between Bot Token or User Token based on your use case: * **Bot Token** - For agents, webhooks, and automation * **User Token** - For user-specific actions and permissions [Learn more →](/dev-tools/build-plane-app/choose-token-flow) ### 3. Implement OAuth Set up the OAuth flow to obtain access tokens: * Redirect users to Plane's consent screen * Handle the callback with authorization code * Exchange code for access tokens * Store tokens securely for API calls [Learn more →](/dev-tools/build-plane-app/choose-token-flow) ### 4. Handle webhooks Set up webhook handlers to receive real-time events: * Verify webhook signatures for security * Process events like work item updates, comments, and more * Respond to events with automated actions [Learn more →](/dev-tools/build-plane-app/webhooks) ## Development tools ::: tip Local Development For local development, use [ngrok](https://ngrok.com) to expose your server: ```bash ngrok http 3000 ``` Use the generated URL (e.g., `https://abc123.ngrok.io`) for your Setup URL, Redirect URI, and Webhook URL. Free ngrok URLs change on restart. Update your app settings when the URL changes. ::: ### Official SDKs Speed up development with official SDKs for Node.js and Python: * OAuth helpers for token management * Typed API clients for all endpoints * Built-in error handling and retries [Learn more →](/dev-tools/build-plane-app/sdks) ### Complete examples See full working implementations: * TypeScript (Express) example * Python (Flask) example * OAuth flow, webhooks, and API integration [Learn more →](/dev-tools/build-plane-app/examples) ## Quick links * [API Reference](/api-reference/introduction) - Explore all available endpoints * [Build an Agent](/dev-tools/agents/overview) - Create AI agents for Plane * [Webhook Events](/dev-tools/intro-webhooks) - All webhook event types --- --- url: 'https://developers.plane.so/self-hosting/self-hosting-101.html' description: >- What self-hosting Plane involves, how it's licensed, what your team operates, and how to plan a deployment. --- # Self-hosting 101 Self-hosting Plane means running the full application stack on infrastructure you control. This page covers what that involves, how Plane is licensed, what your team operates, and how to plan a deployment. If you're ready to deploy, jump to the [deployment scenarios on the overview](/self-hosting/overview). *** ## 1. What self-hosting Plane means Self-hosting Plane means deploying Plane, the project and knowledge management platform, on infrastructure you control. Plane ships as a bundled Docker or Kubernetes deployment that includes the application and everything it depends on, so you can stand it up with a single command on a single machine. Everything user-facing runs inside your network: the web app, the API, file uploads, real-time collaboration, search indexing, and AI inference paths. You control the data, the network path users take to reach Plane, the upgrade timing, the auth provider, and the integrations Plane talks to internally. For the [Commercial Edition](/self-hosting/editions-and-versions#commercial), Plane operates the license validation server at the [Prime portal](https://prime.plane.so/licenses). The [Airgapped Edition](/self-hosting/editions-and-versions#airgapped) removes that dependency and runs entirely offline. For production deployments, you can also point Plane at managed services for the database and storage layers (RDS, Cloud SQL, S3, GCS, and similar) instead of running them yourself. See [Plane Architecture](/self-hosting/plane-architecture) for the full system anatomy and [External services](/self-hosting/govern/database-and-storage) for the managed-service options. *** ## 2. Cloud vs self-hosted | Dimension | [Plane Cloud](https://app.plane.so/sign-in) | Self-hosted | | --------------------------------- | ------------------------------------------- | --------------------------------------------- | | Time to first user | About 30 seconds | 20mins via Docker | | Data residency | US (default), EU available on demand | Anywhere you deploy | | Network isolation | Public internet | VPC, private network, or fully air-gapped | | Upgrade timing | Continuous | You choose your window | | Feature freshness | New features ship here first | Commercial gets them next, Community last | | Operational overhead | Zero | Real and ongoing | | Backups and DR | Plane operates | You design and operate | | Support escalation | One vendor | Your platform team first, then Plane support | | Compliance posture | Plane's certifications apply | Your perimeter, your audit | | Integration with internal systems | Egress only (public APIs) | Direct access to private services | | Cost model | Per-seat subscription | Per-seat subscription + On demand | | Audit logs, SCIM, advanced auth | Business Plus | Available on plans (Commercial and Airgapped) | [Cloud and self-hosted migration](/self-hosting/manage/migrate-plane) is supported in the Cloud-to-self-hosted direction. *** ## 3. How licensing works Plane's licensing has two layers, and confusing them is the most common mistake new self-hosters make. **The edition is the codebase you run.** There are three self-hosted editions: [Community](/self-hosting/editions-and-versions#community), [Commercial](/self-hosting/editions-and-versions#commercial), and [Airgapped](/self-hosting/editions-and-versions#airgapped). Each has its own release cycle. They are separate codebases, not feature toggles on the same binary. **The plan is the set of features your license key unlocks** on the Commercial and Airgapped editions. Plans are Free, Pro, Business, and Enterprise Grid. You activate a plan by pasting a license key from the [Prime portal](https://prime.plane.so/licenses) into your workspace settings. ### The three editions **Community Edition** is open source under [AGPL v3.0](https://github.com/makeplane/plane/blob/preview/LICENSE.txt). Free, no license key, full source available, you can audit and modify it. Feature parity with the Free tier of Cloud, with no Pro, Business, or Enterprise features. To unlock paid features, switch to Commercial. See [Upgrade Community to Commercial](/self-hosting/upgrade-from-community). **Commercial Edition** is closed-source. It includes a built-in Free tier of 12 user seats per workspace, which means you can run Commercial in production at small scale without buying a license. To unlock Pro, Business, or Enterprise Grid features, you activate a license key. Commercial gets full feature parity with Cloud. **Airgapped Edition** is the Commercial Edition adapted for environments without internet access. Same features, offline license activation, updates pulled from your own Docker registry. See [Airgapped requirements](/self-hosting/methods/airgapped-requirements). ### How license activation works License keys come from the [Prime portal](https://prime.plane.so/licenses), which you log into with the email you used to purchase. To activate on a self-hosted instance: 1. Copy the license key from Prime. 2. In your Plane workspace, go to **Workspace Settings > Billing and plans**. 3. Click **Activate this Workspace**, paste the key, click **Activate**. Each license key is bound to one workspace and one machine. To move it (different server, different workspace, reinstall), use **Delink license key** in Billing and plans, then run `prime-cli restart`, then activate elsewhere. The full procedure is at [Activate Pro and Business](/self-hosting/manage/manage-licenses/activate-pro-and-business). The **Sync plan** button in Billing and plans pulls the latest subscription state from Prime, including plan type, seat count, expiration, and feature flags. Use it whenever Prime and your workspace disagree. For Enterprise Grid, see [Activate Enterprise](/self-hosting/manage/manage-licenses/activate-enterprise). For airgapped activation, see [Activate airgapped](/self-hosting/manage/manage-licenses/activate-airgapped) and [Activate airgapped Enterprise](/self-hosting/manage/manage-licenses/activate-airgapped-enterprise). ### When something goes wrong License-state failures (expiry, seat overflow, key conflicts, network errors during activation) surface as specific errors. The full reference is at [License errors](/self-hosting/troubleshoot/license-errors). Point your operations runbook there, since these are usually fast fixes once you know the symptom. ### AGPL in practice If you're running the Community Edition, AGPL v3.0 has a few real-world boundaries: * **Running Community for your internal team.** Fine, no obligations beyond AGPL terms. * **Modifying Community for internal use.** Fine, AGPL specifically allows this. * **Hosting Community as a service for external customers.** AGPL requires you to publish your modifications, including any changes you've made. * **Embedding Community inside a commercial product.** AGPL-affecting territory. Either comply with AGPL's source-disclosure requirements or [talk to sales](https://plane.so/talk-to-sales) about a commercial license. Most teams self-hosting Plane for internal project management never hit these boundaries. If you're not sure where you sit, ask your legal team or contact us before building anything customer-facing on top of Community. *** ## 4. When self-hosted fits Self-hosting Plane fits the following scenarios. **1. Regulatory or contractual data residency.** GDPR with strict country-level residency, sector rules (HIPAA, financial regulations), or customer contracts that specify where data sits. Self-hosting puts data on infrastructure you can point an auditor at. **2. Air-gapped or sovereign cloud.** Your network has no outbound internet, or you operate in a sovereign cloud where third-party SaaS isn't permitted. The [Airgapped Edition](/self-hosting/methods/airgapped-requirements) is built for this. **3. Internal-only integrations.** You need Plane to integrate with services that aren't on the public internet: internal Git, internal ticketing, internal SSO, internal monitoring. Self-hosted lives on the same network as the things it talks to. **4. Upgrade timing control.** Multi-week change windows, frozen periods around financial reporting, change-advisory-board approvals. Self-hosted lets you upgrade on your calendar. See [Upgrade Plane](/self-hosting/manage/upgrade-plane). *** ## 5. What you're running Plane is a multi-service application: eight application services plus a data layer. ### Application services * **Web.** The main user-facing Next.js app at your primary domain. * **Space.** Public-facing project pages (deployed views, intake forms, and similar). * **Admin.** The admin console for instance-level configuration. * **Live.** Real-time collaboration backend (Yjs WebSocket server) for pages and work items. * **API.** The Django REST API that everything else calls. * **Worker.** Celery background workers for async jobs (notifications, webhooks, exports, AI). * **Beat.** Celery scheduler that triggers periodic tasks. * **Migrator.** One-shot DB migration job that runs on each upgrade. ### Data layer * **Postgres.** Primary database. Holds workspaces, projects, work items, pages, users, and everything transactional. * **Redis.** Cache, session store, real-time pub/sub. * **RabbitMQ.** Message broker for the Celery workers. * **Object storage.** S3-compatible (MinIO, AWS S3, GCS, Azure Blob). File uploads, attachments, exports, and AI artifacts. * **OpenSearch** *(optional).* Full-text search index. Without it, search falls back to Postgres-based search. The full breakdown of versions, ports, resource recommendations, and dependency graph is at [Plane Architecture](/self-hosting/plane-architecture). For the complete environment-variable surface across all services, see [Environment variables](/self-hosting/govern/environment-variables). *** ## 6. What your team operates ### Skills your operators need * **Container operations.** Docker basics for [Docker Compose](/self-hosting/methods/docker-compose), Kubernetes and Helm if you go [HA](/self-hosting/methods/kubernetes). * **Postgres operations.** Backups, restores, upgrades, basic tuning. * **TLS and DNS.** [Custom domains](/self-hosting/govern/custom-domain), [SSL certificates](/self-hosting/govern/configure-ssl), [reverse proxy](/self-hosting/govern/reverse-proxy) configuration. * **Identity provider setup.** [SAML](/self-hosting/govern/saml-sso), [OIDC](/self-hosting/govern/oidc-sso), [LDAP](/self-hosting/govern/ldap), or OAuth, depending on what your org uses. * **Object storage operations.** Buckets, lifecycle policies, [private bucket](/self-hosting/govern/private-bucket) configuration. * **Secrets management.** [External secrets](/self-hosting/govern/external-secrets) integration if you use Vault, AWS Secrets Manager, or similar. ### Time budget * **Week 1: deploy and stand up.** Pick a [deployment method](/self-hosting/methods/overview), provision infrastructure, install, configure auth, smoke-test. 1 to 3 days of an FTE for Docker Compose, 3 to 7 days for production Kubernetes. * **Month 1: harden for production.** Backup and restore drill, monitoring, integrations, user onboarding, runbook documentation. Another 3 to 5 days spread across the month. * **Year 1: operate.** Roughly 4 minor upgrades plus 1 to 2 major upgrades, ongoing patches, capacity planning, and user support escalations. Plan for 0.1 to 0.25 FTE at small scale, 0.5 to 1.0 FTE at large scale. ### On-call and incident response Self-hosted Plane sits inside your operational perimeter. Decide ownership and escalation before you go live: who's on the rotation, what your internal runbook covers, when you escalate to Plane support. The [troubleshooting docs](/self-hosting/troubleshoot/overview) cover common cases, and your [support tier](/self-hosting/overview#get-help) determines response times when you need to escalate. *** ## 7. What changes between editions Editions differ in three ways that matter operationally: feature availability, release cadence, and license model. **Feature availability.** Community has parity with the Free tier of Cloud. Commercial and Airgapped get full parity with Cloud's paid plans (Pro, Business, Enterprise Grid), license-gated. **Release cadence.** Cloud is the test bed. New features ship there first, then Commercial, then Community. If your team needs the latest features quickly, that affects which edition fits. **License model.** Community is AGPL with no key. Commercial uses an online license key tied to one workspace and one machine. Airgapped uses an offline license bundle. Edition transitions are supported but generally one-directional in practice. See [Community to Commercial](/self-hosting/upgrade-from-community) and [Community to Airgapped](/self-hosting/manage/community-to-airgapped). Full feature, version, and codebase comparison: [Plane Editions](/self-hosting/editions-and-versions). Latest changes by edition: [Changelog](https://plane.so/changelog). *** ## 8. Lifecycle: deploy, configure, operate, upgrade Self-hosting Plane is four phases. ### Deploy Pick a method, provision infrastructure, install the binary or chart, run the migrator, smoke-test. Hours to days. Decisions: which [deployment method](/self-hosting/methods/overview), where Postgres and object storage live (managed services or self-run), which domain, which network topology. Changing these later is painful. ### Configure Authentication, network and TLS, secrets, integrations, and [instance admin](/self-hosting/govern/instance-admin) setup. Days to a week for a hardened production deployment. [Authentication](/self-hosting/govern/authentication) is often a full day's work. Picking and configuring [Google](/self-hosting/govern/google-oauth), [GitHub](/self-hosting/govern/github-oauth), [SAML](/self-hosting/govern/saml-sso), [OIDC](/self-hosting/govern/oidc-sso), or [LDAP](/self-hosting/govern/ldap), plus the [reset password flow](/self-hosting/govern/reset-password) and [email delivery](/self-hosting/govern/communication), takes most of the configure phase. ### Operate Day-2 work: user management, monitoring, backups, support, [logs](/self-hosting/manage/view-logs), capacity. Ongoing. The non-negotiable here is [Backup and restore](/self-hosting/manage/backup-restore). Set it up before users log in. Test the restore path within the first month. A backup you haven't restored from is a backup you don't actually have. ### Upgrade Plane ships frequently. Minor upgrades every 4 to 8 weeks, majors less often. Each upgrade involves a change window, a backup, the upgrade itself via [Upgrade Plane](/self-hosting/manage/upgrade-plane), and post-upgrade verification. Skipping upgrades for 6+ months means bigger upgrade-time risk, more breaking changes to handle at once, and missed security patches. *** ## 9. Patterns and anti-patterns Patterns we've seen repeated across self-hosted deployments. **Anti-pattern: Postgres in the same Docker container as Plane in production.** Fine for [Docker AIO](/self-hosting/methods/docker-aio) evaluations. In production, you can't snapshot the database independently, you can't scale it, and a container restart is a database restart. Use a managed Postgres or run it in a separate, properly backed-up container. [External services](/self-hosting/govern/database-and-storage) walks through both options. **Anti-pattern: skipping backups for the first three months.** Backups should be running before the first real user logs in. See [Backup and restore](/self-hosting/manage/backup-restore). **Anti-pattern: one environment, no staging.** Every upgrade becomes a production incident. A small staging instance, even on a single VM, lets you run upgrades there first. **Anti-pattern: underestimating object storage growth.** File uploads, page attachments, AI artifacts, and exports add up faster than people expect. Set lifecycle rules early and monitor growth. The [private bucket](/self-hosting/govern/private-bucket) and [database and storage](/self-hosting/govern/database-and-storage) docs cover the basics. **Anti-pattern: ignoring upgrades for six months or more.** The longer you wait, the bigger the gap, the more breaking changes accumulate, and the more security patches you've missed. Pick a cadence, quarterly minimum. **Anti-pattern: one person knows the configuration.** Document your specific setup (env vars, IdP configuration, backup destinations, certificate renewal procedure) somewhere your team can find it. The [environment variables reference](/self-hosting/govern/environment-variables) is where to start. **Pattern that works: managed services for the data layer.** Run Plane application services in containers, but use managed Postgres and managed object storage (RDS, Cloud SQL, S3, GCS). You inherit your cloud provider's backup, scaling, and durability story for the highest-stakes pieces. **Pattern that works: separate licenses per environment.** A separate license key for staging keeps prod clean and makes upgrade testing realistic. **Pattern that works: integrate with your existing observability stack.** Ship Plane logs and metrics into whatever you already use rather than inventing monitoring for it. Start with [View logs](/self-hosting/manage/view-logs). *** ## 10. Planning checklist Before you go to production, have a clear answer to each of these: 1. **Why self-hosting.** The specific reason: data residency, air-gapped network, internal integrations, upgrade control, scale economics, customization. 2. **Owner.** A specific person or team responsible for the system. 3. **Backup and disaster recovery target.** RPO and RTO in concrete numbers. 4. **Upgrade cadence.** Monthly, quarterly, or another rhythm. See [Upgrade Plane](/self-hosting/manage/upgrade-plane). 5. **Escalation path.** Internal on-call rotation and the support tier you escalate to externally. Once these are settled, head to the [Self-hosting overview](/self-hosting/overview) and pick a deployment scenario. *** ## 11. Next steps **Try Plane self-hosted.** [Docker AIO](/self-hosting/methods/docker-aio) gives you a single container with embedded services in about 10 minutes (POC only). **Plan a production deployment.** Read [Plane Editions](/self-hosting/editions-and-versions) to pick your edition. Read [Plane Architecture](/self-hosting/plane-architecture) to plan capacity and network. Then pick a [deployment method](/self-hosting/methods/overview). **Talk to a human.** [Talk to sales](https://plane.so/talk-to-sales) for pricing, contracts, professional services, and airgapped. [Community Discord](https://discord.gg/plane) for open questions. [GitHub issues](https://github.com/makeplane/plane/issues) for bugs and feature requests. [**Continue to: Self-hosting overview →**](/self-hosting/overview) --- --- url: 'https://developers.plane.so/self-hosting/methods/docker-compose.html' description: >- Install Plane using Docker Compose. Step-by-step guide for deploying Plane with Docker on your server with all required services. --- # Docker Compose This guide shows you the steps to deploy a self-hosted instance of Plane using Docker. ::: tip If you want to upgrade from Community to the Commercial edition, see [Upgrade to Commercial Edition](/self-hosting/upgrade-from-community). ::: ## Install Plane Plane Pro and Plane Business are enabled on this edition, so the Free plan on this edition is easier to trial our paid plans from. ### Prerequisites * **CPU:** 2 cores (x64/AMD64 or AArch64/ARM64) * **RAM:** 4GB (8GB recommended for production) * **OS:** Ubuntu, Debian, CentOS, Amazon Linux 2 or 2023, macOS, Windows with WSL2 ::: info Ensure you're using the **latest version of Docker Compose**. Check your Docker Compose version with `docker-compose --version` and update if needed. ::: ### Procedure 1. `ssh` into your machine as the root user (or user with sudo access) per the norms of your hosting provider. 2. Run the command below: ```bash curl -fsSL https://prime.plane.so/install/ | sh - ``` 3. Follow the instructions on the terminal. Hit `Enter` or `Return` to continue. 4. Enter the domain name where you will access the Plane app in the format `domain.tld` or `subdomain.domain.tld`. 5. Choose one of the options below: * **Express**: Plane installs with the default configurations. * **Advanced**: You can customize the database, Redis, storage and other settings. ::: warning When self-hosting Plane for production use, it is strongly recommended to configure [external database and storage](/self-hosting/govern/database-and-storage). This ensures that your data remains secure and accessible even if the local machine crashes or encounters hardware issues. Relying solely on local storage for these components increases the risk of data loss and service disruption. ::: 6. The installation will take a few minutes to complete and you will see the message **Plane has successfully installed**. You can access the Plane application on the domain you provided during the installation. 7. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. ::: details Install Community Edition The Commercial edition comes with a free plan and the flexibility to upgrade to a paid plan at any point. If you still want to install the Community edition, follow the steps below: #### Prerequisites * Docker installed and running. Choose one of the following options: * **Option 1**\ Create an EC2 machine on AWS. It must of minimum **t3.medium/t3a.medium**. Run the below command to install docker engine. ```bash curl -fsSL https://get.docker.com | sh - ``` * **Option 2**\ Install [Docker Desktop](https://www.docker.com/products/docker-desktop/). * OS with bash scripting enabled (Ubuntu, Linux AMI, macOS). Windows systems need to have [gitbash](https://git-scm.com/download/win). * User context used must have access to docker services. In most cases, use `sudo su` to switch as root user. * Use the terminal (or gitbash) window to run all the future steps. #### Installation 1. Create a folder named `plane-selfhost` on your machine for deployment and data storage. ```bash mkdir plane-selfhost ``` 2. Navigate to this folder using the cd command. ```bash cd plane-selfhost ``` 3. Download the latest stable release. ```bash curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/setup.sh ``` 4. Make the file executable. ```bash chmod +x setup.sh ``` 5. Run the following command: ```bash ./setup.sh ``` This will prompt you with the below options. ```bash Select a Action you want to perform: 1) Install (arm64) 2) Start 3) Stop 4) Restart 5) Upgrade 6) View Logs 7) Backup Data 8) Exit Action [2]: 1 ``` 6. Enter `1` as input. This will create a folder `plane-app` or `plane-app-preview` (in case of preview deployment) and will download the `docker-compose.yaml` and `plane.env` files. 7. Enter `8` to exit. 8. Set up the environment variables. You can use any text editor to edit this file. Below are the most importants keys you must refer to: * `LISTEN_HTTP_PORT`: This is set to `80` by default. Make sure the port you choose to use is not preoccupied. For example, `LISTEN_HTTP_PORT=8080` * `LISTEN_HTTPS_PORT`: This is set to `443` by default. Make sure the port you choose to use is not preoccupied. For example, `LISTEN_HTTPS_PORT=4430` * `WEB_URL`: This is set to `http://localhost` by default. Change this to the FQDN you plan to use along with LISTEN\_HTTP\_PORT. For example, `https://plane.example.com:8080` or `http://[IP-ADDRESS]:8080`. * `CORS_ALLOWED_ORIGINS`: This is set to `http://localhost` by default. Change this to the FQDN you plan to use along with LISTEN\_HTTP\_PORT. For example, `https://plane.example.com:8080` or `http://[IP-ADDRESS]:8080`. 9. Run the following command to continue with the setup. ```bash ./setup.sh ``` 10. Enter `2` as input to start the services. You will something like this:\ ![Downloading docker images](/images/docker-compose/download-docker.png) Be patient as it might take some time based on your download speed and system configuration. If all goes well, you must see something like this: ![Downloading completed](/images/docker-compose/download-complete.png) This is the confirmation that all images were downloaded and the services are up and running. You have successfully self-hosted the Plane instance. Access the application by going to IP or domain you have configured it on. For example, `https://plane.example.com:8080` or `http://[IP-ADDRESS]:8080`. ##### Stop server In case you want to make changes to the environment variables in the `plane.env` file, we recommend that you stop the services before doing that. Run the `./setup.sh` command. Enter `3` to stop the services. If all goes well, you will see something like this: ![Stop Services](/images/docker-compose/stopped-docker.png) ##### Restart server In case you want to make changes to `plane.env` variables without stopping the server or noticed some abnormalities in services, you can restart the services. Run the `./setup.sh` command. Enter `4` to restart the services. If all goes well, you will see something like this: ![Restart Services](/images/docker-compose/restart-docker.png) ::: ## Troubleshoot * [Error during Docker Compose execution](/self-hosting/troubleshoot/installation-errors#error-during-docker-compose-execution) * [Migrator container exited](/self-hosting/troubleshoot/installation-errors#migrator-container-exited) --- --- url: 'https://developers.plane.so/self-hosting/methods/kubernetes.html' description: >- Deploy Plane on Kubernetes using Helm charts. Complete guide for production-ready Kubernetes deployment with scaling and management. --- # Kubernetes This guide shows you the steps to deploy a self-hosted instance of Plane using Kubernetes. ::: tip If you want to upgrade from Community to the Commercial edition, see [Upgrade to Commercial Edition](/self-hosting/upgrade-from-community). ::: ## Install Plane Plane Pro and Plane Business are enabled on this edition, so the Free plan on this edition is easier to trial our paid plans from. ### Prerequisites * A working Kubernetes cluster * `kubectl` and `helm` on the client system that you will use to install our Helm charts ::: info Ensure you use use the latest Helm chart version. ::: ### Procedure 1. Open terminal or any other command-line app that has access to Kubernetes tools on your local system. 2. Set the following environment variables: ```bash PLANE_VERSION=v2.4.0 ``` ```bash DOMAIN_NAME= ``` ::: warning When configuring the PLANE\_VERSION environment variable, **do not** set it to `stable`. Always specify the latest version number (e.g., `2.4.0`). Using `stable` can lead to unexpected issues. ::: 3. Add the Plane helm chart repo. ```bash helm repo add plane https://helm.plane.so/ ``` 4. Use one of the following ways to deploy Plane: * **Quick setup**: This is the fastest way to deploy Plane with the default settings. This will create stateful deployments for Postgres, Redis/Valkey, and Minio with a persistent volume claim using the `longhorn` storage class. This also sets up the Ingress routes for you using `nginx` ingress class. To customize these settings, see the [Custom ingress routes](#custom-ingress-routes). Run the following command to deploy Plane: ``` helm upgrade --install plane-app plane/plane-enterprise \ --create-namespace \ --namespace plane \ --set license.licenseDomain=${DOMAIN_NAME} \ --set license.licenseServer=https://prime.plane.so \ --set planeVersion=${PLANE_VERSION} \ --set ingress.enabled=true \ --set ingress.ingressClass=nginx \ --set env.storageClass=longhorn \ --timeout 10m \ --wait \ --wait-for-jobs ``` ::: info This is the minimum required to set up Plane Commercial edition. You can change the default namespace from `plane`, the default app name from `plane-app`, the default storage class from `longhorn`, and the default ingress class from `nginx` to whatever you would like to. To use a custom StorageClass, add `--set env.storageClass=` to the command above. You can also pass other settings referring to the **Configuration Settings** toggle section below. ::: * **Advanced setup**: ::: warning When self-hosting Plane for production use, it is strongly recommended to configure [external database and storage](/self-hosting/methods/kubernetes#configuration-settings). This ensures that your data remains secure and accessible even if the local machine crashes or encounters hardware issues. Relying solely on local storage for these components increases the risk of data loss and service disruption. ::: For more control over your setup, follow the steps below: i. Run the script below to download the `values.yaml` file and edit using any editor like Vim or Nano. Make sure you set the required environment variables listed below: * `planeVersion: v2.4.0` * `license.licenseDomain: ` * `license.licenseServer: https://prime.plane.so` * `ingress.enabled: ` * `ingress.ingressClass: ` * `env.storageClass: ` See the **Configuration settings** toggle section for more details. ```bash helm upgrade --install plane-app plane/plane-enterprise \ --create-namespace \ --namespace plane \ -f values.yaml \ --timeout 10m \ --wait \ --wait-for-jobs ``` ii. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. ## Configuration settings #### License | Setting | Default | Required | Description | | --------------------- | :-----------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | planeVersion | v2.4.0 | Yes | Specifies the version of Plane to be deployed. Copy this from prime.plane.so. | | license.licenseDomain | 'plane.example.com' | Yes | The fully-qualified domain name (FQDN) in the format `sudomain.domain.tld` or `domain.tld` that the license is bound to. It is also attached to your `ingress` host to access Plane. | ### Airgapped Settings | Setting | Default | Required | Description | | ---------------------- | :-----: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | airgapped.enabled | false | No | Enable airgapped mode for the Plane API. | | airgapped.s3Secrets | \[] | No | List of Kubernetes Secrets containing CA certificates to install. Each entry requires `name` (Secret name) and `key` (filename in the Secret). Example: `kubectl -n plane create secret generic plane-s3-ca --from-file=s3-custom-ca.crt=/path/to/ca.crt`. Supports multiple certs (e.g. S3 + internal CA). Available in v2.4.0 and later. | | airgapped.s3SecretName | "" | No | **Deprecated** Name of a single Kubernetes Secret containing the S3 CA cert. Used only when `s3Secrets` is empty. Use `s3Secrets` instead. | | airgapped.s3SecretKey | "" | No | **Deprecated** Key (filename) of the cert file inside the Secret. Used only when `s3Secrets` is empty. Set together with `airgapped.s3SecretName`. Use `s3Secrets` instead. | #### CA certificate configuration (For airgapped deployments only) Plane supports custom CA certificates for connecting to S3-compatible storage and other internal services in airgapped environments. * **New deployments:** Use `airgapped.s3Secrets` as shown in the table above. * **Existing deployments using `s3SecretName` and `s3SecretKey`:** Your configuration still works. Migrate only if you need to use multiple CA certificates. #### Migrating to the new configuration :::warning Requires Plane v2.4.0 or later. ::: The new `s3Secrets` configuration supports multiple CA certificates, useful if you need to trust certificates from different sources (e.g., S3 endpoint CA and internal PKI). If you only need a single certificate, migration is optional. To migrate: 1. Add your existing secret to the `s3Secrets` list: ```yaml airgapped: enabled: true s3Secrets: - name: plane-s3-ca # your existing s3SecretName value key: s3-custom-ca.crt # your existing s3SecretKey value # s3SecretName and s3SecretKey can be removed after migration ``` 2. Remove `s3SecretName` and `s3SecretKey` from your values file. 3. Upgrade your Helm release. #### Docker Registry | Setting | Default | Required | Description | | ----------------------------- | :-----------------: | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | dockerRegistry.enabled | false | No | Enable to configure image pull secrets for pulling images from a private docker registry. When enabled, you can either provide credentials to create a new secret or use an existing Kubernetes secret. | | dockerRegistry.existingSecret | | No | Name of an existing Kubernetes secret containing docker registry credentials. When specified, the chart will use this secret for `imagePullSecrets` instead of creating a new one. The secret should be of type `kubernetes.io/dockerconfigjson`. If left empty, credentials below will be used to create a new secret. | | dockerRegistry.registry | index.docker.io/v1/ | No | Docker registry URL. Only used when `dockerRegistry.existingSecret` is empty. | | dockerRegistry.loginid | | No | Login ID / Username for the docker registry. Only used when `dockerRegistry.existingSecret` is empty. | | dockerRegistry.password | | No | Password or Token for the docker registry. Only used when `dockerRegistry.existingSecret` is empty. | #### Postgres | Setting | Default | Required | Description | | ----------------------------------- | :--------------------: | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.postgres.local\_setup | true | | Plane uses `postgres` as the primary database to store all the transactional data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to `true` when you choose to setup stateful deployment of `postgres`. Mark it as `false` when using a remotely hosted database | | services.postgres.image | `postgres:15.7-alpine` | | Using this key, user must provide the docker image name to setup the stateful deployment of `postgres`. (must be set when `services.postgres.local_setup=true`) | | services.postgres.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of postgres. (must be set when `services.postgres.local_setup=true`) | | services.postgres.servicePort | 5432 | | This key sets the default port number to be used while setting up stateful deployment of `postgres`. | | services.postgres.volumeSize | 2Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | env.pgdb\_username | plane | | Database credentials are requried to access the hosted stateful deployment of `postgres`. Use this key to set the username for the stateful deployment. | | env.pgdb\_password | plane | | Database credentials are requried to access the hosted stateful deployment of `postgres`. Use this key to set the password for the stateful deployment. | | env.pgdb\_name | plane | | Database name to be used while setting up stateful deployment of `Postgres` | | services.postgres.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.postgres.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of postgres. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.postgres.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of postgres. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.postgres.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of postgres. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.postgres.labels | {} | | This key allows you to set custom labels for the stateful deployment of postgres. This is useful for organizing and selecting resources in your Kubernetes cluster. | | services.postgres.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of postgres. This is useful for adding metadata or configuration hints to your resources. | | env.pgdb\_remote\_url | | | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set `services.postgres.local_setup` to `false` and set this key with remote connection url. | #### Redis/Valkey Setup | Setting | Default | Required | Description | | -------------------------------- | :---------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.redis.local\_setup | true | | Plane uses `redis` to cache the session authentication and other static data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to `true` when you choose to setup stateful deployment of `redis`. Mark it as `false` when using a remotely hosted database | | services.redis.image | `valkey/valkey:7.2.11-alpine` | | Using this key, user must provide the docker image name to setup the stateful deployment of `redis`. (must be set when `services.redis.local_setup=true`) | | services.redis.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of redis. (must be set when services.redis.local\_setup=true) | | services.redis.servicePort | 6379 | | This key sets the default port number to be used while setting up stateful deployment of `redis`. | | services.redis.volumeSize | 500Mi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | services.redis.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.redis.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of redis. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.redis.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of redis. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.redis.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of redis. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.redis.labels | {} | | This key allows you to set custom labels for the stateful deployment of redis. This is useful for organizing and selecting resources in your Kubernetes cluster. | | services.redis.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of redis. This is useful for adding metadata or configuration hints to your resources. | | env.remote\_redis\_url | | | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set `services.redis.local_setup` to `false` and set this key with remote connection url. | #### RabbitMQ Setup | Setting | Default | Required | Description | | --------------------------------------- | :---------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.rabbitmq.local\_setup | true | | Plane uses `rabbitmq` as message queuing system. This can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws mq or similar services). Set this to `true` when you choose to setup stateful deployment of `rabbitmq`. Mark it as `false` when using a remotely hosted service | | services.rabbitmq.image | `rabbitmq:3.13.6-management-alpine` | | Using this key, user must provide the docker image name to setup the stateful deployment of `rabbitmq`. (must be set when `services.rabbitmq.local_setup=true`) | | services.rabbitmq.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of `rabbitmq`. (must be set when `services.rabbitmq.local_setup=true`) | | services.rabbitmq.servicePort | 5672 | | This key sets the default port number to be used while setting up stateful deployment of `rabbitmq`. | | services.rabbitmq.managementPort | 15672 | | This key sets the default management port number to be used while setting up stateful deployment of `rabbitmq`. | | services.rabbitmq.volumeSize | 100Mi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | services.rabbitmq.default\_user | plane | | Credentials are requried to access the hosted stateful deployment of `rabbitmq`. Use this key to set the username for the stateful deployment. | | services.rabbitmq.default\_password | plane | | Credentials are requried to access the hosted stateful deployment of `rabbitmq`. Use this key to set the password for the stateful deployment. | | services.rabbitmq.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.rabbitmq.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of rabbitmq. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.rabbitmq.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of rabbitmq. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.rabbitmq.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of rabbitmq. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.rabbitmq.labels | {} | | This key allows you to set custom labels for the stateful deployment of rabbitmq. This is useful for organizing and selecting resources in your Kubernetes cluster. | | services.rabbitmq.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of rabbitmq. This is useful for adding metadata or configuration hints to your resources. | | services.rabbitmq.external\_rabbitmq\_url | | | Users can also decide to use the remote hosted service and link to Plane deployment. Ignoring all the above keys, set `services.rabbitmq.local_setup` to `false` and set this key with remote connection url. | #### OpenSearch Setup | Setting | Default | Required | Description | | ------------------------------------- | :--------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.opensearch.local\_setup | false | | Plane uses `opensearch` as the search and analytics engine. This can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. AWS OpenSearch Service or similar services). Set this to `true` when you choose to setup stateful deployment of `opensearch`. Mark it as `false` when using a remotely hosted service | | services.opensearch.image | opensearchproject/opensearch:3.3.2 | | Using this key, user must provide the docker image name to setup the stateful deployment of `opensearch`. (must be set when `services.opensearch.local_setup=true`) | | services.opensearch.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of `opensearch`. (must be set when `services.opensearch.local_setup=true`) | | services.opensearch.servicePort | 9200 | | This key sets the default port number to be used while setting up stateful deployment of `opensearch`. | | services.opensearch.volumeSize | 5Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | services.opensearch.username | plane | | Credentials are required to access the hosted stateful deployment of `opensearch`. Use this key to set the username for the stateful deployment. | | services.opensearch.password | Secure@Pass#123!%^&\* | | Credentials are required to access the hosted stateful deployment of `opensearch`. Use this key to set the password. **Password Complexity Requirements:** Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one digit, and one special character (e.g., `!@#$%^&*`). | | services.opensearch.memoryLimit | 3Gi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.opensearch.cpuLimit | 750m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.opensearch.memoryRequest | 2Gi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.opensearch.cpuRequest | 500m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.opensearch.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.opensearch.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of opensearch. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.opensearch.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of opensearch. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.opensearch.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of opensearch. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.opensearch.labels | {} | | This key allows you to set custom labels for the stateful deployment of opensearch. This is useful for organizing and selecting resources in your Kubernetes cluster. | | services.opensearch.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of opensearch. This is useful for adding metadata or configuration hints to your resources. | | env.opensearch\_remote\_url | | | Users can also decide to use the remote hosted service and link to Plane deployment. Set `services.opensearch.local_setup` to `false` and set this key with remote connection url. | | env.opensearch\_remote\_username | | | Username for remote OpenSearch service. Required when `services.opensearch.local_setup=false` and `env.opensearch_remote_url` is set. Note: This is not a secret and should be configured in values.yaml, not in external secrets. | | env.opensearch\_remote\_password | | | Password for remote OpenSearch service. Required when `services.opensearch.local_setup=false` and `env.opensearch_remote_url` is set. Can be configured in values.yaml or provided via external secrets (`opensearch_existingSecret` with `OPENSEARCH_PASSWORD`). **Password Complexity Requirements:** Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one digit, and one special character. | | env.opensearch\_index\_prefix | plane\_ | | Prefix to be used for OpenSearch indices. This helps organize indices in a multi-tenant or multi-environment setup. | #### Doc Store (Minio/S3) Setup | Setting | Default | Required | Description | | ------------------------------------- | :----------------: | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.minio.local\_setup | true | | Plane uses `minio` as the default file storage drive. This storage can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws S3 or similar services). Set this to `true` when you choose to setup stateful deployment of `minio`. Mark it as `false` when using a remotely hosted database | | services.minio.image | minio/minio:latest | | Using this key, user must provide the docker image name to setup the stateful deployment of `minio`. (must be set when `services.minio.local_setup=true`) | | services.minio.image\_mc | minio/mc:latest | | Using this key, user must provide the docker image name to setup the job deployment of `minio client`. (must be set when `services.minio.local_setup=true`) | | services.minio.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of minio. (must be set when services.minio.local\_setup=true) | | services.minio.volumeSize | 3Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | services.minio.root\_user | admin | | Storage credentials are requried to access the hosted stateful deployment of `minio`. Use this key to set the username for the stateful deployment. | | services.minio.root\_password | password | | Storage credentials are requried to access the hosted stateful deployment of `minio`. Use this key to set the password for the stateful deployment. | | services.minio.env.minio\_endpoint\_ssl | false | | (Optional) Env to enforce HTTPS when connecting to minio uploads bucket | | env.docstore\_bucket | uploads | Yes | Storage bucket name is required as part of configuration. This is where files will be uploaded irrespective of if you are using `Minio` or external `S3` (or compatible) storage service | | env.doc\_upload\_size\_limit | 5242880 | Yes | Document Upload Size Limit (default to 5Mb) | | services.minio.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.minio.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of minio. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.minio.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of minio. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.minio.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of minio. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.minio.labels | {} | | This key allows you to set custom labels for the stateful deployment of minio. This is useful for organizing and selecting resources in your Kubernetes cluster. | | services.minio.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of minio. This is useful for adding metadata or configuration hints to your resources. | | env.aws\_access\_key | | | External `S3` (or compatible) storage service provides `access key` for the application to connect and do the necessary upload or download operations. To be provided when `services.minio.local_setup=false` | | env.aws\_secret\_access\_key | | | External `S3` (or compatible) storage service provides `secret access key` for the application to connect and do the necessary upload or download operations. To be provided when `services.minio.local_setup=false` | | env.aws\_region | | | External `S3` (or compatible) storage service providers creates any buckets in user selected region. This is also shared with the user as `region` for the application to connect and do the necessary upload or download operations. To be provided when `services.minio.local_setup=false` | | 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. | #### Web Deployment | Setting | Default | Required | Description | | ------------------------------ | :-------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.web.replicas | 1 | Yes | Kubernetes helps you with scaling up or down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.web.memoryLimit | 1000Mi | | Every deployment in Kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.web.cpuLimit | 500m | | Every deployment in Kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.web.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.web.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.web.image | `artifacts.plane.so/makeplane/web-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.web.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of web. | | services.web.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.web.nodeSelector | {} | | This key allows you to set the node selector for the deployment of web. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.web.tolerations | \[] | | This key allows you to set the tolerations for the deployment of web. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.web.affinity | {} | | This key allows you to set the affinity rules for the deployment of web. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.web.labels | {} | | Custom labels to add to the web deployment | | services.web.annotations | {} | | Custom annotations to add to the web deployment | #### Space Deployment | Setting | Default | Required | Description | | -------------------------------- | :---------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.space.replicas | 1 | Yes | Kubernetes helps you with scaling up or down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.space.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.space.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.space.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.space.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.space.image | `artifacts.plane.so/makeplane/space-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.space.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of space. | | services.space.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.space.nodeSelector | {} | | This key allows you to set the node selector for the deployment of space. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.space.tolerations | \[] | | This key allows you to set the tolerations for the deployment of space. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.space.affinity | {} | | This key allows you to set the affinity rules for the deployment of space. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.space.labels | {} | | Custom labels to add to the space deployment | | services.space.annotations | {} | | Custom annotations to add to the space deployment | #### Admin Deployment | Setting | Default | Required | Description | | -------------------------------- | :---------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.admin.replicas | 1 | Yes | Kubernetes helps you with scaling up or down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.admin.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.admin.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.admin.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.admin.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.admin.image | `artifacts.plane.so/makeplane/admin-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.admin.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of admin. | | services.admin.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.admin.nodeSelector | {} | | This key allows you to set the node selector for the deployment of admin. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.admin.tolerations | \[] | | This key allows you to set the tolerations for the deployment of admin. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.admin.affinity | {} | | This key allows you to set the affinity rules for the deployment of admin. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.admin.labels | {} | | Custom labels to add to the admin deployment. | | services.admin.annotations | {} | | Custom annotations to add to the admin deployment. | #### Live Service Deployment | Setting | Default | Required | Description | | ---------------------------------- | :--------------------------------------------: | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.live.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be >=1 | | services.live.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.live.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.live.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.live.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.live.image | `artifacts.plane.so/makeplane/live-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.live.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of live. | | env.live\_sentry\_dsn | | | (optional) Live service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry provided DSN for this integration. | | env.live\_sentry\_environment | | | (optional) Live service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry environment name (as configured in Sentry) for this integration. | | env.live\_sentry\_traces\_sample\_rate | | | (optional) Live service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry trace sample rate (as configured in Sentry) for this integration. | | env.live\_server\_secret\_key | htbqvBJAgpm9bzvf3r4urJer0ENReatceh | | Live Server Secret Key | | env.external\_iframely\_url | "" | | External Iframely service URL. If provided, the local Iframely deployment will be skipped and the live service will use this external URL | | services.live.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service. | | services.live.nodeSelector | {} | | This key allows you to set the node selector for the deployment of live. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.live.tolerations | \[] | | This key allows you to set the tolerations for the deployment of live. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.live.affinity | {} | | This key allows you to set the affinity rules for the deployment of live. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.live.labels | {} | | Custom labels to add to the live deployment. | | services.live.annotations | {} | | Custom annotations to add to the live deployment. | #### Monitor Deployment | Setting | Default | Required | Description | | ---------------------------------- | :-----------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.monitor.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.monitor.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.monitor.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.monitor.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.monitor.image | `artifacts.plane.so/makeplane/monitor-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.monitor.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of monitor. | | services.monitor.volumeSize | 100Mi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | services.monitor.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.monitor.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of monitor. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.monitor.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of monitor. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.monitor.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of monitor. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.monitor.labels | {} | | Custom labels to add to the monitor deployment | | services.monitor.annotations | {} | | Custom annotations to add to the monitor deployment | #### API Deployment | Setting | Default | Required | Description | | ------------------------------ | :-----------------------------------------------: | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.api.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.api.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.api.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.api.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.api.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.api.image | `artifacts.plane.so/makeplane/backend-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.api.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of api. | | env.sentry\_dsn | | | (optional) API service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry-provided DSN for this integration. | | env.sentry\_environment | | | (optional) API service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry environment name (as configured in Sentry) for this integration. | | env.api\_key\_rate\_limit | 60/minute | | (optional) User can set the maximum number of requests the API can handle in a given time frame. | | env.web\_url | | | (optional) Custom Web URL for the application. If not set, it will be auto-generated based on the license domain and SSL settings. | | services.api.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.api.nodeSelector | {} | | This key allows you to set the node selector for the deployment of api. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.api.tolerations | \[] | | This key allows you to set the tolerations for the deployment of api. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.api.affinity | {} | | This key allows you to set the affinity rules for the deployment of api. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.api.labels | {} | | Custom labels to add to the API deployment | | services.api.annotations | {} | | Custom annotations to add to the API deployment | #### Silo Deployment | Setting | Default | Required | Description | | :-------------------------------------------- | :--------------------------------------------- | :-------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.silo.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be >=1 | | services.silo.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.silo.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.silo.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.silo.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.silo.image | `artifacts.plane.so/makeplane/silo-commercial` | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | services.silo.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of `silo`. | | services.silo.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.silo.nodeSelector | {} | | This key allows you to set the node selector for the deployment of silo. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.silo.tolerations | \[] | | This key allows you to set the tolerations for the deployment of silo. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.silo.affinity | {} | | This key allows you to set the affinity rules for the deployment of silo. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.silo.labels | {} | | Custom labels to add to the silo deployment | | services.silo.annotations | {} | | Custom annotations to add to the silo deployment | | services.silo.connectors.slack.enabled | false | | Slack Integration | | services.silo.connectors.slack.client\_id | "" | required if `services.silo.connectors.slack.enabled` is `true` | Slack Client ID | | services.silo.connectors.slack.client\_secret | "" | required if `services.silo.connectors.slack.enabled` is `true` | Slack Client Secret | | services.silo.connectors.github.enabled | false | | Github App Integration | | services.silo.connectors.github.client\_id | "" | required if `services.silo.connectors.github.enabled` is `true` | Github Client ID | | services.silo.connectors.github.client\_secret | "" | required if `services.silo.connectors.github.enabled` is `true` | Github Client Secret | | services.silo.connectors.github.app\_name | "" | required if `services.silo.connectors.github.enabled` is `true` | Github App Name | | services.silo.connectors.github.app\_id | "" | required if `services.silo.connectors.github.enabled` is `true` | Github App ID | | services.silo.connectors.github.private\_key | "" | required if `services.silo.connectors.github.enabled` is `true` | Github Private Key | | services.silo.connectors.gitlab.enabled | false | | Gitlab App Integration | | services.silo.connectors.gitlab.client\_id | "" | required if `services.silo.connectors.gitlab.enabled` is `true` | Gitlab Client ID | | services.silo.connectors.gitlab.client\_secret | "" | required if `services.silo.connectors.gitlab.enabled` is `true` | Gitlab Client Secret | | env.silo\_envs.mq\_prefetch\_count | 10 | | Prefetch count for RabbitMQ | | env.silo\_envs.batch\_size | 60 | | Batch size for Silo | | env.silo\_envs.request\_interval | 400 | | Request interval for Silo | | env.silo\_envs.sentry\_dsn | | | Sentry DSN | | env.silo\_envs.sentry\_environment | | | Sentry Environment | | env.silo\_envs.sentry\_traces\_sample\_rate | | | Sentry Traces Sample Rate | | env.silo\_envs.hmac\_secret\_key | \ | | HMAC Secret Key | | env.silo\_envs.aes\_secret\_key | "dsOdt7YrvxsTIFJ37pOaEVvLxN8KGBCr" | | AES Secret Key | #### Plane AI deployment ::: info Plane AI database Plane AI uses a separate PostgreSQL database. Create a new database (e.g. `plane_pi`) and connect it using `env.pg_pi_db_remote_url` in values, or **PLANE\_PI\_DATABASE\_URL** when using `pi_api_env_existingSecret`. ::: | Setting | Default | Required | Description | | --------------------------------- | :----------------------------------------------: | :------: | ---------------------------------------------------------------------------------------------------------------------------------------- | | services.pi.enabled | false | No | Set to `true` to enable the Plane AI service and its API, worker, beat, and migrator workloads. | | services.pi.replicas | 1 | Yes | Number of replicas for the Plane AI API deployment. It must be >=1. | | services.pi.memoryLimit | 1000Mi | | Memory limit for the Plane AI API deployment. | | services.pi.cpuLimit | 500m | | CPU limit for the Plane AI API deployment. | | services.pi.memoryRequest | 50Mi | | Memory request for the Plane AI API deployment. | | services.pi.cpuRequest | 50m | | CPU request for the Plane AI API deployment. | | services.pi.image | artifacts.plane.so/makeplane/plane-pi-commercial | | Docker image for the Plane AI service. | | services.pi.pullPolicy | Always | | Image pull policy for the Plane AI deployment. | | services.pi.assign\_cluster\_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the Plane AI API service. | | services.pi.nodeSelector | {} | | Node selector for the Plane AI API deployment. | | services.pi.tolerations | \[] | | Tolerations for the Plane AI API deployment. | | services.pi.affinity | {} | | Affinity rules for the Plane AI API deployment. | | services.pi.labels | {} | | Custom labels to add to the Plane AI API deployment. | | services.pi.annotations | {} | | Custom annotations to add to the Plane AI API deployment. | | env.pg\_pi\_db\_name | plane\_pi | | PostgreSQL database name used by Plane AI when `postgres.local_setup=true`. | | env.pg\_pi\_db\_remote\_url | "" | | PostgreSQL connection URL for Plane AI when using a remote database. Required when `postgres.local_setup=false` and Plane AI is enabled. | | env.pi\_envs.follower\_postgres\_uri | Same as Plane DATABASE\_URL | No | Connection string for a Plane PostgreSQL DB read replica. Used for read-heavy operations to reduce load on the primary database. | | env.pi\_envs.internal\_secret | tyfvfqvBJAgpm9bzvf3r4urJer0Ehfdubk | | Internal secret used by Plane AI for OAuth and internal APIs. | | env.pi\_envs.plane\_api\_host | "" | | Override for the Plane API host URL used by Plane AI. Defaults to the license domain. | | env.pi\_envs.cors\_allowed\_origins | "" | | CORS allowed origins for Plane AI API. Defaults to the license domain. | #### Plane AI Worker Deployment | Setting | Default | Required | Description | | -------------------------------- | :-----: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.pi\_worker.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for the Plane AI worker. This key helps you set the number of replicas. It must be >=1. | | services.pi\_worker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for the Plane AI worker deployment to use. | | services.pi\_worker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for the Plane AI worker deployment to use. | | services.pi\_worker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for the Plane AI worker deployment to use. | | services.pi\_worker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for the Plane AI worker deployment to use. | | services.pi\_worker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `pi_worker`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.pi\_worker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of `pi_worker`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.pi\_worker.affinity | {} | | This key allows you to set the affinity rules for the deployment of `pi_worker`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.pi\_worker.labels | {} | | Custom labels to add to the Plane AI worker deployment | | services.pi\_worker.annotations | {} | | Custom annotations to add to the Plane AI worker deployment | #### Plane AI Beat-Worker Deployment | Setting | Default | Required | Description | | ------------------------------------- | :-----: | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.pi\_beat\_worker.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for the Plane AI beat-worker. This key helps you set the number of replicas. It must be >=1. | | services.pi\_beat\_worker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for the Plane AI beat-worker deployment to use. | | services.pi\_beat\_worker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for the Plane AI beat-worker deployment to use. | | services.pi\_beat\_worker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for the Plane AI beat-worker deployment to use. | | services.pi\_beat\_worker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for the Plane AI beat-worker deployment to use. | | services.pi\_beat\_worker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `pi_beat_worker`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.pi\_beat\_worker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of `pi_beat_worker`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.pi\_beat\_worker.affinity | {} | | This key allows you to set the affinity rules for the deployment of `pi_beat_worker`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.pi\_beat\_worker.labels | {} | | Custom labels to add to the Plane AI beat-worker deployment | | services.pi\_beat\_worker.annotations | {} | | Custom annotations to add to the Plane AI beat-worker deployment | #### Worker Deployment | Setting | Default | Required | Description | | ----------------------------- | :-----: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.worker.replicas | 1 | Yes | Kubernetes helps you with scaling up or down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.worker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.worker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.worker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.worker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.worker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of worker. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.worker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of worker. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.worker.affinity | {} | | This key allows you to set the affinity rules for the deployment of worker. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.worker.labels | {} | | Custom labels to add to the worker deployment | | services.worker.annotations | {} | | Custom annotations to add to the worker deployment | #### Beat-Worker Deployment | Setting | Default | Required | Description | | --------------------------------- | :-----: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | services.beatworker.replicas | 1 | Yes | Kubernetes helps you with scaling up or down the deployments. You can run 1 or more pods for each deployment. This key helps you set up the number of replicas you want to run for this deployment. It must be >=1 | | services.beatworker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use the maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | services.beatworker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use the maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | services.beatworker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | services.beatworker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | services.beatworker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of beatworker. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.beatworker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of beatworker. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.beatworker.affinity | {} | | This key allows you to set the affinity rules for the deployment of beatworker. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.beatworker.labels | {} | | Custom labels to add to the beat-worker deployment | | services.beatworker.annotations | {} | | Custom annotations to add to the beat-worker deployment | #### Email Service Deployment | Setting | Default | Required | Description | | ------------------------------------ | --------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.email\_service.enabled | false | | Set to `true` to enable the email service deployment | | services.email\_service.replicas | 1 | | Number of replicas for the email service deployment | | services.email\_service.memoryLimit | 1000Mi | | Memory limit for the email service deployment | | services.email\_service.cpuLimit | 500m | | CPU limit for the email service deployment | | services.email\_service.memoryRequest | 50Mi | | Memory request for the email service deployment | | services.email\_service.cpuRequest | 50m | | CPU request for the email service deployment | | services.email\_service.image | artifacts.plane.so/makeplane/email-commercial | | Docker image for the email service deployment | | services.email\_service.pullPolicy | Always | | Image pull policy for the email service deployment | | services.email\_service.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `email_service`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.email\_service.tolerations | \[] | | This key allows you to set the tolerations for the deployment of `email_service`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.email\_service.affinity | {} | | This key allows you to set the affinity rules for the deployment of `email_service`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.email\_service.labels | {} | | Custom labels to add to the email service deployment | | services.email\_service.annotations | {} | | Custom annotations to add to the email service deployment | | env.email\_service\_envs.smtp\_domain | | Yes | The SMTP Domain to be used with email service | ::: info When the email service is enabled, the cert-issuer will be automatically created to handle TLS certificates for the email service. ::: #### Outbox Poller Service Deployment | Setting | Default | Required | Description | | ------------------------------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.outbox\_poller.enabled | false | | Set to true to enable the outbox poller service deployment | | services.outbox\_poller.replicas | 1 | | Number of replicas for the outbox poller service deployment | | services.outbox\_poller.memoryLimit | 1000Mi | | Memory limit for the outbox poller service deployment | | services.outbox\_poller.cpuLimit | 500m | | CPU limit for the outbox poller service deployment | | services.outbox\_poller.memoryRequest | 50Mi | | Memory request for the outbox poller service deployment | | services.outbox\_poller.cpuRequest | 50m | | CPU request for the outbox poller service deployment | | services.outbox\_poller.pullPolicy | Always | | Image pull policy for the outbox poller service deployment | | services.outbox\_poller.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | services.outbox\_poller.nodeSelector | {} | | This key allows you to set the node selector for the deployment of outbox\_poller. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.outbox\_poller.tolerations | \[] | | This key allows you to set the tolerations for the deployment of outbox\_poller. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.outbox\_poller.affinity | {} | | This key allows you to set the affinity rules for the deployment of outbox\_poller. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.outbox\_poller.labels | {} | | Custom labels to add to the outbox poller deployment | | services.outbox\_poller.annotations | {} | | Custom annotations to add to the outbox poller deployment | | env.outbox\_poller\_envs.memory\_limit\_mb | 400 | | Memory limit in MB for the outbox poller | | env.outbox\_poller\_envs.interval\_min | 0.25 | | Minimum interval in minutes for polling | | env.outbox\_poller\_envs.interval\_max | 2 | | Maximum interval in minutes for polling | | env.outbox\_poller\_envs.batch\_size | 250 | | Batch size for processing outbox messages | | env.outbox\_poller\_envs.memory\_check\_interval | 30 | | Memory check interval in seconds | | env.outbox\_poller\_envs.pool.size | 4 | | Pool size for database connections | | env.outbox\_poller\_envs.pool.min\_size | 2 | | Minimum pool size for database connections | | env.outbox\_poller\_envs.pool.max\_size | 10 | | Maximum pool size for database connections | | env.outbox\_poller\_envs.pool.timeout | 30.0 | | Pool timeout in seconds | | env.outbox\_poller\_envs.pool.max\_idle | 300.0 | | Maximum idle time for connections in seconds | | env.outbox\_poller\_envs.pool.max\_lifetime | 3600 | | Maximum lifetime for connections in seconds | | env.outbox\_poller\_envs.pool.reconnect\_timeout | 5.0 | | Reconnect timeout in seconds | | env.outbox\_poller\_envs.pool.health\_check\_interval | 60 | | Health check interval in seconds | #### Automation Consumer Deployment | Setting | Default | Required | Description | | ---------------------------------------------------- | -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.automation\_consumer.enabled | false | | Set to true to enable the automation consumer service deployment | | services.automation\_consumer.replicas | 1 | | Number of replicas for the automation consumer service deployment | | services.automation\_consumer.memoryLimit | 1000Mi | | Memory limit for the automation consumer service deployment | | services.automation\_consumer.cpuLimit | 500m | | CPU limit for the automation consumer service deployment | | services.automation\_consumer.memoryRequest | 50Mi | | Memory request for the automation consumer service deployment | | services.automation\_consumer.cpuRequest | 50m | | CPU request for the automation consumer service deployment | | services.automation\_consumer.pullPolicy | Always | | Image pull policy for the automation consumer service deployment | | services.automation\_consumer.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | services.automation\_consumer.nodeSelector | {} | | This key allows you to set the node selector for the deployment of automation\_consumer. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.automation\_consumer.tolerations | \[] | | This key allows you to set the tolerations for the deployment of automation\_consumer. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.automation\_consumer.affinity | {} | | This key allows you to set the affinity rules for the deployment of automation\_consumer. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.automation\_consumer.labels | {} | | Custom labels to add to the automation consumer deployment | | services.automation\_consumer.annotations | {} | | Custom annotations to add to the automation consumer deployment | | env.automation\_consumer\_envs.event\_stream\_queue\_name | "plane.event\_stream.automations" | | Event stream queue name for automations | | env.automation\_consumer\_envs.event\_stream\_prefetch | 10 | | Event stream prefetch count | | env.automation\_consumer\_envs.exchange\_name | "plane.event\_stream" | | Exchange name for event stream | | env.automation\_consumer\_envs.event\_types | "issue" | | Event types to process | #### Iframely Deployment | Setting | Default | Required | Description | | ----------------------------------- | -------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | services.iframely.enabled | false | | Set to true to enable the Iframely service deployment | | services.iframely.replicas | 1 | | Number of replicas for the Iframely service deployment | | services.iframely.memoryLimit | 1000Mi | | Memory limit for the Iframely service deployment | | services.iframely.cpuLimit | 500m | | CPU limit for the Iframely service deployment | | services.iframely.memoryRequest | 50Mi | | Memory request for the Iframely service deployment | | services.iframely.cpuRequest | 50m | | CPU request for the Iframely service deployment | | services.iframely.image | artifacts.plane.so/makeplane/iframely:v1.2.0 | | Docker image for the Iframely service deployment | | services.iframely.pullPolicy | Always | | Image pull policy for the Iframely service deployment | | services.iframely.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | services.iframely.nodeSelector | {} | | This key allows you to set the node selector for the deployment of iframely. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.iframely.tolerations | \[] | | This key allows you to set the tolerations for the deployment of iframely. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | services.iframely.affinity | {} | | This key allows you to set the affinity rules for the deployment of iframely. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | services.iframely.labels | {} | | Custom labels to add to the iframely deployment | | services.iframely.annotations | {} | | Custom annotations to add to the iframely deployment | #### External Secrets Config To configure the external secrets for your application, you need to define specific environment variables for each secret category. Below is a list of the required secrets and their respective environment variables. | Secret Name | Env Var Name | Required | Description | Example Value | | ------------------------- | --------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | rabbitmq\_existingSecret | RABBITMQ\_DEFAULT\_USER | Required if `rabbitmq.local_setup=true` | The default RabbitMQ user | plane | | | RABBITMQ\_DEFAULT\_PASS | Required if `rabbitmq.local_setup=true` | The default RabbitMQ password | plane | | pgdb\_existingSecret | POSTGRES\_PASSWORD | Required if `postgres.local_setup=true` | Password for PostgreSQL database | plane | | | POSTGRES\_DB | Required if `postgres.local_setup=true` | Name of the PostgreSQL database | plane | | | POSTGRES\_USER | Required if `postgres.local_setup=true` | PostgreSQL user | plane | | opensearch\_existingSecret | OPENSEARCH\_ENABLED | Yes | Flag to enable OpenSearch | 1 (enabled) or 0 (disabled) | | | OPENSEARCH\_URL | Required if OpenSearch is enabled | OpenSearch connection URL | **k8s service example:** `http://plane-opensearch.plane-ns.svc.cluster.local:9200` **external service example:** `https://your-opensearch-host:9200` | | | OPENSEARCH\_USERNAME | Required if OpenSearch is enabled | Username for OpenSearch | **local setup:** plane **remote setup:** your\_remote\_username | | | OPENSEARCH\_PASSWORD | Required if OpenSearch is enabled | Password for OpenSearch | **local setup:** Secure@Pass#123!%^&\* **remote setup:** your\_remote\_password | | | OPENSEARCH\_INITIAL\_ADMIN\_PASSWORD | Required if `opensearch.local_setup=true` | Initial admin password for local OpenSearch | Secure@Pass#123!%^&\* | | | OPENSEARCH\_INDEX\_PREFIX | Optional | Prefix for OpenSearch indices | plane\_ | | doc\_store\_existingSecret | USE\_MINIO | Yes | Flag to enable MinIO as the storage backend | 1 | | | MINIO\_ROOT\_USER | Yes | MinIO root user | admin | | | MINIO\_ROOT\_PASSWORD | Yes | MinIO root password | password | | | AWS\_ACCESS\_KEY\_ID | Yes | AWS Access Key ID | your\_aws\_key | | | AWS\_SECRET\_ACCESS\_KEY | Yes | AWS Secret Access Key | your\_aws\_secret | | | AWS\_S3\_BUCKET\_NAME | Yes | AWS S3 Bucket Name | your\_bucket\_name | | | AWS\_S3\_ENDPOINT\_URL | Yes | Endpoint URL for AWS S3 or MinIO | `http://plane-minio.plane-ns.svc.cluster.local:9000` | | | AWS\_REGION | Optional | AWS region where your S3 bucket is located | your\_aws\_region | | | FILE\_SIZE\_LIMIT | Yes | Limit for file uploads in your system | 5MB | | app\_env\_existingSecret | SECRET\_KEY | Yes | Random secret key | 60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 | | | REDIS\_URL | Yes | Redis URL | `redis://plane-redis.plane-ns.svc.cluster.local:6379/` | | | DATABASE\_URL | Yes | PostgreSQL connection URL | k8s service example: `postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local:5432/plane` external service example: `postgresql://username:password@your-db-host:5432/plane` | | | AMQP\_URL | Yes | RabbitMQ connection URL | k8s service example: `amqp://plane:plane@plane-rabbitmq.plane-ns.svc.cluster.local:5672/` external service example: `amqp://username:password@your-rabbitmq-host:5672/` | | live\_env\_existingSecret | REDIS\_URL | Yes | Redis URL | `redis://plane-redis.plane-ns.svc.cluster.local:6379/` | | silo\_env\_existingSecret | SILO\_HMAC\_SECRET\_KEY | Yes | Silo HMAC secret Key | `` | | | REDIS\_URL | Yes | Redis URL | redis://plane-redis.plane-ns.svc.cluster.local:6379/ | | | DATABASE\_URL | Yes | PostgreSQL connection URL | k8s service example: postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local:5432/plane external service example: postgresql://username:password@your-db-host:5432/plane | | | AMQP\_URL | Yes | RabbitMQ connection URL | k8s service example: amqp://plane:plane@plane-rabbitmq.plane-ns.svc.cluster.local:5672/ external service example: amqp://username:password@your-rabbitmq-host:5672/ | | | GITHUB\_APP\_NAME | Required if `services.silo.connectors.github.enabled` is true | GitHub app name | your\_github\_app\_name | | | GITHUB\_APP\_ID | Required if `services.silo.connectors.github.enabled` is true | GitHub app ID | your\_github\_app\_id | | | GITHUB\_CLIENT\_ID | Required if `services.silo.connectors.github.enabled` is true | GitHub client ID | your\_github\_client\_id | | | GITHUB\_CLIENT\_SECRET | Required if `services.silo.connectors.github.enabled` is true | GitHub client secret key | your\_github\_client\_secret\_key | | | GITHUB\_PRIVATE\_KEY | Required if `services.silo.connectors.github.enabled` is true | GitHub private key | your\_github\_private\_key | | | SLACK\_CLIENT\_ID | Required if `services.silo.connectors.slack.enabled` is true | Slack client ID | your\_slack\_client\_id | | | SLACK\_CLIENT\_SECRET | Required if `services.silo.connectors.slack.enabled` is true | Slack client secret key | your\_slack\_client\_secret\_key | | | GITLAB\_CLIENT\_ID | Required if `services.silo.connectors.gitlab.enabled` is true | GitLab client ID | your\_gitlab\_client\_id | | | GITLAB\_CLIENT\_SECRET | Required if `services.silo.connectors.gitlab.enabled` is true | GitLab client secret key | your\_gitlab\_client\_secret\_key | | pi\_api\_env\_existingSecret | PLANE\_PI\_DATABASE\_URL | Required if `services.pi.enabled=true` | PostgreSQL connection URL for Plane AI database | **k8s service example**: `postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local/plane_pi` **external**: `postgresql://username:password@your-db-host:5432/plane_pi` | | | FOLLOWER\_POSTGRES\_URI | No | Connection string for a PostgreSQL read replica | Same as DATABASE\_URL. Used for read-heavy operations to reduce load on the primary database. **k8s**: `postgresql://plane:plane@plane-pgdb.plane-ns.svc.cluster.local:5432/plane` | | | AMQP\_URL | Required if `services.pi.enabled=true` | RabbitMQ connection URL | **k8s service example**: `amqp://plane:plane@plane-rabbitmq.plane-ns.svc.cluster.local:5672/` **external**: `amqp://username:password@your-rabbitmq-host:5672/` | | | AES\_SECRET\_KEY | Required if `services.pi.enabled=true` | AES secret key for Plane AI | dsOdt7YrvxsTIFJ37pOaEVvLxN8KGBCr (or your own value) | | | OPENAI\_API\_KEY | required if `services.pi.ai_providers.openai.enabled` is true | OpenAI API key | your\_openai\_api\_key | | | CLAUDE\_API\_KEY | required if `services.pi.ai_providers.claude.enabled` is true | Claude API key | your\_claude\_api\_key | | | GROQ\_API\_KEY | required if `services.pi.ai_providers.groq.enabled` is true | Groq API key | your\_groq\_api\_key | | | COHERE\_API\_KEY | required if `services.pi.ai_providers.cohere.enabled` is true | Cohere API key | your\_cohere\_api\_key | | | CUSTOM\_LLM\_API\_KEY | required if `services.pi.ai_providers.custom_llm.enabled` is true | Custom LLM API key | your\_custom\_llm\_api\_key | | | BR\_AWS\_SECRET\_ACCESS\_KEY | required if `services.pi.ai_providers.embedding_model.enabled` is true | AWS secret for embedding model | your\_aws\_secret\_access\_key | | | BR\_AWS\_SESSION\_TOKEN | required if embedding model uses temporary credentials | AWS session token for embedding model | your\_aws\_session\_token | #### Ingress and SSL Setup | Setting | Default | Required | Description | | --------------------------- | --------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ingress.enabled | true | | Ingress setup in kubernetes is a common practice to expose application to the intended audience. Set it to false if you are using external ingress providers like Cloudflare | | ingress.minioHost | | | Based on above configuration, if you want to expose the minio web console to set of users, use this key to set the host mapping or leave it as EMPTY to not expose interface. | | ingress.rabbitmqHost | | | Based on above configuration, if you want to expose the rabbitmq web console to set of users, use this key to set the host mapping or leave it as EMPTY to not expose interface. | | ingress.ingressClass | nginx | Yes | Kubernetes cluster setup comes with various options of ingressClass. Based on your setup, set this value to the right one (eg. nginx, traefik, etc). Leave it to default in case you are using external ingress provider. | | ingress.ingress\_annotations | { `"nginx.ingress.kubernetes.io/proxy-body-size": "5m"` } | | Ingress controllers comes with various configuration options which can be passed as annotations. Setting this value lets you change the default value to user required. | | ssl.createIssuer | false | | Kubernets cluster setup supports creating issuer type resource. After deployment, this is step towards creating secure access to the ingress url. Issuer is required for you generate SSL certifiate. Kubernetes can be configured to use any of the certificate authority to generate SSL (depending on CertManager configuration). Set it to true to create the issuer. Applicable only when ingress.enabled=true | | ssl.issuer | http | | CertManager configuration allows user to create issuers using http or any of the other DNS Providers like cloudflare, digitalocean, etc. As of now Plane supports http, cloudflare, digitalocean | | ssl.token | | | To create issuers using DNS challenge, set the issuer api token of dns provider like cloudflare or digitalocean (not required for http) | | ssl.server | https://acme-v02.api.letsencrypt.org/directory | | Issuer creation configuration need the certificate generation authority server url. Default URL is the Let's Encrypt server | | ssl.email | plane@example.com | | Certificate generation authority needs a valid email id before generating certificate. Required when ssl.createIssuer=true | | ssl.generateCerts | false | | After creating the issuers, user can still not create the certificate untill sure of configuration. Setting this to true will try to generate SSL certificate and associate with ingress. Applicable only when ingress.enabled=true and ssl.createIssuer=true | | ssl.tls\_secret\_name | | | If you have a custom TLS secret name, set this to the name of the secret. Applicable only when ingress.enabled=true and ssl.createIssuer=false | #### Common Environment Settings | Setting | Default | Required | Description | | ---------------- | :------------------------------------------------: | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | env.storageClass | longhorn | | Creating the persitant volumes for the stateful deployments needs the `storageClass` name. Set the correct value as per your kubernetes cluster configuration. | | env.secret\_key | 60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 | Yes | This must be a random string which is used for hashing/encrypting the sensitive data within the application. Once set, changing this might impact the already hashed/encrypted data | #### Extra Environment Variables | Setting | Default | Required | Description | | -------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | extraEnv | \[] | No | Global extra environment variables that will be applied to all workloads. This allows you to add custom environment variables to all deployments (web, api, worker, etc.). Useful for proxy settings, custom configurations, or any environment-specific variables. Some example variables are HTTP\_PROXY, HTTPS\_PROXY, NO\_PROXY. | ## Custom Ingress Routes If you are planning to use 3rd party ingress providers, here is the available route configuration. | Host | Path | Service | Required | | ----------------------- | :-------------: | --------------------------------------- | :-------------------------------------------------------------------------- | | plane.example.com | / | | Yes | | plane.example.com | /spaces/\* | | Yes | | plane.example.com | /god-mode/\* | | Yes | | plane.example.com | /live/\* | | Yes | | plane.example.com | /silo/\* | | Yes (if `services.silo.enabled=true` ) | | plane.example.com | /pi/\* | | Yes (if `services.pi.enabled=true`) | | plane.example.com | /api/\* | | Yes | | plane.example.com | /auth/\* | | Yes | | plane.example.com | /graphql/\* | | Yes | | plane.example.com | /marketplace/\* | | Yes | | plane.example.com | /uploads/\* | | Yes (Only if using local setup) | | plane-minio.example.com | / | | (Optional) if using local setup, this will enable minio console access | | plane-mq.example.com | / | | (Optional) if using local setup, this will enable management console access | ::: details Install Community Edition The Commercial edition comes with a free plan and the flexibility to upgrade to a paid plan at any point. If you still want to install the Community edition, follow the steps below: #### Prerequisites * A working Kubernetes cluster * `kubectl` and `helm` on the client system that you will use to install our Helm charts #### Installation 1. Open Terminal or any other command-line app that has access to Kubernetes tools on your local system. 2. Add the Helm Repo ```bash helm repo add makeplane https://helm.plane.so/ helm repo update ``` 3. Use one of the following ways to deploy Plane: - **Quick setup** This is the fastest way to deploy Plane with default settings. This will create stateful deployments for Postgres, Redis, and Minio with a persistent volume claim using the `longhorn` storage class. This also sets up the ingress routes for you using `nginx` ingress class. ::: tip To customize this, see `Custom ingress routes` below. ::: Continue to be on the same Terminal window as you have so far, copy the code below, and paste it on your Terminal screen. ```bash helm install plane-app makeplane/plane-ce \ --create-namespace \ --namespace plane-ce \ --set planeVersion=stable \ --set ingress.appHost="plane.example.com" \ --set ingress.minioHost="plane-minio.example.com" \ --set ingress.ingressClass=nginx \ --set postgres.storageClass=longhorn \ --set redis.storageClass=longhorn \ --set minio.storageClass=longhorn \ --timeout 10m \ --wait \ --wait-for-jobs ``` ::: tip This is the minimum required to set up Plane-CE. You can change the default namespace from `plane-ce`, the default app name from `plane-app`, the default storage class from `[postgres, redis, minio].storageClass`, and the default ingress class from `ingress.ingressClass` to whatever you would like to. You can also pass other settings referring to `Configuration Settings` section. ::: * **Advanced setup** For more control over your set-up, run the script below to download the `values.yaml` file and and edit using any editor like Vim or Nano. ```bash helm show values makeplane/plane-ce > values.yaml vi values.yaml ``` ::: tip See **Configuration settings** below for more details. ::: After saving the `values.yaml` file, continue to be on the same Terminal window as on the previous steps, copy the code below, and paste it on your Terminal screen. ```bash helm install plane-app makeplane/plane-ce \ --create-namespace \ --namespace plane-ce \ -f values.yaml \ --timeout 10m \ --wait \ --wait-for-jobs ``` #### Configuration settings ##### Plane Version | Setting | Default | Required | Description | | ------------ | ------- | -------- | ----------- | | planeVersion | v1.1.0 | Yes | | ##### Postgres DB Setup | Setting | Default | Required | Description | | -------------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | postgres.local\_setup | true | | Plane uses postgres as the primary database to store all the transactional data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to true when you choose to setup stateful deployment of postgres. Mark it as false when using a remotely hosted database | | postgres.image | postgres:15.7-alpine | | Using this key, user must provide the docker image name to setup the stateful deployment of postgres. (must be set when `postgres.local_setup=true`) | | postgres.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of postgres. (must be set when `postgres.local_setup=true`) | | postgres.servicePort | 5432 | | This key sets the default port number to be used while setting up stateful deployment of postgres. | | postgres.volumeSize | 5Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | env.pgdb\_username | plane | | Database credentials are requried to access the hosted stateful deployment of postgres. Use this key to set the username for the stateful deployment. | | env.pgdb\_password | plane | | Database credentials are requried to access the hosted stateful deployment of postgres. Use this key to set the password for the stateful deployment. | | env.pgdb\_name | plane | | Database name to be used while setting up stateful deployment of Postgres | | env.pgdb\_remote\_url | | | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set postgres.local\_setup to false and set this key with remote connection url. | | postgres.storageClass | `` | | Creating the persitant volumes for the stateful deployments needs the storageClass name. Set the correct value as per your kubernetes cluster configuration. | | postgres.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | postgres.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of postgres. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | postgres.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of postgres. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | postgres.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of postgres. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | postgres.labels | {} | | This key allows you to set custom labels for the stateful deployment of postgres. This is useful for organizing and selecting resources in your Kubernetes cluster. | | postgres.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of postgres. This is useful for adding metadata or configuration hints to your resources. | ##### Redis/Valkey Setup | Setting | Default | Required | Description | | ----------------------- | ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | redis.local\_setup | true | | Plane uses redis to cache the session authentication and other static data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to true when you choose to setup stateful deployment of redis. Mark it as false when using a remotely hosted database | | redis.image | `valkey/valkey:7.2.5-alpine` | | Using this key, user must provide the docker image name to setup the stateful deployment of redis. (must be set when redis.local\_setup=true) | | redis.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of redis. (must be set when `redis.local_setup=true`) | | redis.servicePort | 6379 | | This key sets the default port number to be used while setting up stateful deployment of redis. | | redis.volumeSize | 1Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | env.remote\_redis\_url | | | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set redis.local\_setup to false and set this key with remote connection url. | | redis.storageClass | `` | | Creating the persitant volumes for the stateful deployments needs the storageClass name. Set the correct value as per your kubernetes cluster configuration. | | redis.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | redis.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of redis. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | redis.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of redis. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | redis.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of redis. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | redis.labels | {} | | This key allows you to set custom labels for the stateful deployment of redis. This is useful for organizing and selecting resources in your Kubernetes cluster. | | redis.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of redis. This is useful for adding metadata or configuration hints to your resources. | ##### RabbitMQ Setup | Setting | Default | Required | Description | | ------------------------------ | --------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | rabbitmq.local\_setup | true | | Plane uses rabbitmq as message queuing system. This can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws mq or similar services). Set this to true when you choose to setup stateful deployment of rabbitmq. Mark it as false when using a remotely hosted service | | rabbitmq.image | rabbitmq:3.13.6-management-alpine | | Using this key, user must provide the docker image name to setup the stateful deployment of rabbitmq. (must be set when `rabbitmq.local_setup=true`) | | rabbitmq.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of rabbitmq. (must be set when `rabbitmq.local_setup=true`) | | rabbitmq.servicePort | 5672 | | This key sets the default port number to be used while setting up stateful deployment of rabbitmq. | | rabbitmq.managementPort | 15672 | | This key sets the default management port number to be used while setting up stateful deployment of rabbitmq. | | rabbitmq.volumeSize | 100Mi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | rabbitmq.storageClass | `` | | Creating the persitant volumes for the stateful deployments needs the storageClass name. Set the correct value as per your kubernetes cluster configuration. | | rabbitmq.default\_user | plane | | Credentials are requried to access the hosted stateful deployment of rabbitmq. Use this key to set the username for the stateful deployment. | | rabbitmq.default\_password | plane | | Credentials are requried to access the hosted stateful deployment of rabbitmq. Use this key to set the password for the stateful deployment. | | rabbitmq.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | rabbitmq.external\_rabbitmq\_url | | | Users can also decide to use the remote hosted service and link to Plane deployment. Ignoring all the above keys, set rabbitmq.local\_setup to false and set this key with remote connection url. | | rabbitmq.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of rabbitmq. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | rabbitmq.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of rabbitmq. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | rabbitmq.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of rabbitmq. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | rabbitmq.labels | {} | | This key allows you to set custom labels for the stateful deployment of rabbitmq. This is useful for organizing and selecting resources in your Kubernetes cluster. | | rabbitmq.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of rabbitmq. This is useful for adding metadata or configuration hints to your resources. | ##### Doc Store (Minio/S3) Setup | Setting | Default | Required | Description | | ---------------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | minio.local\_setup | true | | Plane uses minio as the default file storage drive. This storage can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws S3 or similar services). Set this to true when you choose to setup stateful deployment of postgres. Mark it as false when using a remotely hosted database | | minio.image | minio/minio:latest | | Using this key, user must provide the docker image name to setup the stateful deployment of minio. (must be set when `minio.local_setup=true`) | | minio.image\_mc | minio/mc:latest | | Using this key, user must provide the docker image name to setup the job deployment of minio client. (must be set when `minio.local_setup=true`) | | minio.pullPolicy | IfNotPresent | | Using this key, user can set the pull policy for the stateful deployment of minio. (must be set when `minio.local_setup=true`) | | minio.volumeSize | 5Gi | | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte) | | minio.root\_user | admin | | Storage credentials are requried to access the hosted stateful deployment of minio. Use this key to set the username for the stateful deployment. | | minio.root\_password | password | | Storage credentials are requried to access the hosted stateful deployment of minio. Use this key to set the password for the stateful deployment. | | minio.env.minio\_endpoint\_ssl | false | | (Optional) Env to enforce HTTPS when connecting to minio uploads bucket | | env.docstore\_bucket | uploads | Yes | Storage bucket name is required as part of configuration. This is where files will be uploaded irrespective of if you are using Minio or external S3 (or compatible) storage service | | env.doc\_upload\_size\_limit | 5242880 | Yes | Document Upload Size Limit (default to 5Mb) | | env.aws\_access\_key | | | External S3 (or compatible) storage service provides access key for the application to connect and do the necessary upload/download operations. To be provided when `minio.local_setup=false` | | env.aws\_secret\_access\_key | | | External S3 (or compatible) storage service provides secret access key for the application to connect and do the necessary upload/download operations. To be provided when `minio.local_setup=false` | | env.aws\_region | | | External S3 (or compatible) storage service providers creates any buckets in user selected region. This is also shared with the user as region for the application to connect and do the necessary upload/download operations. To be provided when `minio.local_setup=false` | | 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/download operations. To be provided when minio.`local_setup=false` | | minio.storageClass | `` | | Creating the persitant volumes for the stateful deployments needs the storageClass name. Set the correct value as per your kubernetes cluster configuration. | | minio.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | minio.nodeSelector | {} | | This key allows you to set the node selector for the stateful deployment of minio. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | minio.tolerations | \[] | | This key allows you to set the tolerations for the stateful deployment of minio. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | minio.affinity | {} | | This key allows you to set the affinity rules for the stateful deployment of minio. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | minio.labels | {} | | This key allows you to set custom labels for the stateful deployment of minio. This is useful for organizing and selecting resources in your Kubernetes cluster. | | minio.annotations | {} | | This key allows you to set custom annotations for the stateful deployment of minio. This is useful for adding metadata or configuration hints to your resources. | ##### Web Deployment | Setting | Default | Required | Description | | --------------------- | ------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | web.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | web.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | web.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | web.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | web.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | web.image | artifacts.plane.so/makeplane/plane-frontend | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | web.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of web. | | web.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | web.nodeSelector | {} | | This key allows you to set the node selector for the deployment of web. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | web.tolerations | \[] | | This key allows you to set the tolerations for the deployment of web. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | web.affinity | {} | | This key allows you to set the affinity rules for the deployment of web. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | web.labels | {} | | Custom labels to add to the web deployment | | web.annotations | {} | | Custom annotations to add to the web deployment | ##### Space Deployment | Setting | Default | Required | Description | | ----------------------- | ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | space.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | space.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | space.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | space.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | space.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | space.image | artifacts.plane.so/makeplane/plane-space | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | space.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of space. | | space.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | space.nodeSelector | {} | | This key allows you to set the node selector for the deployment of space. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | space.tolerations | \[] | | This key allows you to set the tolerations for the deployment of space. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | space.affinity | {} | | This key allows you to set the affinity rules for the deployment of space. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | space.labels | {} | | Custom labels to add to the space deployment | | space.annotations | {} | | Custom annotations to add to the space deployment | ##### Admin Deployment | Setting | Default | Required | Description | | ----------------------- | ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | admin.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | admin.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | admin.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | admin.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | admin.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | admin.image | artifacts.plane.so/makeplane/plane-admin | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | admin.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of admin. | | admin.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | admin.nodeSelector | {} | | This key allows you to set the node selector for the deployment of admin. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | admin.tolerations | \[] | | This key allows you to set the tolerations for the deployment of admin. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | admin.affinity | {} | | This key allows you to set the affinity rules for the deployment of admin. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | admin.labels | {} | | Custom labels to add to the admin deployment | | admin.annotations | {} | | Custom annotations to add to the admin deployment | ##### Live Service Deployment | Setting | Default | Required | Description | | ---------------------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | live.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | live.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | live.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | live.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | live.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | live.image | artifacts.plane.so/makeplane/plane-live | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | live.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of live. | | live.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | live.nodeSelector | {} | | This key allows you to set the node selector for the deployment of live. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | live.tolerations | \[] | | This key allows you to set the tolerations for the deployment of live. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | live.affinity | {} | | This key allows you to set the affinity rules for the deployment of live. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | live\_server\_secret\_key | htbqvBJAgpm9bzvf3r4urJer0ENReatceh | Yes | This key sets the secret key for the live server. This is required for secure communication and authentication in the live server component. | | live.labels | {} | | Custom labels to add to the live deployment | | live.annotations | {} | | Custom annotations to add to the live deployment | ##### API Deployment | Setting | Default | Required | Description | | ---------------------- | ------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | api.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | api.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | api.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | api.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | api.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | api.image | artifacts.plane.so/makeplane/plane-backend | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | api.pullPolicy | Always | | Using this key, user can set the pull policy for the deployment of api. | | env.sentry\_dsn | | | (optional) API service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry provided DSN for this integration. | | env.sentry\_environment | | | (optional) API service deployment comes with some of the preconfigured integration. Sentry is one among those. Here user can set the Sentry environment name (as configured in Sentry) for this integration. | | env.api\_key\_rate\_limit | 60/minute | | (optional) User can set the maximum number of requests the API can handle in a given time frame. | | api.assign\_cluster\_ip | false | | Set it to true if you want to assign ClusterIP to the service | | api.nodeSelector | {} | | This key allows you to set the node selector for the deployment of api. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | api.tolerations | \[] | | This key allows you to set the tolerations for the deployment of api. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | api.affinity | {} | | This key allows you to set the affinity rules for the deployment of api. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | api.labels | {} | | Custom labels to add to the API deployment | | api.annotations | {} | | Custom annotations to add to the API deployment | ##### Worker Deployment | Setting | Default | Required | Description | | -------------------- | ------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | worker.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | worker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | worker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | worker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | worker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | worker.image | artifacts.plane.so/makeplane/plane-backend | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | worker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of worker. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | worker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of worker. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | worker.affinity | {} | | This key allows you to set the affinity rules for the deployment of worker. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | worker.labels | {} | | Custom labels to add to the worker deployment | | worker.annotations | {} | | Custom annotations to add to the worker deployment | ##### Beat-Worker Deployment | Setting | Default | Required | Description | | ------------------------ | ------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | beatworker.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be `>=1` | | beatworker.memoryLimit | 1000Mi | | Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use. | | beatworker.cpuLimit | 500m | | Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use. | | beatworker.memoryRequest | 50Mi | | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use. | | beatworker.cpuRequest | 50m | | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use. | | beatworker.image | artifacts.plane.so/makeplane/plane-backend | | This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment | | beatworker.nodeSelector | {} | | This key allows you to set the node selector for the deployment of beatworker. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | beatworker.tolerations | \[] | | This key allows you to set the tolerations for the deployment of beatworker. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | | beatworker.affinity | {} | | This key allows you to set the affinity rules for the deployment of beatworker. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster. | | beatworker.labels | {} | | Custom labels to add to the beat-worker deployment | | beatworker.annotations | {} | | Custom annotations to add to the beat-worker deployment | ##### Common Environment Settings | Setting | Default | Required | Description | | -------------------------- | -------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | env.secret\_key | 60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 | Yes | This must a random string which is used for hashing/encrypting the sensitive data within the application. Once set, changing this might impact the already hashed/encrypted data | | env.default\_cluster\_domain | cluster.local | Yes | Set this value as configured in your kubernetes cluster. cluster.local is usally the default in most cases. | --- --- url: 'https://developers.plane.so/api-reference/issue/overview.html' description: >- Plane Issue API overview. Learn about endpoints, request/response format, and how to work with issue via REST API. --- # Overview Work items are the fundamental unit of work in Plane. They represent tasks that need to be accomplished — assignable, trackable, and actionable to-dos in your project management workflow. [Learn more about Work Items](https://docs.plane.so/core-concepts/issues/overview) ## The Work Item object ### Attributes * `name` *string* **(required)** Name of the work item * `created_at` , `updated_at` *timestamp* Timestamp of the work item when it was created and when it was last updated * `estimate_point` *integer* or *null* Total estimate points for the work item takes value between (0,7). * `description_html` *string* HTML description of the work item * `description_stripped` *string* Stripped version of the html description auto generated using the application. * `priority` *string* Priority of the work item takes in 5 values * none * urgent * high * medium * low * `start_date` *date* Start date of the work item * `target_date` *date* Target date of the work item * `sequence_id` *integer* Auto generated from the system the unique identifier of the work item * `sort_order` *decimal* Auto generated from the system during creation used for ordering * `completed_at` *timestamp* or *null* Timestamp when the work item is moved to any completed group state * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `project` *uuid* The project which the work item is part of auto generated from backend * `workspace` *uuid* The workspace which the work item is part of auto generated from backend * `parent` *uuid* The uuid of the parent work item which should be part of the same workspace * `state` *uuid* The uuid of the state which is present in the project where the work item is being created. * `assignees` - *\[uuid,]* The array of uuids of the users who are part of the project where the work item is being created or updated. * `labels` - *\[uuid,]* The array of uuids of the labels which are present in the project where the work item is being created or updated. * `type` *uuid* The uuid of the work item type for the work item. * `module` *uuid* The uuid of the module the work item belongs to. * `is_draft` *boolean* Whether the work item is a draft. * `archived_at` *timestamp* or *null* Timestamp when the work item was archived. * `description_binary` *string* Binary description of the work item. **Expandable Fields** The following fields can be expanded when retrieving a work item by including them in the `expand` query parameter: * `type` - Expands to full WorkItemType object * `module` - Expands to full Module object * `labels` - Expands to array of full Label objects * `assignees` - Expands to array of full User objects * `state` - Expands to full State object * `project` - Expands to full Project object ```json { "id": "e1c25c66-5bb8-465e-a818-92a483423443", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "estimate_point": null, "name": "First Work Item", "description_html": "

", "description_stripped": "", "priority": "none", "start_date": "2023-09-01", "target_date": "2023-10-04", "sequence_id": 421, "sort_order": 265535.0, "completed_at": null, "archived_at": null, "is_draft": false, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "parent": null, "state": "f3f045db-7e74-49f2-b3b2-0b7dee4635ae", "assignees": ["797b5aea-3f40-4199-be84-5f94e0d04501"], "labels": [] } ``` --- --- url: 'https://developers.plane.so/api-reference/project/overview.html' description: >- Plane Project API overview. Learn about endpoints, request/response format, and how to work with project via REST API. --- # Overview Projects organize your team's work within a workspace. Each project contains work items, cycles, modules, and other resources. [Learn more about Projects](https://docs.plane.so/core-concepts/projects/overview) ### The Project Object ### Attributes * `name` *string* (**required**) Name of the project * `identifier` *string* (**required**) Unique Identifier of project for the workspace * `description` *string* Project description * `total_members` *integer* Total members present in the project. * `total_cycles` *integer* Total number of cycles present in the project. * `total_modules` *integer* Total number of modules present in the project. * `is_member` *boolean* The current requesting user is a member of the project or not * `member_role` *integer* The current requesting users role in the project. * `is_deployed` *integer* Represents if the project is deployed and publicly visible. * `created_at` *timestamp* The timestamp of the time when the project was created * `updated_at` *timestamp* The timestamp of the time when the project was last updated * `network` *integer* Is the project public or secret it takes in two values either (0,2) * **0 - Secret** * **2 - Public** * `emoji` *string* HTML emoji DEX code without the `&#` * `icon_prop` *json* saves the data of the project icon * `module_view` *bool* Enable disable module for the project in the UI * `cycle_view` *bool* Enable disable cycle for the project in the UI * `inbox_view` *bool* Enable disable intake for the project in the UI * `page_view` *bool* Enable disable pages for the project in the UI * `issue_views_view` *bool* Enable disable project views for the project in the UI * `cover_image` *url* URL for the image for the project cover * `archive_in` *integer* Months in which the issue should be automatically archived can take values between (0,12) * `close_in` *integer* Months in which the issue should be auto closed can take values between (0,12) * `created_by` , `updated_by` *uuid* This values are auto saved and represent the id of the user that created or the updated the project * `workspace` *uuid* The workspace uuid where the project is created saved automatically * `default_assignee` *uuid* The uuid of the user who is a workspace member that have issues assigned automatically if the issue does not have any assignee * `project_lead` *uuid* The uuid of the user who is a workspace member that leads the project * `estimate` *uuid* UUID of the estimate of the project * `default_state` Default state which will be used when the issues will be auto closed ```json { "id": "00918ea1-52f7-48bd-abe3-d3efe76ff7dd", "total_members": 1, "total_cycles": 0, "total_modules": 0, "is_member": true, "member_role": 20, "is_deployed": false, "created_at": "2023-11-19T10:40:15.426652Z", "updated_at": "2023-11-19T10:40:15.426672Z", "name": "Project X", "description": "", "description_text": null, "description_html": null, "network": 2, "identifier": "PROJX", "emoji": null, "icon_prop": null, "module_view": true, "cycle_view": true, "issue_views_view": true, "page_view": true, "inbox_view": false, "cover_image": null, "archive_in": 0, "close_in": 0, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "default_assignee": null, "project_lead": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "estimate": null, "default_state": null } ``` --- --- url: 'https://developers.plane.so/dev-tools/intro-webhooks.html' description: >- Configure webhooks for Plane. Setup real-time event notifications and automate workflows with webhook integrations. --- # Webhooks A webhook triggers a HTTP POST request on the specified url, whenever there is a change in an event. Like a new project is created, updated or deleted then a webhook can be triggered to receive the required payload. ## Creating a webhook `url` You are required to provide a url in which you want the payloads to be triggered. Then select the events for which you want the webhook to be triggered. After you create the webhook, a secret key will be created automatically and will be downloaded in csv format. ```ini "Content-Type": "application/json", "User-Agent": "Autopilot", "X-Plane-Delivery": "f819eff4-cd50-4987-bc97-e5be1e04c94f", "X-Plane-Event": "project", "X-Plane-Signature": "7896ae9addb1f73931132b4f3e052bf12c410b837b24898e75dcd660c7" ``` | Header | Description | | ----------------- | -------------------------------------------------------------------- | | X-Plane-Delivery | It is a randomly generated UUID for uniquely identifying the payload | | X-Plane-Event | It describes the event for which the webhook triggered | | X-Plane-Signature | A signature is generated based on the secret and the payload | ### Webhook Payload Example for the project `update` ```json "event": "project", "action": "update", "webhook_id": "3c2c32ac-82df-48b3-be2a-a3e21dbe8692", "workspace_id": "d2d97c94-a6ad-4012-b526-5577c0d7c769", "data": { "id":"22b6fc9c-1849-45da-b103-52a3e3a6b4c1", "workspace_detail": { "name":"Testing Project", "slug":"testing-project", "id":"bob1b192-f988-4bf9-b569-825de8cb0678" }, "created_at":"2023-10-25T04:38:59.566962Z", "updated_at":"2023-10-25T06:44:48.543685Z", "name":"vfecddcwerj", "description":"", "description_text":null, "description_html":null, "network":2, "identifier":"TRACE", "emoji":null, "icon_prop":null, "module_view":true, "cycle_view":true, "issue_views_view":true, "page_view":true, "inbox_view":true, "cover_image":null, "archive_in":0, "close_in":0, "created_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4", "updated_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4", "workspace":"bob1b192-f988-4bf9-b569-825de8cb0678", "default_assignee":null, "project_lead":null, "estimate":null, "default_state":null }, ``` User can choose the things for which they want the webhook to be triggered. Currently Plane supports the following events for which webhook can be trigged: ``` - Project - Issue - Cycle - Module - Issue Comment ``` ## Verifying Signature ``` import hashlib import hmac secret_token = os.environ.get("WEBHOOK_SECRET") received_signature = request.headers.get('X-Plane-Signature') received_payload = json.dumps(request.json).encode('utf-8') expected_signature = hmac.new(secret_token.encode('utf-8'), msg=received_payload, digestmod=hashlib.sha256).hexdigest() if not hmac.compare_digest(expected_signature, received_signature): raise HTTPException(status_code=403, detail="Invalid Signature provided") ``` ## How webhook works Your webhook consumer is a simple HTTP endpoint. It must satisfy the following conditions: * It's available in a publicly accessible non-localhost URL. * It will respond to the Plane Webhook push (HTTP POST request) with a `HTTP 200` ("OK") response. If a delivery fails (i.e. server unavailable or responded with a non-200 HTTP status code), the push will be retried a couple of times. Here an exponential backoff delay is used: the attempt will be retried after approximately 10 minutes, then 30 minutes, and so on. The webhooks are triggered for POST, PATCH, and DELETE requests. * For DELETE requests, the response only includes the ID of the deleted entity. ```json "action":"delete", "data":{ "id":"9a28bd00-ed9c-4f5d-8be9-fc05cbb1fc57" }, "event":"issue", "webhook_id":"f1a2fe64-c8d4-4eed-b3ef-498690052c1d", "workspace_id":"c467e125-59e3-44ec-b5ee-f9c1e138c611" ``` * However, for both POST and PATCH requests, the complete payload is sent in the response. ```json "event":"issue", "action":"update", "webhook_id":"f1a2fe64-c8d4-4eed-b3ef-498690052c1d", "workspace_id":"c467e125-59e3-44ec-b5ee-f9c1e138c611", "data":{ ... } ``` ::: info Whenever an issue is added to the module, the corresponding issue webhook will be triggered. Similarly, any updates made to the cycle issue will also activate the issue webhook. ::: --- --- url: 'https://developers.plane.so/dev-tools/mcp-server.html' description: >- Setup MCP Server for Plane. Integrate Plane with Model Context Protocol for AI-powered project management. --- # MCP server The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that defines how AI applications discover and call external tools. Any client that speaks MCP can talk to any server that speaks MCP. The Plane MCP Server is a bridge that lets AI models interact with Plane. It exposes Plane's full API surface as MCP tools, so your AI tool can create work items, manage sprints, track time, and organise work without you leaving your editor or chat interface. ## Transport modes The server supports four transport modes. The right one depends on your deployment and usecase. | Transport | For | Auth method | How to start | | ------------------- | --------------------------------- | -------------------------- | ------------------------ | | HTTP with OAuth | Plane Cloud users, simplest setup | Browser-based OAuth flow | `plane-mcp-server http` | | HTTP with PAT Token | Automated workflows, CI/CD | API key in request headers | `plane-mcp-server http` | | Local Stdio | Local dev, self-hosted Plane | Environment variables | `plane-mcp-server stdio` | | SSE (Legacy) | Existing integrations | Browser-based OAuth flow | `plane-mcp-server http` | ## Authentication model The server has three authentication mechanisms, one per transport variant. ### OAuth auth (HTTP with OAuth, SSE) For cloud deployments, the server acts as an **OAuth proxy** to Plane's OAuth system: 1. The MCP client redirects the user to the Plane OAuth authorization page 2. The user logs into Plane and grants access 3. Plane returns an OAuth token which the server validates by calling `/api/v1/users/me/` 4. Subsequent MCP requests carry this token, from which the server extracts the workspace slug The server supports OAuth redirect URIs for all major MCP clients: `cursor://`, `vscode://`, `vscode-insiders://`, `windsurf://`, `claude://` ### Header auth (HTTP with PAT Token) For automated workflows, the MCP client sends two headers with every request: * `x-api-key` - a Plane API token * `x-workspace-slug` - the workspace identifier The server validates the API key against Plane's `/api/v1/users/me/` endpoint on each request. No browser interaction required. ### Environment variable auth (stdio) For stdio mode, credentials are read from environment variables at startup: * `PLANE_API_KEY` - your Plane API token * `PLANE_WORKSPACE_SLUG` - your workspace identifier * `PLANE_BASE_URL` - API URL for self-hosted instances (defaults to `https://api.plane.so`) ## Identifier system Plane uses two kinds of identifiers for work items. * **Readable identifier** - human-friendly, e.g., `ENG-42` * Composed of the project identifier (`ENG`) and a sequence number (`42`) * Used in URLs, UI, and team communication * **UUID** - machine-friendly, e.g., `3fa85f64-5717-4562-b3fc-2c963f66afa6` * Used by all other tools for `project_id`, `work_item_id`, `cycle_id`, etc. * Returned by every API response *** ## How-to guides Plane hosts the MCP server for you at **`https://mcp.plane.so`**. If you run your own instance of the MCP server, replace `https://mcp.plane.so` with your own server's public URL (e.g., `https://mcp.yourcompany.com`) in all client config examples below. ### Prerequisites **For all modes:** * A Plane account with access to at least one workspace **For stdio mode (local and self-hosted deployments)** * Python 3.10+ installed (`python --version`) * `uv` package manager (recommended). See [Installing uv](https://docs.astral.sh/uv/getting-started/installation/) #### Get your API key (required for stdio and PAT token modes) 1. Open Plane and go to your workspace. 2. Generate a token. You can use either: * **Personal Access Token** - go to **Profile Settings → API Tokens**. * **Workspace Access Token** - go to **Workspace Settings → Access Tokens**. 3. Click **Add access token**, name it (e.g., "MCP Server"), click **Generate token**. 4. Copy the token as it will not be shown again. #### Get your workspace slug The slug is the short identifier in your Plane URL. For: ``` https://app.plane.so/acme-corp/ ``` the slug is `acme-corp`. ### Claude Desktop Config file: `/claude_desktop_config.json`. Quit Claude Desktop before editing, then relaunch and click the hammer icon (🔨) to confirm Plane tools are listed. #### Stdio Spawns the server as a local subprocess. Credentials come from environment variables. ```json { "mcpServers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug", "PLANE_BASE_URL": "https://plane.yourcompany.com" } } } } ``` #### HTTP with OAuth Connects to a remote MCP server. Claude Desktop opens a browser window for the Plane OAuth flow on first use. ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/mcp", "type": "http" } } } ``` #### HTTP with PAT Token Connects to the PAT endpoint using API key headers. No browser interaction required - suitable for shared team setups where users authenticate via their own API key. ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } } } ``` #### SSE (Legacy) For existing integrations already using the SSE transport. ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/sse", "type": "sse" } } } ``` *** ### Claude Code (CLI) Claude Code manages MCP servers via `claude mcp add` or a settings file. #### Stdio ```bash claude mcp add plane \ -e PLANE_API_KEY=your_api_key_here \ -e PLANE_WORKSPACE_SLUG=your-workspace-slug \ -e PLANE_BASE_URL=https://plane.yourcompany.com \ -- uvx plane-mcp-server stdio ``` Settings file (`.claude/settings.json` for project scope, `~/.claude/settings.json` for user scope): ```json { "mcpServers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug" } } } } ``` #### HTTP with OAuth ```bash claude mcp add plane \ --transport http \ --url https://mcp.plane.so/http/mcp ``` Claude Code will open a browser for the Plane OAuth flow. Settings file equivalent: ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/mcp", "type": "http" } } } ``` #### HTTP with PAT Token ```bash claude mcp add plane \ --transport http \ --url https://mcp.plane.so/http/api-key/mcp \ --header "x-api-key: your_api_key_here" \ --header "x-workspace-slug: your-workspace-slug" ``` Settings file equivalent: ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } } } ``` #### SSE (Legacy) ```bash claude mcp add plane \ --transport sse \ --url https://mcp.plane.so/sse ``` Settings file equivalent: ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/sse", "type": "sse" } } } ``` Verify any configuration with: ```bash claude mcp list ``` #### Using Plane in Claude Code sessions ```bash claude > Look up work item ENG-42 and implement what it describes. > After fixing the bug, mark ENG-42 as done and log 90 minutes of work. > Create work items for each TODO in src/auth.ts and add them to the current sprint. ``` *** ### Claude.ai / Claude Chat (Web) Claude.ai supports remote MCP servers for eligible plans. Because it runs in a browser it cannot spawn local processes, stdio is not available here. #### HTTP with OAuth 1. Go to \*\*Customize → Connectors \*\* in Claude.ai. 2. Click **Add custom connector**. 3. Enter the server URL: `https://mcp.plane.so/http/mcp` 4. Claude.ai redirects you through the Plane OAuth flow. #### HTTP with PAT Token If your Claude.ai plan supports custom headers in integrations: * URL: `https://mcp.plane.so/http/api-key/mcp` * Headers: `x-api-key: your_api_key_here`, `x-workspace-slug: your-workspace-slug` #### SSE (Legacy) * URL: `https://mcp.plane.so/sse` *** ### Cursor Config file: `~/.cursor/mcp.json` Open Cursor → **Settings** → search **MCP** → open the config file. Restart Cursor (`Cmd/Ctrl + Shift + P → Reload Window`) after saving. #### Stdio ```json { "mcpServers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug", "PLANE_BASE_URL": "https://plane.yourcompany.com" } } } } ``` #### HTTP with OAuth The `cursor://` redirect URI is registered natively in the OAuth provider. ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/mcp", "type": "http" } } } ``` #### HTTP with PAT Token ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } } } ``` #### SSE (Legacy) ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/sse", "type": "sse" } } } ``` *** ### VS Code VS Code supports MCP through GitHub Copilot (requires a Copilot subscription). Open the Copilot chat panel (`Ctrl+Alt+I`), switch to **Agent** mode. The `vscode://` and `vscode-insiders://` redirect URIs are registered in the OAuth provider. Config can be set at workspace level (`.vscode/mcp.json`) or user level (VS Code `settings.json` under the `"mcp"` key). Examples below use `.vscode/mcp.json`. #### Stdio ```json { "servers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug", "PLANE_BASE_URL": "https://plane.yourcompany.com" } } } } ``` #### HTTP with OAuth ```json { "servers": { "plane": { "url": "https://mcp.plane.so/http/mcp", "type": "http" } } } ``` #### HTTP with PAT Token ```json { "servers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } } } ``` #### SSE (Legacy) ```json { "servers": { "plane": { "url": "https://mcp.plane.so/sse", "type": "sse" } } } ``` *** ### Windsurf Config file: `~/.codeium/windsurf/mcp_config.json` Restart Windsurf after saving, then open the Cascade panel. The `windsurf://` redirect URI is registered in the OAuth provider. #### Stdio ```json { "mcpServers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug", "PLANE_BASE_URL": "https://plane.yourcompany.com" } } } } ``` #### HTTP with OAuth ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/mcp", "type": "http" } } } ``` #### HTTP with PAT Token ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } } } ``` #### SSE (Legacy) ```json { "mcpServers": { "plane": { "url": "https://mcp.plane.so/sse", "type": "sse" } } } ``` *** ### Zed Config file: `~/.config/zed/settings.json` under `"context_servers"`. Zed uses a different schema from other clients - the stdio command goes inside a `"command"` object with `"path"` instead of `"command"`. #### Stdio ```json { "context_servers": { "plane-mcp-server": { "command": { "path": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "your_api_key_here", "PLANE_WORKSPACE_SLUG": "your-workspace-slug", "PLANE_BASE_URL": "https://plane.yourcompany.com" } }, "settings": {} } } } ``` #### HTTP with OAuth ```json { "context_servers": { "plane-mcp-server": { "url": "https://mcp.plane.so/http/mcp", "settings": {} } } } ``` #### HTTP with PAT Token ```json { "context_servers": { "plane-mcp-server": { "url": "https://mcp.plane.so/http/api-key/mcp", "headers": { "x-api-key": "your_api_key_here", "x-workspace-slug": "your-workspace-slug" }, "settings": {} } } } ``` #### SSE (Legacy) ```json { "context_servers": { "plane-mcp-server": { "url": "https://mcp.plane.so/sse", "settings": {} } } } ``` Open the AI panel (`Cmd + Shift + A`) to use Plane tools in conversation. ## Self-hosted Plane deployments Set `PLANE_BASE_URL` to the public URL of your Plane instance (e.g., https://plane.yourcompany.com). This is used for user-facing OAuth redirects and API calls in stdio mode. In HTTP/SSE mode, the server also makes internal server-to-server calls to Plane for token validation. If your infrastructure routes internal traffic differently from public traffic (e.g., via a private network, service mesh, or internal load balancer), set `PLANE_INTERNAL_BASE_URL` to the internal address. When set, all server-to-server calls use this URL and only OAuth redirects use `PLANE_BASE_URL`. If `PLANE_INTERNAL_BASE_URL` is not set, it falls back to PLANE\_BASE\_URL for all calls. Before connecting a client, verify your credentials reach the instance: ```bash curl -H "x-api-key: YOUR_API_KEY" \ "https://plane.yourcompany.com/api/v1/users/me/" ``` A `200` response confirms the API key and URL are correct. *** ## Tool reference The server exposes 100+ tools across 20 modules. All tools are registered identically regardless of transport mode. The same tools are available via stdio, HTTP/OAuth, HTTP/PAT, and SSE. ### Users #### `get_me` Returns the profile of the currently authenticated user. No parameters. *** ### Workspaces #### `get_workspace_members` Returns all members of the workspace. #### `get_workspace_features` Returns enabled features for the workspace. #### `update_workspace_features` Updates workspace-level feature flags. *** ### Projects #### `list_projects` Returns all projects the current user is a member of. #### `create_project` Creates a new project. | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------ | | `name` | string | **Yes** | Project display name | | `identifier` | string | **Yes** | Short uppercase code, max 12 chars (e.g., `ENG`) | | `description` | string | No | Project description | | `network` | string | No | `0` (secret) or `2` (public) | #### `retrieve_project` Returns details of a single project. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `update_project` Updates project fields. All fields are optional. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | other fields | partial | No | #### `delete_project` Deletes a project. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `get_project_worklog_summary` Returns time-tracking summary for a project. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `get_project_members` Returns all members of a project. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `get_project_features` Returns the feature configuration for a project (modules, cycles, pages, etc.). | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `update_project_features` Updates which features are enabled on a project. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | feature fields | partial | No | *** ### Work Items #### `list_work_items` Lists work items in a project, or searches across the workspace when filters are provided. When any filter parameter is set, the tool uses Plane's advanced search endpoint (supports workspace-wide search). Without filters it uses the standard paginated list endpoint. | Parameter | Type | Required | Description | | ------------------ | ----------- | ----------- | --------------------------------------------------------------- | | `project_id` | UUID string | Conditional | Required when no filters are provided | | `query` | string | No | Free-text search across name and description | | `assignee_ids` | UUID\[] | No | Filter by assignee | | `state_ids` | UUID\[] | No | Filter by state | | `state_groups` | string\[] | No | `backlog` · `unstarted` · `started` · `completed` · `cancelled` | | `priorities` | string\[] | No | `urgent` · `high` · `medium` · `low` · `none` | | `label_ids` | UUID\[] | No | Filter by label | | `type_ids` | UUID\[] | No | Filter by work item type | | `cycle_ids` | UUID\[] | No | Filter by cycle | | `module_ids` | UUID\[] | No | Filter by module | | `is_archived` | boolean | No | Filter by archived status | | `created_by_ids` | UUID\[] | No | Filter by creator | | `workspace_search` | boolean | No | Search across all projects (requires filters) | | `limit` | integer | No | Max results when using filters | | `cursor` | string | No | Pagination cursor (list mode) | | `per_page` | integer | No | Results per page, 1–100 (list mode) | | `expand` | string | No | Comma-separated fields to expand | | `fields` | string | No | Comma-separated fields to include | | `order_by` | string | No | Sort field | #### `create_work_item` Creates a new work item in a project. | Parameter | Type | Required | Description | | ------------------ | ----------- | -------- | --------------------------------------------- | | `project_id` | UUID string | **Yes** | Target project | | `name` | string | **Yes** | Work item title | | `description_html` | string | No | HTML body | | `state_id` | UUID string | No | Initial state | | `priority` | string | No | `urgent` · `high` · `medium` · `low` · `none` | | `assignee_ids` | UUID\[] | No | Assigned members | | `label_ids` | UUID\[] | No | Labels | | `type_id` | UUID string | No | Work item type | | `parent_id` | UUID string | No | Parent work item (sub-item) | | `start_date` | string | No | `YYYY-MM-DD` | | `due_date` | string | No | `YYYY-MM-DD` | #### `retrieve_work_item` Returns a single work item by UUID. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `retrieve_work_item_by_identifier` Returns a work item using its human-readable identifier (e.g., `ENG-42`). | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | --------------------------- | | `project_identifier` | string | **Yes** | Project prefix, e.g., `ENG` | | `work_item_identifier` | string | **Yes** | Issue number, e.g., `42` | #### `update_work_item` Updates one or more fields on a work item. Only supplied fields are changed. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | other fields | partial | No | #### `delete_work_item` Permanently deletes a work item. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `search_work_items` Searches work items by text query within a project. | Parameter | Type | Required | Description | | ------------ | ----------- | -------- | ----------- | | `project_id` | UUID string | **Yes** | | | `query` | string | **Yes** | Search text | *** ### Work Item Activities #### `list_work_item_activities` Returns the activity log (history of changes) for a work item. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `retrieve_work_item_activity` Returns a single activity entry. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `activity_id` | UUID string | **Yes** | *** ### Work Item Comments #### `list_work_item_comments` Returns all comments on a work item. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `retrieve_work_item_comment` Returns a single comment. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `comment_id` | UUID string | **Yes** | #### `create_work_item_comment` Adds a comment to a work item. Comments are stored as HTML. | Parameter | Type | Required | Description | | -------------- | ----------- | -------- | --------------------------------------------------- | | `project_id` | UUID string | **Yes** | | | `work_item_id` | UUID string | **Yes** | | | `comment_html` | string | **Yes** | HTML content, e.g., `

Fixed in commit abc123

` | #### `update_work_item_comment` Updates a comment's content. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `comment_id` | UUID string | **Yes** | | `comment_html` | string | **Yes** | #### `delete_work_item_comment` Deletes a comment. | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `comment_id` | UUID string | **Yes** | *** ### Work Item Links External URLs attached to a work item (e.g., Figma designs, PRs, docs). #### `list_work_item_links` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `retrieve_work_item_link` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `link_id` | UUID string | **Yes** | #### `create_work_item_link` | Parameter | Type | Required | Description | | -------------- | ----------- | -------- | -------------------------- | | `project_id` | UUID string | **Yes** | | | `work_item_id` | UUID string | **Yes** | | | `url` | string | **Yes** | External URL | | `title` | string | No | Display title for the link | #### `update_work_item_link` | Parameter | Type | Required | | --------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `link_id` | UUID string | **Yes** | | `url` / `title` | string | No | #### `delete_work_item_link` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `link_id` | UUID string | **Yes** | *** ### Work Item Relations Relations between work items (e.g., "blocks", "is blocked by", "duplicate of"). #### `list_work_item_relations` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `create_work_item_relation` | Parameter | Type | Required | Description | | ---------------------- | ----------- | -------- | ----------------------------------------------------------------------- | | `project_id` | UUID string | **Yes** | | | `work_item_id` | UUID string | **Yes** | Source work item | | `related_work_item_id` | UUID string | **Yes** | Target work item | | `relation_type` | string | **Yes** | `blocking` · `blocked_by` · `duplicate_of` · `duplicate` · `relates_to` | #### `remove_work_item_relation` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `relation_id` | UUID string | **Yes** | *** ### Work Item Properties Custom fields defined per project. #### `list_work_item_properties` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `create_work_item_property` | Parameter | Type | Required | Description | | --------------- | ----------- | -------- | ------------------------ | | `project_id` | UUID string | **Yes** | | | `name` | string | **Yes** | Property name | | `property_type` | string | **Yes** | Type of the custom field | #### `retrieve_work_item_property` | Parameter | Type | Required | | ------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `property_id` | UUID string | **Yes** | #### `update_work_item_property` | Parameter | Type | Required | | ------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `property_id` | UUID string | **Yes** | #### `delete_work_item_property` | Parameter | Type | Required | | ------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `property_id` | UUID string | **Yes** | *** ### Work Item Types Custom work item type definitions (e.g., Bug, Feature, Task, Epic). #### `list_work_item_types` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `create_work_item_type` | Parameter | Type | Required | | ------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `name` | string | **Yes** | | `description` | string | No | | `is_active` | boolean | No | #### `retrieve_work_item_type` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `type_id` | UUID string | **Yes** | #### `update_work_item_type` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `type_id` | UUID string | **Yes** | #### `delete_work_item_type` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `type_id` | UUID string | **Yes** | *** ### Worklogs Time tracking for work items. All durations are in **minutes**. #### `list_work_logs` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `create_work_log` | Parameter | Type | Required | Description | | -------------- | ----------- | -------- | -------------------- | | `project_id` | UUID string | **Yes** | | | `work_item_id` | UUID string | **Yes** | | | `duration` | integer | **Yes** | Minutes logged (≥ 0) | | `description` | string | No | What was done | #### `update_work_log` | Parameter | Type | Required | | -------------------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `work_log_id` | UUID string | **Yes** | | `duration` / `description` | - | No | #### `delete_work_log` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | | `work_log_id` | UUID string | **Yes** | *** ### States Workflow states for a project's work items. #### `list_states` / `create_state` / `retrieve_state` / `update_state` / `delete_state` All state tools accept `project_id`. Create and update accept: | Field | Type | Required | Description | | ------------- | ------ | ---------------- | --------------------------------------------------------------- | | `name` | string | **Yes** (create) | Display name | | `color` | string | **Yes** (create) | Hex color code, e.g., `#FF5733` | | `group` | string | **Yes** (create) | `backlog` · `unstarted` · `started` · `completed` · `cancelled` | | `description` | string | No | | *** ### Labels Tags for work items. #### `list_labels` / `create_label` / `retrieve_label` / `update_label` / `delete_label` All label tools accept `project_id`. Create and update accept: | Field | Type | Required | | -------- | ----------- | ---------------- | | `name` | string | **Yes** (create) | | `color` | string | **Yes** (create) | | `parent` | UUID string | No | *** ### Cycles Time-boxed iterations (sprints). #### `list_cycles` Returns all cycles in a project including upcoming, active, and completed. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `list_archived_cycles` Returns archived cycles only. | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `create_cycle` | Parameter | Type | Required | Description | | ------------- | ----------- | -------- | ------------ | | `project_id` | UUID string | **Yes** | | | `name` | string | **Yes** | Cycle name | | `start_date` | string | No | `YYYY-MM-DD` | | `end_date` | string | No | `YYYY-MM-DD` | | `description` | string | No | | #### `retrieve_cycle` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `cycle_id` | UUID string | **Yes** | #### `update_cycle` / `delete_cycle` Accept `project_id` and `cycle_id`. #### `add_work_items_to_cycle` | Parameter | Type | Required | | --------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `cycle_id` | UUID string | **Yes** | | `work_item_ids` | UUID\[] | **Yes** | #### `remove_work_item_from_cycle` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `cycle_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `list_cycle_work_items` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `cycle_id` | UUID string | **Yes** | #### `transfer_cycle_work_items` Moves all incomplete work items from one cycle to another. | Parameter | Type | Required | Description | | -------------- | ----------- | -------- | ------------ | | `project_id` | UUID string | **Yes** | | | `cycle_id` | UUID string | **Yes** | Source cycle | | `new_cycle_id` | UUID string | **Yes** | Target cycle | *** ### Modules Feature groupings within a project. #### `list_modules` / `list_archived_modules` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `create_module` | Parameter | Type | Required | | ------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `name` | string | **Yes** | | `description` | string | No | | `start_date` | string | No | | `target_date` | string | No | | `lead` | UUID string | No | | `members` | UUID\[] | No | #### `retrieve_module` / `update_module` / `delete_module` / `archive_module` Accept `project_id` and `module_id`. #### `add_work_items_to_module` | Parameter | Type | Required | | --------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `module_id` | UUID string | **Yes** | | `work_item_ids` | UUID\[] | **Yes** | #### `remove_work_item_from_module` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `module_id` | UUID string | **Yes** | | `work_item_id` | UUID string | **Yes** | #### `list_module_work_items` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `module_id` | UUID string | **Yes** | *** ### Epics Large work items that group related items. The server resolves the Epic work item type automatically. #### `list_epics` / `create_epic` / `retrieve_epic` / `update_epic` / `delete_epic` | Parameter | Type | Required | | ----------------------- | ----------- | ------------------- | | `project_id` | UUID string | **Yes** | | `epic_id` | UUID string | Varies by operation | | name, description, etc. | - | Varies | *** ### Milestones Point-in-time goals within a project. #### `list_milestones` / `create_milestone` / `retrieve_milestone` / `update_milestone` / `delete_milestone` | Parameter | Type | Required | | -------------- | ----------- | ---------------- | | `project_id` | UUID string | **Yes** | | `milestone_id` | UUID string | Varies | | `name` | string | **Yes** (create) | #### `add_work_items_to_milestone` | Parameter | Type | Required | | --------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `milestone_id` | UUID string | **Yes** | | `work_item_ids` | UUID\[] | **Yes** | #### `remove_work_items_from_milestone` | Parameter | Type | Required | | --------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `milestone_id` | UUID string | **Yes** | | `work_item_ids` | UUID\[] | **Yes** | #### `list_milestone_work_items` | Parameter | Type | Required | | -------------- | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `milestone_id` | UUID string | **Yes** | *** ### Initiatives Workspace-scoped strategic goals that span multiple projects. #### `list_initiatives` / `create_initiative` / `retrieve_initiative` / `update_initiative` / `delete_initiative` Initiatives are workspace-scoped - no `project_id` required. `retrieve_initiative`, `update_initiative`, and `delete_initiative` accept an `initiative_id` UUID. *** ### Intake Triage queue for incoming work items before they enter a project. #### `list_intake_work_items` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | #### `create_intake_work_item` | Parameter | Type | Required | | ------------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `name` | string | **Yes** | | `description_html` | string | No | #### `retrieve_intake_work_item` / `update_intake_work_item` / `delete_intake_work_item` Accept `project_id` and `work_item_id`. *** ### Pages Wiki-style documents. Pages can be workspace-scoped or project-scoped. #### `retrieve_workspace_page` | Parameter | Type | Required | | --------- | ----------- | -------- | | `page_id` | UUID string | **Yes** | #### `retrieve_project_page` | Parameter | Type | Required | | ------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `page_id` | UUID string | **Yes** | #### `create_workspace_page` | Parameter | Type | Required | | ------------------ | ------ | -------- | | `name` | string | **Yes** | | `description_html` | string | No | #### `create_project_page` | Parameter | Type | Required | | ------------------ | ----------- | -------- | | `project_id` | UUID string | **Yes** | | `name` | string | **Yes** | | `description_html` | string | No | *** ## Common workflows ### Look up a work item by ID ``` What is work item ENG-42 about? ``` Model calls `retrieve_work_item_by_identifier` with `project_identifier="ENG"` and `work_item_identifier="42"`. ### Create a work item ``` Create a high-priority bug in the ENG project called "Login times out on Safari". Description: The OAuth callback redirects to a blank page on Safari 17+. Assign it to me. ``` Model calls `list_projects` → `retrieve_work_item_by_identifier` (or `get_me` to resolve "me") → `create_work_item`. ### Update work item state ``` Mark ENG-88 as done and add a comment: "Fixed in commit abc1234, needs QA." ``` Model resolves the UUID, calls `list_states` to find the Done state UUID, calls `update_work_item` and `create_work_item_comment`. ### Sprint planning ``` Create a cycle called "Sprint 15" in ENG starting 2025-06-02, ending 2025-06-15. Then move all incomplete issues from Sprint 14 into it. ``` Model calls `create_cycle` then `list_cycles` to find Sprint 14's UUID, then `transfer_cycle_work_items`. ### Log time ``` Log 90 minutes on ENG-42: "Implemented retry logic for the upload endpoint." ``` ### Search across the workspace ``` Show me all high-priority bugs assigned to me that are still in progress. ``` Model calls `list_work_items` with filters `priorities=["high"]`, `state_groups=["started"]`, and the current user's UUID as `assignee_ids`. ### Manage a module ``` Add ENG-55, ENG-56, and ENG-57 to the "Checkout Redesign" module. ``` Model calls `list_modules` to find the UUID, then `add_work_items_to_module` with the resolved work item UUIDs. *** ## Troubleshooting The server propagates errors from the Plane SDK as MCP tool errors. | Scenario | HTTP Status | Cause | Resolution | | --------------------------------- | ----------- | --------------------------------------- | --------------------------------------------------- | | Invalid API key | 401 | `PLANE_API_KEY` is wrong or revoked | Regenerate the token in Plane settings | | Invalid OAuth token | 401 | Token expired or revoked | Re-authorise through OAuth flow | | Missing `x-workspace-slug` header | - | Header auth missing workspace | Include `x-workspace-slug` header | | Wrong workspace slug | 404 | Slug doesn't exist | Check the exact slug in your Plane URL | | Insufficient permissions | 403 | User role too low | Check your role in the workspace/project | | Resource not found | 404 | UUID or identifier doesn't exist | Verify the ID; check if resource was deleted | | Validation error | 400 | Required field missing or invalid value | Check required fields and value constraints | | Redis unavailable | - | Token storage down | Set `REDIS_HOST`/`REDIS_PORT` or omit for in-memory | | Network error | - | Cannot reach Plane API | Verify `PLANE_BASE_URL` and connectivity | **Verify connectivity (stdio/PAT):** ```bash curl -H "x-api-key: YOUR_KEY" \ "https://api.plane.so/api/v1/users/me/" ``` **Run stdio mode manually to debug startup:** ```bash PLANE_API_KEY=your_key PLANE_WORKSPACE_SLUG=your-slug plane-mcp-server stdio ``` **Test the HTTP server is running:** ```bash curl http://localhost:8211/http/mcp # Should return MCP protocol response or 401 ``` *** *Plane MCP Server is open source and licensed under MIT. Source at [github.com/makeplane/plane-mcp-server](https://github.com/makeplane/plane-mcp-server).* --- --- url: 'https://developers.plane.so/dev-tools/agents/overview.html' description: >- Learn how to build AI agents that integrate with Plane workspaces, enabling automated task handling, intelligent responses, and seamless collaboration. --- # Agents overview ::: info Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. ::: ## What are agents in Plane? Agents in Plane are AI-powered applications that can interact with your workspace similar to how human users do. They can be @mentioned in work item comments, receive prompts from users, and respond with intelligent actions. Agents enable automation and AI assistance directly within your project management workflow. Key capabilities of Plane agents: * **Mentionable** — Users can @mention agents in work item comments to trigger interactions * **Contextual awareness** — Agents receive full context about the work item, project, and conversation * **Activity tracking** — All agent interactions are tracked through the Agent Run system * **Real-time responses** — Agents can send thoughts, actions, and responses back to users ## Agent installation Agents are installed as OAuth applications in your Plane workspace. When you create an OAuth app with the **Enable App Mentions** option enabled, it becomes an agent that users can mention and interact with. ### Installation flow 1. A workspace admin installs your agent via the OAuth consent flow 2. Plane creates a **bot user** for your agent in that workspace 3. The bot user ID is returned in the app installation details 4. Users can now @mention your agent in work item comments When installed, agents appear in the mention picker alongside regular workspace members. Users can tag them in comments just like any team member. ::: info Agents installed in your workspace do not count as billable users. ::: ## Agent Run lifecycle The Agent Run system tracks the complete lifecycle of an agent interaction, from when a user mentions the agent to when the agent completes its task. ### What is an Agent Run? An **Agent Run** represents a single interaction session between a user and an agent. When a user @mentions an agent in a comment, Plane automatically creates an Agent Run to track: * The triggering comment and work item * The conversation thread between user and agent * All activities (thoughts, actions, responses) from the agent * The current status of the interaction ### What is an Agent Run Activity? An **Agent Run Activity** is a single unit of communication within an Agent Run. Activities can be: * **Prompt** — A message from a user to the agent * **Thought** — Internal reasoning from the agent (ephemeral) * **Action** — A tool invocation by the agent (ephemeral) * **Response** — A final response from the agent (creates a comment) * **Elicitation** — A question from the agent requesting user input * **Error** — An error message from the agent ### How Agent Run works The Agent Run flow consists of three main phases: ```mermaid sequenceDiagram participant User participant Plane participant Agent User->>Plane: @mentions agent in comment Plane->>Plane: Create AgentRun + Activity (prompt) Plane->>Agent: Send webhook (agent_run_activity) loop Agent Processing Agent->>Plane: Create Activity (thought/action) Agent->>Agent: Process and reason end Agent->>Plane: Create Activity (response) Plane->>User: Show response as comment ``` #### Phase 1: Trigger 1. User @mentions the agent in a work item comment 2. Plane detects the mention and creates a new **Agent Run** 3. An **Agent Run Activity** is created with the user's prompt 4. The Agent Run status is set to `created` #### Phase 2: Webhook 1. Plane triggers a webhook to your agent's webhook URL 2. The webhook payload includes: * The Agent Run details * The triggering activity (user prompt) * Work item and project context * Workspace information #### Phase 3: Agent response 1. Your agent processes the webhook and starts working 2. The agent sends activities back to Plane via the API: * `thought` activities to show reasoning (ephemeral, don't create comments) * `action` activities to show tool usage (ephemeral) * `response` or `elicitation` when complete (creates a comment) 3. Plane updates the Agent Run status based on activities 4. Non-ephemeral activities (response, elicitation) create comment replies visible to users ### Agent Run states Agent Runs transition through various states based on activities: | Status | Description | | ------------- | ----------------------------------------------------------------- | | `created` | The run has been initiated but not yet started processing | | `in_progress` | The agent is actively processing the request | | `awaiting` | The agent is waiting for additional input from the user | | `completed` | The agent has successfully finished processing | | `stopping` | A stop request has been received and is being processed | | `stopped` | The run has been successfully stopped | | `failed` | The run encountered an error and cannot continue | | `stale` | The run has not been updated in 5 minutes and is considered stale | ### Continuing a conversation When a user replies to an agent's response: 1. If an active Agent Run exists for that thread, the reply is added as a new activity 2. The webhook is triggered again with the updated context 3. The agent can continue the conversation with full history This enables multi-turn conversations where users and agents can have back-and-forth interactions. ## Next steps Ready to build your own agent? Continue to [Building an Agent](/dev-tools/agents/building-an-agent) to learn how to create and deploy your first Plane agent. --- --- url: 'https://developers.plane.so/self-hosting/editions-and-versions.html' description: >- Compare Plane Community, Pro, Business, and Enterprise editions. Understand features, pricing tiers, and version differences for self-hosted deployments. --- # Understanding Plane's editions Plane comes in four editions by how its deployed. Our Cloud is our only hosted edition as of 2025. Additionally, we offer three unique self-hosted editions tailored to meet two sets of unique needs—the open-source Community Edition, the recommended Commercial Edition, and the Airgapped Edition. ## About our self-hosted editions ### Community Built with transparency in mind, the Community Edition, * Is governed by the AGPL v3.0 license, ensuring free and open usage * Allows contribution to the repo by way of modifications and customizations * Has no code dependencies or restrictions on and from the Commercial Edition It’s ideal for those who want to try Plane first, audit the code for security, and see how each one of services works with the others. Several tens of thousands of uses have used it and a significant number have contributed to it. The Community Edition is at par with the Free tier of the Cloud edition in its feature availability. To upgrade to paid plans, you must first switch to the Commercial Edition. ### Commercial Designed for teams that want governance, compliance, and privacy controls, the Commercial Edition is ideal for teams that want to try Plane with an intent to unlock advanced work management and security features. This edition also comes with a Free tier, but also lets you upgrade seamlessly to all our paid plans. It offers, * Full feature parity with our Cloud * A bundle of 12 Free user seats per workspace so there are no surprises when you upgrade * An intuitive upgrade flow that automatically calculates the number of seats you need by the number of users with paid roles in your workspace, so you never have to guess ### Airgapped Built for organizations with strict security and compliance requirements, the Airgapped Commercial Edition provides the same powerful features as the Commercial Edition but operates in completely isolated environments without internet connectivity. The Airgapped Edition offers: * **Complete isolation** Operates entirely within your network perimeter with no external dependencies or outbound connections. * **Full feature parity** Includes all features available in the standard Commercial Edition, including advanced work management, security controls, and governance tools * **Version updates** Updates from your own docker registry. * **Self-contained architecture** All services, dependencies, and resources are bundled for deployment in restricted networks * **Compliance-ready** Designed to meet requirements for environments that prohibit external network communication ## Why we separate editions We’ve designed Plane’s editions to serve diverse user needs while staying true to the ethos of open source. * The **Community Edition** is completely open-source, with no restrictions beyond those outlined in the [AGPL v3.0 license](https://github.com/makeplane/plane/blob/preview/LICENSE.txt). This is the edition that is now ranking at #1 in our space on GitHub. * The **Commercial Edition** remains closed-source to offer enterprise-grade features and seamless scalability for businesses. * The **Airgapped Edition** extends the Commercial Edition's capabilities to isolated environments, ensuring organizations with strict security requirements can still benefit from Plane's full feature set. Unlike some open-core companies, we’ve adopted a clean separation to keep things simple and transparent. There’s no hidden code that limits modifications on the Community Edition, and no forced migrations from one edition to another. ## Differences in versions between editions Each of our editions is built on a distinct codebase. Versions with each differ for how we ship new code per our three separate release cycles. This distinction allows us to * Use the Cloud as a test bed for new features before they come to our self-hosted editions * Innovate quickly on a more controlled Commercial Edition * Be intentful and deliberate with changes to the Community Edition For both the Commercial, Airgapped, and Community Editions, version updates are in your control. Regular updates ensure you’re benefiting from the latest features and improvements. See [Update Plane](/self-hosting/manage/upgrade-plane) for how to upgrade your versions. ## Changelog We maintain a detailed changelog for all editions. [Check it out](https://plane.so/changelog) and bookmark it to stay informed about the latest features, bug fixes, and improvements by edition. --- --- url: 'https://developers.plane.so/self-hosting/plane-architecture.html' description: >- Understand Plane self-hosted architecture including web server, API, workers, database, Redis, and storage components. System requirements and service topology. --- # Plane self-hosted architecture Plane consists of multiple services working together to provide project management capabilities. ![Plane architecture](/images/airgapped/plane-architecture.webp#hero) ### Frontend services **Web**\ The main application interface where users interact with projects, work items, and pages. This service serves the UI and handles client-side routing. **Space**\ This powers public sharing. It lets you publish projects, views, pages to the web, so others can view without needing to log in. **Admin**\ Instance administration interface for workspace owners and administrators. Manages billing, licensing, workspace settings, and user permissions. ### API server **API**\ The core REST API that handles all data operations. All frontend services communicate with this API for creating, reading, updating, and deleting data. **Worker**\ Background job processor that handles async operations like file processing, notification dispatch, and data imports. Workers pull jobs from RabbitMQ and execute them independently. **Beat worker**\ Scheduled task executor that runs periodic jobs like data cleanup, report generation, and reminder notifications. Uses a cron-like scheduling system. **Migrator**\ Database schema management service that runs on deployment to apply schema changes and data migrations. Runs once during upgrades then exits. ### Supporting services **Proxy**\ Handles incoming traffic and routes it to the appropriate services. Manages certificates and reverse proxying. In Docker deployments, Plane uses Caddy for automatic SSL certificate management and traffic routing. **Live**\ Real-time collaboration service powered by WebSockets. Handles cursor positions, live updates, and presence indicators for multiple users working simultaneously. **Monitor**\ Used for license validation and activation. It checks the license status and ensures your instance is compliant. **Silo**\ Integration backend that manages connections to GitHub, GitLab, and Slack. Handles OAuth flows, webhook processing, and API communication with external systems. **Intake**\ Email ingestion service that converts incoming emails into work items or comments. Requires SMTP configuration and DNS setup. ### Infrastructure dependencies **PostgreSQL**\ Primary relational database storing all application data including projects, work items, users, and configuration. Plane requires PostgreSQL 15.7+ or 16.x. **Redis/Valkey**\ In-memory cache and session store. Used for caching frequently accessed data, storing user sessions, and managing real-time collaboration state. **RabbitMQ**\ Message queue for asynchronous task processing. Workers pull jobs from queues for background operations like imports, exports, and notifications. **MinIO/S3**\ Object storage for file uploads, attachments, and generated exports; can be replaced with any S3-compatible storage system. **OpenSearch**\ Optional search indexing service for enhanced search capabilities. Not required for basic Plane functionality. --- --- url: 'https://developers.plane.so/self-hosting/methods/overview.html' description: >- Choose the best deployment method for your infrastructure. Deploy Plane with Docker, Kubernetes, Podman, or in airgapped environments. --- # Install Plane Choose a deployment method based on your infrastructure and requirements. ## System requirements * **CPU:** 2 cores (x64/AMD64 or AArch64/ARM64) * **RAM:** 4GB (8GB recommended for production) * **OS:** Ubuntu, Debian, CentOS, Amazon Linux 2 or 2023, macOS, Windows with WSL2 ## Deployment methods Plane supports a wide range of deployment options from simple single-container setups to enterprise-grade Kubernetes clusters. ### Container deployments Core deployment methods for running Plane with containerized services: ### Platform deployments Deploy Plane using specialized platforms and orchestration tools: ### Airgapped deployments For environments without internet access or with strict security requirements: --- --- url: 'https://developers.plane.so/self-hosting/methods/docker-aio.html' description: >- Deploy Plane with Docker All-in-One (AIO) setup. Quick installation guide for running Plane in a single Docker container. --- # Docker AIO (All-in-One) The Plane Commercial All-in-One (AIO) Docker image packages all Plane services into a single container, making it the fastest way to get Plane running. ## What's included Your single AIO container includes all these services running together: * **Web App** - The main Plane web interface you'll use * **Space** - Public project spaces for external collaboration * **Admin** - Administrative interface * **API Server** - Backend API * **Live Server** - Real-time collaboration features * **Silo** - Integration services * **Monitor** - Feature flags and payments * **Email Server** - SMTP server for notifications * **Proxy** (Port 80, 20025, 20465, 20587) - Caddy reverse proxy * **Worker and Beat Worker** - Background task processing ### Port Mapping The following ports are exposed: * `80`: Main web interface (HTTP) * `443`: HTTPS (if SSL configured) * `20025`: SMTP port 25 * `20465`: SMTP port 465 (SSL/TLS) * `20587`: SMTP port 587 (STARTTLS) ## Prerequisites * [Docker](https://docs.docker.com/engine/) * Set up these external services: * *PostgreSQL*\ For data storage * *Redis*\ For caching and session management * *RabbitMQ* For message queuing * *S3-compatible storage*\ For file uploads (AWS S3 or MinIO) ## Install Plane 1. Download the image with: ```bash docker pull artifacts.plane.so/makeplane/plane-aio-commercial:stable ``` 2. Run the following command to deploy the Plane AIO container. Make sure to replace all placeholder values (e.g., `your-domain.com`, `user:pass`) with your actual configuration. ::: warning All environment variables are required for the container to function correctly. ::: ```bash docker run --name plane-aio --rm -it \ -p 80:80 \ -p 20025:20025 \ -p 20465:20465 \ -p 20587:20587 \ -e DOMAIN_NAME=your-domain.com \ -e DATABASE_URL=postgresql://user:pass@host:port/database \ -e REDIS_URL=redis://host:port \ -e AMQP_URL=amqp://user:pass@host:port/vhost \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=your-access-key \ -e AWS_SECRET_ACCESS_KEY=your-secret-key \ -e AWS_S3_BUCKET_NAME=your-bucket \ artifacts.plane.so/makeplane/plane-aio-commercial:stable ``` If you're running on an IP address, use this example: ```bash MYIP=192.168.68.169 docker run --name myaio --rm -it \ -p 80:80 \ -p 20025:20025 \ -p 20465:20465 \ -p 20587:20587 \ -e DOMAIN_NAME=${MYIP} \ -e DATABASE_URL=postgresql://plane:plane@${MYIP}:15432/plane \ -e REDIS_URL=redis://${MYIP}:16379 \ -e AMQP_URL=amqp://plane:plane@${MYIP}:15673/plane \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID= \ -e AWS_SECRET_ACCESS_KEY= \ -e AWS_S3_BUCKET_NAME=plane-app \ -e AWS_S3_ENDPOINT_URL=http://${MYIP}:19000 \ -e FILE_SIZE_LIMIT=10485760 \ artifacts.plane.so/makeplane/plane-aio-commercial:stable ``` 3. Once it's running, you can access the Plane application on the domain you provided during the deployment. 4. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. ## Volume mounts ### Recommended persistent volumes ```bash -v /path/to/logs:/app/logs \ -v /path/to/data:/app/data ``` ### Workspace license DB ```bash -v /path/to/monitordb:/app/monitor ``` ### SSL certificate support For HTTPS support, mount certificates: ```bash -v /path/to/certs:/app/email/tls ``` ## Environment variables (optional) ### Network and Protocol * `SITE_ADDRESS`: Server bind address (default: `:80`) * `APP_PROTOCOL`: Protocol to use (`http` or `https`, default: `http`) ### Email configuration * `INTAKE_EMAIL_DOMAIN`: Domain for intake emails (default: `intake.`) * `LISTEN_SMTP_PORT_25`: SMTP port 25 mapping (default: `20025`) * `LISTEN_SMTP_PORT_465`: SMTP port 465 mapping (default: `20465`) * `LISTEN_SMTP_PORT_587`: SMTP port 587 mapping (default: `20587`) * `SMTP_DOMAIN`: SMTP server domain (default: `0.0.0.0`) * `TLS_CERT_PATH`: Path to TLS certificate file (optional) * `TLS_PRIV_KEY_PATH`: Path to TLS private key file (optional) ### Security and secrets * `MACHINE_SIGNATURE`: Unique machine identifier (auto-generated if not provided) * `SECRET_KEY`: Django secret key (default provided) * `SILO_HMAC_SECRET_KEY`: Silo HMAC secret (default provided) * `AES_SECRET_KEY`: AES encryption key (default provided) * `LIVE_SERVER_SECRET_KEY`: Live server secret (default provided) ### File handling * `FILE_SIZE_LIMIT`: Maximum file upload size in bytes (default: `5242880` = 5MB) ### Integration callbacks * `INTEGRATION_CALLBACK_BASE_URL`: Base URL for OAuth callbacks ### API configuration * `API_KEY_RATE_LIMIT`: API key rate limit (default: `60/minute`) ### Third-party integrations * `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`: GitHub integration * `GITHUB_APP_NAME`, `GITHUB_APP_ID`, `GITHUB_PRIVATE_KEY`: GitHub App integration * `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`: Slack integration * `GITLAB_CLIENT_ID`, `GITLAB_CLIENT_SECRET`: GitLab integration ## Build the image To build the AIO image yourself: ```bash cd deploy/aio/commercial ./build.sh --release=v1.11.1 ``` Available build options: * `--release`: Plane version to build (required) * `--image-name`: Custom image name (default: `plane-aio-commercial`) ## Troubleshoot The container will validate required environment variables on startup and display helpful error messages if any are missing. ### Logs All service logs are available in `/app/logs/`: * Access logs: `/app/logs/access/` * Error logs: `/app/logs/error/` ### Health checks The container runs multiple services managed by Supervisor. Check service status: ```bash docker exec -it supervisorctl status ``` ## Production considerations * Use proper SSL certificates for HTTPS * Configure proper backup strategies for data * Monitor resource usage and scale accordingly * Use external load balancer for high availability * Regularly update to latest versions * Secure your environment variables and secrets --- --- url: 'https://developers.plane.so/self-hosting/methods/docker-swarm.html' description: >- Deploy Plane on Docker Swarm cluster. Guide for running Plane in a distributed Docker Swarm environment with high availability. --- # Deploy Plane with Docker Swarm This guide shows you the steps to deploy a self-hosted instance of the Plane Commercial Edition using Docker Swarm. ## Install Plane ### Prerequisites * Before you get started, make sure you have a Docker Swarm environment set up and ready to go. * Your setup should support either amd64 or arm64 architectures. ### Procedure 1. **Download the required deployment files** * `swarm-compose.yml` – Defines Plane's services and dependencies. ```bash curl -fsSL https://prime.plane.so/releases//swarm-compose.yml -o swarm-compose.yml ``` * `variables.env` – Stores environment variables for your deployment. ```bash curl -fsSL https://prime.plane.so/releases//variables.env -o plane.env ``` ::: warning The `` value should be v1.8.3 or higher. ::: 2. **Configure environment variables**\ Before deploying, edit the `variables.env` file in your preferred text editor and update the following values: * `DOMAIN_NAME` – (required) Your application's domain name. * `SITE_ADDRESS` – (required) The full domain name (FQDN) of your instance. * `MACHINE_SIGNATURE` – (required) A unique identifier for your machine. You can generate this by running below code in terminal: ```sh sed -i 's/MACHINE_SIGNATURE=.*/MACHINE_SIGNATURE='$(openssl rand -hex 16)'/' plane.env ``` * `CERT_EMAIL` – (optional) Email address for SSL certificate generation (only needed if you're setting up HTTPS). 3. **Configure external DB, Redis, and RabbitMQ** ::: warning When self-hosting Plane for production use, it is strongly recommended to configure external database and storage. This ensures that your data remains secure and accessible even if the local machine crashes or encounters hardware issues. Relying solely on local storage for these components increases the risk of data loss and service disruption. ::: * `DATABASE_URL` – Connection string for your external database. * `REDIS_URL` – Connection string for your external Redis instance. * `AMQP_URL` – Connection string for your external RabbitMQ server. 4. **Load the environment variables** ```bash set -o allexport; source ; set +o allexport; ``` 5. **Deploy the stack** ```bash docker stack deploy -c plane ``` That's it! This will deploy Plane as a Swarm stack, and your instance should be accessible on your configured domain. 6. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. --- --- url: 'https://developers.plane.so/self-hosting/methods/download-config.html' description: >- Download docker-compose.yml and variables.env files for a specific Plane release as a zip archive. --- # Download Docker config files If you're running a custom Docker setup and don't use `prime-cli`, you can download the `docker-compose.yml` and `variables.env` files for any Plane release directly. ## Endpoint ```bash curl "https://prime.plane.so/api/v2/setup/?version=&airgapped=&platform=" -o plane.zip ``` **Authentication:** None required (public endpoint) ## Parameters | Parameter | Required | Default | Description | | ----------- | -------- | ------- | ---------------------------------------------------------------------------- | | `version` | Yes | — | Release tag (e.g., `v2.4.0`) | | `airgapped` | No | `false` | Set to `true` for airgapped compose files | | `platform` | No | `amd64` | Target architecture: `amd64` or `arm64`. Only applies when `airgapped=true`. | ## What's in the zip **Standard download** * `docker-compose.yml` * `variables.env` **Airgapped download** * `airgapped-docker-compose-{platform}.yml` * `variables.env` ## Quick download **Standard setup** ```bash curl "https://prime.plane.so/api/v2/setup/?version=v2.4.0" -o plane.zip unzip plane.zip ``` **Airgapped setup (AMD64)** ```bash curl "https://prime.plane.so/api/v2/setup/?version=v2.4.0&airgapped=true" -o plane.zip unzip plane.zip ``` **Airgapped setup (ARM64)** ```bash curl "https://prime.plane.so/api/v2/setup/?version=v2.4.0&airgapped=true&platform=arm64" -o plane.zip unzip plane.zip ``` Replace `v2.4.0` with the version you need. See the [releases page](https://plane.so/changelog?category=self-hosted) for available versions. ### Error responses | Status | Cause | Response | | ------ | ----------------------------------- | ------------------------------------------------------- | | 400 | Missing `version` parameter | `{"error": "version query parameter is required"}` | | 400 | Invalid `platform` value | `{"error": "platform must be amd64 or arm64"}` | | 400 | Server missing GitHub configuration | `{"error": "missing required settings"}` | | 404 | Release tag not found | `{"error": "release not found"}` | | 404 | Config files missing from release | `{"error": "assets not found in release: "}` | | 500 | GitHub API failure | `{"error": "Failed to fetch release information"}` | --- --- url: 'https://developers.plane.so/self-hosting/govern/high-availability.html' description: >- How to deploy Plane Commercial Edition on Kubernetes with high availability using the plane-enterprise Helm chart. --- # High Availability on Kubernetes This guide covers what high availability means, how the `plane-enterprise` Helm chart workloads behave under failure, and exactly what to configure so your deployment survives the loss of a single availability zone or node without manual recovery. The setup is cloud-agnostic. If you're deploying on AWS with Karpenter, there's a dedicated section for you. Read this alongside the chart's [README](https://github.com/makeplane/helm-charts/blob/master/charts/plane-enterprise/README.md) and [values.yaml](https://github.com/makeplane/helm-charts/blob/master/charts/plane-enterprise/values.yaml). ## What HA means here Plane Commercial Edition is a single-region application. There's one primary Postgres, one Redis, one message queue, one search cluster. High availability here means Plane keeps serving traffic when **one AZ or one node disappears**, not that you can run two independent active-active regions. That's an important distinction for how you plan your infrastructure. You're engineering for node and AZ fault tolerance, not geographic redundancy. The playbook: run stateless workloads with multiple replicas spread across AZs, and replace every in-chart stateful service with a managed, multi-AZ equivalent. ## Workload tiers Every workload in the chart falls into one of three tiers. The tier determines how you scale it, how it recovers from failure, and what HA configuration it needs. ### Tier 1 - Stateless, scale horizontally These run as `Deployment`s with no local state. Scale them freely across nodes and AZs. `api`, `web`, `space`, `admin`, `live`, `worker`, `silo`, `email_service`, `outbox_poller`, `automation_consumer`, `pi`, `pi_worker`, `runner`, `iframely` Run at least `replicas: 2` per service. Use `replicas >= 2` for `api`, `worker`, `web`, and `live` - they carry the most traffic. ### Tier 2 - Singletons (replicas: 1 only) These do scheduled or coordinator work. **Do not scale any of them past `replicas: 1`** - running two copies doubles job execution. | Workload | Kind | Why it stays at 1 | | ---------------- | ----------- | -------------------------------------------- | | `monitor` | StatefulSet | Coordinator role; owns a `ReadWriteOnce` PVC | | `beatworker` | Deployment | Celery beat - schedules periodic Plane jobs | | `pi_beat_worker` | Deployment | PI beat - schedules periodic PI jobs | | `migrator` | Job | DB migration; runs once per release | | `pi-migrator` | Job | PI DB migration; runs once per release | The stateless singletons (`beatworker`, `pi_beat_worker`) reschedule onto a healthy node within seconds when their node fails. `monitor` is different: it owns an AZ-bound `ReadWriteOnce` PVC. On AZ failure, Kubernetes has to reschedule it onto a node in a live AZ and reattach the volume - expect a **60–120 second** recovery window. That's acceptable because `monitor` is an internal component, not user-facing. `migrator` and `pi-migrator` are run-once-per-release Jobs. They aren't long-running, but they still must not run in parallel. ### Tier 3 - Local stateful (not HA) The chart ships optional in-cluster StatefulSets for development and small deployments: `postgres`, `redis`, `rabbitmq`, `opensearch`, `minio` These use single-replica `ReadWriteOnce` PVCs. They're **not HA.** Their data is pinned to one disk in one AZ, and the chart doesn't configure replication, failover, or quorum. **For every HA deployment, set `local_setup: false` for every Tier-3 service** and point Plane at managed, multi-AZ equivalents. The [External managed services](#external-managed-services) section has the exact value keys. ## Cluster prerequisites Your cluster needs the following before installing in HA mode. **1. Worker nodes in at least three AZs.** Three is the minimum for any quorum service (etcd, Postgres synchronous replicas, OpenSearch master quorum). Two AZs survive single-AZ loss for stateless workloads but can't maintain quorum. **2. A default `StorageClass` with `volumeBindingMode: WaitForFirstConsumer`.** This is non-negotiable when Tier-2 singletons run on nodes provisioned just-in-time (Karpenter, Cluster Autoscaler). Without it, a PVC can bind to a zone before the pod schedules, leaving the pod unable to find a matching node. Example for AWS EBS gp2: ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp2 parameters: type: gp2 fsType: ext4 provisioner: ebs.csi.aws.com volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Retain allowVolumeExpansion: true ``` Then set this in `values.yaml`: ```yaml env: storageClass: gp2 ``` **3. A cross-zone load balancer.** Traffic must reach pods in any AZ. | Cloud | Recommendation | | ------- | ------------------------------------------------- | | AWS | NLB or ALB with cross-zone load balancing enabled | | GCP | Default global LB | | Azure | Standard Load Balancer with zones `[1,2,3]` | | On-prem | MetalLB in BGP mode, or an external LB | **4. A working `IngressClass`.** The chart supports `traefik` (default) or `nginx`. Deploy the ingress controller with `replicas >= 2` spread across AZs. **5. AZ-aware node labels.** Kubernetes uses `topology.kubernetes.io/zone` for AZ awareness. Managed clusters populate this automatically. Verify your nodes carry this label if you're on a self-managed cluster. ## Recommended topology ```text ┌──────────────────────────┐ │ External Load Balancer │ │ (cross-zone enabled) │ └────────────┬─────────────┘ │ ┌───────────────────┼───────────────────┐ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ AZ-a │ │ AZ-b │ │ AZ-c │ │ │ │ │ │ │ │ ingress │ │ ingress │ │ ingress │ │ api x N │ │ api x N │ │ api x N │ │ web x N │ │ web x N │ │ web x N │ │ worker │ │ worker │ │ worker │ │ … │ │ … │ │ … │ └─────────┘ └─────────┘ └─────────┘ │ ┌─────────────────┼─────────────────┐ │ │ │ ┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐ │ Managed │ │ Managed │ │ Object │ │ Postgres │ │ Redis │ │ Storage │ │ (multi-AZ) │ │ (multi-AZ) │ │ (S3-class) │ └──────────────┘ └──────────────┘ └──────────────┘ ┌──────────────┐ ┌──────────────┐ │ Managed │ │ Managed │ │ RabbitMQ │ │ OpenSearch │ │ (cluster) │ │ (multi-AZ) │ └──────────────┘ └──────────────┘ ``` Tier-1 pods spread across AZs. All Tier-3 state lives in managed services that handle their own replication and failover. ## External managed services ### Value keys The chart supports pointing each stateful component at a remote managed service. Use these value keys. | Component | Disable local | External URL / credentials | | ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Postgres | `services.postgres.local_setup: false` | `env.pgdb_remote_url`, `env.pg_pi_db_remote_url`; optional read replica via `services.postgres.read_replica.enabled` + `services.postgres.read_replica.remote_url` | | Redis | `services.redis.local_setup: false` | `env.remote_redis_url` | | RabbitMQ | `services.rabbitmq.local_setup: false` | `services.rabbitmq.external_rabbitmq_url` | | OpenSearch | `services.opensearch.local_setup: false` | `env.opensearch_remote_url`, `env.opensearch_remote_username`, `env.opensearch_remote_password`; optional `env.opensearch_index_prefix` for multi-tenant clusters | | Object store | `services.minio.local_setup: false` | `env.aws_access_key`, `env.aws_secret_access_key`, `env.aws_region`, `env.aws_s3_endpoint_url`, `env.docstore_bucket` | ### What HA looks like for each service Setting `local_setup: false` doesn't make your data tier HA on its own. The managed service you point Plane at must also be HA. Here's what each one needs. * **Postgres** - Multi-AZ primary with synchronous replication and automated failover. Use RDS Multi-AZ, Cloud SQL HA, Azure Flexible Server zone-redundant, or self-managed Patroni. * **Redis** - A replica group with automatic failover. Use ElastiCache Multi-AZ, Memorystore HA, or Redis Sentinel/Cluster. Redis failover drops in-flight connections; Plane reconnects automatically. * **RabbitMQ** - A true cluster with quorum queues across ≥3 nodes in ≥3 AZs. CloudAMQP and Amazon MQ for RabbitMQ in cluster mode both work. A single-node managed RabbitMQ is **not** HA. * **OpenSearch** - ≥3 master-eligible nodes across 3 AZs, plus data nodes spread across AZs. * **Object storage** - S3, GCS, and Azure Blob are multi-AZ by design. ## Spreading pods across availability zones ### How the chart exposes scheduling controls The chart exposes `nodeSelector`, `tolerations`, and `affinity` on every service (see `templates/_helpers.tpl` → `plane.podScheduling`). Use these to spread Tier-1 pods across AZs. :::info The chart doesn't natively support `topologySpreadConstraints` - that's on the roadmap. Use `podAntiAffinity` in the meantime. It's functionally equivalent for AZ spreading. ::: ### Recommended pattern: soft AZ anti-affinity + hard node anti-affinity Use a hard rule to prevent two replicas landing on the same node, and a soft rule to prefer spreading across AZs. The soft AZ rule means the scheduler can still place pods if one AZ is under pressure. ```yaml services: api: replicas: 3 affinity: podAntiAffinity: # Hard: never put two api pods on the same node requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app.name operator: In values: - --api topologyKey: kubernetes.io/hostname # Soft: prefer spreading api pods across AZs preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app.name operator: In values: - --api topologyKey: topology.kubernetes.io/zone ``` The chart labels every workload with `app.name` set to {{ .Release.Namespace }}-{{ .Release.Name }}-\. For a release named `plane` in namespace `plane`, that's `plane-plane-api` for the API. :::warning **Watch for this** The hard hostname anti-affinity rule requires at least as many schedulable nodes as the workload's replica count. Three `api` replicas need three nodes available, or pods sit `Pending`. If you can't guarantee that (small cluster, dedicated taints), relax the hostname rule to `preferredDuringSchedulingIgnoredDuringExecution`. ::: Apply this pattern to every Tier-1 service: `web`, `space`, `admin`, `live`, `worker`, `silo`, `email_service`, `outbox_poller`, `automation_consumer`, `pi`, `pi_worker`, `runner`, `iframely`. ### Pinning workloads to specific node pools Use `nodeSelector` and `tolerations` to route a workload to a specific pool - for example, spot instances for batch workers: ```yaml services: worker: replicas: 6 nodeSelector: workload-class: batch tolerations: - key: workload-class operator: Equal value: batch effect: NoSchedule ``` ## PodDisruptionBudgets :::info Native PDB rendering is planned for a future release. Apply the manifests below yourself until then. ::: PDBs protect Tier-1 deployments from voluntary disruption - a node drain or cluster upgrade - taking a service down entirely. Without them, Kubernetes can evict all pods of a deployment simultaneously. Apply this manifest in the same namespace as your release. Replace `RELEASE` and `NAMESPACE` with your values. ```yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-api-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-api --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-web-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-web --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-space-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-space --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-admin-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-admin --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-live-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-live --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-worker-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-worker --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: plane-silo-pdb namespace: NAMESPACE spec: minAvailable: 1 selector: matchLabels: app.name: NAMESPACE-RELEASE-silo ``` Add similar PDBs for `pi`, `pi_worker`, `outbox_poller`, `automation_consumer`, `email_service`, `runner`, and `iframely` if you have enabled them. :::warning **Don't create PDBs for Tier-2 singletons** (`beatworker`, `pi_beat_worker`, `monitor`, `migrator`). A `minAvailable: 1` PDB on a `replicas: 1` workload blocks node drains entirely. ::: ## HorizontalPodAutoscalers :::info Native HPA rendering is planned for a future release. Apply the manifests below yourself until then. ::: HPAs scale Tier-1 services automatically under load. The thresholds below match the default resource requests in `values.yaml`. Tune `averageUtilization` and `maxReplicas` based on observed production load. ```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: plane-api-hpa namespace: NAMESPACE spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: RELEASE-api-wl minReplicas: 3 maxReplicas: 12 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: plane-worker-hpa namespace: NAMESPACE spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: RELEASE-worker-wl minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: plane-web-hpa namespace: NAMESPACE spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: RELEASE-web-wl minReplicas: 2 maxReplicas: 8 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 ``` :::warning **Never create an HPA for `beatworker`, `pi_beat_worker`, `monitor`, or any migration Job.** Scheduled jobs would fire multiple times. ::: ## Karpenter on AWS If you're on EKS, Karpenter is the recommended node provisioner for Plane Commercial Edition. It's AZ-aware, provisions nodes in seconds, and lets you mix on-demand and spot capacity per workload type. ### Minimum versions * Karpenter ≥ v1.0 * Kubernetes ≥ 1.29 * AWS Load Balancer Controller ≥ v2.7 * AWS EBS CSI driver installed ### EC2NodeClass One `EC2NodeClass` covers most installs. Use AL2023, IMDSv2-only, and gp2 root volumes. ```yaml apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: plane-default spec: amiFamily: AL2023 amiSelectorTerms: - alias: al2023@latest role: KarpenterNodeRole-CLUSTER_NAME subnetSelectorTerms: - tags: karpenter.sh/discovery: CLUSTER_NAME securityGroupSelectorTerms: - tags: karpenter.sh/discovery: CLUSTER_NAME blockDeviceMappings: - deviceName: /dev/xvda ebs: volumeType: gp2 volumeSize: 100Gi encrypted: true deleteOnTermination: true metadataOptions: httpEndpoint: enabled httpTokens: required httpPutResponseHopLimit: 1 ``` ### NodePools Two NodePools cover most deployments: an on-demand pool for general Tier-1 workloads, and a spot pool for batch workers (`worker`, `pi_worker`, `runner`, `outbox_poller`, `automation_consumer`). ```yaml apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: plane-general spec: template: spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: plane-default requirements: - key: kubernetes.io/arch operator: In values: [amd64] - key: karpenter.sh/capacity-type operator: In values: [on-demand] - key: karpenter.k8s.aws/instance-category operator: In values: [c, m] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["5"] - key: topology.kubernetes.io/zone operator: In values: [REGION-a, REGION-b, REGION-c] expireAfter: 720h limits: cpu: "200" memory: 400Gi disruption: consolidationPolicy: WhenEmptyOrUnderutilized consolidateAfter: 1m --- apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: plane-spot spec: template: spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: plane-default taints: - key: workload-class value: batch effect: NoSchedule requirements: - key: kubernetes.io/arch operator: In values: [amd64] - key: karpenter.sh/capacity-type operator: In values: [spot] - key: karpenter.k8s.aws/instance-category operator: In values: [c, m, r] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["5"] - key: topology.kubernetes.io/zone operator: In values: [REGION-a, REGION-b, REGION-c] expireAfter: 24h limits: cpu: "400" memory: 800Gi disruption: consolidationPolicy: WhenEmptyOrUnderutilized consolidateAfter: 5m ``` Match the spot NodePool taint with tolerations in your values: ```yaml services: worker: tolerations: - key: workload-class operator: Equal value: batch effect: NoSchedule nodeSelector: karpenter.sh/nodepool: plane-spot ``` ### How Karpenter interacts with AZ spread * Karpenter respects `podAntiAffinity` when deciding which AZ to provision a node in. The affinity patterns from the previous section are sufficient to drive Karpenter's AZ distribution - no extra configuration needed. * Don't add `karpenter.sh/do-not-disrupt: "true"` to Tier-1 pods. They're stateless. Let Karpenter consolidate them freely. * Do add it to Tier-2 singletons (`beatworker`, `pi_beat_worker`, `monitor`) and to in-flight long-running Jobs (`migrator`). They tolerate rescheduling, but you don't want Karpenter bouncing them during a deployment: ```yaml services: beatworker: annotations: karpenter.sh/do-not-disrupt: "true" ``` * `consolidateAfter: 1m` on the on-demand pool keeps the cluster cost-efficient. Raise it to `5m` or `10m` if you see churn during normal scaling. The spot pool's `expireAfter: 24h` forces daily node recycling, spreading the impact of spot interruptions across time rather than concentrating them. ## Ingress and load balancer * Deploy the ingress controller (`traefik` or `nginx`) with `replicas >= 2` spread across AZs using the same `podAntiAffinity` pattern. * Enable cross-zone load balancing on the cloud LB. On AWS: ```yaml service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" ``` * The `live` service uses WebSockets. Make sure your ingress controller and LB don't have idle-timeout values that drop long-lived connections. The default AWS NLB idle timeout is 350s - that's usually fine. ALB defaults to 60s and needs raising for WebSocket connections. * The chart configures request-body size limits via `ingress.traefik.maxRequestBodyBytes` (Traefik) and `nginx.ingress.kubernetes.io/proxy-body-size` (nginx). Tune these to your expected file upload size. ## Backup and disaster recovery HA protects against AZ and node failure. Backups protect against logical corruption, accidental deletion, and ransomware. You need both. | Component | Backup mechanism | Recommended retention | | ------------------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | Postgres | Managed-service automated backups + PITR | 30 days, PITR ≥ 7 days | | Object storage | Bucket versioning + lifecycle to a different bucket/region | 90 days | | OpenSearch | Snapshots to object storage | 7 days | | Redis | Optional; treat as cache + queue. Document what your team loses on a full Redis failure (sessions, in-flight Celery tasks). | - | | RabbitMQ | Definitions export (users, queues, bindings) on a schedule; messages are transient | - | | Kubernetes objects | Velero, namespace-scoped, daily | 30 days | **Run a restore drill** before go-live and at least once per quarter. A backup that's never been restored is an assumption, not a guarantee. ## Pre-go-live checklist Work through every item before sending real traffic. * \[ ] Cluster has worker nodes in ≥3 AZs * \[ ] Default `StorageClass` is `WaitForFirstConsumer` * \[ ] `env.storageClass` is set to that class * \[ ] All Tier-3 `local_setup` flags are `false` * \[ ] Managed Postgres is multi-AZ with synchronous replica * \[ ] Managed Redis has replica + auto-failover * \[ ] Managed RabbitMQ is a true cluster across ≥3 AZs * \[ ] Managed OpenSearch has ≥3 masters across ≥3 AZs * \[ ] Object storage is multi-AZ (S3/GCS/Blob) with versioning enabled * \[ ] Every Tier-1 service has `replicas >= 2` (3 for `api`, `worker`, `web`) * \[ ] Every Tier-1 service has a `podAntiAffinity` block (hostname + zone) * \[ ] Every Tier-1 service has a PDB * \[ ] HPAs applied for `api`, `worker`, `web` at minimum * \[ ] No HPA or PDB on `beatworker`, `pi_beat_worker`, `monitor`, `migrator` * \[ ] Ingress controller runs with `replicas >= 2` spread across AZs * \[ ] LB has cross-zone load balancing enabled * \[ ] Backups configured and a restore drill has succeeded * \[ ] Failure drill: cordon and drain every node in one AZ; Plane stays up * \[ ] Failure drill: kill the active Postgres node; Plane recovers ## Known chart gaps The following capabilities aren't natively provided by the chart and need to be applied separately. | Gap | Workaround | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | No native `topologySpreadConstraints` in `plane.podScheduling` | Use `podAntiAffinity` as shown in the spreading section - functionally equivalent for AZ spread | | No PDBs rendered by the chart | Apply the PDB manifests from the PodDisruptionBudgets section | | No HPAs rendered by the chart | Apply the HPA manifests from the HorizontalPodAutoscalers section | | In-chart Tier-3 StatefulSets are single-replica, RWO | Set `local_setup: false` and use managed services | | `monitor` is a singleton StatefulSet | Accept the 60–120s reschedule window on AZ failure - it's internal and non-user-facing | ## Reference values.yaml for HA A minimal example that disables every local stateful service and gives each Tier-1 workload three replicas with AZ anti-affinity. Adapt names to your release. ```yaml planeVersion: v2.6.0 license: licenseServer: https://prime.plane.so licenseDomain: plane.example.com ingress: enabled: true ingressClass: traefik env: storageClass: gp2 pgdb_remote_url: "postgres://plane:***@pg-primary.example.internal:5432/plane?sslmode=require" pg_pi_db_remote_url: "postgres://plane:***@pg-primary.example.internal:5432/plane_pi?sslmode=require" remote_redis_url: "redis://:***@redis.example.internal:6379/0" opensearch_remote_url: "https://opensearch.example.internal:9200" opensearch_remote_username: plane opensearch_remote_password: "***" aws_access_key: "***" aws_secret_access_key: "***" aws_region: us-east-1 aws_s3_endpoint_url: https://s3.us-east-1.amazonaws.com docstore_bucket: plane-uploads-prod web_url: https://plane.example.com instance_admin_email: admin@example.com cors_allowed_origins: https://plane.example.com services: postgres: local_setup: false read_replica: enabled: true remote_url: "postgres://plane:***@pg-reader.example.internal:5432/plane?sslmode=require" redis: local_setup: false rabbitmq: local_setup: false external_rabbitmq_url: "amqps://plane:***@rabbitmq.example.internal:5671/plane" opensearch: local_setup: false minio: local_setup: false api: replicas: 3 affinity: &spread-api podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - { key: app.name, operator: In, values: [plane-plane-api] } topologyKey: kubernetes.io/hostname preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - { key: app.name, operator: In, values: [plane-plane-api] } topologyKey: topology.kubernetes.io/zone web: { replicas: 3 } space: { replicas: 2 } admin: { replicas: 2 } live: { replicas: 3 } worker: { replicas: 4 } silo: { enabled: true, replicas: 2 } beatworker: { replicas: 1 } # singleton - do not scale pi_beat_worker: { replicas: 1 } # singleton - do not scale ``` Repeat the `affinity` block (varying the pod label) for every Tier-1 service. YAML anchors (`&spread-api` / `*spread-api`) help avoid repetition. --- --- url: 'https://developers.plane.so/self-hosting/methods/podman-quadlets.html' description: >- Install Plane using Podman Quadlets. Guide for deploying Plane with Podman as a Docker alternative with systemd integration. --- # Deploy Plane with Podman Quadlets This guide shows you the steps to deploy a self-hosted instance of Plane using Podman Quadlets. ## Prerequisites Before we start, make sure you've got these covered: * A non-root user account with `systemd --user support` (most modern Linux setups have this) * Podman version **4.4 or higher** ## Set up Podman 1. Add the Podman repository. ```bash echo 'deb http://download.opensuse.org/repositories/home:/alvistack/Debian_12/ /' | sudo tee /etc/apt/sources.list.d/home:alvistack.list ``` 2. Add the GPG key. ```bash curl -fsSL https://download.opensuse.org/repositories/home:alvistack/Debian_12/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_alvistack.gpg > /dev/null ``` 3. Refresh your package lists. ```bash sudo apt update ``` 4. Install Podman and its dependencies. ```bash sudo apt install -y podman uidmap netavark passt ``` The `uidmap` package handles user namespace mapping, `netavark` takes care of networking, and `passt` helps with network connectivity. 5. Download and extract Podman Quadlets. ```bash mkdir podman-quadlets curl -fsSL https://prime.plane.so/releases/v2.4.0/podman-quadlets.tar.gz -o podman-quadlets.tar.gz tar -xvzf podman-quadlets.tar.gz -C podman-quadlets ``` The directory contains an `install.sh` script that will handle the installation and configuration. ## Install Plane The installation script sets up Plane and configures all required services. You have two options: ### Without sudo access ```bash ./install.sh --domain your-domain.com --base-dir /your/custom/path ``` This installs Plane in your specified directory, which is useful if you want to maintain control over the installation location. ### With sudo access ```bash ./install.sh --domain your-domain.com ``` This installs Plane in `/opt/plane`, which is a standard system location. ::: info Systemd configurations are installed in `~/.config/containers/systemd/` ::: ## Configure external services (optional) If you use external services for database, Redis, RabbitMQ, OpenSearch, or object storage (MinIO/S3), edit `plane.env` in your Plane installation directory (e.g. `/opt/plane` or your custom path from `--base-dir`) before starting services. See [Environment variables](/self-hosting/govern/environment-variables) for more details. * **Database** — In the **DB SETTINGS** section, set `DATABASE_URL` or individual variables (`PGHOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_PORT`). * **Redis** — In the **REDIS SETTINGS** section, set `REDIS_URL` or `REDIS_HOST` and `REDIS_PORT`. * **RabbitMQ** — Set `AMQP_URL` (e.g. `amqp://username:password@your-rabbitmq-host:5672/vhost`). * **OpenSearch** — Set `OPENSEARCH_ENABLED=1`, `OPENSEARCH_URL`, and optionally `OPENSEARCH_USERNAME` and `OPENSEARCH_PASSWORD`. See [Configure OpenSearch for advanced search](/self-hosting/govern/advanced-search). * **MinIO / S3** — In the **DATA STORE SETTINGS** section, set `USE_MINIO=0` for external S3, then set `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_S3_ENDPOINT_URL`, and `AWS_S3_BUCKET_NAME`. After editing `plane.env`, start or restart services as described in [Start Plane](#start-plane) so the changes take effect. ## Start Plane ::: warning Note that you should run these commands without `sudo`. ::: 1. Reload systemd to recognize new configurations. ```bash systemctl --user daemon-reload ``` 2. Start the network service. ```bash systemctl --user start plane-nw-network.service ``` 3. Start core dependencies. ```bash systemctl --user start plane-{db,redis,mq,minio}.service ``` 4. Start backend services. ```bash systemctl --user start {api,worker,beat-worker,migrator,monitor}.service ``` 5. Start frontend services. ```bash systemctl --user start {web,space,admin,live,proxy}.service ``` The startup sequence is important: network first, then dependencies, followed by backend services, and finally frontend services. 6. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. ### Verify service status Check that all services are running correctly: 1. Check network status. ```bash systemctl --user status plane-nw-network.service ``` 2. Check core dependencies. ```bash systemctl --user status plane-{db,redis,mq,minio}.service ``` 3. Check backend services. ```bash systemctl --user status {api,worker,beat-worker,migrator,monitor}.service ``` 4. Check frontend services. ```bash systemctl --user status {web,space,admin,live,proxy}.service ``` Your Plane installation should now be running successfully with Podman Quadlets. This setup provides automatic service restart capabilities and standard systemd management commands for maintaining your installation. ## Troubleshoot To debug service issues, examine the logs using: ```bash journalctl --user -u --no-pager ``` The logs will provide detailed information about any configuration issues or errors that may occur. --- --- url: 'https://developers.plane.so/self-hosting/methods/airgapped-requirements.html' description: >- System requirements and architecture overview for Plane airgapped deployments. Hardware specs, network topology, and prerequisites for offline installations. --- # Airgapped deployment architecture ::: info Airgapped deployments are available exclusively for Enterprise Grid customers with a minimum commitment of 100 seats. Contact our [Sales team](mailto:sales@plane.so) for trials, exceptions to the seat cut-off, tailored pricing, and licensing info. ::: This document explains Plane's architecture and specific requirements for airgapped deployments. Review this before beginning your airgapped installation on [Docker](/self-hosting/methods/airgapped-edition) or [Kubernetes](/self-hosting/methods/airgapped-edition-kubernetes). ## What is an airgapped deployment? An airgapped deployment operates in a completely isolated network environment with no external internet connectivity. This isolation is common in highly regulated industries, government facilities, and organizations with strict security requirements. Plane supports fully airgapped deployments where all components - application services, databases, storage, and integrations - operate entirely within your isolated network perimeter. ## Deployment methods Plane's Airgapped Edition can be deployed using Docker or Kubernetes. Choose the method that best fits your infrastructure. ## Airgapped cluster architecture Here's how Plane operates in an airgapped environment with internal enterprise applications: ![Airgapped cluster architecture](/images/airgapped/airgapped-cluster.webp#hero) This diagram illustrates a critical principle: **all OAuth flows and API communication remain internal to the airgapped cluster**. When integrating with self-hosted GitHub Enterprise, GitLab, or other internal services, the entire authentication and data exchange happens within your isolated network — no internet access required. For a detailed breakdown of Plane's services and infrastructure dependencies, see [Plane self-hosted architecture](/self-hosting/plane-architecture). **Critical guarantees for airgapped environments** * **No telemetry**\ Plane does not send application data, usage metrics, or telemetry outside the cluster. No analytics, crash reports, or usage statistics leave your network. * **Offline licensing**\ License validation happens through uploaded license files downloaded from the Prime portal. No internet connection required after initial license file transfer. * **Zero external dependencies**\ After initial image import, no external network connectivity is required for Plane to operate. All features work entirely within your isolated environment. * **Internal-only communication**\ All service-to-service communication stays within your cluster. Services never attempt to reach external APIs, CDNs, or third-party services. ### How integrations stay internal The airgapped cluster diagram above shows the complete data flow. Key points: * **OAuth providers** - Your internal GitHub Enterprise or GitLab instance acts as the OAuth provider * **Authorization endpoints** - All OAuth URLs point to internal systems, never external SaaS services * **API communication** - Plane makes API calls only to your internal instances * **Webhook delivery** - Internal systems send webhooks to Plane's internal endpoints * **No SaaS fallback** - Plane never attempts to reach github.com, gitlab.com, or slack.com APIs This architecture ensures complete network isolation while maintaining full integration functionality. *** ## Kubernetes-specific requirements ### Base environment Deploying airgapped Plane via Kubernetes requires preparing all dependencies to operate without any external network access. #### Container images and artifacts * Maintain an internal OCI or container registry to host all Plane service images * Prepare a controlled process to pull, verify, and mirror Plane container images and Helm charts from an online staging environment into the airgapped registry #### Kubernetes environment **Supported versions:** Kubernetes 1.31 – 1.33 **Required components:** * IngressClass configured * StorageClass available * cert-manager configured with an internal CA **Node requirements:** * Ensure node OS dependencies and container runtime packages are available from mirrored package repositories like apt, yum, or offline bundles ### Scaling Horizontal scaling is handled via replica counts configurable in `values.yaml`. Plane avoids using StatefulSets where possible due to the complexity of scaling stateful workloads in Kubernetes. The `monitor` service uses a StatefulSet. **For airgapped clusters:** * Ensure metrics-server images are mirrored if using HPA * If using node autoscaling, ensure node images are pre-loaded and registries accessible on bootstrap ### Secrets management Plane supports using existing external secret stores, provided they are reachable within the airgapped environment: * AWS Secrets Manager for private VPC with no internet * HashiCorp Vault * Self-hosted Bitwarden * Kubernetes Secrets * SOPS, sealed-secrets, if preferred ### Additional considerations * Ensure all secret providers can function without external network access * cert-manager must use an internal certificate authority * Keys and secret rotation policies should be part of the airgap operational procedures --- --- url: 'https://developers.plane.so/self-hosting/methods/airgapped-edition.html' description: >- Deploy Plane in airgapped environment without internet access. Complete guide for offline Plane installation. --- # Deploy Plane Airgapped on Docker :::info Airgapped deployments are available exclusively for Enterprise Grid customers with a minimum commitment of 100 seats. Contact our [Sales team](mailto:sales@plane.so) for trials, exceptions to the seat cut-off, tailored pricing, and licensing info. ::: This guide walks you through deploying Plane Commercial in an airgapped Docker environment using Docker Compose and pre-configured images from your private registry. ## Prerequisites Before starting, ensure you have: * Docker (version 24 or later) installed and running * Docker Compose Plugin installed (you should be able to run `docker compose` or `docker-compose`) * Access to a private Docker registry containing Plane images * Required ports opened to access the application (80, 443) :::warning While Docker can run stateful services with persistent volumes, we strongly recommend using external managed services for better reliability in backup/restore operations and disaster recovery. Consider these alternatives: * **MinIO**: Replace with AWS S3, Google Cloud Storage, or any S3-compatible service * **Redis**: Replace with Valkey or a managed Redis service * **PostgreSQL**: Use a managed PostgreSQL service * **RabbitMQ**: Use a managed message queue service * **OpenSearch**: Use a managed OpenSearch service ::: ## Install Plane 1. **Prepare Docker images for airgapped environment** Refer to [this document](/self-hosting/methods/clone-docker-images) to download the Docker images from the Plane artifact registry to your internal registry. :::info This process will NOT download or clone these infrastructure images: * `valkey/valkey:7.2.11-alpine` * `postgres:15.7-alpine` * `rabbitmq:3.13.6-management-alpine` * `minio/minio:latest` * `minio/mc:latest` * `opensearchproject/opensearch:3.3.2` If you're using local infrastructure services, you'll need to pull and transfer these images separately. ::: 2. **Download Docker Compose configuration** ```bash # Download docker-compose.yml curl -fsSL https://prime.plane.so/releases//docker-compose-airgapped.yml -o docker-compose.yml # Download environment template curl -fsSL https://prime.plane.so/releases//variables-airgapped.env -o plane.env ``` 3. **Configure environment variables** Edit the `plane.env` file to configure your deployment: ```bash # Generate a unique machine signature export MACHINE_SIGNATURE=$(uuidgen) # Set your domain export DOMAIN_NAME=plane.yourcompany.com export WEB_URL=https://plane.yourcompany.com export CORS_ALLOWED_ORIGINS=https://plane.yourcompany.com ``` **Update image references** in `docker-compose.yml` to point to your private registry: ```yaml services: web: image: your-registry.io/plane/web-commercial:${APP_RELEASE_VERSION} api: image: your-registry.io/plane/backend-commercial:${APP_RELEASE_VERSION} space: image: your-registry.io/plane/space-commercial:${APP_RELEASE_VERSION} admin: image: your-registry.io/plane/admin-commercial:${APP_RELEASE_VERSION} live: image: your-registry.io/plane/live-commercial:${APP_RELEASE_VERSION} monitor: image: your-registry.io/plane/monitor-commercial:${APP_RELEASE_VERSION} silo: image: your-registry.io/plane/silo-commercial:${APP_RELEASE_VERSION} ``` **Infrastructure services** (if using local setup): ```yaml services: redis: image: valkey/valkey:7.2.11-alpine postgres: image: postgres:15.7-alpine rabbitmq: image: rabbitmq:3.13.6-management-alpine minio: image: minio/minio:latest ``` ## Start Plane 1. Start the services: ```bash docker compose --env-file plane.env up -d ``` 2. Watch the logs to make sure everything starts properly. * To monitor the database migration process: ```bash docker compose logs -f migrator ``` * To monitor the API service startup: ```bash docker compose logs -f api ``` The API is healthy when you see: `api-1 listening at` Once all services are running smoothly, you can access Plane by opening your browser and going to the domain you configured. You now have Plane running in your air-gapped environment. If you run into any issues, check the logs using the commands above, or reach out to our support team for assistance. 3. [Activate your license key](/self-hosting/manage/manage-licenses/activate-airgapped) --- --- url: >- https://developers.plane.so/self-hosting/methods/airgapped-edition-kubernetes.html description: >- Deploy Plane on Kubernetes using Helm charts. Complete guide for production-ready Kubernetes deployment with scaling and management. --- # Deploy Plane Airgapped on Kubernetes ::: info Airgapped deployments are available exclusively for Enterprise Grid customers with a minimum commitment of 100 seats. Contact our [Sales team](mailto:sales@plane.so) for trials, exceptions to the seat cut-off, tailored pricing, and licensing info. ::: This guide walks you through deploying Plane Commercial in an airgapped Kubernetes environment using Helm charts and pre-packaged Docker images. ## What you'll need Before starting, ensure you have: * Kubernetes cluster (v1.31 - v1.33) * Helm 3.x installed * `kubectl` configured to access your cluster * `cert-manager` available in the cluster * A valid and working ingress controller (nginx, traefik, etc) * Required ports opened to access the application (80, 443) * SMTP ports opened if using email intake (25, 465, 587) ::: warning While Kubernetes can run stateful services with persistent volumes, and Plane's Helm chart supports deploying PostgreSQL, MinIO, RabbitMQ, and Redis, we strongly recommend using external managed services for better reliability in backup/restore operations and disaster recovery. Consider these alternatives: * **MinIO**: Replace with AWS S3, Google Cloud Storage, or any S3-compatible service * **Redis**: Replace with Valkey or a managed Redis service * **PostgreSQL**: Use a managed PostgreSQL service * **RabbitMQ**: Use a managed message queue service * **OpenSearch**: Use a managed OpenSearch service ::: ## Install Plane 1. **Download Plane Enterprise Helm chart** Get the Plane Enterprise Helm chart from the official release. Check for the latest version at [Artifact Hub](https://artifacthub.io/packages/helm/makeplane/plane-enterprise). ```bash # Using wget wget https://github.com/makeplane/helm-charts/releases/download/plane-enterprise-1.6.4/plane-enterprise-1.6.4.tgz # Using curl curl -L -O https://github.com/makeplane/helm-charts/releases/download/plane-enterprise-1.6.4/plane-enterprise-1.6.4.tgz ``` 2. **Prepare Docker images for airgapped environment** Refer to [this document](/self-hosting/methods/clone-docker-images) to download the Docker images from the public repository to your internal repository. ::: info This process will NOT download or clone these infrastructure images: * `valkey:7.2.5-alpine` * `postgres:15.7-alpine` * `rabbitmq:3.13.6-management-alpine` * `minio/minio:latest` * `minio/mc:latest` * `opensearchproject/opensearch:3.3.2` If you're using `local_setup: true` for any of these services, you'll need to pull and transfer these images separately. ::: 3. **Configure custom values file** a. Extract the default values from the Helm chart. ```bash helm show values plane-enterprise-1.6.4.tgz > custom-values.yaml ``` b. Update Docker image references Edit the `custom-values.yaml` file to point to your local or private registry images and configure important settings. **Basic configuration:** ```yaml # Specify the Plane version planeVersion: # Enable airgapped mode (REQUIRED) airgapped: enabled: true # Must be TRUE for airgapped installations # If using custom root CA for S3 storage s3Secrets: - name: plane-s3-ca key: s3-custom-ca.crt - name: plane-s3-ca-2 key: s3-custom-ca-2.crt ``` **Service images:** ```yaml services: web: image: /web-commercial api: image: /backend-commercial space: image: /space-commercial admin: image: /admin-commercial live: image: /live-commercial monitor: image: /monitor-commercial email_service: enabled: true image: /email-commercial silo: enabled: true image: /silo-commercial iframely: enabled: true image: /iframely:v1.2.0 ``` **Infrastructure services:** Configure whether to use local (in-cluster) or external services: ```yaml services: # Database and infrastructure images redis: local_setup: true # Set to false if using external service image: valkey/valkey:7.2.11-alpine postgres: local_setup: true # Set to false if using external service image: postgres:15.7-alpine rabbitmq: local_setup: true # Set to false if using external service image: rabbitmq:3.13.6-management-alpine external_rabbitmq_url: "" # Required only if using remote RabbitMQ minio: local_setup: true # Set to false if using external service image: minio/minio:latest image_mc: minio/mc:latest ``` **Environment variables:** ```yaml env: storageClass: "" remote_redis_url: "" # Required only if using remote Redis pgdb_remote_url: "" # Required only if using remote PostgreSQL # Required if MinIO local_setup is false aws_access_key: "" aws_secret_access_key: "" aws_region: "" aws_s3_endpoint_url: "" ``` c. **Configure integrations and importers** To set up integrations with external systems like Slack, GitHub, and GitLab, configure these values in `custom-values.yaml`: ```yaml services: silo: enabled: true connectors: slack: enabled: false client_id: "" client_secret: "" github: enabled: false client_id: "" client_secret: "" app_name: "" app_id: "" private_key: "" gitlab: enabled: false client_id: "" client_secret: "" env: silo_envs: batch_size: 100 mq_prefetch_count: 1 request_interval: 400 hmac_secret_key: "" aes_secret_key: "dsOdt7YrvxsTIFJ37pOaEVvLxN8KGBCr" ``` d. **Configure intake email** The email intake feature in Plane lets you capture incoming emails. Before or after setting up the application, configure DNS settings following [this guide](https://developers.plane.so/self-hosting/govern/configure-dns-email-service). Add these required values to `custom-values.yaml`: ```yaml ingress: enabled: true ingressClass: 'nginx' # Or as per your cluster ingress_annotations: {} ssl: tls_secret_name: '' # If you have a custom TLS secret name # If you want to use Let's Encrypt, set createIssuer and generateCerts to true createIssuer: false issuer: http # Allowed: cloudflare, digitalocean, http token: '' # Not required for http server: https://acme-v02.api.letsencrypt.org/directory email: plane@example.com # A valid email address generateCerts: true services: email_service: enabled: true replicas: 1 memoryLimit: 1000Mi cpuLimit: 500m memoryRequest: 50Mi cpuRequest: 50m image: /email-commercial: pullPolicy: Always nodeSelector: {} tolerations: [] affinity: {} labels: {} annotations: {} env: email_service_envs: smtp_domain: '' ``` 4. **Install or upgrade with custom values** Install Plane Enterprise using your customized values file: ```bash helm upgrade plane-app plane-enterprise-1.6.4.tgz \ --install \ --create-namespace \ --namespace plane \ -f custom-values.yaml \ --timeout 10m \ --wait \ --wait-for-jobs ``` 5. **Verify installation** Check that all components are running: ```bash # Check all pods kubectl get pods -n plane # Check services kubectl get services -n plane # Check ingress kubectl get ingress -n plane # Check persistent volumes kubectl get pv,pvc -n plane # Get the ingress URL kubectl get ingress -n plane -o wide ``` You now have Plane running in your air-gapped environment. If you run into any issues, check the logs using the commands above, or reach out to our support team for assistance. 6. [Activate your license key](/self-hosting/manage/manage-licenses/activate-airgapped). ## Additional configuration For more advanced Plane configuration options, refer to the [Kubernetes documentation](https://developers.plane.so/self-hosting/methods/kubernetes#configuration-settings). --- --- url: 'https://developers.plane.so/self-hosting/methods/clone-docker-images.html' description: >- Mirror Plane Docker images to your private container registry. Pull, tag, and push images for airgapped or restricted network deployments. --- # Clone Docker images to your private registry ::: info **Part of airgapped deployment**\ This guide is part of the airgapped deployment process. If you're setting up Plane in an airgapped environment, return to that guide after copying your images. ::: This guide shows you how to copy Docker images from the Plane artifact registry to your destination registry using the `crane` tool. ## Prerequisites ### Install crane Crane is a tool for interacting with remote container images and registries. Install it on a machine with internet access. **macOS:** ```bash brew install crane ``` **Linux:** ```bash # Download the latest release VERSION=$(curl -s https://api.github.com/repos/google/go-containerregistry/releases/latest | grep '"tag_name"' | cut -d'"' -f4) curl -sL "https://github.com/google/go-containerregistry/releases/download/${VERSION}/go-containerregistry_Linux_x86_64.tar.gz" | tar xz crane sudo mv crane /usr/local/bin/ # Verify installation crane version ``` **Windows (using WSL or Git Bash):** ```bash # Download and extract curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Windows_x86_64.tar.gz" | tar xz crane.exe ``` ## Plane images to copy The following Plane Commercial images need to be transferred to your private registry: ``` artifacts.plane.so/makeplane/admin-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/web-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/space-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/live-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/monitor-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/backend-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/iframely:v1.2.0 artifacts.plane.so/makeplane/silo-commercial:${APP_RELEASE_VERSION} artifacts.plane.so/makeplane/email-commercial:${APP_RELEASE_VERSION} ``` ::: warning **Infrastructure images not included:** The Plane artifact registry does not include infrastructure images. If you're using `local_setup: true` for any infrastructure services, you'll need to pull these separately from public registries: * `valkey/valkey:7.2.11-alpine` * `postgres:15.7-alpine` * `rabbitmq:3.13.6-management-alpine` * `minio/minio:latest` * `minio/mc:latest` ::: ## Configure environment variables Set your version and destination registry before copying images. ```bash # Set your Plane version export APP_RELEASE_VERSION="v2.4.0" # Replace with your desired version # Set your destination registry export DESTINATION_REGISTRY="your-registry.io/your-namespace" ``` **Example destination registry values:** ```bash # Docker Hub export DESTINATION_REGISTRY="docker.io/yourcompany" # Google Container Registry export DESTINATION_REGISTRY="gcr.io/your-project" # Private registry export DESTINATION_REGISTRY="your-private-registry.com/plane" # AWS ECR export DESTINATION_REGISTRY="123456789012.dkr.ecr.us-east-1.amazonaws.com/plane" # Azure Container Registry export DESTINATION_REGISTRY="yourregistry.azurecr.io/plane" ``` ## Authenticate to your destination registry Before copying images, authenticate crane to your destination registry. **Docker Hub:** ```bash crane auth login docker.io -u YOUR_USERNAME -p YOUR_PASSWORD ``` **Google Container Registry:** ```bash gcloud auth configure-docker ``` **AWS ECR:** ```bash aws ecr get-login-password --region REGION | crane auth login --username AWS --password-stdin AWS_ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com ``` **Azure Container Registry:** ```bash az acr login --name YOUR_REGISTRY_NAME ``` **Harbor or other private registries:** ```bash crane auth login your-registry.com -u YOUR_USERNAME -p YOUR_PASSWORD ``` ## Copy images to your registry You can copy images individually or use the provided script to copy all images at once. ### Option 1: Copy individual images **Basic image copy:** ```bash crane copy \ artifacts.plane.so/makeplane/backend-commercial:${APP_RELEASE_VERSION} \ ${DESTINATION_REGISTRY}/backend-commercial:${APP_RELEASE_VERSION} ``` **Copy with specific platform (architecture):** ```bash crane copy \ --platform linux/amd64 \ artifacts.plane.so/makeplane/backend-commercial:${APP_RELEASE_VERSION} \ ${DESTINATION_REGISTRY}/backend-commercial:${APP_RELEASE_VERSION} ``` **Verify source image before copying:** ```bash # Check if source image exists crane manifest artifacts.plane.so/makeplane/backend-commercial:${APP_RELEASE_VERSION} # List all available tags crane ls artifacts.plane.so/makeplane/backend-commercial ``` **Verify image after copying:** ```bash # Get image digest crane digest ${DESTINATION_REGISTRY}/backend-commercial:${APP_RELEASE_VERSION} # Verify manifest crane manifest ${DESTINATION_REGISTRY}/backend-commercial:${APP_RELEASE_VERSION} ``` ### Option 2: Copy all images with a script Create a file named `copy-plane-images.sh`: ```bash #!/bin/bash set -e # Configuration APP_RELEASE_VERSION="${APP_RELEASE_VERSION:-v2.4.0}" DESTINATION_REGISTRY="${DESTINATION_REGISTRY}" if [ -z "$DESTINATION_REGISTRY" ]; then echo "Error: DESTINATION_REGISTRY environment variable is not set" echo "Example: export DESTINATION_REGISTRY='docker.io/yourcompany'" exit 1 fi # Source registry SOURCE_REGISTRY="artifacts.plane.so/makeplane" # Image list declare -a IMAGES=( "admin-commercial:${APP_RELEASE_VERSION}" "web-commercial:${APP_RELEASE_VERSION}" "space-commercial:${APP_RELEASE_VERSION}" "live-commercial:${APP_RELEASE_VERSION}" "monitor-commercial:${APP_RELEASE_VERSION}" "backend-commercial:${APP_RELEASE_VERSION}" "iframely:v1.2.0" "silo-commercial:${APP_RELEASE_VERSION}" "email-commercial:${APP_RELEASE_VERSION}" ) echo "Starting image copy process..." echo "Source: ${SOURCE_REGISTRY}" echo "Destination: ${DESTINATION_REGISTRY}" echo "Version: ${APP_RELEASE_VERSION}" echo "" # Copy each image for IMAGE in "${IMAGES[@]}"; do SOURCE="${SOURCE_REGISTRY}/${IMAGE}" DESTINATION="${DESTINATION_REGISTRY}/${IMAGE}" echo "Copying: ${SOURCE} -> ${DESTINATION}" crane copy "${SOURCE}" "${DESTINATION}" if [ $? -eq 0 ]; then echo "✓ Successfully copied ${IMAGE}" else echo "✗ Failed to copy ${IMAGE}" exit 1 fi echo "" done echo "All images copied successfully!" ``` Make the script executable and run it: ```bash chmod +x copy-plane-images.sh ./copy-plane-images.sh ``` The script will copy all Plane images to your destination registry. Each image copy is verified, and the script exits if any copy fails. ## Troubleshooting ### Authentication issues **Error:** `unauthorized: authentication required` **Solution:** Re-authenticate to your destination registry: ```bash crane auth login your-destination-registry.com ``` Verify your credentials are correct and that you have push permissions to the registry. ### Network timeouts **Error:** Large images timing out during transfer **Solution:** Crane handles retries automatically, but you can also: * Check your network connection stability * Try copying during off-peak hours * Use a machine with better network connectivity * Copy images individually rather than using the batch script ### Permission denied **Error:** `denied: requested access to the resource is denied` **Solution:** * Ensure you have push permissions to the destination registry * Verify your authentication credentials are correct * Check that the destination repository exists, or that you have permission to create it * For organizational registries, confirm your account has the necessary roles ### Image not found **Error:** `MANIFEST_UNKNOWN: manifest unknown` **Solution:** * Verify the source image exists: ```bash crane ls artifacts.plane.so/makeplane/backend-commercial ``` * Check that you're using the correct version tag * Ensure `APP_RELEASE_VERSION` is set correctly * Verify the image name is spelled correctly ### Rate limiting **Error:** `429 Too Many Requests` **Solution:** * Wait a few minutes and retry * Authenticate to increase rate limits * For Docker Hub, ensure you're using an authenticated account (free accounts have higher limits than anonymous) * Spread out image copies over time if hitting limits repeatedly ## Additional resources * [Crane documentation](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md) * [Crane GitHub repository](https://github.com/google/go-containerregistry) --- --- url: 'https://developers.plane.so/self-hosting/methods/coolify.html' description: >- Deploy Plane using Coolify, an open-source PaaS. One-click deployment with automatic SSL, domain configuration, and container management. --- # Deploy Plane with Coolify This guide shows you the steps to deploy a self-hosted instance of Plane using Coolify. ## Install Plane ### Prerequisites * Before you get started, make sure you have a Coolify environment set up and ready to go. * Your setup should support either amd64 or arm64 architectures. ### Procedure 1. **Download the required deployment files** `coolify-compose.yml` – Defines Plane's services and dependencies. ```bash curl -fsSL https://prime.plane.so/releases//coolify-compose.yml -o coolify-compose.yml ``` ::: warning The `` value should be v1.8.2 or higher. ::: 2. Create a new project in Coolify. 3. Add a new resource. 4. Select **Docker Compose Empty** as the deployment method. 5. Copy and paste the contents of the `coolify-compose.yml` file into the editor. 6. Configure external DB, Redis, RabbitMQ and any other required environment variables in the UI. ::: warning When self-hosting Plane for production use, it is strongly recommended to configure external database and storage. This ensures that your data remains secure and accessible even if the local machine crashes or encounters hardware issues. Relying solely on local storage for these components increases the risk of data loss and service disruption. ::: * `DATABASE_URL` – Connection string for your external database. * `REDIS_URL` – Connection string for your external Redis instance. * `AMQP_URL` – Connection string for your external RabbitMQ server. 7. Deploy to launch your Plane instance. Once the deployment is complete, your Plane instance should be accessible on the configured domain. 8. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. --- --- url: 'https://developers.plane.so/self-hosting/methods/portainer.html' description: >- Deploy and manage Plane using Portainer container management UI. Visual Docker management with stack deployment and monitoring. --- # Deploy Plane with Portainer This guide shows you the steps to deploy a self-hosted instance of Plane using Portainer. ## Install Plane ### Prerequisites * Before you get started, make sure you have a Portainer environment set up and ready to go. * Your setup should support either amd64 or arm64 architectures. ### Procedure 1. **Download the required deployment files** `portainer-compose.yml` – Defines Plane's services and dependencies. ```bash curl -fsSL https://prime.plane.so/releases//portainer-compose.yml -o portainer-compose.yml ``` `variables.env` – Stores environment variables for your deployment. ```bash curl -fsSL https://prime.plane.so/releases//variables.env -o plane.env ``` ::: warning The `` value should be v1.8.2 or higher. ::: 2. Click **+ Add stack** on Portainer. 3. Copy and paste the contents of `portainer-compose.yml` into the editor. 4. Load environment variables from the `variables.env` file. 5. **Configure environment variables**\ Before deploying, edit the following variables: * `DOMAIN_NAME` – (required) Your application's domain name. * `SITE_ADDRESS` – (required) The full domain name (FQDN) of your instance. * `MACHINE_SIGNATURE` – (required) A unique identifier for your machine. You can generate this by running below code in terminal: ```sh sed -i 's/MACHINE_SIGNATURE=.*/MACHINE_SIGNATURE='$(openssl rand -hex 16)'/' plane.env ``` * `CERT_EMAIL` – (optional) Email address for SSL certificate generation (only needed if you're setting up HTTPS). 6. **Configure external DB, Redis, and RabbitMQ** ::: warning When self-hosting Plane for production use, it is strongly recommended to configure external database and storage. This ensures that your data remains secure and accessible even if the local machine crashes or encounters hardware issues. Relying solely on local storage for these components increases the risk of data loss and service disruption. ::: * `DATABASE_URL` – Connection string for your external database. * `REDIS_URL` – Connection string for your external Redis instance. * `AMQP_URL` – Connection string for your external RabbitMQ server. 7. Click **Deploy the stack**. That's it! Once the deployment is complete, Plane should be up and running on your configured domain. 8. If you've purchased a paid plan, [activate your license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) to unlock premium features. --- --- url: 'https://developers.plane.so/self-hosting/govern/instance-admin.html' description: >- Configure Plane instance admin settings. Learn about God mode and administrative controls for self-hosted Plane. --- # Instance admin and God mode An instance is a single self-managed installation of Plane on a private cloud or server that the `Instance admin` controls and administers. A single instance can house multiple workspaces. ::: info There may also be cases where a user IRL is running multiple instances, e.g., when using Plane for several clients. An `Instance admin` role will have to be declared for each of those instances, but it is okay to use the same email address for all of them. ::: This role lets instance admins access `/god-mode`, a route for features that help them administer and govern their Plane instance better for all users of that instance. ::: tip New instances allow skipping going to God Mode and setting up your workspace instead. Whatever you choose after secure instance set-up, we highly recommend coming quickly to /god-mode to set up at least your SMTP server so your users can start getting invite emails to projects. ::: ## Settings God Mode features a few screens as shown below. ### General The General settings page allows you to view or configure core instance details and telemetry preferences. Here’s what you can manage: * **Name of instance**\ Customize the name of your instance. * **Email**\ Displays the instance admin email address. * **Instance ID**\ Displays a unique identifier for your instance. * **Chat with us**\ Enable or disable in-app chat support for users. Disabling telemetry automatically turns this off. * **Let Plane collect anonymous usage data**\ Plane collects anonymized usage data (no PII) to help improve features and overall experience. You can turn this off anytime. See [Telemetry](/self-hosting/telemetry) for more info. ![](/images/instance-admin/god-mode-general.webp#hero) ### Email Set up your SMTP server here so you can send essential emails—password resets, exports, changes to your instance—and Plane-enabled emails—onboarding, tips and tricks, new features— to all your users. [Learn more here](/self-hosting/govern/communication). ![](/images/instance-admin/god-mode-email.webp#hero) ### Authentication Control what SSO and OAuth services your users can use to sign up and log in to your Plane instance. You can also toggle unique code and password logins on and off from here. [Learn more here](/self-hosting/govern/authentication). * **Allow anyone to sign up without an invite**\ Toggle this setting off if you want your users to join the instance only if they receive an invite. Once SSO is configured, instance admins can also use SSO to log in to God Mode. ::: info This is where you will see new SSO services and custom OAuth configs in the future. ::: ![](/images/instance-admin/god-mode-authentication.webp#hero) ### Workspaces The Workspaces section allows you to manage all workspaces within your Plane instance. * **View all Workspaces**\ Access a complete list of workspaces on your instance. * **Create Workspaces**\ You can create new workspaces directly from this section. If workspace creation is restricted, only the instance admin will have this ability. * **Restrict Workspace creation**\ Toggle the **Prevent anyone from creating a workspace** option to prevent anyone else from creating workspaces. Once enabled, only you (the instance admin) can create new workspaces. To add users to a workspace, you will need to [invite them](https://docs.plane.so/core-concepts/workspaces/members#add-member) after creating it. ::: info Workspace deletion is currently not supported. ::: ![](/images/instance-admin/god-mode-workspaces.webp#hero) ### User management View and manage all users across the instance, invite instance admins, and control access to God Mode. ![User management](/images/instance-admin/user-management.webp#hero) * View all users with their account type, status, and joining date * Invite new instance admins * Grant or remove admin access for existing users * Remove users from the instance See [User management](/self-hosting/manage/manage-instance-users) for details. ### Images in Plane You can use your own third-party libraries to update images in project settings. Configure your Unsplash key here. When we add more image libraries, they will show up here. ![](/images/instance-admin/god-mode-images.webp#hero) ## FAQs ::: details How do you know who an Instance admin is? Whoever spins up the instance or upgrades to v0.14, we assume, is the instance admin. When you see Let's secure your instance, enter your email-password combo. If you are already using Plane with those credentials, you will be logged in and will see /god-mode features. If not, we will create a new user on your local instance and you will see /god-mode. Our shrewd guess right now is users are technical enough to upgrade to or bring up a new instance with v0.14 are instance admins. If there’s a case where this isn’t true, please reach out to us before you upgrade or set up your fresh instance. ::: ::: details What if I don’t complete secure instance set-up at the time of the upgrade? We strongly recommend completing set-up at upgrade so your regular users can access Plane without trouble. Because we are introducing several sensitive admin features in `God Mode`, we will show an instance-not-set-up screen to your regular users until such a time that you can complete the setup. ![success-on-setup-existing-instances-self-hosted](/images/faq-2.png) ::: ::: details What has changed with how existing regular users of my instance log in? All existing users will log in with their usual email address-password combos if they are already doing it. If they haven’t been using a password when not OAuthing into Plane, they will now need to. If OAuth is enabled, users can continue using your OAuth methods. New users will need to choose a password or OAuth into Plane. ::: ::: details What will happen to the default captain@plane.so account that you shipped so far? For all new instances, there won’t be a `captain@plane.so` account. Instance set-up will allow you to set up a workspace and set workspace and project admins. For existing instances, the instance admin’s email will be added to each project with the same permissions as `captain@plane.so’s` so you can remove that email completely from your workspaces and projects. ::: ::: details This is unreal, but I have an instance that has a /god-mode path already. I can’t access my Plane instance. Help! That is unreal! Please reach out to us immediately on [support](https://discord.com/login?redirect_to=%2Fchannels%2F1031547764020084846%2F1094927053867995176) or on our [Discord](https://discord.com/invite/A92xrEGCge) and mark your message urgent. We will help you get your instance back pronto. ::: ::: details How will emails for password resets and onboarding be sent to users of my instance(s)? We have always let you configure your own SMTP server to send emails from within your instance. It’s also why we are being deliberate about leading the instance admin of an existing instance to `/god-mode` first. After completing secure instance set-up now, you can configure your SMTP server on the UI instead of via `.env` variables. We strongly recommend you do that to avoid password-reset failures and failures in email delivery. Please [reach out](https://discord.com/login?redirect_to=%2Fchannels%2F1031547764020084846%2F1094927053867995176) to us on [Discord](https://discord.com/invite/A92xrEGCge) if you haven’t set up SMTP and are facing troubles with your users logging in. ::: ::: details Why are you introducing passwords for app.plane.so users? What’s happening with unique links to sign up and sign in? Unique links are secure and relatively easier, but we have heard from enough of our Cloud users that they would like to log in using a more permanent and easier method. Should you want to continue using unique codes, you are covered. We will keep that option alive for good. While using Google or GitHub are good options already, not all of you would want to use them. For those that prefer a password and would like to do away with codes, we want to make that option available. ::: ::: details Is there a God Mode for Cloud admins, too? Not now, but soon enough, there will be a `God Mode` for Cloud admins. ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/authentication.html' description: >- Configure authentication methods for self-hosted Plane. Setup OAuth, SSO, SAML, OIDC, LDAP and other authentication providers. --- # Overview Plane offers several methods you can choose from to let your users log in to your Plane instance. Configure these methods in Authentication on /god-mode of your instance. ## Authentication methods ### Unique code Plane lets your users log in with codes sent over email. This is disabled if SMTP is not configured for your instance. See [Communication](https://app.plane.so/plane/projects/e3ea12b0-62e3-4b8d-8ada-3379f4efc563/pages/e83af23e-b120-47b0-b241-2bee39037505) to set up SMTP if you wish to enable unique codes. ### Passwords Your users can log in with passwords that they or you set for them. This is toggled on when SMTP isn't configured for your instance. Disable it if you would like to use another authentication method below. --- --- url: 'https://developers.plane.so/self-hosting/govern/google-oauth.html' description: >- Setup Google OAuth authentication for Plane. Step-by-step guide to enable Google sign-in for your self-hosted instance. --- # Google OAuth Plane already ships with out-of-the-box support for Google OAuth. This is the easiest option to configure for Google Workspace users. ## Configure Plane as an app on Google API Console First, you will need to identify Plane as an approved OAuth app to Google. 1. Go to the [Google API console](https://console.cloud.google.com/apis) and create a new project. 2. Navigate to the **OAuth consent screen** under **APIs & Services**. Choose how you want to configure and register the Plane app, including your target users, and click **Create**. ![](/images/authentication/google/google-auth-1.png) 3. Configure the OAuth consent screen with information about the app. ![](/images/authentication/google/google-auth-2.png) 4. Navigate to the **Credentials** screen, click **Create Credentials**, and select **OAuth client ID** from the options given. ![](/images/authentication/google/google-auth-3.png) 5. Select **Web application** under the **Application type** dropdown list. Update the following fields. 1. **Authorized JavaScript origins**\ The HTTP origins that host your web application, e.g., `https://app.plane.so` 2. **Authorized redirect URIs**\ Append the path that users should be redirected to after they have authenticated with Google. `https:///auth/google/callback` and `https:///auth/mobile/google/callback/` where `` is your self-hosted instance's domain. 3. Click **Create**. 4. Get the Client ID and Client secret under **OAuth 2.0 Client IDs** on the **Credentials** screen. ![](/images/authentication/google/google-auth-4.png) ## Configure Plane ![Google Oauth Configuration](/images/custom-sso/google-oauth.png) 1. Go to `Google` on the Authentication screen of `/god mode`. 2. Add the client ID + the client secret from Google API Console. 3. Click `Save `. Your Plane instance should now work with `Sign in with Google`. ::: info We don't restrict domains in with Google OAuth yet. It's on our roadmap. ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/github-oauth.html' description: >- Configure GitHub OAuth authentication for Plane. Enable GitHub sign-in for your self-hosted Plane instance. --- # Github OAuth Plane supports GitHub OAuth so your users can sign-in with GitHub instead. ## Configure Plane as an OAuth app on GitHub 1. Log in to your [GitHub account](https://github.com/). 2. Click your profile's avatar and navigate to **Settings.** 3. Click **Developer Settings** and then **OAuth Apps**. ![](/images/authentication/github/github-auth-1.png) 4. Click **Register a new application**. 5. Configure the following OAuth credentials for your Plane app. 1. **Homepage URL**\ The domain, with HTTPS, on which you host Plane, e.g., `https://app.plane.so` 2. **Authorization Callback URL**\ Append the path that users should be redirected to after they have authenticated with GitHub. e.g., `https:////auth/github/callback/` and `https:///auth/mobile/github/callback/` where `` is your self-hosted instance's domain. 6. Click `Register application` to save it. ![](/images/authentication/github/github-auth-2.png) 7. Find the app you just registered and click through to find the client ID and the client secret. You will need this for the next steps. ## Configure Plane ![GitHub Oauth Configuration](/images/custom-sso/github-oauth.png) 1. Go to `GitHub` on the Authentication screen of `/god mode`. 2. Add the client ID + the client secret from the GitHub app you just registered. 3. Click `Save `. Your Plane instance should now work with GitHub sign-in. --- --- url: 'https://developers.plane.so/self-hosting/govern/oidc-sso.html' description: >- Setup OIDC SSO authentication for Plane. Configure OpenID Connect single sign-on for enterprise authentication. --- # OIDC SSO Plane enables custom SSO via any identity provider with an official and supported implementation of OIDC standards. This page cites examples from Okta, but we will soon publish provider-specific instructions in phases. ## OIDC You will need to configure values on your IdP first and then on Plane later. ### On your preferred IdP Create a Plane client or application per your IdP's documentation and configure ↓. ::: tip `domain.tld` is the domain that you have hosted your Plane app on. ::: | **Config** | **Key** | | ------------ | ------------------------------------------ | | Origin URL | `http(s)://domain.tld/auth/oidc/` | | Callback URL | `http(s)://domain.tld/auth/oidc/callback/` | | Logout URL | `http(s)://domain.tld/auth/oidc/logout/` | ### On Plane Go to `/god-mode/authentication/oidc` on your Plane app and find the configs ↓. ::: tip Your IdP will generate some of the following configs for you. Others, you will specify yourself. Just copy them over to each field. ::: ![OIDC Configuration](/images/custom-sso/oidc-oauth.png) * Copy the `CLIENT_ID` for the Plane client or app you just created over from your IdP and paste it in the field for it. With providers like Keycloak, you have to choose a unique ID per app your configure. With providers like Okta and Auth0, you copy over the generated ID over to Plane. Typically, you will find it on the Plane application Home or Settings page on your IdP. * Copy the `CLIENT_SECRET` for the Plane client or app you created over from your IdP and paste it in the field for it. The secret is usually auto-generated and you just need to copy it over from the Plane app or client's Home or Settings page. * Copy the `TOKEN URL` from your IdP and paste it into the field for it on `/god-mode/authentication/oidc/`.\ Typically used to maintain user authentication and to persist it with refreshes, this URL lives in the `.well-known/` directory for the Plane app or client on your IdP. * Copy the `User info URL` from your IdP and paste it into the field for it on `/god-mode/authentication/oidc/`. Used to get an authenticating user's information from the IdP. Plane requires the `email` field for user authentication. The `first_name` and `last_name` fields are optional but recommended for a complete user profile. This URL can be copied from the `.well-known/` directory. * Copy the `Authorize URL` over from the `.well-known/` directory and paste it into the field for it on Plane's `/god-mode/authentication/oidc/`.\ This is the URL that Plane's login screen redirects to when your users click `Sign up with ` or `Login with `. ![Login with IdP](/images/custom-sso/plane-login.png) To test if this URL is right, see if clicking the `Login with ` button brings up your IdP's authentication screen. ![Login with Okta](/images/custom-sso/okta-signin.webp#hero) * Finally, choose a name for your IdP on Plane so you can recognize this set of configs. --- --- url: 'https://developers.plane.so/self-hosting/govern/saml-sso.html' description: >- Configure SAML SSO authentication for Plane. Setup Security Assertion Markup Language for enterprise SSO. --- # SAML SSO Plane enables custom SSO via any identity provider with an official and supported implementation of SAML standards. This page cites examples from Okta, but we will soon publish provider-specific instructions in phases. ## SAML You will need to configure values on your IdP first and then on Plane later. ::: tip `domain.tld` is the domain that you have hosted your Plane app on. ::: ### On your preferred IdP Create a Plane client or application per your IdP's documentation and configure ↓. | **Config** | **Value** | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | Entity ID Metadata that identifies Plane as an authorized service on your IdP | `http(s)://domain.tld/auth/saml/` | | ACS URL Assertion Consumer service that your IdP will redirect to after successful authentication by a user This is roughly the counterpart of the `Callback URL` in SAML set-ups. | `http(s)://domain.tld/auth/saml/callback/` Plane supports HTTP-POST bindings. | | SLS URL Single Logout Service that your IdP will recognize to end a Plane session when a user logs out This is roughly the counterpart of the `Logout URL` in SAML set-ups. | `http(s)://domain.tld/auth/saml/logout/` | ::: tip When setting these values up on the IdP, it’s important to remember Plane does not need to provide a signing certificate like other service providers. ::: ### Let your IdP identify your users on Plane. | **Config** | **Value** | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name ID format | emailAddress By default, your IdP should send back a username, but Plane recognizes email addresses as the username. Set the value to the above so Plane recognizes the user correctly. | ### Set additional attribute values. By default, your IdP will send the value listed under `Property`. You have to map it to the SAML attribute Plane recognizes. | **Default property value** | **Plane SAML attribute** | | -------------------------- | ------------------------ | | user.firstName | first\_name | | user.lastName | last\_name | | user.email | email | ::: info **first\_name** and **last\_name** are optional but recommended for complete user profiles. If these are not provided, Plane will create the user account with just the email address. ::: ::: tip Depending on your IdP, you will have to find both the `Name ID format` and the three other user identification properties on different screens. Please refer to your IdP's documentation when configuring these up on your IdP. Additionally, you may have to configure the IdP to sign assertions. Irrespective of that, you have to copy the signing certificate from the IdP. ::: ### On Plane ![SAML Configuration](/images/custom-sso/saml-oauth.png) ::: tip You will find all of the values for the fields below in the `/metadata` endpoint your IdP generates for the Plane app or client. ::: * Copy the `ENTITY_ID` for the Plane client or app you just created over from your IdP and paste it in the field for it. * Copy the `SSO URL` for the Plane client or app from your IdP and paste it in the field for it. This will bring up the IdP's authentication screen for your users. ![SSO URL](/images/custom-sso/okta-signin.webp#hero) * Copy the `SLS URL` for the Plane client or app from your IdP and paste it in the `Logout URL` field on Plane's `/god-mode/authentication/saml/`. * Add the name of the IdP that you want to show on your Plane instance's log-in or sign-up screens. ![Log-in Screen](/images/custom-sso/instance-login.png) * Finally, paste the signing certificate from your IdP that you got in the last step of setting up your Plane client or app on your IdP above and paste it in the field for it. --- --- url: 'https://developers.plane.so/self-hosting/govern/ldap.html' description: >- Setup LDAP authentication for Plane. Configure Lightweight Directory Access Protocol for directory-based authentication. --- # LDAP authentication LDAP (Lightweight Directory Access Protocol) authentication lets your team sign in to Plane using their existing corporate credentials. Instead of creating separate Plane passwords, users authenticate through your organization's directory service. ## Before you begin You'll need: * Plane Commercial Edition with an active Enterprise Grid license. * Don't have an Enterprise license? Contact Sales at to get started. * Already have a license? See how to [activate your Enterprise license](/self-hosting/manage/manage-licenses/activate-enterprise). * Connection details for your LDAP server. * A service account on your LDAP server with read-only access to the user directory. ## Configure LDAP authentication 1. Sign in to your Plane instance in [God Mode](/self-hosting/govern/instance-admin). ![Turn on LDAP](/images/ldap/enable-ldap.webp#hero) 2. Select **Authentication** from the left pane. 3. Click **Configure** next to **LDAP** at the bottom of the page. 4. Enter your LDAP server details. ![LDAP configuration](/images/ldap/ldap-configuration.webp#hero) * **Server URI (required)** This is the address of your LDAP server. Include the protocol and port number. **Format:** * For unencrypted connections: `ldap://hostname:389` * For encrypted connections (recommended): `ldaps://hostname:636` **Examples:** ``` ldap://ldap.company.com:389 ldaps://ad.company.com:636 ldap://192.168.1.100:389 ``` * **Bind DN (required)** This is the username of the service account that Plane will use to search your directory. Think of it as Plane's "read-only" account on your LDAP server. The format varies depending on your directory service: **Active Directory examples:** ``` cn=PlaneService,ou=Service Accounts,dc=company,dc=com plane-svc@company.com ``` **OpenLDAP examples:** ``` cn=admin,dc=example,dc=com cn=readonly,ou=services,dc=example,dc=com ``` * **Bind Password (required)** Enter the password for your service account (Bind DN). Plane encrypts and stores this securely in its database. * **User Search Base (required)** This defines where in your directory Plane should look for users. Think of it as the "starting folder" for user searches. Use the most specific path possible for better performance. **Examples:** ``` ou=users,dc=example,dc=com ou=employees,ou=people,dc=company,dc=com cn=users,dc=company,dc=local ``` * **User Search Filter (optional)** This tells Plane how to find users when they try to sign in. Use `{username}` as a placeholder - Plane replaces it with whatever the user types in the login field. **Common filters by directory type:** | Directory Type | Filter | What it does | | ---------------- | -------------------------------- | -------------------------------- | | OpenLDAP | `(uid={username})` | Searches by user ID | | Active Directory | `(sAMAccountName={username})` | Searches by Windows login name | | Active Directory | `(userPrincipalName={username})` | Searches by email-style username | | Any | `(mail={username})` | Searches by email address | **Default:** If you don't specify a filter, Plane uses `(uid={username})`. **Combined filter example:**\ If you want users to sign in with either their username OR email: ``` (|(uid={username})(mail={username})) ``` * **User Attributes (optional)** List the LDAP attributes Plane should retrieve to create user profiles. Plane uses these to populate the user's display name and email in Plane. **How Plane maps attributes:** | Plane needs | LDAP provides (in order of preference) | | ------------- | ------------------------------------------------------------ | | Email address | `mail`, `userPrincipalName` | | First name | `givenName`, or first part of `cn` if `givenName` is missing | | Last name | `sn`, or last part of `cn` if `sn` is missing | **Recommended setting:** ``` mail,cn,givenName,sn,userPrincipalName,displayName ``` **Default:** If you don't specify attributes, Plane uses `mail,cn,givenName,sn`. * **Provider Name (optional)** This is the label that appears on Plane's login button. Choose something your team will recognize. **Examples:** * `Corporate Directory` * `Company SSO` * `Active Directory` **Default:** If you don't specify a name, Plane shows `LDAP`. The login button will display as: **"Sign in with \[Provider Name]"** 5. Click **Save changes** to apply your LDAP settings. Plane will validate the connection to your LDAP server. 6. Users will see **Sign in with LDAP** on Plane's login page and can use their directory credentials to sign in. ![Sign in using LDAP](/images/ldap/sign-in-ldap.webp#hero) ## How LDAP authentication works LDAP authentication in Plane works through a two-phase process. First, Plane locates the user in your directory, then it verifies their credentials. This separation is fundamental to how LDAP works and explains why you need both a service account (Bind DN) and the user's own credentials. ### The service account pattern Unlike simpler authentication systems where you might directly check a username and password against a database, LDAP uses what's called a "bind" operation. Plane needs to authenticate twice: once as itself (using the Bind DN) to search your directory, and once as the user to verify their password. This is why you configure a Bind DN and password - it's Plane's identity on your LDAP server. Think of it as Plane introducing itself before asking about your users. The Bind DN only needs read access because Plane is just looking up information, never modifying your directory. ### The authentication flow When a user tries to sign in, here's what happens behind the scenes: **Connection and service authentication**\ Plane connects to your LDAP server using the Server URI, then authenticates using the Bind DN credentials. If this fails, no users can sign in; the service account must work first. **User search**\ Now authenticated, Plane searches for the user starting from the User Search Base and applying the User Search Filter. For example, if a user enters "jsmith" and your filter is `(uid={username})`, Plane searches for `(uid=jsmith)`. The search returns the user's distinguished name (DN), their full path in the directory. **User authentication**\ Plane now attempts to bind again using the user's DN and their entered password. If the bind succeeds, the password is correct. This is why LDAP authentication is secure. Plane never stores user passwords; they go straight to your LDAP server for verification. **Profile creation**\ Once authentication succeeds, Plane retrieves the User Attributes you configured (like email, first name, last name) from the user's LDAP record. If this is the user's first time signing in, Plane creates their profile using this information. If they've signed in before, Plane updates their profile with any changes from LDAP. **Session establishment**\ Finally, Plane creates a session for the user and redirects them into the workspace. From this point on, the user's session works identically to any other Plane session. The LDAP interaction is complete. ### Why search filters matter The User Search Filter determines sign-in flexibility. A simple filter like `(uid={username})` requires exact usernames, but you can make it more flexible: `(mail={username})` lets users sign in with email `(|(uid={username})(mail={username}))` allows either username or email Filters can also restrict access: `(&(uid={username})(memberOf=cn=plane-users,ou=groups,dc=company,dc=com))` only permits specific group members. ### The role of user attributes User Attributes tell Plane which LDAP fields to retrieve after authentication. This is separate from the search filter. The filter finds the user, the attributes populate their profile. Plane specifically looks for email addresses (required) and names (optional). If your LDAP server uses different attribute names, you need to include them. --- --- url: 'https://developers.plane.so/self-hosting/govern/reset-password.html' description: >- Reset user and admin passwords for self-hosted Plane. Recover access to your instance using CLI commands or database operations. --- # Reset password Users can reset their password through the terminal of the Plane application. You need to login to backend docker container and run the below command for resetting a user’s password. 1. Get the container id for **plane-api**. ```bash docker ps ``` 2. Log in to the container. ```bash docker exec -it /bin/sh ``` 3. Run the reset password command. ```bash python manage.py reset_password ``` ::: tip The email should be of an already existing user on the Plane application. If the email is not attached to any user the command will throw an error. ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/communication.html' description: >- Configure SMTP email settings for Plane. Setup email notifications and communication for your self-hosted instance. --- # Configure SMTP for email notifications Either during your set-up or sometime later, you will want to set SMTP settings to let your users get emails to reset passwords, onboard themselves right, and get notifications for changes, and receive exports of your data. ::: info Plane currently supports SMTP authentication only via email and password. OAuth-based SMTP configurations aren’t supported. ::: ## Configuration Plane offers an interface to configure Simple Mail Transfer Protocol (SMTP) and SSL for encrypted email communication. Navigate to `Email` in `/god-mode`and you will see ↓. ![](/images/instance-admin/email-settings.png) * **Host**\ The address of your SMTP server. * **Port**\ The port for outgoing emails. * **Sender email address**\ The email address you wish to use as the sender of emails. * **Email security**\ Toggle `TLS` or `SSL` as the email security layer for your emails. If you do not wish to use either of them, you can choose the `No email security` option. * **Authentication**\ You can configure the username and password to authenticate the SMTP server to send emails. It's an optional configuration, but we would advise you to provide authentication details for a secure email delivery experience. * **Username**\ Specify the username for the SMTP configuration here. * **Password**\ Specify the password for the SMTP configuration here. ::: tip **Google Workspaces** If your Plane instance is not accessible on the internet, Gmail may block profile photos or other embedded images in email notifications. This occurs because Gmail uses Google's secure image proxy to serve images for security purposes. To resolve this issue, you must configure the Image URL proxy allowlist in your Google Workspace settings to include your Plane instance's URL. Refer to Google’s documentation for instructions: [Allowlist image URLs](https://support.google.com/a/answer/3299041?hl=en). ::: ## Configuration for popular email services providers ### Amazon SES 1. Sign in to [**https://console.aws.amazon.com/ses**](https://console.aws.amazon.com/ses). 2. Navigate to **SMTP Settings** in the sidebar. 3. Click **Create My SMTP Credentials**. 4. Follow prompts to create a user in the **Create User for SMTP** dialog box, then click **Create**. 5. Select **Show User SMTP Credentials** to view the user's SMTP credentials. 6. Return to your Plane instance's `/god-mode` and enter the obtained details. Ensure to review [**email quotas**](https://docs.aws.amazon.com/ses/latest/dg/quotas.html) for your Amazon SES server. Consider managing email recipients using groups to optimize usage. --- --- url: 'https://developers.plane.so/self-hosting/govern/database-and-storage.html' description: >- Configure external database and storage for Plane. Setup PostgreSQL, Redis, and S3-compatible storage services. --- # Configure external services The Prime CLI lets you easily configure your Commercial Edition instance, providing options to customize the PostgreSQL database, Redis, external storage, and other advanced settings. ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: 1. Run the Prime CLI with ↓: `sudo prime-cli` 2. Once the CLI is running, enter `configure`, which will guide you through a step-by-step form where you can specify the following: * `Listening port`\ Define the port for the built-in reverse proxy.\ *Default*: `80` * `Max file-upload size`\ Set the maximum file size (in MB) that members can upload. *Default*: `5 MB` * `External Postgres URL`\ Provide the URL of your external PostgreSQL instance if you want to switch from the default Plane configuration. *Default*: `Postgres 15.5` in the Docker container. ::: warning Don’t use a database on your local machine. If you use `localhost` in the URL, it won’t work. Make sure to use a database hosted on a network-accessible server. Avoid using special characters in your PostgreSQL password. ::: * `External Redis URL`\ Specify the URL of your external Redis instance to override the default Redis configuration.\ *Default*: `Redis 7.2.4` * `External storage`\ Plane currently supports only S3 compatible storages.\ *Default*: `MinIO` 1. Ensure your IAM user has the following permissions on your S3 bucket. * **s3:GetObject**\ To access the objects. * **s3:PutObject**\ To upload new assets using the presigned url. 2. Configure the CORS policy on your bucket to enable presigned uploads. Use the example policy below, making sure to replace `` with your actual domain. ``` [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "POST", "PUT", "DELETE", "HEAD" ], "AllowedOrigins": [ "", ], "ExposeHeaders": [ "ETag", "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2" ], "MaxAgeSeconds": 3000 } ] ``` 3. Switch to your external storage by providing the following values: * S3 access key ID  * S3 secret access key * S3 bucket name * S3 region  * S3 endpoint URL 3. After confirming your choices, your instance will automatically restart with the updated configuration. ::: details Community Edition To configure external Postgres, Redis, and S3 storage for the Plane Community Edition, you’ll need to adjust several environment variables in the plane.env file. Follow this guide to set up each component using the correct values for your external services. 1. Open the `plane.env` file on your server where Plane is installed. 2. In the **DB SETTINGS** section, update the variables to connect to your external Postgres instance: ```bash # DB SETTINGS PGHOST=your-external-postgres-host # Replace with the hostname or IP address of your Postgres server. PGDATABASE= # Leave blank when using external database. POSTGRES_USER=your-postgres-username # The username to access Postgres. POSTGRES_PASSWORD=your-postgres-password # Password for the Postgres user. POSTGRES_DB=your-database-name # The name of the database Plane should connect to. POSTGRES_PORT=5432 # Port where Postgres is accessible (usually 5432). PGDATA=/var/lib/postgresql/data # No need to change this for external Postgres. DATABASE_URL= # Leave this empty if you're providing values for the variables above. If you choose to use the DATABASE_URL, you can leave all the other database-related variables empty. ``` ::: warning Don’t use a database on your local machine. If you use `localhost` in the URL, it won’t work. Make sure to use a database hosted on a network-accessible server. Avoid using special characters in your PostgreSQL password. ::: 3. In the **REDIS SETTINGS** section, update the variables to connect to your external Redis instance: ```bash # REDIS SETTINGS REDIS_HOST=your-external-redis-host # Hostname or IP of the Redis server. REDIS_PORT=6379 # Port where Redis is accessible (default is 6379). REDIS_URL= # Leave this empty if you're providing values for the variables above. If you choose to use the REDIS_URL, you can leave all the other redis-related variables empty. ``` 4. In the **DATA STORE SETTINGS** section, update the variables for any S3-compatible storage: ```bash # DATA STORE SETTINGS USE_MINIO=0 # Set to 0 if using an external S3, 1 if using MinIO (default). AWS_REGION=your-s3-region # For AWS, set the region, e.g., "us-west-1". AWS_ACCESS_KEY_ID=your-s3-access-key # Access key for S3. AWS_SECRET_ACCESS_KEY=your-s3-secret-key # Secret key for S3. AWS_S3_ENDPOINT_URL=https://your-s3-endpoint # URL for S3 API endpoint (e.g., "https://s3.amazonaws.com" for AWS). AWS_S3_BUCKET_NAME=your-s3-bucket-name # Name of the S3 bucket for storing Plane data. MINIO_ROOT_USER= # Leave blank when using external S3. MINIO_ROOT_PASSWORD= # Leave blank when using external S3. BUCKET_NAME= # Leave blank when using external S3. FILE_SIZE_LIMIT=5242880 # Set maximum file upload size in bytes (5MB here). ``` 5. Save your changes to the `plane.env` file. 6. Restart Plane services to apply the new settings using the `setup.sh` script. ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/custom-domain.html' description: >- Configure custom domain for self-hosted Plane. Setup your own domain name for your Plane instance. --- # Configure custom domain During installation, you configure a domain for your instance. If you need to change that domain later, whether you're moving to a production domain, switching to a different hostname, or updating your DNS configuration, this guide walks you through the process. :::info **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. If you're running Kubernetes or another deployment method, the environment variable names are the same, but the configuration method differs based on your setup. ::: :::warning **Plan for downtime**\ Changing domains requires restarting Plane services. Your instance will be unavailable for a few minutes during the restart. Plan accordingly or notify your users. ::: ## Check current domain configuration First, see which environment variables currently reference your old domain. This helps you identify exactly what needs updating. ```bash cat /opt/plane/plane.env | grep ``` **Example output:** ```ini DOMAIN_NAME=localhost SITE_ADDRESS=http://localhost WEB_URL=http://localhost CORS_ALLOWED_ORIGINS=http://localhost,https://localhost ``` This shows you all the variables that contain your current domain. You'll update each of these in the next step. ## Update domain in environment file 1. Open the Plane environment configuration file: ```bash vim /opt/plane/plane.env ``` 2. Find and update these environment variables with your new domain: * **DOMAIN\_NAME** Set this to your bare domain name without protocol: ```ini DOMAIN_NAME=plane.company.com ``` Don't include `http://` or `https://` here, just the hostname. * **SITE\_ADDRESS** Set this to your full domain URL: ```ini SITE_ADDRESS=https://plane.company.com ``` Include the protocol (`https://` for SSL, `http://` if you haven't set up SSL yet). * **WEB\_URL** This should match your SITE\_ADDRESS: ```ini WEB_URL=https://plane.company.com ``` Again, include the full protocol. **CORS\_ALLOWED\_ORIGINS** List all domains that should be allowed to make cross-origin requests to your Plane instance. This typically includes both HTTP and HTTPS versions of your domain: ```ini CORS_ALLOWED_ORIGINS=https://plane.company.com,http://plane.company.com ``` Separate multiple entries with commas, no spaces. If you have multiple domains or subdomains that need access, add them all here. ## Restart Plane services Apply your configuration changes by restarting Plane: ```bash sudo prime-cli restart ``` This process typically takes a few minutes. You'll see output indicating the status of each service as it restarts. ::: details Community Edition Our steps differ slightly depending on whether you are hosting on a public IP or a private/internal IP. Follow the steps listed below. #### Update configuration in .env file Open your project's `.env` file in a text editor. This file contains configuration settings for your application. Locate the following lines: ``` WEB_URL= CORS_ALLOWED_ORIGINS= ``` Replace `` with your actual domain name, including the protocol (http:// or https://). For example: ``` WEB_URL=https://example.com CORS_ALLOWED_ORIGINS=https://example.com ``` If you are hosting Plane on a public IP, then follow the steps here. However, if you are hosting Plane on an internal IP then follow these steps. #### Set DNS A record (for public IP) If your server has a public IP address, you need to configure the DNS A record to point to this IP address. This allows users to access your application using your custom domain name. Here’s how to do it: * Log in to your domain registrar's website or DNS hosting provider. * Navigate to the DNS management section. * Find the option to edit your domain's DNS records. * Add a new A record with the hostname set to `@` (or your subdomain if applicable) and the IP address set to your server's public IP address. * Save the changes. It may take some time for the DNS changes to propagate. #### Configure reverse proxy (for internal IP) If your server is behind a firewall or router and has an internal IP address, you'll need to set up a reverse proxy to route requests from your custom domain to your server. Follow these steps: * Configure a CNAME record in your domain's DNS settings that points to your reverse proxy server's hostname. This allows your domain to resolve to the reverse proxy server. * Set up reverse proxy redirection on your reverse proxy server to forward incoming requests to your server's internal IP address and port. * Depending on the reverse proxy software you're using (e.g., Nginx, Apache, etc.), the configuration process may vary. Refer to the documentation for your specific reverse proxy server for detailed instructions on setting up reverse proxy redirection. * Once the reverse proxy is properly configured, ensure that your firewall/router allows incoming traffic on the necessary ports to reach your server. By following these steps, you will be able to access your self-hosted instance of Plane using your custom domain name, whether your server has a public IP address or is behind a firewall with an internal IP address. --- --- url: 'https://developers.plane.so/self-hosting/govern/configure-ssl.html' description: >- Configure SSL/TLS certificates for Plane. Setup HTTPS encryption for secure self-hosted Plane deployment. --- # Set up SSL This guide shows you how to configure SSL/TLS certificates for your self-hosted Plane instance. Plane handles certificate provisioning and renewal automatically using Let's Encrypt. ::: info **Applies to:** Docker deployments of Plane Commercial Edition without an external reverse proxy. If you're using an external reverse proxy (nginx, Caddy, Traefik) or a load balancer, configure SSL there instead and skip this guide. ::: ## Before you begin Ensure you have: * A registered domain name pointing to your Plane server * DNS records configured (A or CNAME record pointing to your server's IP) * Ports 80 and 443 open on your server's firewall * Prime CLI installed (included with Plane Commercial Edition) ::: warning **DNS must be configured first.** Let's Encrypt validates domain ownership by making HTTP requests to your domain. Ensure your domain resolves to your server's IP address before proceeding. ::: ## Configure SSL settings ### Open the configuration file Edit your Plane environment configuration: ```bash vim /opt/plane/plane.env ``` ### Set required variables Add or update these environment variables: ```bash # SSL Configuration CERT_EMAIL=admin@yourcompany.com SITE_ADDRESS=plane.yourcompany.com WEB_URL=https://plane.yourcompany.com ``` **Variable explanations:** **CERT\_EMAIL**\ A valid email address for Let's Encrypt certificate registration. Let's Encrypt uses this to send renewal reminders and important notices about your certificates. **SITE\_ADDRESS**\ Your domain name **without** protocol. Use only the domain (e.g., `plane.company.com`), not `https://plane.company.com`. Plane's built-in proxy uses this to request certificates from Let's Encrypt. **WEB\_URL**\ Your full Plane URL **with** the `https://` protocol. This tells Plane services how to construct URLs for redirects, emails, and API responses. ### DNS provider configuration (optional) If you're using Cloudflare or another DNS provider with API access, you can use DNS validation instead of HTTP validation. This is useful if: * Your server is behind a firewall that blocks port 80 * You need wildcard certificates * HTTP validation isn't working due to network restrictions **For Cloudflare:** ```bash CERT_ACME_DNS=acme_dns cloudflare ``` Replace `` with your Cloudflare API token. Create one at **Cloudflare Dashboard** → **My Profile** → **API Tokens** with **Zone:DNS:Edit** permissions. **For other DNS providers:** Check the [acme.sh DNS API documentation](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) for provider-specific configuration. ## Apply SSL configuration Restart Plane to apply the SSL settings: ```bash sudo prime-cli restart ``` Prime CLI will: 1. Stop all Plane services 2. Request a new SSL certificate from Let's Encrypt 3. Configure the built-in proxy to use HTTPS 4. Restart all services with SSL enabled This process typically takes 30-60 seconds. ## Verify SSL is working Check that your Plane instance is accessible via HTTPS: ```bash curl -I https://plane.yourcompany.com ``` You should see a response with `HTTP/2 200` or `HTTP/1.1 200` and SSL-related headers. Visit your Plane instance in a browser at `https://plane.yourcompany.com`. You should see a secure connection (padlock icon) without certificate warnings. ## Using custom SSL certificates Custom SSL certificates (from a corporate CA or purchased certificates) are not currently supported in Plane's deployment. --- --- url: 'https://developers.plane.so/self-hosting/govern/integrations/github.html' description: >- Connect GitHub to your self-hosted Plane instance. Sync pull requests, commits, and branches with Plane work items for seamless development tracking. --- # Configure GitHub for Plane integration This guide walks you through setting up a GitHub App to enable GitHub integration for your Plane workspace on a self-hosted instance. Since self-hosted environments don’t come pre-configured for GitHub, you’ll need to set up the necessary authentication, permissions, and webhooks to ensure smooth integration. This guide covers configuration for both: * **[GitHub Cloud](/self-hosting/govern/integrations/github#github-cloud)** The standard cloud-hosted GitHub service * **[GitHub Enterprise Server](/self-hosting/govern/integrations/github#github-enterprise-server)** Self-hosted GitHub instances for organizations with specific compliance or security requirements In this guide, you’ll: 1. [Create and configure a GitHub App](/self-hosting/govern/integrations/github#create-github-app) 2. [Set up permissions and events](/self-hosting/govern/integrations/github#set-up-permissions-and-events) 3. [Configure your Plane instance](/self-hosting/govern/integrations/github#configure-plane-instance) ::: warning **Activate GitHub integration** After creating and configuring the GitHub app and configuring the instance as detailed on this page, you'll need to [setup the GitHub integration](https://docs.plane.so/integrations/github) within Plane. ::: ## Create GitHub App To configure GitHub integration, you'll need to create a GitHub App within your organization. :::tabs key:github-edition \== GitHub Cloud {#github-cloud} Follow these steps to create a GitHub App, set callback URLs, and configure webhooks so Plane can sync PRs and commits from GitHub Cloud. #### GitHub Cloud 1. Go to **Settings > Developer Settings > GitHub Apps** in your GitHub organization. 2. Click **New GitHub App**. ![Create GitHub App](/images/integrations/github/create-github-app.webp#hero) 3. In the **Register new GitHub App** page, provide a **GitHub App name** and **Homepage URL**. ![App name and homepage URL](/images/integrations/github/app-name-homepage-url.webp#hero) 4. In the **Identifying and authorizing users** section, add the following **Callback URLS**. ```bash https:///silo/api/github/auth/callback https:///silo/api/github/auth/user/callback ``` These URLs allow Plane to verify and enable workspace connection with the Github App. ![Add Callback URL](/images/integrations/github/add-callback-url.webp#hero) :::warning Make sure to opt out of **Expire user authorization tokens** feature. ::: 5. In the **Post installation** section, add the below **Setup URL**. ```bash https:///silo/api/github/auth/callback ``` Redirects users to this URL after GitHub app installation. ![Add setup URL](/images/integrations/github/add-setup-url.webp#hero) 6. Turn on **Redirect on update**. 7. In the **Webhook** section, add the below **Webhook URL**. ```bash https:///silo/api/github/github-webhook ``` This allows Plane to receive updates from GitHub repositories. ![Add Webhook URL](/images/integrations/github/add-webhook-url.webp#hero) \== GitHub Enterprise Server {#github-enterprise-server} These steps cover hostname, callback URLs, and private key differences for on‑prem GitHub deployments. #### GitHub Enterprise Server 1. Go to **Settings > Developer Settings > GitHub Apps** in your GitHub organization. 2. Click **New GitHub App**. ![Create GitHub App](/images/integrations/github/create-github-app.webp#hero) 3. In the **Register new GitHub App** page, provide a **GitHub App name** and **Homepage URL**. ![App name and homepage URL](/images/integrations/github/app-name-homepage-url.webp#hero) 4. In the **Identifying and authorizing users** section, add the following **Callback URLS**. **For Plane cloud instance** ```bash https://silo.plane.so/api/oauth/github-enterprise/auth/callback https://silo.plane.so/api/oauth/github-enterprise/auth/user/callback ``` **For Plane self-hosted instance** ```bash https:///silo/api/oauth/github-enterprise/auth/callback https:///silo/api/oauth/github-enterprise/auth/user/callback ``` These URLs allow Plane to verify and enable workspace connection with the Github App. ![Add Callback URL](/images/integrations/github/add-callback-url.webp#hero) :::warning Make sure to opt out of **Expire user authorization tokens** feature. ::: 5. In the **Post installation** section, add the below **Setup URL**. **For Plane cloud instance** ```bash https://silo.plane.so/api/oauth/github-enterprise/auth/callback ``` **For Plane self-hosted instance** ```bash https:///silo/api/oauth/github-enterprise/auth/callback ``` Redirects users to this URL after GitHub app installation. ![Add setup URL](/images/integrations/github/add-setup-url.webp#hero) 6. Turn on **Redirect on update**. 7. In the **Webhook** section, add the below **Webhook URL**. **For Plane cloud instance** ```bash https://silo.plane.so/api/github-enterprise/github-webhook ``` **For Plane self-hosted instance** ```bash https:///silo/api/github-enterprise/github-webhook ``` This allows Plane to receive updates from GitHub repositories. ![Add Webhook URL](/images/integrations/github/add-webhook-url.webp#hero) ::: ### Set up permissions and events 1. Add repository and account permissions by setting the **Access** dropdown next to each permission, as shown in the tables below. ![Setup permissions](/images/integrations/github/setup-permissions.webp#hero) **Repository permissions** | Permission            | Access level     | Purpose | | ---------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | Issues | Read and write | Enables reading, creating, updating, closing, and commenting on issues within the repository. | | Metadata | Read-only | Provides read-only access to repository metadata, such as its name, description, and visibility. | | Pull requests | Read and write | Allows reading, creating, updating, merging, and commenting on pull requests. | **Account permissions** | Permission           | Access level     | Purpose | | ---------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------- | | Email addresses | Read-only | Grants access to users' email addresses, typically for notifications or communication. | | Profile | Read and write | Enables access to user profile details like name, username, and avatar. | 2. In the **Subscribe to events** section, turn on all the required events below. ![Subscribe to events](/images/integrations/github/subscribe-to-events.webp#hero) | Event                                             | Purpose | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Installation target | This is where the repositories or organizations where your GitHub App is installed. This determines which repositories Plane can sync with. | | Meta | Includes metadata about the app's configuration and setup. This is essential for maintaining integration stability. | | Issue comment | Triggers when a comment is added, edited, or deleted on an issue. Useful for keeping comments synced between Plane and GitHub. | | Issues | Triggers when an issue is created, updated, closed, reopened, assigned, labeled, or transferred. Ensures issue status and details remain consistent between Plane and GitHub. | | Pull request | Fires when a pull request is opened, closed, merged, edited, or labeled. Essential for tracking development progress. | | Pull request review | Activates when a review is submitted, edited, or dismissed. Keeps review activities aligned between Plane and GitHub. | | Pull request review comment | Fires when a review comment is added, modified, or removed. Ensures feedback is reflected across both platforms. | | Pull request review thread | Triggers when a review discussion thread is resolved or reopened. Helps maintain visibility on code review discussions. | | Push | Activates when new commits are pushed to a repository. Useful for tracking code updates and changes. | | Repository sub issues | Tracks issues within a repository that are linked to or managed by another issue. Ensures accurate synchronization of related issues. | 3. Click the **Create GitHub App** button at the bottom of the page. ## Configure Plane instance :::tabs key:github-edition \== GitHub Cloud {#github-cloud} 1. Go back to **Settings > Developer Settings > GitHub Apps**. 2. Click **Edit** on the GitHub you created. 3. In the **General** tab, under the **Client secrets** section, click **Generate a new client secret**. ![General tab](/images/integrations/github/general-tab.webp#hero) 4. Scroll down to the **Private keys** section. ![Private keys](/images/integrations/github/private-keys.webp#hero) 5. Click **Genereate a private key**. 6. Retrieve the following details from the **General** tab: * App ID * Client ID * Client secret * GitHub App name * Private key 7. Before adding the Private key as an environment variable, you'll need to convert it to base64. Since private keys are typically multi-line, they can cause parsing errors or issues when setting environment variables. To avoid this, run the following command to convert the key to base64: ```bash cat private_key.pem | base64 -w 0 ``` 8. Add these environment variables with the values to your Plane instance's `.env` file. ```bash GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GITHUB_APP_NAME= GITHUB_APP_ID= GITHUB_PRIVATE_KEY= ``` 9. Save the file and restart the instance. 10. Once you've completed the instance configuration, [activate the GitHub integration in Plane](https://docs.plane.so/integrations/github). \== GitHub Enterprise Server {#github-enterprise-server} 1. Go back to **Settings > Developer Settings > GitHub Apps**. 2. Click **Edit** on the GitHub you created. 3. In the **General** tab, under the **Client secrets** section, click **Generate a new client secret**. ![General tab](/images/integrations/github/general-tab.webp#hero) 4. Scroll down to the **Private keys** section. ![Private keys](/images/integrations/github/private-keys.webp#hero) 5. Click **Generate a private key**. 6. Retrieve the following details from the **General** tab: * App ID * App Slug (You can find this in browser url) * Client ID * Client secret * Private key 7. Convert the Private key to convert it to base64. Since private keys are typically multi-line, they can cause parsing errors or issues when setting environment variables. To avoid this, run the following command to convert the key to base64: ```bash cat private_key.pem | base64 -w 0 ``` 8. Once you've created the app, [activate the GitHub Enterprise integration in Plane](https://docs.plane.so/integrations/github?edition=github-enterprise#connect-github-organization). ::: ## Troubleshooting ### Invalid private key This error usually occurs when the private key is not correctly generated. To fix this, follow the below steps. 1. Generate a new private key. 2. Convert the private key to base64. ```bash cat private_key.pem | base64 -w 0 ``` 3. Add the private key to the `.env` file. ```bash GITHUB_PRIVATE_KEY= ``` 4. Save the file and restart the instance. ### Unable to connect GitHub organization account or personal account This error usually occurs when the callback URL is not correctly configured or the GitHub App is not marked public. To fix this, follow the below steps. 1. Check if the callback URL is correctly configured. 2. Check if your GitHub App is marked public. ### Application secret value not found This error usually occurs when the application secret is not correctly configured. To fix this, follow the below steps. 1. Delete the `plane_app_details_github` key from redis cache. `del plane_app_details_github`. 2. Set the `SILO_BASE_URL` in env with plane self hosted url and restart the api server. `export SILO_BASE_URL=https://` 3. Run this command in api server shell `python manage.py reset_marketplace_app_secrets` to reset the application secrets. 4. Try to connect again to the organization account to Plane. ### Github integration suddenly stopped working after a while This error usually occurs when the GitHub integration is not correctly configured. To fix this, follow the below steps. 1. Make sure you've `opted out` of Server Token expiration and reconnect once again to the organization account to Plane. Check in Github App Settings > Optional Features --- --- url: 'https://developers.plane.so/self-hosting/govern/integrations/gitlab.html' description: >- Connect GitLab to your self-hosted Plane instance. Sync merge requests and commits with Plane work items for development workflow tracking. --- # Configure GitLab for Plane integration This guide walks you through setting up a GitLab application to enable GitLab integration for your Plane workspace on a self-hosted instance. Since self-hosted environments don’t come pre-configured for GitLab, you’ll need to create an application, configure authentication, and set the necessary permissions to ensure seamless integration. This guide covers configuration for both: * **[GitLab.com](/self-hosting/govern/integrations/gitlab#gitlab-cloud)** The standard cloud-hosted GitLab service * **[GitLab Self-managed](/self-hosting/govern/integrations/gitlab#gitlab-self-managed)** Self-hosted GitLab instances for organizations with specific compliance or security requirements In this guide, you’ll: 1. [Create and configure a GitLab Application](/self-hosting/govern/integrations/gitlab#create-gitlab-application) 2. [Configure your Plane instance](/self-hosting/govern/integrations/gitlab#configure-plane-instance) ::: warning **Activate GitLab integration** After creating and configuring the GitLab application and configuring the instance as detailed on this page, you'll need to [setup the GitLab integration](https://docs.plane.so/integrations/gitlab) within Plane. ::: ## Create GitLab Application :::tabs key:gitlab-edition \== GitLab Cloud {#gitlab-cloud} Follow these steps to register an application on the public GitLab service, set the redirect URI and scopes, and then configure your Plane instance so it can sync merge requests and commits. #### GitLab Cloud 1. On the left sidebar in GitLab, select your avatar. 2. Select **Preferences** tab. 3. Navigate to the **Applications** tab. 4. Click on **Add new application** to begin the setup. ![Add GitLab application](/images/integrations/gitlab/add-gitlab-application.webp#hero) 5. Provide a **Name** for your application. 6. Enter the following **Redirect URI**, replacing \[YOUR\_DOMAIN] with your actual domain: ```bash https://[YOUR_DOMAIN]/silo/api/gitlab/auth/callback ``` 7. Check the **Confidential** box. ![Add app details](/images/integrations/gitlab/add-app-details.webp#hero) 8. Set permissions by selecting the required **Scopes**. The table below explains each scope: | Permission | Explanation | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api` | Grants full read/write access to the API, including all groups, projects, container registry, dependency proxy, and package registry. Required for API requests. | | `read_api` | Allows read-only access to all groups, projects, container registry, and package registry. | | `read_user` | Grants read-only access to user profiles via the /user API endpoint, including username, public email, and full name. Also provides access to /users endpoints. | | `read_repository` | Enables read-only access to repositories in private projects via Git-over-HTTP or the Repository Files API. | | `profile` | Grants read-only access to the user's profile data using OpenID Connect. | | `email` | Provides read-only access to the user's primary email address using OpenID Connect. | 9. Click **Save Application** to finalize the setup. \== GitLab Self-managed {#gitlab-self-managed} These instructions cover registering an OAuth app on your private GitLab server, using the correct callback URLs, and assigning the required scopes for Plane to access your repos and users. #### GitLab Self-managed 1. Log in to your GitLab instance. 2. Click on your profile icon in the top-right corner. 3. From the dropdown menu that appears, select **Edit profile**. 4. Look for and select the **Applications** option within this menu. 5. On the Applications page, click **Add new application** to begin configuring your OAuth application. Fill in the application details with the following configuration: * **Name** Enter a descriptive name for your application (e.g., `Plane Local Dev` or `Plane Integration`). * **Redirect URI** The redirect URI depends on your Plane deployment: **For Plane Cloud:** `https://silo.plane.so/api/oauth/gitlab-enterprise/auth/callback` **For Plane Self-Hosted:** `https:///silo/api/oauth/gitlab-enterprise/auth/callback` Replace `` with your actual Plane instance domain. * **Confidential** Keep the **Confidential** checkbox enabled. This ensures the application uses a client secret for secure authentication. * **Scopes** Select the following scopes to grant Plane the necessary permissions: * **api** - Grants complete read/write access to the API, including all groups and projects * **read\_api** - Grants read access to the API, including all groups and projects * **read\_user** - Grants read-only access to your profile information * **read\_repository** - Grants read-only access to repositories on private projects * **profile** - Grants read-only access to the user's profile data using OpenID Connect * **email** - Grants read-only access to the user's primary email address using OpenID Connect 6. Click **Save application** to create the OAuth application. ::: ## Configure Plane instance :::tabs key:gitlab-edition \== GitLab Cloud {#gitlab-cloud} 1. Copy the **Application ID** and **Secret** from the newly created application. ![Copy credentials](/images/integrations/gitlab/copy-credentials.webp#hero) 2. Add these environment variables with the values to your Plane instance's `.env` file. ```bash GITLAB_CLIENT_ID= GITLAB_CLIENT_SECRET= ``` 3. Save the file and restart the instance. 4. Once you've completed the instance configuration, [activate the GitLab integration in Plane](https://docs.plane.so/integrations/gitlab?edition=gitlab-cloud). \== GitLab Self-managed {#gitlab-self-managed} 1. Copy the **Application ID** and **Secret** from the newly created application. ![Copy credentials](/images/integrations/gitlab/copy-credentials.webp#hero) 2. Once you've created the application, [activate the GitLab Self-managed integration in Plane](https://docs.plane.so/integrations/gitlab?edition=gitlab-self-managed). ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/integrations/sentry.html' description: >- Connect Sentry error monitoring to your self-hosted Plane instance. Automatically create work items from Sentry alerts and track error resolution. --- # Configure Sentry for Plane integration This guide shows you how to set up Sentry integration for your self-hosted Plane instance. Unlike Plane Cloud where Sentry comes pre-configured, self-hosted instances require you to create a custom integration in Sentry and configure your Plane deployment with the necessary credentials. ::: info **What you'll accomplish:** 1. Create a Sentry custom integration with proper permissions and webhooks 2. Configure your Plane instance with Sentry credentials 3. Enable error tracking and automatic issue creation from Sentry alerts ::: ## Before you begin You'll need: * Administrator access to your Sentry organization * Access to your Plane instance configuration files * Your Plane instance domain (e.g., `plane.yourcompany.com`) ## Create Sentry custom integration A custom integration (also called a public integration) connects your Sentry organization to Plane, enabling bidirectional communication for issue tracking and alert handling. 1. Log in to your Sentry organization. 2. Go to **Settings** → **Developer Settings** → **Custom Integrations**. 3. Click **Create New Integration**. 4. Select **Public Integration**. 5. Fill in these fields on the integration creation screen: | Field | Value | | ----------------------- | ----------------------------------------------------------- | | **Name** | `Plane` (or any name you prefer) | | **Author** | Your organization name | | **Webhook URL** | `https://[YOUR_DOMAIN]/silo/api/sentry/sentry-webhook/` | | **Redirect URL** | `https://[YOUR_DOMAIN]/silo/api/oauth/sentry/auth/callback` | | **Verify Installation** | Disabled (recommended) | | **Alert Rule Action** | Enabled | ::: tip Replace `[YOUR_DOMAIN]` with your actual Plane instance domain. For example, if your Plane instance is at `plane.company.com`, your Webhook URL would be `https://plane.company.com/silo/api/sentry/sentry-webhook/` ::: **Field explanations:** **Webhook URL**\ Sentry sends event notifications to this endpoint. Plane processes these webhooks to sync Sentry issues with Plane work items. **Redirect URL**\ After OAuth authorization, Sentry redirects users back to this URL to complete the connection. **Alert Rule Action**\ Enables automatic Plane work item creation when Sentry alert rules trigger. ### Configure integration schema The schema defines how Sentry and Plane interact—what fields appear when creating issues and how alert rules behave. Paste this schema into the **Schema** field: ```json { "elements": [ { "link": { "uri": "/api/sentry/issues/link", "required_fields": [ { "uri": "/api/sentry/issues", "name": "identifier", "type": "select", "label": "Issue", "skip_load_on_open": true } ] }, "type": "issue-link", "create": { "uri": "/api/sentry/issues/create", "optional_fields": [ { "uri": "/api/sentry/users", "name": "assignee_ids", "type": "select", "async": false, "label": "Assignees", "multiple": true, "depends_on": ["project_id"] }, { "uri": "/api/sentry/priorities", "name": "priorities", "type": "select", "async": false, "label": "Priorities", "depends_on": ["project_id"] }, { "uri": "/api/sentry/labels", "name": "labels", "type": "select", "async": false, "label": "Labels", "multiple": true, "depends_on": ["project_id"] }, { "uri": "/api/sentry/states", "name": "state", "type": "select", "async": false, "label": "State", "depends_on": ["project_id"] }, { "uri": "/api/sentry/modules", "name": "module", "type": "select", "async": false, "label": "Module", "depends_on": ["project_id"] }, { "uri": "/api/sentry/cycles", "name": "cycle", "type": "select", "async": false, "label": "Cycle", "depends_on": ["project_id"] } ], "required_fields": [ { "name": "title", "type": "text", "label": "Title", "default": "issue.title" }, { "name": "description", "type": "textarea", "label": "Description", "default": "issue.description" }, { "uri": "/api/sentry/projects", "name": "project_id", "type": "select", "async": false, "label": "Project" } ] } }, { "type": "alert-rule-action", "title": "Create Plane Work Item or Intake Issue", "settings": { "uri": "/api/sentry/alert-rule", "type": "alert-rule-settings", "description": "Create a Plane Work Item or Intake Issue when an alert is triggered", "optional_fields": [ { "uri": "/api/sentry/users", "name": "assignee_ids", "type": "select", "async": false, "label": "Assignees", "multiple": true, "depends_on": ["project_id"] }, { "uri": "/api/sentry/states", "name": "state", "type": "select", "async": false, "label": "State", "depends_on": ["project_id"] }, { "uri": "/api/sentry/labels", "name": "labels", "type": "select", "async": false, "label": "Labels", "multiple": true, "depends_on": ["project_id"] } ], "required_fields": [ { "name": "type", "type": "select", "label": "Type", "options": [ ["intake", "Intake"], ["work_item", "Work Item"] ] }, { "uri": "/api/sentry/projects", "name": "project_id", "type": "select", "async": false, "label": "Project", "depends_on": ["type"] } ] } } ] } ``` **What this schema enables** **Work item linking**\ The first element defines how users create new Plane work items from Sentry or link existing ones. Required fields (title, description, project) ensure every work item has essential information. Optional fields (assignees, priority, labels, state, module, cycle) provide flexibility for detailed work item tracking. **Alert rule actions**\ The second element enables automatic work item creation when Sentry alerts fire. You can configure whether alerts create regular work items or intake work items for triage, and set default assignees, states, and labels. ### Set permissions Configure these permissions to allow Sentry to interact with Plane appropriately: | Permission | Access Level | Why This Matters | | ----------------- | ------------ | ------------------------------------------------------------ | | **Project** | Read | Access project details, tags, and debug files from Sentry | | **Team** | Read | Retrieve team member lists for assignee dropdowns | | **Release** | No Access | Not required for Plane integration | | **Distribution** | No Access | Not required for Plane integration | | **Issue & Event** | Read & Write | Create and link issues, sync status updates bidirectionally | | **Organization** | Read | Resolve organization IDs and retrieve repository information | | **Member** | Read | Access member details for assignee functionality | | **Alerts** | Read | Enable alert rule actions for automatic issue creation | ### Enable webhooks Webhooks keep Plane and Sentry synchronized. When issues change in Sentry, Plane receives notifications and updates accordingly. Enable the **issue** webhook with these events: | Event | Why It's Needed | | -------------- | -------------------------------------------------------------- | | **created** | Notify Plane when new Sentry issues are detected | | **resolved** | Update linked Plane work items when Sentry issues are resolved | | **assigned** | Sync assignee changes from Sentry to Plane | | **archived** | Reflect archived status in Plane | | **unresolved** | Update Plane when resolved issues reopen | ### Save and retrieve credentials After saving your integration, Sentry generates OAuth credentials: * **Client ID** - A public identifier for your integration * **Client Secret** - A private key used to authenticate API requests ::: warning **Important** The Client Secret is only displayed once immediately after creating the integration. Copy it now and store it securely. If you lose it, you'll need to regenerate the integration. ::: Copy both the Client ID and Client Secret. You'll need these in the next step. ## Configure your Plane instance Add Sentry credentials to your Plane instance so it can communicate with Sentry's API. ### Locate your configuration file **For Docker deployments:** * Edit `plane.env` in your Plane installation directory **For Kubernetes deployments:** * Edit your `custom-values.yaml` file or ConfigMap containing environment variables ### Add environment variables Add these variables to your Plane configuration: **For Docker (`plane.env`):** ```bash # Sentry Integration SENTRY_BASE_URL=https://sentry.io SENTRY_CLIENT_ID= SENTRY_CLIENT_SECRET= SENTRY_INTEGRATION_SLUG=plane ``` **For Kubernetes (`custom-values.yaml`):** ```yaml env: silo_envs: sentry_base_url: "https://sentry.io" sentry_client_id: "" sentry_client_secret: "" sentry_integration_slug: "plane" ``` Replace `` and `` with the credentials from Step 1. ### Environment variable reference | Variable | Required | Default | Description | | ------------------------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SENTRY_BASE_URL` | No | `https://sentry.io` | Base URL of your Sentry instance. For self-hosted Sentry, use your Sentry domain (e.g., `https://sentry.company.com`) | | `SENTRY_CLIENT_ID` | Yes | - | Client ID from your Sentry custom integration | | `SENTRY_CLIENT_SECRET` | Yes | - | Client Secret from your Sentry custom integration (only shown once during creation) | | `SENTRY_INTEGRATION_SLUG` | No | - | The slug identifier for your integration. Find this in your integration's URL: `https://org.sentry.io/settings/developer-settings/plane-local` (here `plane-local` is the slug) | ::: info **Using self-hosted Sentry?**\ If you're running your own Sentry instance, change `SENTRY_BASE_URL` to your Sentry domain. All other configuration remains the same. ::: ### Restart Plane Apply the configuration changes: **For Docker:** ```bash docker compose down docker compose up -d ``` **For Kubernetes:** ```bash helm upgrade plane-app plane-enterprise.tgz \ --namespace plane \ -f custom-values.yaml ``` ## Activate integration in your workspace Once you’ve completed the instance configuration, [activate the Sentry integration](https://docs.plane.so/integrations/sentry#set-up-sentry-integration) in Plane. For questions about Sentry integration, contact . --- --- url: 'https://developers.plane.so/self-hosting/govern/integrations/slack.html' description: >- Connect Slack to your self-hosted Plane instance. Get notifications for work item updates and interact with Plane from Slack channels. --- # Configure Slack for Plane integration This guide walks you through setting up a Slack App to enable Slack integration for your Plane workspace on a self-hosted instance. Since self-hosted environments don’t come pre-configured for Slack, you’ll need to set up the necessary authentication, permissions, and event subscriptions to ensure seamless communication between Plane and Slack. In this guide, you’ll: 1. [Create and configure a Slack App](/self-hosting/govern/integrations/slack#create-slack-app) 2. [Configure your Plane instance](/self-hosting/govern/integrations/slack#configure-plane-instance) ::: warning **Activate Slack integration** After creating and configuring the Slack app and configuring the instance as detailed on this page, you'll need to [set up the Slack integration](https://docs.plane.so/integrations/slack) within Plane. ::: ## Create Slack App To configure Slack integration, you'll need to create a Slack App within your organization. Follow these steps: 1. Go to [Your Apps](https://api.slack.com/apps) on Slack. 2. Click **Create an App**. ![Create Slack App](/images/integrations/slack/create-slack-app.webp#hero) 3. Choose **From a manifest**. ![Choose Manifest](/images/integrations/slack/choose-from-manifest.webp#hero) 4. Select the workspace where you want the app installed. 5. Remove the default manifest and paste the one below, making sure to update the placeholders with your actual values. ![Manifest](/images/integrations/slack/app-from-manifest.webp#hero) :::tabs key:manifest-file \== JSON {json} ```json { "display_information": { "name": "[YOUR_APP_NAME]", "description": "[YOUR_APP_DESCRIPTION]", "background_color": "#224dab" }, "features": { "bot_user": { "display_name": "[YOUR_APP_NAME]", "always_online": false }, "shortcuts": [ { "name": "Create new issue", "type": "message", "callback_id": "issue_shortcut", "description": "Create a new issue in plane" }, { "name": "Link Work Item", "type": "message", "callback_id": "link_work_item", "description": "Links thread with an existing work item" } ], "slash_commands": [ { "command": "/plane", "url": "https://[YOUR_DOMAIN]/silo/api/slack/command/", "description": "Create issue in Plane", "should_escape": false } ], "unfurl_domains": ["[YOUR_DOMAIN]"] }, "oauth_config": { "redirect_urls": [ "https://[YOUR_DOMAIN]/silo/api/slack/team/auth/callback/", "https://[YOUR_DOMAIN]/silo/api/slack/user/auth/callback/" ], "scopes": { "user": ["chat:write", "identify", "im:read", "im:write", "links:write", "links:read"], "bot": [ "channels:join", "channels:read", "users:read", "users:read.email", "chat:write", "chat:write.customize", "channels:history", "groups:history", "mpim:history", "im:history", "links:read", "links:write", "groups:read", "im:read", "mpim:read", "reactions:read", "reactions:write", "files:read", "files:write", "im:write", "commands" ] } }, "settings": { "event_subscriptions": { "request_url": "https://[YOUR_DOMAIN]/silo/api/slack/events", "bot_events": ["link_shared", "message.channels", "message.im"] }, "interactivity": { "is_enabled": true, "request_url": "https://[YOUR_DOMAIN]/silo/api/slack/action/", "message_menu_options_url": "https://[YOUR_DOMAIN]/silo/api/slack/options/" }, "org_deploy_enabled": false, "socket_mode_enabled": false, "token_rotation_enabled": true } } ``` \== YAML {yaml} ```yaml display_information: name: [YOUR_APP_NAME] description: [YOUR_APP_DESCRIPTION] background_color: "#224dab" features: bot_user: display_name: [YOUR_APP_NAME] always_online: false shortcuts: - name: Create new issue type: message callback_id: issue_shortcut description: Create a new issue in plane - name: Link Work Item type: message callback_id: link_work_item description: Links thread with an existing work item slash_commands: - command: /plane url: https://[YOUR_DOMAIN]/silo/api/slack/command/ description: Create issue in Plane should_escape: false unfurl_domains: - [YOUR_DOMAIN] oauth_config: redirect_urls: - https://[YOUR_DOMAIN]/silo/api/slack/team/auth/callback/ - https://[YOUR_DOMAIN]/silo/api/slack/user/auth/callback/ scopes: user: - chat:write - identify - im:read - im:write - links:write - links:read bot: - channels:join - channels:read - users:read - users:read.email - chat:write - chat:write.customize - channels:history - groups:history - mpim:history - im:history - links:read - links:write - groups:read - im:read - mpim:read - reactions:read - reactions:write - files:read - files:write - im:write - commands settings: event_subscriptions: request_url: https://[YOUR_DOMAIN]/silo/api/slack/events bot_events: - link_shared - message.channels - message.im interactivity: is_enabled: true request_url: https://[YOUR_DOMAIN]/silo/api/slack/action/ message_menu_options_url: https://[YOUR_DOMAIN]/silo/api/slack/options/ org_deploy_enabled: false socket_mode_enabled: false token_rotation_enabled: true ``` ::: 6. Review the permissions and click **Create**. ![Review summary](/images/integrations/slack/review-summary.webp#hero) ### Manifest reference The manifest file defines the configuration for integrating Plane with Slack. It requests access to several features, enabling Plane to interact with Slack efficiently. #### Features | Feature | Explanation | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `bot_user` | Required to send thread messages while syncing issues or sending Plane notifications to Slack. | | `slack_commands` | A Slack command (`/plane`) allows users to create issues directly from Slack using a slash command. | | `shortcuts` | After activation, users can create issues from messages inside Slack. | | `unfurl_domain` | Specifies the domain where Plane is hosted. When an issue, cycle, or module link is pasted in Slack, it generates a preview of the entity. | #### Variables | Variable | Explanation | | ---------------------- | ----------------------------------------------------------------------------------------------------------- | | `YOUR_DOMAIN` | The domain where Plane is hosted. This is required for sending webhook events and authentication callbacks. | | `YOUR_APP_NAME` | The name you want to give your Slack app. "Plane" is a good default option. | | `YOUR_APP_DESCRIPTION` | A short description of your Slack app’s purpose. | #### Event subscription For thread sync and link unfurling to work, event subscriptions must be enabled. These events send relevant activity to Plane. | Bot event | Explanation | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `link_shared` | When a link is shared in Slack and its hostname matches `unfurl_domain`, Plane receives the event and generates a preview of the entity. | | `message_channels` | When a message is posted in a channel, an event is triggered in Plane to support thread sync. | | `message_im` | When a direct message (DM) is posted, an event is triggered in Plane to support thread sync. | #### User permissions | Permission | Explanation | | ------------- | -------------------------------------------------------------------------------- | | `chat:write` | Allows the bot to send messages in channels and conversations it is a member of. | | `identify` | Allows the bot to verify its own identity and retrieve basic information. | | `im:read` | Enables the bot to view direct messages (DMs) where it has been added. | | `im:write` | Allows the bot to send direct messages (DMs) to users. | | `links:write` | Permits the bot to add, edit, and remove link unfurls. | | `links:read` | Allows the bot to view link unfurls and associated metadata. | #### Bot permissions | Permission | Explanation | | ---------------------- | -------------------------------------------------------------------------- | | `channels:join` | Allows the bot to join public channels. | | `channels:read` | Permits viewing public channel information and members. | | `users:read` | Allows viewing user information and presence status. | | `users:read.email` | Enables access to users' email addresses. | | `chat:write` | Allows sending messages in channels and conversations. | | `chat:write.customize` | Enables customization of the bot's name and profile when sending messages. | | `channels:history` | Allows viewing message history in public channels. | | `groups:history` | Permits viewing message history in private channels. | | `mpim:history` | Enables access to message history in multi-person direct messages. | | `im:history` | Allows viewing message history in direct messages. | | `links:read` | Permits viewing link unfurls and associated metadata. | | `links:write` | Allows adding, editing, and removing link unfurls. | | `groups:read` | Enables viewing private channel information and members. | | `im:read` | Allows viewing direct messages where the bot is added. | | `mpim:read` | Permits viewing multi-person direct messages. | | `reactions:read` | Enables viewing emoji reactions on messages. | | `reactions:write` | Allows adding and removing emoji reactions. | | `files:read` | Permits viewing and downloading files. | | `files:write` | Enables uploading, editing, and deleting files. | | `im:write` | Allows sending direct messages to users. | | `commands` | Enables the bot to add and respond to slash commands. | ## Configure Plane instance After creating your Slack app, follow these steps: 1. Go to the **Event Subscriptions** tab. 2. Click **Retry** to verify your event subscription URL. ![Event subscriptions](/images/integrations/slack/event-subscriptions.webp#hero) 3. Navigate to the **Basic Information** tab on Slack to find your `client_id` and `client_secret`. 4. Add these environment variables with the values to your Plane instance's `.env` file. ```bash SLACK_CLIENT_ID= SLACK_CLIENT_SECRET= ``` 5. Save the file and restart the instance. 6. Once you've completed the instance configuration, [activate the Slack integration in Plane](https://docs.plane.so/integrations/slack). --- --- url: >- https://developers.plane.so/self-hosting/govern/configure-dns-email-service.html description: >- Configure DNS MX records for Plane intake email. Route incoming emails to create work items automatically in your self-hosted instance. --- # Configure DNS for Intake Email This guide explains how to configure DNS settings to enable the [Intake Email](https://docs.plane.so/intake/intake-email) feature for your self-hosted Plane instance. These configurations enable your server to accept messages sent to your project's dedicated Intake address, which are then converted into work items in your project's Intake section. ## Prerequisites Ensure that the Plane server allows inbound traffic on the following email-related ports: `25`, `465`, and `587`. If any of these ports are currently in use, you can free them by running: ```bash fuser -k 25/tcp 465/tcp 587/tcp ``` ## Generate SSL/TLS Certificate for Email Domain ::: warning Mandatory for Docker Compose deployments only. ::: Before configuring DNS records for Intake Email, secure your email domain with an SSL/TLS certificate. This ensures encrypted communication between mail servers and improves email trust and deliverability. 1. **Install Certbot**\ Update your system and install Certbot. ```bash sudo apt update && sudo apt install certbot ``` For NGINX: ```bash sudo apt install python3-certbot-nginx ``` For Apache: ```bash sudo apt install python3-certbot-apache ``` 2. **Generate SSL Certificate**\ Choose the method that matches your web server setup: For NGINX: ```bash sudo certbot --nginx -d ``` For Apache: ```bash sudo certbot --apache -d ``` For standalone (no web server): ```bash sudo certbot certonly --standalone -d ``` 3. **Copy Certificate Files**\ Copy the generated certificate files to Plane's expected directory: ```bash sudo cp /etc/letsencrypt/live//fullchain.pem /opt/plane/data/email/tls/cert.pem sudo cp /etc/letsencrypt/live//privkey.pem /opt/plane/data/email/tls/key.pem ``` 4. **Configure Environment Variables**\ Add the following settings to your plane.env file: ```bash # If using SMTP_DOMAIN as FQDN (e.g., intake.example.com), # generate a valid SSL certificate and set these paths accordingly. SMTP_DOMAIN=intake.example.com TLS_CERT_PATH=tls/cert.pem TLS_PRIV_KEY_PATH=tls/key.pem INTAKE_EMAIL_DOMAIN=intake.example.com ``` ::: warning Important: `SMTP_DOMAIN` and `INTAKE_EMAIL_DOMAIN` must be identical. ::: ## Configure DNS records 1. **Create an A Record**\ This record points to the server running your email service. ```bash Type: A Host: # Example: plane.example.com Value: # Your server's public IP address TTL: Auto | 3600 ``` ::: tip You can alternatively use a CNAME record if you're using a cloud load balancer. ::: 2. **Add an MX Record**\ This record directs email traffic to your mail server. ```bash Type: MX Host: # Example: intake.example.com Value: # Same as your A record host Priority: 10 TTL: Auto | 3600 ``` 3. **Configure an SPF Record**\ This record helps prevent email spoofing. ```bash Type: TXT Host: # Example: intake.example.com Value: "v=spf1 ip4: -all" TTL: Auto | 3600 ``` 4. **Set Up a DMARC record**\ This record specifies how receiving mail servers should handle authentication failures. ```bash Type: TXT Host: _dmarc. # Example: _dmarc.intake.example.com Value: "v=DMARC1; p=reject; rua=mailto:" TTL: Auto | 3600 ``` ## Verify your configuration After setting up your DNS records, verify that they're correctly configured: ```bash # Verify A record dig A # Verify MX record dig MX # Verify SPF record dig TXT # Verify DMARC record dig TXT _dmarc. ``` You can also use [MXToolbox](https://mxtoolbox.com) to check for any issues with your DNS configuration. ## Test your mail server Once your DNS records have propagated, test your SMTP connections: ```bash # Test SMTP connection on standard ports telnet 25 telnet 465 telnet 587 ``` ## Troubleshooting * MX Record issues * Ensure there's a proper dot at the end of the domain. * Check that the priority number is correct (lower = higher priority). * Allow 24-48 hours for DNS changes to fully propagate. * A Record issues * Verify that the IP address is correct. * Ensure your mail subdomain matches the MX record. ## See also [Intake Email](https://docs.plane.so/intake/intake-email) --- --- url: 'https://developers.plane.so/self-hosting/govern/advanced-search.html' description: >- Enable full-text search in Plane with OpenSearch. Configure advanced search indexing for work items, projects, and pages in your self-hosted instance. --- # Configure OpenSearch for advanced search Plane uses OpenSearch to provide advanced search capabilities across your workspace. This guide walks you through setting up OpenSearch integration on your self-hosted instance. ## Before you begin You'll need: * An OpenSearch instance running version 2.19 or later (self-hosted or managed service like AWS OpenSearch). ## What you get with advanced search Once configured, advanced search provides: * **Full-text search** across work items, projects, cycles, modules, pages, and more * **Fuzzy matching** that tolerates typos and variations in spelling * **Autocomplete** with instant suggestions as you type * **Multi-entity search** that searches across all content types in a single query Users can access advanced search using the global search shortcut (Cmd/Ctrl + K) or search within specific projects and sections. ## Configure OpenSearch Set environment variables in your Plane configuration. See [Environment variables reference](/self-hosting/govern/environment-variables#opensearch) for details. ### For Docker deployments 1. **Add configuration to your environment file** Edit `/opt/plane/plane.env`. ```bash # OpenSearch Settings OPENSEARCH_ENABLED=1 OPENSEARCH_URL=https://your-opensearch-instance:9200/ OPENSEARCH_USERNAME=admin OPENSEARCH_PASSWORD=your-secure-password OPENSEARCH_INDEX_PREFIX=plane ``` 2. **Restart Plane services** ```bash prime-cli restart ``` or if managing containers directly: ```bash docker compose down docker compose up -d ``` 3. **Create search indices** Access the API container and create the necessary indices: ```bash # Access the API container docker exec -it plane-api-1 sh # Create all search indices (run once) python manage.py manage_search_index index rebuild --force ``` 4. **Index your existing data** Index all existing content into OpenSearch: ```bash # For small datasets python manage.py manage_search_index document index --force # For large datasets (recommended) python manage.py manage_search_index --background document index --force ``` The background option processes indexing through Celery workers, which is better for instances with large amounts of data. ### For Kubernetes deployments The Plane Helm chart provides auto-setup for OpenSearch. If you're using your own OpenSearch instance, configure it through Helm values. 1. **Configure Helm values** Get the current values file: ```bash helm show values plane/plane-enterprise > values.yaml ``` Edit `values.yaml` to add OpenSearch configuration: ```yaml env: # OpenSearch configuration opensearch_remote_url: "https://your-opensearch-instance:9200/" opensearch_remote_username: "admin" opensearch_remote_password: "your-secure-password" opensearch_index_prefix: "plane" ``` Refer to the [Plane Helm chart documentation](https://artifacthub.io/packages/helm/makeplane/plane-enterprise?modal=values\&path=env.opensearch_remote_url) for complete values structure. 2. **Upgrade your deployment** ```bash helm upgrade --install plane-app plane/plane-enterprise \ --create-namespace \ --namespace plane \ -f values.yaml \ --timeout 10m \ --wait \ --wait-for-jobs ``` 3. **Create search indices** Run these commands in the API pod. ```bash # Get the API pod name API_POD=$(kubectl get pods -n plane --no-headers | grep api | head -1 | awk '{print $1}') # Create all search indices (run once) kubectl exec -n plane $API_POD -- python manage.py manage_search_index index rebuild --force ``` 4. **Index your existing data** Run these commands in the API pod. ```bash # For small datasets kubectl exec -n plane $API_POD -- python manage.py manage_search_index document index --force # For large datasets (recommended) kubectl exec -n plane $API_POD -- python manage.py manage_search_index --background document index --force ``` ## Verify the setup ### Check OpenSearch connection Test that Plane can connect to your OpenSearch instance: ```bash # Access your API container or pod docker exec -it plane-api-1 sh # For Docker # OR kubectl exec -n plane $API_POD -- sh # For Kubernetes # Start Python shell python manage.py shell ``` Then run: ```python from django.conf import settings from opensearchpy import OpenSearch client = OpenSearch( hosts=[settings.OPENSEARCH_DSL['default']['hosts']], http_auth=settings.OPENSEARCH_DSL['default']['http_auth'], use_ssl=True, verify_certs=False ) # Check cluster health print(client.cluster.health()) # List indices - you should see your Plane indices print(client.cat.indices(format='json')) ``` ### Verify indices were created List all created indices: ```bash python manage.py manage_search_index list ``` You should see indices for work items, projects, cycles, modules, pages, and other searchable entities. ### Test search functionality 1. Sign in to your Plane instance. 2. Press **Cmd/Ctrl + K** to open global search. 3. Type a search query and verify results appear. 4. Test search within projects, work items, and pages. ## Maintenance ### Resync data If search results become stale or inconsistent, resync your data: ```bash python manage.py manage_search_index document index --force ``` This reindexes all content without recreating the index structure. ### Complete rebuild For a complete reset (recreates indices and reindexes all data): ```bash # Recreate all indices python manage.py manage_search_index index rebuild --force # Reindex all documents python manage.py manage_search_index document index --force ``` Use this if index structure needs updating or if you're experiencing persistent issues. ### Monitor logs Check API logs OpenSearch-related errors: **Docker:** ```bash docker compose logs api | grep -i opensearch ``` **Kubernetes:** ```bash kubectl logs -n plane -l app.kubernetes.io/component=api | grep -i opensearch ``` ## Understanding how it works Advanced search in Plane maintains search indices separately from your main database. This separation is why search can be fast even with thousands of work items - OpenSearch is purpose-built for search operations, while your database handles transactional operations. ### Why Plane uses OpenSearch Traditional database searches struggle with fuzzy matching and typos. If you search for "authentcation" (with a typo), a database won't find "authentication". OpenSearch handles this naturally because it analyzes text differently - it breaks words into tokens, normalizes variations, and understands linguistic patterns. This is why autocomplete feels instant. OpenSearch pre-processes text to match partial words, while your database would need to scan entire tables to achieve similar results. ### The synchronization challenge The trade-off with separate search indices is keeping them synchronized with your database. When someone updates a work item, that change must reach OpenSearch for search results to remain accurate. Plane solves this through an event-driven architecture. Every time data changes in your database, Django emits a signal. These signals trigger updates to OpenSearch. ### Batching for efficiency Direct, immediate updates would overwhelm both your database and OpenSearch. Imagine a user creating 50 work items in quick succession, that would mean 50 separate API calls to OpenSearch, each with network overhead. Instead, Plane batches updates through Redis. When a signal fires, the update goes into a Redis queue. A Celery worker processes this queue every 5 seconds, combining multiple updates into efficient batch operations. This is why you might notice a brief delay (up to 5 seconds) before new content appears in search results. The batching pattern also provides resilience. If OpenSearch is temporarily unavailable, updates accumulate in Redis and process once connectivity returns. This requires Redis 6.2+ which supports the LPOP count operation needed for efficient batch retrieval. ### The complete flow ![OpenSeach flow](/images/open-search/opensearch-flow.webp#hero) When you search, queries bypass this synchronization process entirely. The Plane API sends your search query directly to OpenSearch, which returns results almost instantly. Your database isn't involved in search queries at all — this is the key to search performance. ### Index organization Plane creates nine separate indices in OpenSearch, one for each searchable entity type. This separation might seem redundant - why not put everything in one index? The answer lies in how different entities need different search behaviors. Work items use fuzzy matching and field prioritization (title matches rank higher than description matches). Projects emphasize metadata filtering—status, member counts, and timelines. Pages analyze long-form content structure. Each index is optimized for its content type: | Index | Content | Search Features | | ------------------------- | ----------- | ------------------------------------------------------------------------ | | `{prefix}_issues` | Work items | Full-text search, field weighting (title > description), state filtering | | `{prefix}_issue_comments` | Comments | Comment search within work items, parent-child relationships | | `{prefix}_projects` | Projects | Project discovery, metadata filtering (dates, counts, status) | | `{prefix}_cycles` | Cycles | Cycle search, time-based filtering and aggregations | | `{prefix}_modules` | Modules | Module/sprint search, planning aggregations | | `{prefix}_pages` | Pages | Page content search, rich text analysis for long-form content | | `{prefix}_workspaces` | Workspaces | Workspace search and discovery | | `{prefix}_issue_views` | Saved views | Saved view search and filtering | | `{prefix}_teamspaces` | Teamspaces | Teamspace discovery | The `{prefix}` is whatever you configured in `OPENSEARCH_INDEX_PREFIX`, or empty if you didn't set a prefix. This prefix exists because you might run multiple Plane instances pointing to the same OpenSearch cluster. The prefix prevents different instances from accidentally sharing or conflicting with each other's indices. --- --- url: >- https://developers.plane.so/self-hosting/govern/plane-ai/configure-plane-ai.html description: >- Enable Plane AI on your self-hosted instance. Set up a dedicated database, configure OpenSearch, add an LLM API key, and connect PI to your Plane deployment. --- # Configure Plane AI Plane AI brings AI-powered features to your workspace, including natural language chat, duplicate detection, and search across work items, pages, and projects. This guide walks you through configuring Plane AI on your self-hosted instance. For an overview of what Plane AI can do, see [Plane AI](https://docs.plane.so/ai/pi-chat). ## Prerequisites Plane AI requires four things to work: 1. **An LLM API key** (OpenAI or Anthropic) - powers AI responses. 2. **OpenSearch** - An OpenSearch instance running version 2.19 or later (self-hosted or AWS OpenSearch) configured for [advanced search](/self-hosting/govern/advanced-search). Search over your workspace data (work items, pages, cycles) runs through OpenSearch indices. 3. **A dedicated database** - Plane AI must not share the main Plane application database. 4. **Read access to the main Plane database** - PI reads workspace data directly from the main Plane DB. ## Supported LLM providers ### OpenAI * GPT-5.4 * GPT-5.2 ### Anthropic * Claude Sonnet 4.5 * Claude Sonnet 4.6 You can provide API keys for both OpenAI and Anthropic, making all models available to users. If you provide only one key, users will only have access to that provider's models. :::tip Custom or self-hosted models To use Ollama, Groq, LiteLLM, AWS Bedrock, or any OpenAI-compatible endpoint, see [Custom LLM models](#custom-llm-models). ::: ## Set up databases Plane AI needs two database connections. ```bash PLANE_PI_DATABASE_URL=postgresql://user:password@host:5432/plane-pi FOLLOWER_POSTGRES_URI=postgresql://user:password@host:5432/plane ``` * **`PLANE_PI_DATABASE_URL`** - PI's own dedicated database. Must not be shared with the main Plane application database. * **`FOLLOWER_POSTGRES_URI`** - Read connection to the main Plane database. PI reads workspace data (issues, pages, projects) directly from here. Can be a read replica. Both are checked at startup - PI will not start if either is unreachable. ## Configure OpenSearch Add to `/opt/plane/plane.env`: ```bash OPENSEARCH_URL=https://your-opensearch-instance:9200/ OPENSEARCH_USERNAME=admin OPENSEARCH_PASSWORD=your-secure-password ``` If you haven't set up OpenSearch yet, see [OpenSearch for advanced search](/self-hosting/govern/advanced-search) first. ## Configure an LLM provider Add at least one to `/opt/plane/plane.env`: ```bash OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx CLAUDE_API_KEY=xxxxxxxxxxxxxxxx ``` ### Custom LLM models :::warning The custom model should have at least 1 trillion parameters for all Plane AI features to work reliably. Larger, more capable models yield better results. ::: Plane AI supports one custom LLM alongside OpenAI and Anthropic. * OpenAI-compatible - any model exposed via an OpenAI Chat Completions API, including models served by Ollama, Groq, Cerebras, and similar runtimes. * AWS Bedrock - models accessed directly through Amazon Bedrock using your AWS credentials. One custom model can be configured alongside your public provider keys. :::tip No OpenAI-compatible API? Proxy any model through - it exposes any LLM behind the OpenAI API. Then use the OpenAI-compatible setup below. If you need to use an LLM that isn't from OpenAI or Anthropic - for example, an open-source model or a regional provider for compliance reasons - you can proxy it through [LiteLLM](https://docs.litellm.ai).Then use the OpenAI-compatible setup below. ::: #### OpenAI-compatible Add to `/opt/plane/plane.env`: ```bash CUSTOM_LLM_ENABLED=true CUSTOM_LLM_PROVIDER=openai CUSTOM_LLM_MODEL_KEY=your-model-id # model ID as the endpoint expects it CUSTOM_LLM_BASE_URL=https://your-endpoint/v1 CUSTOM_LLM_API_KEY=your-api-key # use any non-empty string if no key is required CUSTOM_LLM_NAME=Your Model Name # display name shown to users CUSTOM_LLM_MAX_TOKENS=64000 # optional; max output tokens per response ``` **Examples:** ```bash # Groq CUSTOM_LLM_MODEL_KEY=llama-3.3-70b-versatile CUSTOM_LLM_BASE_URL=https://api.groq.com/openai/v1 CUSTOM_LLM_API_KEY=gsk_xxxxxxxxxxxx # Ollama (local) CUSTOM_LLM_MODEL_KEY=llama3 CUSTOM_LLM_BASE_URL=http://localhost:11434/v1 CUSTOM_LLM_API_KEY=ollama # LiteLLM proxy CUSTOM_LLM_MODEL_KEY=your-litellm-model CUSTOM_LLM_BASE_URL=http://litellm:4000/v1 CUSTOM_LLM_API_KEY=your-litellm-master-key ``` #### AWS Bedrock ##### Standard credentials Use for IAM user access with an explicit access key and secret. ```bash CUSTOM_LLM_ENABLED=true CUSTOM_LLM_PROVIDER=bedrock CUSTOM_LLM_MODEL_KEY=anthropic.claude-3-5-sonnet-20241022-v2:0 # Bedrock model ID CUSTOM_LLM_API_KEY=your-aws-secret-access-key CUSTOM_LLM_AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=your-aws-access-key-id # standard AWS env var, picked up by boto3 CUSTOM_LLM_NAME=Claude via Bedrock CUSTOM_LLM_MAX_TOKENS=64000 # optional; max output tokens per response ``` :::warning IAM permission required The IAM user must have `bedrock:InvokeModel` permission on the target model. ::: #### Inference profile (IRSA / EKS Pod Identity) Use for Kubernetes deployments where the pod has an ambient IAM role. No static credentials needed. ```bash CUSTOM_LLM_ENABLED=true CUSTOM_LLM_PROVIDER=bedrock CUSTOM_LLM_MODEL_KEY=claude-sonnet-4-6 CUSTOM_LLM_AWS_REGION=us-east-1 BEDROCK_INFERENCE_PROFILE_ARN=arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/xxxx # or use BEDROCK_INFERENCE_PROFILE_ID=global.anthropic.claude-sonnet-4-6 CUSTOM_LLM_NAME=Claude via Inference Profile CUSTOM_LLM_MAX_TOKENS=64000 # optional; max output tokens per response ``` Plane AI activates inference profile mode automatically when a profile ARN or ID is set and ambient AWS credentials are present (`AWS_ROLE_ARN`, `AWS_WEB_IDENTITY_TOKEN_FILE`, `AWS_CONTAINER_CREDENTIALS_FULL_URI`, or `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE`). ## Connect Plane AI to your Plane deployment Plane AI runs as a separate service and must be wired to your Plane deployment. Two things matter here. **In the Plane API env** (`/opt/plane/plane.env`), tell Plane where PI is reachable: ```bash PI_BASE_URL=https://plane.example.com ``` Plane builds the PI URL as `PI_BASE_URL + PI_BASE_PATH` - with the default `PI_BASE_PATH=/pi` this becomes `https://plane.example.com/pi`. **In the PI env** (`/opt/plane/plane.env`), set the OAuth redirect URI to match: ```bash PLANE_OAUTH_REDIRECT_URI=https://plane.example.com/pi/api/v1/oauth/callback/ ``` ## Enable Plane AI services :::tip Other deployment methods For Coolify, Portainer, Docker Swarm, and Podman Quadlets, use the same environment variables as Docker Compose - only the replica variables differ. ::: :::tabs key:deployment-method \== Docker Compose {#docker-compose} In `/opt/plane/plane.env`, set replica counts to `1`: ```bash PI_API_REPLICAS=1 PI_BEAT_REPLICAS=1 PI_WORKER_REPLICAS=1 PI_MIGRATOR_REPLICAS=1 ``` \== Kubernetes {#kubernetes} In `values.yaml`, enable the Plane AI service: ```yaml services: pi: enabled: true ``` This activates the Plane AI API, worker, beat-worker, and migrator workloads. Configure replicas and resource limits through the [Plane AI values block](/self-hosting/methods/kubernetes#plane-ai-deployment). ::: ## Optional configuration ### Voice input Enables speech-to-text in Plane AI chat. Get a key at [console.groq.com](https://console.groq.com). ```bash GROQ_API_KEY=your-groq-api-key ``` ### File uploads Enables file attachments in Plane AI. ```bash AWS_S3_BUCKET_NAME=your-bucket-name AWS_S3_REGION=us-east-1 AWS_ACCESS_KEY_ID=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-key ``` For MinIO or S3-compatible storage, also add: ```bash AWS_S3_ENDPOINT_URL=http://your-minio-host:9000 USE_MINIO=1 ``` ## Restart Plane :::tabs key:deployment-method \== Docker Compose {#docker-compose} ```bash prime-cli restart ``` Or directly: ```bash docker compose down docker compose up -d ``` \== Kubernetes {#kubernetes} ```bash helm upgrade --install plane-app plane/plane-enterprise \ --namespace plane \ -f values.yaml \ --wait ``` ::: --- --- url: >- https://developers.plane.so/self-hosting/govern/plane-ai/configure-embedding-model.html description: >- Configure an embedding model for Plane AI semantic search, duplicate detection, and vector retrieval over work items and pages. --- # Configure embedding model for semantic search Configuring the embedding model is optional. Without it, Plane AI uses BM25 keyword search. With it, you get vector similarity search, duplicate issue detection, and semantic retrieval over work items and pages. This builds on the OpenSearch connection configured in [Configure Plane AI](/self-hosting/govern/plane-ai/configure-plane-ai). Make sure `OPENSEARCH_URL`, `OPENSEARCH_USERNAME`, and `OPENSEARCH_PASSWORD` are already set before continuing. ## Supported embedding models | Provider | Model | Dimension | | --------------- | -------------------------------------- | --------- | | **Cohere** | `cohere/embed-v4.0` | 1536 | | | `cohere/embed-english-v3.0` | 1024 | | | `cohere/embed-english-v2.0` | 4096 | | **OpenAI** | `openai/text-embedding-ada-002` | 1536 | | | `openai/text-embedding-3-small` | 1536 | | | `openai/text-embedding-3-large` | 3072 | | **AWS Bedrock** | `bedrock/amazon.titan-embed-text-v1` | 1536 | | | `bedrock/amazon.titan-embed-text-v2` | 1024 | | | `bedrock/cohere.embed-english-v3` | 1024 | | | `bedrock/cohere.embed-multilingual-v3` | 1024 | ## Configure the embedding model Two options depending on your OpenSearch setup. ### Option A: Use an existing OpenSearch model ID Use this if you've already deployed an embedding model in OpenSearch - either via the [AWS OpenSearch embedding guide](/self-hosting/govern/plane-ai/aws-opensearch-embedding) or manually on self-hosted OpenSearch. ```bash EMBEDDING_MODEL=cohere/embed-v4.0 # must match the deployed model OPENSEARCH_ML_MODEL_ID= # model ID returned by OpenSearch on deploy # OPENSEARCH_EMBEDDING_DIMENSION=1024 # only if not using default 1536 ``` :::tip `OPENSEARCH_EMBEDDING_DIMENSION` must match the model's actual output dimension (see table above). It defaults to `1536` - only set it explicitly if your model uses a different value. A mismatch between the configured dimension and the model's real output breaks indexing. ::: ### Option B: Automatic deployment (self-hosted OpenSearch only) Plane AI can create and deploy the embedding model automatically when the migrator starts. Provide the model name and provider credentials - no manual OpenSearch setup needed. **Cohere:** ```bash EMBEDDING_MODEL=cohere/embed-v4.0 COHERE_API_KEY=your-cohere-api-key ``` **OpenAI:** ```bash EMBEDDING_MODEL=openai/text-embedding-3-small OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx ``` **AWS Bedrock:** ```bash EMBEDDING_MODEL=bedrock/amazon.titan-embed-text-v1 BR_AWS_ACCESS_KEY_ID=your-access-key BR_AWS_SECRET_ACCESS_KEY=your-secret-key BR_AWS_REGION=us-east-1 ``` :::warning IAM permission required for Bedrock The IAM user for `BR_AWS_ACCESS_KEY_ID` and `BR_AWS_SECRET_ACCESS_KEY` needs `bedrock:InvokeModel` permission on the Titan foundation model. Without it, embedding requests fail with a 403 error. Attach this policy to the IAM user: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "bedrock:InvokeModel", "Resource": "arn:aws:bedrock:::foundation-model/amazon.titan-embed-text-v1" } ] } ``` Replace `` with your `BR_AWS_REGION` value. ::: :::info AWS managed OpenSearch Auto-deploy requires direct ML access to OpenSearch. AWS managed OpenSearch restricts this, so deploy the model manually using the [AWS OpenSearch embedding guide](/self-hosting/govern/plane-ai/aws-opensearch-embedding), then use the model ID option above. ::: ## Restart Plane After updating `/opt/plane/plane.env`, restart Plane. The Plane AI migrator runs automatically on start and handles embedding model deployment, index creation, and pipeline setup. ## Vectorize existing data New content is indexed automatically. For existing work items and pages, run this in the API container: Generate embeddings for your existing content by running this command in the API container. **Docker:** ```bash docker exec -it plane-api-1 sh python manage.py manage_search_index --background --vectorize document index --force ``` **Kubernetes:** ```bash API_POD=$(kubectl get pods -n plane --no-headers | grep api | head -1 | awk '{print $1}') kubectl exec -n plane $API_POD -- python manage.py manage_search_index --background --vectorize document index --force ``` The `--background` flag processes vectorization through Celery workers. This is recommended for instances with large amounts of existing content. ## Changing the embedding model If you update the model or manually override the dimension size by setting `OPENSEARCH_EMBEDDING_DIMENSION`, you must recreate your search indices so they adopt the new dimension size, then reindex and revectorize your workspace. Ensure that the model associated with your `OPENSEARCH_ML_MODEL_ID` and your `EMBEDDING_MODEL` configuration share this same dimension size. ### Model only (same dimension) Update `EMBEDDING_MODEL` and provider credentials in `/opt/plane/plane.env`, restart Plane, then revectorize: ```bash docker exec -it plane-api-1 sh python manage.py manage_search_index --background --vectorize document index --force ``` ### Dimension change Update `EMBEDDING_MODEL`, `OPENSEARCH_EMBEDDING_DIMENSION`, and provider credentials in `/opt/plane/plane.env`, restart Plane, then rebuild and revectorize: ```bash # Rebuild indices with the new dimension python manage.py manage_search_index index rebuild --force # Reindex and revectorize all existing documents python manage.py manage_search_index --background --vectorize document index --force ``` `OPENSEARCH_EMBEDDING_DIMENSION` must match the actual output dimension of the model in `EMBEDDING_MODEL`. A mismatch breaks indexing. --- --- url: >- https://developers.plane.so/self-hosting/govern/plane-ai/aws-opensearch-embedding.html description: >- Step-by-step guide to deploying a Cohere embedding model on AWS OpenSearch for use with Plane AI semantic search. --- # Deploy an embedding model on AWS OpenSearch This guide walks you through deploying a Cohere embedding model on AWS OpenSearch (managed) for Plane AI semantic search. For other connector blueprints and embedding model configurations, see the [OpenSearch ML Commons remote inference blueprints](https://github.com/opensearch-project/ml-commons/tree/2.x/docs/remote_inference_blueprints). ## Before you begin Make sure you have: * An AWS OpenSearch domain with **fine-grained access control** enabled. * Admin access to OpenSearch Dashboards. * AWS CLI configured locally. * An IAM user with permissions to create roles, policies, and access Secrets Manager. * A Cohere API key. ## Create an IAM policy 1. Go to **IAM → Policies → Create Policy**. 2. Select **JSON** and paste the following: ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "PassRoleAccess", "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam:::role/plane-opensearch-access-role" }, { "Sid": "SecretManagerAccess", "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:ListSecrets"], "Resource": "*" } ] } ``` 3. Click **Next**, name the policy `plane-opensearch-access-policy`, and click **Create Policy**. ## Create an IAM role Create an IAM role that OpenSearch can assume to access Secrets Manager. 1. Go to **IAM → Roles → Create Role**. 2. Name the role `plane-opensearch-access-role`. 3. Set this **Trust Relationship**: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::user/" }, "Action": "sts:AssumeRole" } ] } ``` 4. Attach the `plane-opensearch-access-policy` you created in Step 1. 5. Click **Create Role** and note the role ARN. ## Grant ML permissions to the IAM role 1. Open **OpenSearch Dashboards**. 2. Go to **Security → Roles → `ml_full_access`**. 3. Open the **Mapped users** tab and click **Map users**. 4. Under **Backend roles**, add the role ARN: ``` arn:aws:iam:::role/plane-opensearch-access-role ``` ## Assume the role locally Run this command to get temporary credentials: ```bash aws sts assume-role \ --role-arn arn:aws:iam:::role/plane-opensearch-access-role \ --role-session-name session ``` Export the credentials from the response: ```bash export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= export AWS_SESSION_TOKEN= ``` ## Store the Cohere API key in Secrets Manager 1. Go to **Secrets Manager → Store a new secret**. 2. Select **Other type of secret**. 3. Set the key to `azure_ai_foundry_key_cohere` and the value to your Cohere API key. 4. Click **Next**, name the secret `plane-ai/cohere`, and click **Store**. 5. Note the **Secret ARN** — you'll need it in the next step. ## Create a Cohere connector in OpenSearch Using the temporary credentials from Step 4, send a `POST` request to your OpenSearch cluster. **Endpoint:** `POST https:///_plugins/_ml/connectors/_create` **Request body:** ```json { "name": "Cohere", "description": "Cohere embedding connector", "version": "1", "protocol": "http", "parameters": { "endpoint": "https://azureaitrials-resource.services.ai.azure.com/models", "model": "embed-v-4-0", "api_version": "2024-05-01-preview", "input_type": "search_document", "truncate": "END" }, "credential": { "secretArn": "", "roleArn": "arn:aws:iam:::role/plane-opensearch-access-role" }, "actions": [ { "action_type": "predict", "method": "POST", "url": "${parameters.endpoint}/embeddings?api-version=${parameters.api_version}", "headers": { "api-key": "${credential.secretArn.azure_ai_foundry_key_cohere}", "x-ms-model-mesh-model-name": "embed-v-4-0" }, "request_body": "{ \"texts\": ${parameters.texts}, \"truncate\": \"${parameters.truncate}\", \"model\": \"${parameters.model}\", \"input_type\": \"${parameters.input_type}\" }", "pre_process_function": "connector.pre_process.cohere.embedding", "post_process_function": "connector.post_process.cohere.embedding" } ] } ``` Save the `connector_id` from the response. ## Configure the OpenSearch cluster Run these commands in **Dev Tools** in OpenSearch Dashboards. ### Allow the connector's external endpoints ```json PUT /_cluster/settings { "persistent": { "plugins.ml_commons.trusted_connector_endpoints_regex": [ "^https://api\\.cohere\\.ai(/.*)?$", "^https://azureaitrials-resource\\.services\\.ai\\.azure\\.com(/.*)?$" ] } } ``` ### Register the embedding model ```json POST /_plugins/_ml/models/_register { "name": "cohere_4_0_embed", "function_name": "remote", "connector_id": "", "description": "Cohere Embedding Model" } ``` Save the `model_id` from the response. ### Deploy the model ``` POST /_plugins/_ml/models//_deploy ``` ### Verify deployment status ``` GET /_plugins/_ml/models/ ``` Wait until the response shows: ```json "model_state": "DEPLOYED" ``` ### Test inference (optional) ```json POST /_plugins/_ml/models//_predict { "parameters": { "inputs": ["hello world"] } } ``` ## Configure Plane Add the deployed model ID and configuration to `/opt/plane/plane.env`: ```bash OPENSEARCH_ML_MODEL_ID= EMBEDDING_MODEL=cohere/embed-v4.0 OPENSEARCH_EMBEDDING_DIMENSION=1536 ``` Restart Plane and complete the remaining steps in [Configure embedding model](/self-hosting/govern/plane-ai/configure-embedding-model). --- --- url: 'https://developers.plane.so/self-hosting/govern/external-secrets.html' description: >- Use external secrets operators with Plane on Kubernetes. Integrate AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault for secure configuration. --- # Configure external secrets for Kubernetes deployments This guide explains how to integrate Plane with external secret management solutions, enabling secure and centralized management of sensitive configuration data. The examples provided cover AWS Secrets Manager and HashiCorp Vault integrations, but you can adapt these patterns to your preferred secret management solution. ## AWS Secrets Manager 1. Create a dedicated IAM user (e.g., `external-secret-access-user`). You can uncheck **Console Access Required**. 2. Generate `ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` and keep them handy. 3. Note the user's ARN for later use (format: `arn:aws:iam:::user/`). 4. Create IAM policy (e.g., `external-secret-access-policy`) with the following JSON: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:GetResourcePolicy", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:ListSecretVersionIds" ], "Resource": ["arn:aws:secretsmanager:::secret:*"] } ] } ``` Replace `` and `` with your AWS region and account ID. 5. Create IAM role (e.g., external-secret-access-role) with the following trust relationship: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "" }, "Action": "sts:AssumeRole" } ] } ``` Replace `` with the ARN of the user created in step 1. 6. Attach the AWS IAM policy created in step 4 to the IAM role. 7. Create secrets in AWS Secrets Manager with your Plane configuration values. For example, store RabbitMQ credentials with a name like `prod/secrets/rabbitmq`. | Key | Value | | --------------------- | -------- | | RABBITMQ\_DEFAULT\_USER | plane | | RABBITMQ\_DEFAULT\_PASS | plane123 | Follow this pattern to manage all the [environment variables](/self-hosting/methods/kubernetes#external-secrets-config) in AWS Secrets Manager. 8. Create a Kubernetes secret containing AWS credentials in your application namespace: ```sh kubectl create secret generic aws-creds-secret \ --from-literal=access-key= \ --from-literal=secret-access-key= \ -n ``` 9. Apply the following YAML to create a ClusterSecretStore resource: ```yaml apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: name: cluster-aws-secretsmanager namespace: spec: provider: aws: service: SecretsManager role: arn:aws:iam:::role/ region: eu-west-1 auth: accessKeyIDSecretRef: name: aws-creds-secret key: access-key secretAccessKeySecretRef: name: aws-creds-secret key: secret-access-key ``` Replace `` and `` with your AWS account ID and the role name created in Step 5. 10. Create an ExternalSecret resource to fetch secrets from AWS and create a corresponding Kubernetes secret: ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: rabbitmq-external-secrets namespace: spec: refreshInterval: 1m secretStoreRef: name: cluster-aws-secretsmanager # ClusterSecretStore name kind: ClusterSecretStore target: name: rabbitmq-secret # Target Kubernetes secret name creationPolicy: Owner data: - secretKey: RABBITMQ_DEFAULT_USER remoteRef: key: prod/secrets/rabbitmq property: RABBITMQ_DEFAULT_USER - secretKey: RABBITMQ_DEFAULT_PASS remoteRef: key: prod/secrets/rabbitmq ``` Make sure to set all [environment variables](/self-hosting/methods/kubernetes#external-secrets-config) in the AWS Secrets Manager, and then access them via ExternalSecret resources in your Kubernetes cluster. ## HashiCorp Vault 1. Access the Vault UI at `https:///`. 2. Set up a KV secrets engine if not already configured. 3. Create a secret with your Plane configuration values (e.g., `secrets/rabbitmq_secrets`). For this example, we're setting up RabbitMQ credentials: | Key | Value | | --------------------- | -------- | | RABBITMQ\_DEFAULT\_USER | plane | | RABBITMQ\_DEFAULT\_PASS | plane123 | Follow this pattern to manage all the other [environment variables](/self-hosting/methods/kubernetes#external-secrets-config) in the Vault. 4. Create a Kubernetes secret containing your Vault token in your application namespace: ```sh kubectl create secret generic vault-token -n --from-literal=token= ``` 5. Apply the following YAML to create a ClusterSecretStore resource: ```yaml apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: name: vault-backend namespace: spec: provider: vault: server: "https://" # the address of your vault instance path: "secrets" # path for accessing the secrets version: "v2" # Vault API version auth: tokenSecretRef: name: "vault-token" # Use a k8s secret called vault-token key: "token" # Use this key to access the vault token ``` Replace `` with your Vault server address. 6. Create an ExternalSecret resource to fetch secrets from Vault and create a corresponding Kubernetes secret: ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: rabbitmq-external-secrets namespace: # application-namespace spec: refreshInterval: "1m" secretStoreRef: name: vault-backend # ClusterSecretStore name kind: ClusterSecretStore target: name: rabbitmq-secret # Target Kubernetes secret name creationPolicy: Owner data: - secretKey: RABBITMQ_DEFAULT_USER remoteRef: key: secrets/data/rabbitmq_secrets property: RABBITMQ_DEFAULT_USER - secretKey: RABBITMQ_DEFAULT_PASS remoteRef: key: secrets/data/rabbitmq_secrets ``` Follow this pattern to manage all the environment variables in the Vault, then access them via ExternalSecret resources in your Kubernetes cluster. --- --- url: 'https://developers.plane.so/self-hosting/govern/reverse-proxy.html' description: >- Configure Nginx, Caddy, or Traefik as a reverse proxy for self-hosted Plane. Setup upstream proxying, headers, and WebSocket support. --- # Configure external reverse proxy This page provides configuration for setting up an external reverse proxy with Plane. ## Plane environment setup Make sure to update the following environment variables in your plane.env file. 1. Assign free ports for Plane to listen on. Update the following variables with two different unsused ports: ```bash LISTEN_HTTP_PORT= LISTEN_HTTPS_PORT= ``` 2. Update the SITE\_ADDRESS variable to `:80` ```bash SITE_ADDRESS=:80 ``` This is required so that generated links and redirects work correctly behind the proxy: 3. After editing plane.env, restart your instance so the changes take effect: ```bash sudo prime-cli restart ``` ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: ## Proxy setup 1. Choose the appropriate [configuration template](#configuration-templates) for your reverse proxy. 2. Replace the following placeholders: * ``\ Your Plane application's domain name. * ``\ The IP address where Plane is hosted. * ``\ The port Plane listens on. 3. For Traefik, also update `your-email@example.com` with your email. Ensure that your reverse proxy setup follows the template provided, and that the forwarded headers and ports are correctly set to match the environment variable configuration. ## Configuration templates All configurations include: * Automatic HTTPS redirection * WebSocket support * Standard proxy headers * SSL/TLS certificate management * NGINX: Uses Certbot * Caddy: Handles certificates automatically * Traefik: Uses Let’s Encrypt ::: details NGINX configuration ```bash server { server_name ; location / { proxy_pass http://:/; # Set headers for proxied request proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; proxy_http_version 1.1; } client_max_body_size 10M; listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live//fullchain.pem; ssl_certificate_key /etc/letsencrypt/live//privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; } server { if ($host = ) { return 301 https://$host$request_uri; } listen 80; server_name ; return 404; } ``` ::: ::: details Caddy configuration ```bash { tls { # Caddy will automatically handle certificates } redir / https://{host}{uri} permanent reverse_proxy : { header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up Host {http.request.host} header_up Upgrade {http.request.header.Upgrade} header_up Connection {http.request.header.Connection} transport http { tls_insecure_skip_verify read_buffer 4096 write_buffer 4096 } } request_body { max_size 10MB } } ``` ::: ::: details Traefik configuration ```bash entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https permanent: true websecure: address: ":443" certificatesResolvers: letsencrypt: acme: email: your-email@example.com # Replace with your email storage: acme.json httpChallenge: entryPoint: web providers: http: routers: plane-router: rule: "Host(``)" service: plane-service entryPoints: - websecure tls: certResolver: letsencrypt services: plane-service: loadBalancer: servers: - url: "http://:" passHostHeader: true responseForwarding: flushInterval: "100ms" serversTransport: maxIdleConnsPerHost: 100 forwardingTimeouts: dialTimeout: 30s responseHeaderTimeout: 30s idleConnTimeout: 90s middlewares: headers: headers: customRequestHeaders: X-Forwarded-Proto: "https" X-Real-IP: "{{ .RemoteAddr }}" ``` ::: --- --- url: 'https://developers.plane.so/self-hosting/govern/private-bucket.html' description: >- Switch Plane storage from public to private S3 buckets. Secure file uploads with signed URLs and access control for commercial editions. --- # Switch from public to private buckets • Commercial Edition ::: warning Starting with v1.4.0 of the Commercial edition Plane will use private storage buckets for any file uploaded to your Plane instance. ::: ::: info New installations with default storage, which is MiniO, don't need to change anything. For S3 or S3-compatible storage, please see [this](https://developers.plane.so/self-hosting/govern/database-and-storage). ::: While you can use the current public storage paradigm that Plane has followed so far, we highly recommend you migrate to private storage buckets which ensure greater security and give you more control over how files are accessed. ::: info To keep public storage on external S3 compatible services, you still have to update your CORS policy. ::: See the instructions to switch to private storage by the provider you use below. ## For default MinIO storage Simply run the command ↓. ```bash docker exec -it python manage.py update_bucket ``` A successful run keeps any public files you already have accessible while moving you to private storage. ## For external storage • S3 or S3 compatible There are two parts to this—updating your CORS policy and then switching to private storage. ### Update bucket's CORS policy ::: warning This step is critical if you are using external storage to ensure continued functionality. ::: Here’s a sample CORS policy for your reference. Just replace `` with your actual domain and apply the policy to your bucket. ```bash [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "POST", "PUT", "DELETE", "HEAD" ], "AllowedOrigins": [ "", ], "ExposeHeaders": [ "ETag", "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2" ], "MaxAgeSeconds": 3000 } ] ``` ### Switch to private storage ::: warning Don't start from here if you haven't updated your CORS policy. ::: To migrate from public to private bucket storage, follow the instructions below: 1. First, make sure you have the following permissions on your S3 bucket. If you don't, make changes to get those permissions on your bucket first. * **s3:GetObject**\ So you can access your public files so far To access existing objects publicly * **s3:ListBucket**\ So you can apply policies to your bucket for public access * **s3:PutObject**\ So you can create new files * **s3:PutBucketPolicy**\ So you can update your buckets' policy 2. Now, run the command ↓. ```bash docker exec -it python manage.py update_bucket ``` ::: tip 1. If the command finds the necessary permissions missing, it will generate a `permissions.json` file which you can use to update your bucket policy manually. Here’s how the `permissions.json` file should look. ```bash { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": [ "arn:aws:s3:::/", "arn:aws:s3:::/" ] } ] } ``` 2. To copy the `permissions.json` file to the local machine, run the command ↓. ```bash docker cp :/code/permissions.json . ``` ::: ## Troubleshoot * [Bucket policy exceeds size limit](/self-hosting/troubleshoot/storage-errors#bucket-policy-exceeds-size-limit) --- --- url: 'https://developers.plane.so/self-hosting/govern/environment-variables.html' description: >- Configure environment variables for Plane. Complete reference of all configuration options and settings. --- # Environment variables reference This guide provides a comprehensive overview of all environment variables used in the Commercial Edition. These variables allow you to customize your Plane instance to best fit your organization's needs. ## Where to find the .env file The environment file for Plane Commercial Edition is located at: ```bash /opt/plane/plane.env ``` This is where you'll make all configuration changes. Remember to restart the instance after making changes to ensure they take effect. ## Environment variables ### General settings | Variable | Description | Default Value | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | **INSTALL\_DIR** | Directory where Plane is installed. | /opt/plane | | **DOMAIN\_NAME** | Primary domain name for your Plane instance. This determines how users will access your installation. | localhost | | **APP\_RELEASE\_VERSION** | The version of Plane Commercial Edition you're running. This helps with troubleshooting and ensures compatibility. | *Current release version* | | **WEB\_URL** | The complete base URL for the web application including protocol (e.g., `https://plane.example.com`). | http://localhost | | **CORS\_ALLOWED\_ORIGINS** | Comma-separated list of origins allowed to make cross-origin requests to your API. Usually, this should include your WEB\_URL. | http://localhost | | **DEBUG** | Toggles debug mode for more verbose logging and debugging information. | 0 (disabled) | ### Scaling and performance | Variable | Description | Default Value | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------- | | **WEB\_REPLICAS** | Number of web server replicas for load balancing. | 1 | | **SPACE\_REPLICAS** | Number of space service replicas for workspaces. | 1 | | **ADMIN\_REPLICAS** | Number of admin service replicas. | 1 | | **API\_REPLICAS** | Number of API service replicas. | 1 | | **WORKER\_REPLICAS** | Number of worker service replicas for background tasks. | 1 | | **BEAT\_WORKER\_REPLICAS** | Number of beat worker replicas for scheduled tasks. | 1 | | **LIVE\_REPLICAS** | Number of live service replicas for real-time updates. | 1 | | **GUNICORN\_WORKERS** | Number of Gunicorn workers for handling web requests. Increase for better performance on high-traffic instances. | 2 | | **SILO\_REPLICAS** | Number of Silo (integration) service replicas. | 1 | | **IFRAMELY\_REPLICAS** | Number of Iframely service replicas for link previews and embeds. | 1 | | **EMAIL\_REPLICAS** | Number of email service replicas. Set to `1` to enable the built-in email intake service. | 0 | | **AUTOMATION\_CONSUMER\_REPLICAS** | Number of automation consumer replicas for processing automation events. | 1 | | **OUTBOX\_POLLER\_REPLICAS** | Number of outbox poller replicas for event processing. | 1 | | **PI\_API\_REPLICAS** | Number of Plane Intelligence API replicas. Set to `1` to enable AI features. | 0 | | **PI\_BEAT\_REPLICAS** | Number of Plane Intelligence beat worker replicas for scheduled AI tasks. | 0 | | **PI\_WORKER\_REPLICAS** | Number of Plane Intelligence worker replicas for background AI processing. | 0 | | **PI\_MIGRATOR\_REPLICAS** | Number of Plane Intelligence migrator replicas. Set to `1` to run PI database migrations. | 0 | ### Networking and security | Variable | Description | Default Value | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | ------------- | | **LISTEN\_HTTP\_PORT** | Port for HTTP traffic. | 80 | | **LISTEN\_HTTPS\_PORT** | Port for HTTPS traffic. | 443 | | **APP\_PROTOCOL** | Protocol to be used, either `http` or `https`. | http | | **TRUSTED\_PROXIES** | CIDR notation of trusted proxies for request forwarding. Important when behind load balancers or reverse proxies. | 0.0.0.0/0 | | **SSL\_VERIFY** | Whether to verify SSL certificates for outgoing connections. Set to `0` only in development environments. | 1 | | **LISTEN\_SMTP\_PORT\_25** | Port for SMTP traffic on port 25. | 25 | | **LISTEN\_SMTP\_PORT\_465** | Port for SMTPS traffic on port 465. | 465 | | **LISTEN\_SMTP\_PORT\_587** | Port for SMTP submission traffic on port 587. | 587 | ### SSL and certificates | Variable | Description | Default Value | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | | **CERT\_EMAIL** | Email used for SSL certificate registration with Let's Encrypt or other ACME providers. | admin@example.com | | **CERT\_ACME\_CA** | ACME Certificate Authority URL for SSL certificate issuance. | https://acme-v02.api.letsencrypt.org/directory | | **CERT\_ACME\_DNS** | DNS provider configuration for SSL certificate domain validation. Format varies by provider. | | | **SITE\_ADDRESS** | The domain name and port required by Caddy for serving your Plane instance. This determines how Caddy will handle incoming requests. | localhost:80 | ### Database settings | Variable | Description | Default Value | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | **PGHOST** | Hostname or IP address of your PostgreSQL server. | plane-db | | **PGDATABASE** | Name of the PostgreSQL database Plane will use. | plane | | **POSTGRES\_USER** | Username for PostgreSQL authentication. | plane | | **POSTGRES\_PASSWORD** | Password for PostgreSQL authentication. **Critical:** Use a strong, unique password here. | plane | | **POSTGRES\_DB** | Same as PGDATABASE - the name of the PostgreSQL database. | plane | | **POSTGRES\_PORT** | TCP port your PostgreSQL server is listening on. | 5432 | | **PGDATA** | Directory path where PostgreSQL data is stored. Only relevant if you're managing PostgreSQL within the same container/system. | /var/lib/postgresql/data | | **DATABASE\_URL** | Full connection string for PostgreSQL. If provided, this takes precedence over individual connection parameters. Format: `postgresql://username:password@host:port/dbname` | | ### Redis settings | Variable | Description | Default Value | | -------------- | -------------------------------------------- | ------------- | | **REDIS\_HOST** | Hostname or IP address of your Redis server. | plane-redis | | **REDIS\_PORT** | TCP port your Redis server is listening on. | 6379 | | **REDIS\_URL** | Full connection string for Redis. | | ### RabbitMQ settings | Variable | Description | Default Value | | -------------------------- | --------------------------------------------------------------------------------------- | ------------- | | **RABBITMQ\_HOST** | Hostname or IP address of your RabbitMQ server. | plane-mq | | **RABBITMQ\_PORT** | TCP port your RabbitMQ server is listening on. | 5672 | | **RABBITMQ\_DEFAULT\_USER** | Username for RabbitMQ authentication. | plane | | **RABBITMQ\_DEFAULT\_PASS** | Password for RabbitMQ authentication. | plane | | **RABBITMQ\_DEFAULT\_VHOST** | Virtual host for RabbitMQ, providing logical separation of resources. | plane | | **RABBITMQ\_VHOST** | Virtual host name for RabbitMQ used by application services. | plane | | **AMQP\_URL** | Full connection string for RabbitMQ. Format: `amqp://username:password@host:port/vhost` | | ### Authentication and security | Variable | Description | Default Value | | -------------------------- | ------------------------------------------------------------------------------------------------------- | ------------- | | **SECRET\_KEY** | Secret key used for various cryptographic operations, including JWT token signing. | | | **MACHINE\_SIGNATURE** | Unique identifier for your instance, used for licensing and authentication. | | | **LIVE\_SERVER\_SECRET\_KEY** | Secret key for communication between the API and live (real-time) service. Must match on both services. | | | **PI\_INTERNAL\_SECRET** | Secret key for internal communication with the Plane Intelligence service. | | | **SILO\_HMAC\_SECRET\_KEY** | HMAC secret key for authenticating requests between the API and Silo (integration) service. | | | **AES\_SECRET\_KEY** | AES encryption key used for encrypting sensitive integration credentials at rest. | | ### File Storage (MinIO / S3) | Variable | Description | Default Value | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | **USE\_MINIO** | Determines whether to use MinIO for object storage. Set to `1` to enable MinIO, `0` to use configured S3 or local storage. | 1 | | **USE\_STORAGE\_PROXY** | Whether to proxy file storage requests through the application server. Set to `1` to enable. | 0 | | **AWS\_REGION** | AWS region for S3 storage services. | | | **AWS\_ACCESS\_KEY\_ID** | Access key for MinIO or AWS S3 authentication. | | | **AWS\_SECRET\_ACCESS\_KEY** | Secret key for MinIO or AWS S3 authentication. | | | **AWS\_S3\_ENDPOINT\_URL** | Custom endpoint URL for MinIO or S3-compatible storage. | http://plane-minio:9000 | | **AWS\_S3\_BUCKET\_NAME** | S3 bucket name for file storage. | uploads | | **MINIO\_ROOT\_USER** | Username for MinIO authentication. This is effectively your MinIO admin account. | access-key | | **MINIO\_ROOT\_PASSWORD** | Password for MinIO root user authentication. Keep this secure as it provides full access to your storage. | secret-key | | **BUCKET\_NAME** | S3 bucket name where all file uploads will be stored. This bucket will be automatically created if it doesn't exist. | uploads | | **FILE\_SIZE\_LIMIT** | Maximum file upload size in bytes. | 5242880 (5MB) | | **MINIO\_ENDPOINT\_SSL** | Force HTTPS for MinIO when dealing with SSL termination. Set to `1` to enable. | 0 | ### GitHub integration | Variable | Description | Default Value | | ------------------------ | ------------------------------------------------ | ------------- | | **GITHUB\_CLIENT\_ID** | OAuth client ID for GitHub integration. | | | **GITHUB\_CLIENT\_SECRET** | OAuth client secret for GitHub integration. | | | **GITHUB\_APP\_NAME** | GitHub App name for enhanced GitHub integration. | | | **GITHUB\_APP\_ID** | GitHub App ID for enhanced GitHub integration. | | | **GITHUB\_PRIVATE\_KEY** | Private key for GitHub App authentication. | | ### Slack integration | Variable | Description | Default Value | | ----------------------- | ------------------------------------------ | ------------- | | **SLACK\_CLIENT\_ID** | OAuth client ID for Slack integration. | | | **SLACK\_CLIENT\_SECRET** | OAuth client secret for Slack integration. | | ### GitLab integration | Variable | Description | Default Value | | ------------------------ | ------------------------------------------- | ------------- | | **GITLAB\_CLIENT\_ID** | OAuth client ID for GitLab integration. | | | **GITLAB\_CLIENT\_SECRET** | OAuth client secret for GitLab integration. | | ### OpenSearch | Variable | Description | Default Value | | --------------------------- | ----------------------------------------------------------- | ------------------------------------ | | **OPENSEARCH\_ENABLED** | Enable OpenSearch integration | 1 | | **OPENSEARCH\_URL** | OpenSearch endpoint URL | https://opensearch.example.com:9200/ | | **OPENSEARCH\_USERNAME** | Authentication username | admin | | **OPENSEARCH\_PASSWORD** | Authentication password | your-secure-password | | **OPENSEARCH\_INDEX\_PREFIX** | Prefix for all index names (useful for multi-tenant setups) | (empty) | ### Plane AI #### Plane AI replicas To start Plane AI services, set each replica count to `1`: | Variable | Description | Required | | ------------------------ | ---------------------------------- | -------- | | **PI\_API\_REPLICAS** | Plane AI API replica count | Yes | | **PI\_BEAT\_REPLICAS** | Plane AI Beat Worker replica count | Yes | | **PI\_WORKER\_REPLICAS** | Plane AI Worker replica count | Yes | | **PI\_MIGRATOR\_REPLICAS** | Plane AI Migrator replica count | Yes | #### Database settings ::: info Plane AI database Plane AI uses a separate PostgreSQL database. Create a new database (e.g. `plane_pi`) on your PostgreSQL server, then set **PLANE\_PI\_DATABASE\_URL** to its connection string. Example: `postgresql://user:password@host:5432/plane_pi` ::: | Variable | Description | Default Value | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | **PLANE\_PI\_DATABASE\_URL** | Connection string for the Plane AI database. A separate database used by the PI service. | postgresql://plane:plane@plane-db/plane\_pi | | **FOLLOWER\_POSTGRES\_URI** | Connection string for a Plane PostgreSQL DB read replica. Used for read-heavy operations to reduce load on the primary database. | Same as DATABASE\_URL | #### LLM provider API keys Plane AI supports multiple LLM providers. Configure one or more by adding their API keys. | Variable | Description | Required | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | | **OPENAI\_API\_KEY** | API key for OpenAI models | Optional | | **CLAUDE\_API\_KEY** | API key for Anthropic models | Optional | | **GROQ\_API\_KEY** | API key for speech-to-text features | Optional | | **CUSTOM\_LLM\_ENABLED** | Set to `true` to enable a custom LLM. Supports OpenAI-compatible endpoints and AWS Bedrock. | Optional | | **CUSTOM\_LLM\_PROVIDER** | Backend provider for the custom model. Accepted values: `openai` (default), `bedrock`. | Optional | | **CUSTOM\_LLM\_MODEL\_KEY** | Identifier key for the custom model (e.g. a model ID or name). | Optional | | **CUSTOM\_LLM\_BASE\_URL** | Base URL of the custom model's OpenAI-compatible endpoint. Required when `CUSTOM_LLM_PROVIDER=openai`. | Optional | | **CUSTOM\_LLM\_API\_KEY** | API key for authenticating with the custom endpoint. Required for `openai` provider; used as the AWS access key ID when `CUSTOM_LLM_PROVIDER=bedrock`. | Optional | | **CUSTOM\_LLM\_AWS\_REGION** | AWS region for the Bedrock model (e.g. `us-east-1`). Required when `CUSTOM_LLM_PROVIDER=bedrock`. | Optional | | **CUSTOM\_LLM\_NAME** | Display name for the custom model shown in the UI. Defaults to `Custom LLM`. | Optional | | **CUSTOM\_LLM\_MAX\_TOKENS** | Maximum token limit for the custom model. Defaults to `64000`. | Optional | #### Provider base URLs Use these when routing requests through self-hosted gateways, proxies, or compatible third-party endpoints. | Variable | Description | Default | | ------------------- | ------------------------------------------ | --------- | | **OPENAI\_BASE\_URL** | Custom base URL for OpenAI-compatible APIs | OpenAI | | **CLAUDE\_BASE\_URL** | Custom base URL for Claude-compatible APIs | Anthropic | | **COHERE\_BASE\_URL** | Custom base URL for Cohere APIs | Cohere | | **GROQ\_BASE\_URL** | Custom base URL for Groq APIs | Groq | #### Embedding model configuration These settings are required for semantic search and Plane AI Chat. Configure one of the following options. | Variable | Description | Required | | ---------------------------------- | ---------------------------------------------------------------------------------------- | ----------- | | **OPENSEARCH\_ML\_MODEL\_ID** | ID of an existing embedding model deployed in OpenSearch. | Conditional | | **EMBEDDING\_MODEL** | Model used for generating embeddings and query construction (e.g., `cohere/embed-v4.0`). | Required | | **OPENSEARCH\_EMBEDDING\_DIMENSION** | The dimension of the embedding model (e.g., `1536`). | Required | | **COHERE\_API\_KEY** | API key for Cohere embedding models | Conditional | | **BR\_AWS\_ACCESS\_KEY\_ID** | AWS access key ID for Bedrock Titan embedding | Conditional | | **BR\_AWS\_SECRET\_ACCESS\_KEY** | AWS secret access key for Bedrock Titan embedding | Conditional | | **BR\_AWS\_REGION** | AWS region for Bedrock Titan embedding | Conditional | For setup instructions, supported models, and IAM permissions, see [Configure Plane AI](/self-hosting/govern/plane-ai/configure-plane-ai). ### API settings | Variable | Description | Default Value | | ---------------------- | ----------------------------------------------------------------------- | ------------- | | **API\_KEY\_RATE\_LIMIT** | Rate limit for API requests to prevent abuse. Format: `number/timeunit` | 60/minute | ### Email settings | Variable | Description | Default Value | | ----------------------- | ----------------------------------------------------------------------------------------------- | ------------- | | **SMTP\_DOMAIN** | Domain used for the built-in SMTP email intake service. | 0.0.0.0 | | **TLS\_CERT\_PATH** | File path to the TLS certificate for email service encryption. | | | **TLS\_PRIV\_KEY\_PATH** | File path to the TLS private key for email service encryption. | | | **INTAKE\_EMAIL\_DOMAIN** | Domain name for intake email addresses (e.g., `example.com` for `issue+id@example.com` format). | example.com | ### Integration (Silo) settings | Variable | Description | Default Value | | --------------------------------- | ---------------------------------------------------------------------- | ------------- | | **SILO\_BASE\_PATH** | Base path for the Silo integration service. | /silo | | **WEBHOOK\_SECRET** | Secret key for verifying webhook payloads from external integrations. | plane-silo | | **BATCH\_SIZE** | Number of events to process in a single batch for integration syncs. | 60 | | **DEDUP\_INTERVAL** | Interval (in seconds) for deduplication of integration events. | 3 | | **MQ\_PREFETCH\_COUNT** | Number of messages the Silo service prefetches from the message queue. | 10 | | **INTEGRATION\_CALLBACK\_BASE\_URL** | Base URL for integration callbacks. Defaults to `WEB_URL` if not set. | | ### Outbox poller settings | Variable | Description | Default Value | | -------------------------------------------- | --------------------------------------------------------------------- | ------------- | | **OUTBOX\_POLLER\_MEMORY\_LIMIT\_MB** | Maximum memory usage (in MB) before the outbox poller restarts. | 512 | | **OUTBOX\_POLLER\_INTERVAL\_MIN** | Minimum polling interval (in seconds) for the outbox poller. | 0.25 | | **OUTBOX\_POLLER\_INTERVAL\_MAX** | Maximum polling interval (in seconds) for the outbox poller. | 2 | | **OUTBOX\_POLLER\_BATCH\_SIZE** | Number of outbox events to process per polling cycle. | 250 | | **OUTBOX\_POLLER\_MEMORY\_CHECK\_INTERVAL** | Interval (in seconds) between memory usage checks. | 30 | | **OUTBOX\_POLLER\_POOL\_SIZE** | Default connection pool size for the outbox poller. | 4 | | **OUTBOX\_POLLER\_POOL\_MIN\_SIZE** | Minimum number of connections in the pool. | 2 | | **OUTBOX\_POLLER\_POOL\_MAX\_SIZE** | Maximum number of connections in the pool. | 10 | | **OUTBOX\_POLLER\_POOL\_TIMEOUT** | Timeout (in seconds) for acquiring a connection from the pool. | 30.0 | | **OUTBOX\_POLLER\_POOL\_MAX\_IDLE** | Maximum idle time (in seconds) for a connection before it is closed. | 300.0 | | **OUTBOX\_POLLER\_POOL\_MAX\_LIFETIME** | Maximum lifetime (in seconds) for a connection before it is recycled. | 3600 | | **OUTBOX\_POLLER\_POOL\_RECONNECT\_TIMEOUT** | Timeout (in seconds) for reconnecting a dropped connection. | 5.0 | | **OUTBOX\_POLLER\_POOL\_HEALTH\_CHECK\_INTERVAL** | Interval (in seconds) between connection health checks. | 30 | ### Automation consumer settings | Variable | Description | Default Value | | -------------------------------------- | --------------------------------------------------------------- | ------------------------------ | | **AUTOMATION\_EVENT\_STREAM\_QUEUE\_NAME** | RabbitMQ queue name for automation event processing. | plane.event\_stream.automations | | **AUTOMATION\_EVENT\_STREAM\_PREFETCH** | Number of messages to prefetch for automation event processing. | 10 | | **AUTOMATION\_EXCHANGE\_NAME** | RabbitMQ exchange name for automation events. | plane.event\_stream | ### Plane Intelligence (PI) settings | Variable | Description | Default Value | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | | **OPENAI\_API\_KEY** | API key for OpenAI services used by Plane Intelligence. | | | **OPENAI\_BASE\_URL** | Custom base URL for OpenAI-compatible API endpoints. | | | **CLAUDE\_API\_KEY** | API key for Anthropic Claude services used by Plane Intelligence. | | | **CLAUDE\_BASE\_URL** | Custom base URL for Claude API endpoints. | | | **GROQ\_API\_KEY** | API key for Groq services used by Plane Intelligence. | | | **GROQ\_BASE\_URL** | Custom base URL for Groq API endpoints. | | | **COHERE\_API\_KEY** | API key for Cohere services used by Plane Intelligence. | | | **COHERE\_BASE\_URL** | Custom base URL for Cohere API endpoints. | | | **CUSTOM\_LLM\_ENABLED** | Enable a custom OpenAI-compatible LLM provider. Set to `true` to enable. | false | | **CUSTOM\_LLM\_MODEL\_KEY** | Model key identifier for the custom LLM. | gpt-oss-120b | | **CUSTOM\_LLM\_BASE\_URL** | Base URL for the custom LLM API endpoint. | | | **CUSTOM\_LLM\_API\_KEY** | API key for the custom LLM provider. | | | **CUSTOM\_LLM\_NAME** | Display name for the custom LLM in the Plane UI. | GPT-OSS-120B | | **CUSTOM\_LLM\_DESCRIPTION** | Description of the custom LLM shown in the Plane UI. | A self-hosted OpenAI-compatible model | | **CUSTOM\_LLM\_MAX\_TOKENS** | Maximum token limit for the custom LLM. | 128000 | | **EMBEDDING\_MODEL** | Model key for generating embeddings (e.g. `cohere/embed-v4.0`). Required for PI API startup when Plane AI is enabled. | | | **OPENSEARCH\_ML\_MODEL\_ID** | OpenSearch ML model ID for the deployed embedding model. | | | **OPENSEARCH\_EMBEDDING\_DIMENSION** | Vector dimension for `knn_vector` fields; must match the embedding model and stay aligned with the API service. See [Configure embedding model](/self-hosting/govern/plane-ai/configure-embedding-model). | 1536 | | **BR\_AWS\_ACCESS\_KEY\_ID** | AWS access key for Amazon Bedrock integration. | | | **BR\_AWS\_SECRET\_ACCESS\_KEY** | AWS secret key for Amazon Bedrock integration. | | | **BR\_AWS\_SESSION\_TOKEN** | AWS session token for Amazon Bedrock integration (for temporary credentials). | | | **FASTAPI\_APP\_WORKERS** | Number of FastAPI workers for the PI service. | 1 | | **PLANE\_OAUTH\_STATE\_EXPIRY\_SECONDS** | Expiry time (in seconds) for PI OAuth state tokens. | 82800 | | **CELERY\_VECTOR\_SYNC\_ENABLED** | Enable periodic vector synchronization for AI-powered search. | 0 | | **CELERY\_VECTOR\_SYNC\_INTERVAL** | Interval (in seconds) for vector synchronization. | 3 | | **CELERY\_WORKSPACE\_PLAN\_SYNC\_ENABLED** | Enable periodic workspace plan synchronization. | 0 | | **CELERY\_WORKSPACE\_PLAN\_SYNC\_INTERVAL** | Interval (in seconds) for workspace plan synchronization. | 86400 | | **CELERY\_DOCS\_SYNC\_ENABLED** | Enable periodic documents synchronization for AI indexing. | 0 | | **CELERY\_DOCS\_SYNC\_INTERVAL** | Interval (in seconds) for documents synchronization. | 86400 | ::: details Community Edition This guide provides a comprehensive overview of all environment variables available for configuring your self-hosted Plane Community Edition. Use these variables to customize your instance to fit your deployment needs. ## Where to find the environment file The environment configuration file is located at: ```bash plane-selfhost/plane-app/plane.env ``` ## Environment Variables ### General settings | Variable | Description | Default Value | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | **APP\_DOMAIN** | Domain name for your Plane instance. This determines how users will access your installation. | localhost | | **APP\_RELEASE** | Release version of Plane. Helps with compatibility and troubleshooting. | stable | | **WEB\_URL** | The complete base URL for the web application including protocol. Essential for email links and integrations. | http://${APP\_DOMAIN} | | **CORS\_ALLOWED\_ORIGINS** | Comma-separated list of origins allowed to make cross-origin requests to your API. | http://${APP\_DOMAIN} | | **DEBUG** | Toggles debug mode for verbose logging. Set to `1` to enable, `0` to disable. Not recommended in production as it may expose sensitive information. | 0 | | **LISTEN\_HTTP\_PORT** | Port for HTTP traffic. The primary port your users will connect to. | 80 | | **LISTEN\_HTTPS\_PORT** | Port for HTTPS traffic. The primary port your users will connect to. | 443 | ### Scaling and performance | Variable | Description | Default Value | | ------------------------ | ------------------------------------------------------------------------------------------------- | ------------- | | **WEB\_REPLICAS** | Number of web server replicas for serving the frontend UI. Increase for better load distribution. | 1 | | **SPACE\_REPLICAS** | Number of space service replicas handling workspace-related operations. | 1 | | **ADMIN\_REPLICAS** | Number of admin service replicas for administrative functions. | 1 | | **API\_REPLICAS** | Number of API service replicas processing API requests. | 1 | | **WORKER\_REPLICAS** | Number of worker service replicas handling background tasks. | 1 | | **BEAT\_WORKER\_REPLICAS** | Number of beat worker replicas for scheduled/periodic tasks. | 1 | | **LIVE\_REPLICAS** | Number of live service replicas for real-time updates and WebSocket connections. | 1 | | **GUNICORN\_WORKERS** | Number of Gunicorn workers per API instance. Increase for better request handling capacity. | 1 | ### API settings | Variable | Description | Default Value | | ---------------------- | ----------------------------------------------------------------------- | ------------- | | **API\_KEY\_RATE\_LIMIT** | Rate limit for API requests to prevent abuse. Format: `number/timeunit` | 60/minute | ### Database settings | Variable | Description | Default Value | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | **PGHOST** | Hostname or IP address of your PostgreSQL server. | plane-db | | **PGDATABASE** | Name of the PostgreSQL database Plane will use. | plane | | **POSTGRES\_USER** | Username for PostgreSQL authentication. | plane | | **POSTGRES\_PASSWORD** | Password for PostgreSQL authentication. Use a strong, unique password. | plane | | **POSTGRES\_DB** | Same as PGDATABASE - the name of the PostgreSQL database. | plane | | **POSTGRES\_PORT** | TCP port your PostgreSQL server is listening on. | 5432 | | **PGDATA** | Directory path where PostgreSQL data is stored. Only relevant if you're managing PostgreSQL directly. | /var/lib/postgresql/data | | **DATABASE\_URL** | Full connection string for PostgreSQL. If provided, overrides individual settings. Format: `postgresql://username:password@host:port/dbname` | | ### Redis settings | Variable | Description | Default Value | | -------------- | ------------------------------------------------------------------------------- | ------------- | | **REDIS\_HOST** | Hostname or IP address of your Redis server. | plane-redis | | **REDIS\_PORT** | TCP port your Redis server is listening on. | 6379 | | **REDIS\_URL** | Full connection string for Redis. Format: `redis://username:password@host:port` | | ### RabbitMQ settings | Variable | Description | Default Value | | --------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------- | | **RABBITMQ\_HOST** | Hostname or IP address of your RabbitMQ server. | plane-mq | | **RABBITMQ\_PORT** | TCP port your RabbitMQ server is listening on. | 5672 | | **RABBITMQ\_USER** | Username for RabbitMQ authentication. | plane | | **RABBITMQ\_PASSWORD** | Password for RabbitMQ authentication. Use a strong, unique password. | plane | | **RABBITMQ\_VHOST** | Virtual host for RabbitMQ, providing logical separation of resources. | plane | | **AMQP\_URL** | Full connection string for RabbitMQ. If not provided, it's constructed from individual settings. | amqp://plane:plane@plane-mq:5672/plane | ### File Storage (MinIO / S3) | Variable | Description | Default Value | | ------------------------- | --------------------------------------------------------------------------------------------------- | ------------- | | **USE\_MINIO** | Whether to use MinIO for object storage. Set to `1` to enable, `0` to use other configured storage. | 1 | | **MINIO\_ENDPOINT\_SSL** | Force HTTPS for MinIO when handling SSL termination. Set to `1` to enable. | 0 | | **AWS\_REGION** | AWS region for S3 storage services. Applies when using S3 or MinIO. | | | **AWS\_ACCESS\_KEY\_ID** | Access key for MinIO or AWS S3 authentication. | access-key | | **AWS\_SECRET\_ACCESS\_KEY** | Secret key for MinIO or AWS S3 authentication. | secret-key | | **AWS\_S3\_ENDPOINT\_URL** | Endpoint URL for MinIO or S3-compatible storage. | | | **AWS\_S3\_BUCKET\_NAME** | S3 bucket name for file storage. All uploads will be stored in this bucket. | uploads | | **FILE\_SIZE\_LIMIT** | Maximum file upload size in bytes. | 5242880 (5MB) | ### Security settings | Variable | Description | Default Value | | -------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------- | | **SECRET\_KEY** | Secret key used for cryptographic operations like session handling and token generation. Should be a long, random string. | | ::: --- --- url: 'https://developers.plane.so/self-hosting/telemetry.html' description: >- Understand what telemetry data Plane collects, how it is used, and how to opt out. Privacy-focused data collection for self-hosted instances. --- # Data collection and usage Plane collects anonymized data to enhance your user experience, ensure product stability, and drive continuous improvements. This article talks about what we collect, what we don't, and how we use collected data. ## What Plane collects With the exception of the instance admin's email address on self-hosted Plane, we don't collect any personally identifiable information (PII). All usage data is anonymized and isn't connected to individual users. ### Instance admin details Plane collects just the instance admin's name and email to communicate essential upgrade news, security alerts, and other critical notifications. ### Instance setup When an instance is created, Plane collects just the instance ID so we can track upgrades and troubleshoot paid features. ### Usage for billing Plane tracks the number of workspaces, members, and roles to bill you correctly and ensure accurate reporting in your workspace's **Billing and plans** screen and the Prime portal. ### Application data Plane tracks anonymized workspace activity, including but not limited to the number of projects, issues, cycles, modules, comments, issue types, custom properties, timesheet downloads, active importers, and integrations. This data helps us understand product usage, adoption, and build new features. ### User interaction and behavior Plane collects anonymized data for how you work with Plane when creating, updating, or deleting Plane entities, including but not restricted to projects, issues, cycles, modules, and others. This data helps us understand user journeys, pain points, and overall behavior. ### Performance data Plane collects machine configurations, environment details, OS flavors, network throughput, and stack traces to help troubleshoot and prevent performance hiccups. ## Data sample ```json { "Created At": "October 7, 2024, 1:50 PM", "Page Count": "14", "Is Telemetry Enabled": "true", "Updated At": "October 7, 2024, 1:50 PM", "User Count": "97", "Cycle Count": "62", "Cycle Issue Count": "710", "Module Issue Count": "428", "ID": "1234567890", "Updated By ID": null, "Current Version": "0.23.0", "Issue Count": "2,000", "Instance ID": "1234567890", "Latest Version": "0.23.0", "Module Count": "81", "Name": "Test", "Workspace Count": "11", "Project Count": "20" } ``` ## How we use the data * **Improving your experience**\ Analyzing real-world usage guides us in making Plane more intuitive and user-friendly. * **Detecting and fixing bugs**\ Identifying bugs and errors as they occur enables us to quickly investigate and fix issues, minimize downtime, and improve product performance. * **Enhancing security**\ Monitoring for abnormal patterns helps us identify and mitigate potential security threats or vulnerabilities, ensuring data protection and platform integrity. * **Driving product decisions**\ Understanding how you interact with Plane helps us prioritize future developments and focus on high-impact features and improvements that drive better outcomes. ## Disable telemetry As much as we'd love to understand your usage better, we agree that you should be in control of your data. If for any reason you don't want to send us that data, you can turn telemetry collection off in three clicks. 1. Go to your instance's God Mode by appending `/god-mode` to the domain you have hosted Plane on. 2. In the **General Settings** pane on the right, disable the **Telemetry** toggle button. ![Disable telemetry](/images/disable-telemetry.webp#hero) 3. Click **Save changes**. --- --- url: 'https://developers.plane.so/self-hosting/manage/upgrade-plane.html' description: >- Upgrade self-hosted Plane to the latest version. Step-by-step guide for updating your Plane installation safely. --- # Update Plane version Keeping Plane up to date ensures you’re using the latest features, improvements, and security fixes. Here’s how to upgrade your Plane installation with a single command. ::: info The upgrade process may involve a brief downtime as services are updated and restarted. ::: ## Prerequisites We recommend creating a backup of your data before any version updates. See [Backup data](/self-hosting/manage/backup-restore). ## Check version You can quickly check your Plane version by clicking the **?** icon on the sidebar. ![Check version number](https://media.docs.plane.so/product/check-version.webp#hero) ## Update version ::: warning For Commercial Edition v1.13.0, ensure you're using the **latest version of Docker Compose**. Check your Docker Compose version with `docker-compose --version` and update if needed. **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: 1. Update your Prime CLI with the command ↓: ```bash sudo prime-cli update-cli ``` The latest version of the CLI ensures your Plane upgrades happen smoothly. 2. To update Plane to the latest version, run: ```bash sudo prime-cli upgrade ``` This command checks for the latest version of Plane and applies the upgrade if a new version is available. ::: details Community Edition > \[!WARNING] > This guide covers how to upgrade from version 0.14.0 and above. If you’re running version 0.13.2 or below, first follow the guide to [upgrade to version v0.14.0](/self-hosting/manage/upgrade-from-0.13.2-0.14.0) before continuing with these steps. #### Prerequisites Before starting, make a backup of your Plane instance. For detailed steps, see the [Backup data](/self-hosting/manage/backup-restore#backup-data) section. This is strongly recommended to ensure you have a safe restore point. #### Update version 1. Download the latest stable release with ↓: ```bash curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/setup.sh ``` 2. Execute the setup script with ↓: ```bash ./setup.sh ``` This will bring up a menu with several options. Select option `5` to upgrade: ```bash Select a Action you want to perform: 1) Install (x86_64) 2) Start 3) Stop 4) Restart 5) Upgrade 6) View Logs 7) Backup Data 8) Exit Action [2]: 5 ``` Choosing this option stops all services and downloads the latest `docker-compose.yaml` and `variables-upgrade.env` files. The `plane.env` file won’t be overwritten, so your existing environment settings are safe. You’ll see a message indicating the services have been stopped. ![Stopped Docker services](/images/docker-compose/stopped-docker.png) 3. After the update completes, select `6` to exit the prompt. 4. After the upgrade, open `variables-upgrade.env` and compare it with your `plane.env` file. Copy any new variables from `variables-upgrade.env` to your `plane.env` file and set the correct values. This step is essential to ensure that all configuration changes are in place for the latest version. 5. Once your `plane.env` file is updated, start your Plane instance again by selecting option `2`. ::: --- --- url: >- https://developers.plane.so/self-hosting/manage/upgrade-from-0.13.2-0.14.0.html description: >- Upgrade self-hosted Plane to the latest version. Step-by-step guide for updating your Plane installation safely. --- # Mandatory checkpoint at v0.14.0 If you’re upgrading from `v0.13.2` or below, there are some additional migration steps due to significant changes in the self-hosting setup. Follow these instructions to migrate your data to the new volume structure in `v0.14.0`. 1. First, stop the running `v0.13-2` (or older) instance of Plane. If it's still running, you might hit a "ports not available" error, which will prevent the `v0.14-0` containers from starting up correctly. ```bash docker compose down ``` 2. Create a new folder for `v0.14-0` to ensure a clean installation. ```bash mkdir plane-selfhost cd plane-selfhost ``` 3. Set up the environment variable for the `RELEASE` variable, then download and prepare the installation script: ```bash export RELEASE=v0.14-dev curl -fsSL https://raw.githubusercontent.com/makeplane/plane/master/deploy/selfhost/install.sh | sed -e 's@BRANCH=${BRANCH:-master}@BRANCH='"$RELEASE"'@' -e 's@APP_RELEASE="stable"@APP_RELEASE='"$RELEASE"'@' > setup.sh chmod +x setup.sh ``` 4. Execute the script to install Plane: ```bash ./setup.sh install ``` 5. Start up your new v0.14-0 Plane instance: ```bash ./setup.sh start ``` 6. Now stop the instance to initialize the new Docker volumes: ```bash ./setup.sh stop ``` 7. Download the migration script: ```bash curl -fsSL -o migrate.sh https://raw.githubusercontent.com/makeplane/plane/master/deploy/selfhost/migration-0.13-0.14.sh chmod +x migrate.sh ``` 8. Run the migration script: ```bash ./migrate.sh ``` You’ll see the following instructions: ``` ****************************************************************** This script is solely for the migration purpose only. This is a 1 time migration of volume data from v0.13.2 => v0.14.x Assumption: 1. Postgres data volume name ends with _pgdata 2. Minio data volume name ends with _uploads 3. Redis data volume name ends with _redisdata Any changes to this script can break the migration. Before you proceed, make sure you run the below command to know the docker volumes docker volume ls -q | grep -i "_pgdata" docker volume ls -q | grep -i "_uploads" docker volume ls -q | grep -i "_redisdata" ******************************************************* Given below list of REDIS volumes, identify the prefix of source and destination volumes leaving "_redisdata" --------------------- plane-app_redisdata v0132_redisdata Provide the Source Volume Prefix : ``` 9. Open a second terminal and run the commands shown above to identify your source and destination volume prefixes. For example, if you run `docker volume ls -q | grep -i "_pgdata"`, you might see something like: ![](/images/update-plane/docker-volumes.png) In this example, `plane-013-dev` is the prefix for `v0.13.2`, and `plane-app` is the prefix for `v0.14.0`. 10. Return to the original terminal, enter the source volume prefix `plane-013-dev` and destination volume prefix `plane-app`, and press ENTER: ```bash Provide the Source Volume Prefix : plane-013-dev Provide the Destination Volume Prefix : plane-app ``` If there are any issues, an error will appear. For a successful migration, there will be no error, and the process will exit quietly. 11. Restart the upgraded v0.14.0 instance with: ```bash ./setup.sh restart ``` 12. Login as instance admin by appending `/god-mode` to your domain. 13. Once logged in, just click **Save Changes** to finalize your setup. 14. You’re all set! Log in to your updated `v0.14-0` instance to check if all of your data has migrated successfully. 15. Now, [update to the latest version](/self-hosting/manage/upgrade-plane#update-version). --- --- url: >- https://developers.plane.so/self-hosting/manage/update-plane/airgapped-edition/update-airgapped-docker.html description: >- Upgrade your airgapped Plane instance running on Docker by cloning images, replacing configuration files, and uploading a new license. --- # Update Airgapped Edition on Docker Since airgapped instances can't pull updates from the internet, updating the version requires manually transferring the latest Docker images and configuration files from a machine with internet access. ## Prerequisites * A machine with internet access to download images and files. * Access to your airgapped Docker registry. * Your current `plane.env` file backed up. ## Update Plane 1. On a machine with internet access, pull the latest Plane images and push them to your airgapped Docker registry. Follow the guide for [cloning and pushing Plane Docker images](https://developers.plane.so/self-hosting/methods/clone-docker-images). Once complete, the latest Plane images are available in your internal registry. 2. On the same machine with internet access, download the updated `docker-compose.yml` and environment template for your target version. ```bash # Download docker-compose.yml curl -fsSL https://prime.plane.so/releases//docker-compose-airgapped.yml -o docker-compose.yml # Download environment template curl -fsSL https://prime.plane.so/releases//variables-airgapped.env -o plane.env ``` Transfer both files to your airgapped instance and replace the existing ones. Before replacing your existing `plane.env`, compare it with the new template. Copy over any custom values from your old plane.env into the new template. The new template may include additional variables required by the latest version, so always use the new file as the base and bring your existing values into it. :::info Replace `` with the version you're upgrading to (e.g., v2.5.2). Check the [release notes](https://plane.so/changelog?category=self-hosted) for the latest available release version. ::: 3. Download the latest license file for the new version from [prime.plane.so](https://prime.plane.so). Follow [this guide](https://developers.plane.so/self-hosting/manage/manage-licenses/activate-airgapped) to activate license. 4. Restart the instance to bring the instance back up with the new configuration. ```bash docker compose up -d ``` Verify the upgrade by checking the version in your Plane application. --- --- url: >- https://developers.plane.so/self-hosting/manage/update-plane/airgapped-edition/update-airgapped-kubernetes.html description: >- Upgrade your airgapped Plane instance running on Kubernetes by cloning images, updating the Helm chart, and redeploying. --- # Update Airgapped Edition on Kubernetes Since airgapped clusters can't pull updates from the internet, upgrading requires manually transferring Docker images to your private registry and updating the Helm chart. ## Prerequisites * A machine with internet access to download images and the Helm chart. * Access to your airgapped Docker registry used by the cluster. * Your current Helm `values.yaml` file backed up. ## Update Plane 1. On a machine with internet access, pull the latest Plane images and push them to your air-gapped Docker registry. Follow the guide for [cloning and pushing Plane Docker images](https://developers.plane.so/self-hosting/methods/clone-docker-images). Once complete, the latest Plane images are available in your internal registry. 2. Download the latest Plane Enterprise Helm chart. You can check the most recent version on [Artifact Hub](https://artifacthub.io/packages/helm/makeplane/plane-enterprise). ```bash # Using wget wget https://github.com/makeplane/helm-charts/releases/download/plane-enterprise-/plane-enterprise-.tgz # Using curl curl -L -O https://github.com/makeplane/helm-charts/releases/download/plane-enterprise-/plane-enterprise-.tgz ``` Transfer the `.tgz` file to a machine that can access the cluster. :::info Replace \ with the latest Helm chart version (e.g., 2.2.4). You can check the most recent version on [Artifact Hub](https://artifacthub.io/packages/helm/makeplane/plane-enterprise). ::: Before replacing your existing `values.yaml`, compare it with the new Helm chart's default values. Copy over any custom configuration from your old `values.yaml` into the new template. The new chart version may include additional or renamed fields, so always use the new default values as the base and bring your existing configuration into it. 3. In your `values.yaml`, update `planeVersion` to match the version of Plane images you pushed to the registry. ```yaml planeVersion: ``` :::info Replace `` with the version you're upgrading to (e.g., v2.5.2). Check the [release notes](https://plane.so/changelog?category=self-hosted) for the latest available release version. ::: 4. Once the Helm chart and `values.yaml` file are updated, redeploy the Helm release in your Kubernetes cluster to complete the update. Verify the upgrade by checking the version in your Plane application. --- --- url: >- https://developers.plane.so/self-hosting/manage/manage-licenses/activate-pro-and-business.html description: >- Activate your Plane Pro or Business Edition license. Upgrade from Community Edition to unlock advanced project management features. --- # Activate Pro or Business on Commercial Edition Pro and Business plan licenses are activated at the workspace level as each license is tied to a specific workspace. ## Activate license key Activate a paid plan license on your self-hosted Plane instance using a license key from the Prime portal. 1. Login to the [Prime portal](https://prime.plane.so/licenses) with the same email address you used to purchase one of our paid plans. 2. Go to [Manage licenses](https://prime.plane.so/licenses). Copy the value of the **License key** for the license you want to activate. ![Manage licenses](https://media.docs.plane.so/activate-license/copy-license-key.webp#hero) > \[!TIP] > Click the **Get more licenses** button as in the image above on the [Prime portal](https://prime.plane.so/licenses/plans) to buy more licenses. 3. On the Plane app, navigate to **Workspace Settings > Billing and plans** 4. Click the **Activate this Workspace** button. ![Activate workspace](https://media.docs.plane.so/activate-license/enter-license-key-selfhosted.webp#hero-tr) 5. Paste the license key in the **Enter license key** box. 6. Click **Activate**. You will see a confirmation. 7. That's it. To check your plan at any time and find additional details, just go to the **Billing and plans** tab in **Workspace Settings**. ![Manage subscription](https://media.docs.plane.so/activate-license/pro-activated-cloud.webp#hero-tr) ## Sync plan If you've made changes to your subscription, like renewing your license, upgrading your plan, or adjusting seats, use **Sync plan** to pull those updates into your workspace. Syncing refreshes your workspace with the latest subscription information from the Prime server, including plan type, seat count, expiration dates, and feature access. **To sync your plan:** 1. Navigate to **Workspace Settings > Billing and plans**. 2. Click **Sync plan**. 3. Wait for the sync to complete. You'll see a confirmation once your workspace is updated. Use this whenever you see a mismatch between what's in the Prime portal and what appears in your workspace, or after making any subscription changes externally. ## Delink license key Your license key is linked to both a workspace and an instance, meaning it can only be used on one workspace on one machine at a time. If you switch machines or reinstall the Commercial edition, you’ll need to reactivate your workspace. This helps prevent any misuse of the license on multiple machines or workspaces. To make it easier for you to move between machines or workspaces, we've added a new Delink feature. This lets you free up your license from its current workspace, so you can reuse it on a new machine or workspace. Here’s how to delink your license key from a workspace: 1. Head over to the **Billing and Plans** screen of the workspace that's currently using the license. 2. Click **Delink license key**. This will release the license key, making it available for use on another machine or workspace. ![Delink license key](https://media.docs.plane.so/activate-license/delink-license-key.webp#hero-tl) 3. Restart the instance using `prime-cli restart`. 4. If you’re switching machines or reinstalling the Commercial edition, see [Move Plane instance to another server](https://developers.plane.so/self-hosting/manage/migrate-plane). 5. Ensure you are connected to the internet and reactivate the new workspace using the license key you delinked earlier. --- --- url: >- https://developers.plane.so/self-hosting/manage/manage-licenses/activate-enterprise.html description: Activate your Enterprise Grid license on the Commercial Edition. --- # Activate Enterprise Grid on Commercial Edition Enterprise Grid licenses are activated at the instance level through God Mode, not through individual workspace settings. This gives instance administrators centralized control over Enterprise features across all workspaces. ## Activate license key 1. Sign in to the [Prime portal](https://prime.plane.so/licenses) with the email address you used to purchase your Enterprise Grid. 2. Navigate to **Manage licenses**. 3. Copy the license key for your Enterprise Grid. 4. Sign in to your Plane instance in [God Mode](/self-hosting/govern/instance-admin). 5. Select **Billing** from the left pane. 6. Paste your license key in the **Activate Enterprise license** field. 7. Click **Activate**. ![Activate Enterprise license](/images/activate-license/activate-enterprise-plan.webp#hero) Once activated, Enterprise features become available across all workspaces on your instance. --- --- url: >- https://developers.plane.so/self-hosting/manage/manage-licenses/activate-airgapped.html description: >- Activate and configure your Plane Airgapped Edition license. Offline license activation for air-gapped environments without internet access. --- # Activate Pro or Business on Airgapped Edition Once your air-gapped installation is running, you'll need to activate your workspace with the license file. 1. Login to the [Prime portal](https://prime.plane.so/licenses) with the same email address you used to purchase the paid plan. 2. Go to [Manage licenses](https://prime.plane.so/licenses). 3. Click **Download license** to download the license file for your Plane version. ![Download license file](/images/activate-license/download-license.webp#hero) 4. Navigate to the [Workspace Settings](https://docs.plane.so/core-concepts/workspaces/overview#workspace-settings) in the Plane application. 5. Select **Billing and plans** on the right pane. 6. Click the **Activate this workspace** button. ![Upload license file](/images/activate-license/upload-airgapped-license-file.webp#hero) 7. Upload the license file to activate your workspace. You now have Plane running in your air-gapped environment. If you run into any issues, check the logs, or reach out to our support team for assistance. --- --- url: >- https://developers.plane.so/self-hosting/manage/manage-licenses/activate-airgapped-enterprise.html description: >- Offline Enterprise Grid license activation for airgapped environments without internet access. --- # Activate Enterprise Grid on Airgapped Edition Once your air-gapped installation is running, you'll need to activate your workspace with the license file. 1. Login to the [Prime portal](https://prime.plane.so/licenses) with the same email address you used to purchase the paid plan. 2. Go to [Manage licenses](https://prime.plane.so/licenses). 3. Click **Download license** to download the license file for your Plane version. ![Download license file](/images/activate-license/download-license.webp#hero) 4. Sign in to your Plane instance in [God Mode](/self-hosting/govern/instance-admin). 5. Select **Billing** from the left pane. 6. Upload the license file to activate your instance. ![Upload license file](/images/activate-license/upload-airgapped-enterprise.webp#hero) 7. Click **Activate**. --- --- url: 'https://developers.plane.so/self-hosting/manage/backup-restore.html' description: >- Backup and restore Plane data. Complete guide for backing up database, storage, and configuration files. --- # Backup and restore data Backing up your data regularly helps prevent data loss and allows you to restore your system quickly if necessary. Follow these instructions to back up and restore your data using Plane’s command-line interface. ## For Docker Compose ### Backup data ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: Create a backup of your Plane data with ↓: ```bash sudo prime-cli backup ``` This command initiates a full backup of all critical data, storing it in the default backup location at: ```bash /opt/plane/backups ``` Each backup file will be timestamped to ensure you can easily identify the latest or a specific backup if needed. ### Backup plane.env If you need to back up only the `plane.env` file, you'll need to do it manually. Here’s how: 1. Navigate to the `/opt/plane` folder on your machine or server where Plane is installed.. 2. Locate the `plane.env` file. 3. Copy this file to a different location as a backup, so you can restore it if needed. ### Restore data You can restore your data from a previous backup with ↓: ```bash sudo prime-cli restore ``` This command prompts the restoration process, which will overwrite the current data with the data from the most recent backup file. Ensure you have selected the correct backup before running this command, as restoring will replace your current data. ::: details Community Edition #### Backup data To create a backup, start by running the setup script: ```bash ./setup.sh ``` You’ll see a menu of options—just type 7 to select "Backup Data." ``` Select an Action you want to perform: 1) Install (x86_64) 2) Start 3) Stop 4) Restart 5) Upgrade 6) View Logs 7) Backup Data 8) Exit Action [2]: 7 ``` The system will start backing up the PostgreSQL, Redis, and upload data: ``` Backing Up plane-app_pgdata Backing Up plane-app_redisdata Backing Up plane-app_uploads Backup completed successfully. Backup files are stored in /....../plane-app/backup/20240502-1120 ``` The backup files are stored locally, so you can copy them to an external storage service if needed for extra security. #### Backup plane.env If you need to back up only the `plane.env` file, you'll need to do it manually. Here’s how: 1. Navigate to the folder on your machine or server where Plane is installed.. 2. Locate the `plane.env` file. 3. Copy this file to a different location as a backup, so you can restore it if needed. *** #### Restore data Follow these steps to restore data from a backup: 1. Make sure Plane-CE is installed and started, then stop it. This ensures the necessary Docker volumes are ready. 2. Use the command ↓ to download the restore script. It’s easiest to save it in the same directory as `setup.sh`. ```bash curl -fsSL -o restore.sh https://raw.githubusercontent.com/makeplane/plane/refs/heads/preview/deployments/cli/community/restore.sh chmod +x restore.sh ``` 3. Now, run the command ↓ to restore your data, specifying the path to your backup folder (the folder with the `*.tar.gz` files): ```bash ./restore.sh ``` Here’s an example output for restoring from /opt/plane-selfhost/plane-app/backup/20240722-0914: ```bash -------------------------------------------- ____ _ ///////// | _ \| | __ _ _ __ ___ ///////// | |_) | |/ _` | '_ \ / _ \ ///// ///// | __/| | (_| | | | | __/ ///// ///// |_| |_|\__,_|_| |_|\___| //// //// -------------------------------------------- Project management tool from the future -------------------------------------------- Found /opt/plane-selfhost/plane-app/backup/20240722-0914/pgdata.tar.gz .....Restoring plane-app_pgdata .....Successfully restored volume plane-app_pgdata from pgdata.tar.gz Found /opt/plane-selfhost/plane-app/backup/20240722-0914/redisdata.tar.gz .....Restoring plane-app_redisdata .....Successfully restored volume plane-app_redisdata from redisdata.tar.gz Found /opt/plane-selfhost/plane-app/backup/20240722-0914/uploads.tar.gz .....Restoring plane-app_uploads .....Successfully restored volume plane-app_uploads from uploads.tar.gz Restore completed successfully. ``` 4. Start your Plane instance again with ↓: ```bash ./setup.sh start ``` That’s it! You’re back up and running with your restored data. ::: ## Other deployment methods For Kubernetes, or other deployment methods, use your platform's native backup tools. Plane stores data in two places that need to be backed up: | Component | What it contains | | ----------------------- | ---------------------------------------------------------------------------- | | **PostgreSQL database** | All Plane data — workspaces, projects, work items, users, comments, settings | | **Object storage** | Attachments, uploaded images, files (MinIO, S3, or S3-compatible storage) | ### Configuration files Also back up your environment configuration — this includes database connection strings, storage credentials, and other settings. * **Kubernetes:** Helm values file, ConfigMaps, and Secrets * **Other platforms:** Environment variables or configuration files specific to your setup :::tip Store backups in a separate location from your Plane installation — ideally offsite or in a different cloud region. ::: --- --- url: 'https://developers.plane.so/self-hosting/upgrade-from-community.html' description: >- Upgrade self-hosted Plane to the latest version. Step-by-step guide for updating your Plane installation safely. --- # Upgrade from Community to Commercial Edition The Commercial edition comes with the free plan and the flexibility to upgrade to a paid plan at any point. > \[!WARNING] > The instructions provided on this page are specific to installations using Docker. If you are running Plane on Kubernetes, you'll need to manually create a database dump and back up your file storage by copying the relevant volumes or storage paths. ## Prerequisites * Install the [Commercial Edition](/self-hosting/methods/docker-compose#install-plane) on a fresh machine, not the one running the Plane Community Edition. * Be sure to log in as the root user or as a user with sudo access. The `/opt` folder requires sudo or root privileges. :::tabs key:upgrade-options \== Standard setup (built-in DB & storage) {#standard-setup} This upgrade path is for installations using Plane's default PostgreSQL database and MinIO object storage. ## Back up data on Community instance 1. Download the latest version of `setup.sh`. ```bash curl -fsSL https://github.com/makeplane/plane/releases/latest/download/setup.sh -o setup.sh ``` 2. Run the setup.sh backup script to take the backup of the Community Edition instance. ```bash ./setup.sh backup ``` 3. When done, your data will be backed up to the folder shown on the screen. e.g., `/plane-selfhost/plane-app/backup/20240522-1027` This folder will contain 3 `tar.gz` files. * `pgdata.tar.gz` * `redisdata.tar.gz` * `uploads.tar.gz` 4. Copy all the three files from the server running the Community Edition to any folder on the server running the Commercial Edition. e.g., `~/ce-backup` ## Restore data on Commercial instance 1. Start any command-line interface like Terminal and go into the folder with the back-up files. ``` cd ~/ce-backup ``` 2. Copy and paste the script below on Terminal and hit Enter. ``` TARGET_DIR=/opt/plane/data sudo mkdir -p $TARGET_DIR for FILE in *.tar.gz; do if [ -e "$FILE" ]; then tar -xzvf "$FILE" -C "$TARGET_DIR" else echo "No .tar.gz files found in the current directory." exit 1 fi done mv $TARGET_DIR/pgdata/ $TARGET_DIR/db mv $TARGET_DIR/redisdata/ $TARGET_DIR/redis mkdir -p $TARGET_DIR/minio mv $TARGET_DIR/uploads/ $TARGET_DIR/minio/uploads/ ``` 3. This script will extract your Community Edition data and restore it to `/opt/plane/data`. \== Managed services (external DB and storage) {#managed-services} This upgrade path is for installations using external or managed database and object storage services (like AWS RDS and S3). Since your data already lives in external services, you only need to update your configuration — no backup and restore required. ## Update configuration for Commercial Edition 1. Open the `plane.env` file located at `/opt/plane/plane.env`. 2. Configure database connection. 1. Find the `DATABASE_URL` environment variable. 2. Verify it points to your external database: ```ini DATABASE_URL=postgresql://user:password@your-db-host:5432/plane ``` If you need to change it, update the value with your managed database connection string. 3. Configure object storage 1. Find the `#DATASTORE SETTINGS` section in `plane.env` 2. Update these environment variables for your external storage: ```ini USE_MINIO=0 AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_S3_ENDPOINT_URL=https://s3.amazonaws.com AWS_S3_BUCKET_NAME=plane-uploads ``` :::info Setting `USE_MINIO=0` disables the local MinIO service and enables external object storage (S3 or S3-compatible services). ::: 3. Restart Plane services to apply the configuration: ```bash prime-cli restart ``` Your Commercial Edition instance is now connected to your existing external database and storage. ::: :::details Manual backup and restore without CLI Use this method if you prefer to back up data manually or if the setup.sh script isn't working for your environment. ### What gets migrated * PostgreSQL database (all Plane data) * MinIO uploads (attachments, images, files) ### Prerequisites * Plane CE and Commercial versions should be compatible * Shell access to both servers * Docker installed on both servers ### Back up data on Community instance 1. Create backup folders: ```bash mkdir -p ~/ce-backups/db mkdir -p ~/ce-backups/minio/uploads cd ~/ce-backups ``` 2. Back up PostgreSQL data: ```bash docker cp plane-app-plane-db-1:/var/lib/postgresql/data/. db/ ``` 3. Back up MinIO uploads: ```bash docker cp plane-app-plane-minio-1:/export/uploads minio/uploads/ ``` 4. Verify backup sizes: ```bash du -sh db minio/uploads ``` Make sure sizes look reasonable (not just a few KB). 5. Transfer backup to Commercial server: ```bash scp -r ~/ce-backups user@commercial-server:/tmp/ ``` ### Restore data on Commercial instance 1. Stop Plane: ```bash prime-cli stop ``` Verify all containers are down: ```bash docker ps ``` 2. Back up existing Commercial data (safety precaution): ```bash mv /opt/plane/data/db /opt/plane/data/db.bak mv /opt/plane/data/minio/uploads /opt/plane/data/minio/uploads.bak ``` 3. Restore PostgreSQL: ```bash mv /tmp/ce-backups/db /opt/plane/data/db ``` 4. Restore MinIO uploads: ```bash mv /tmp/ce-backups/minio/uploads /opt/plane/data/minio/uploads ``` 5. Start Plane: ```bash prime-cli restart ``` ### Validate the migration * Login works * Projects are visible * Attachments open correctly ### Rollback If something fails, restore from the backup you created in step 2: ```bash prime-cli stop rm -rf /opt/plane/data/db mv /opt/plane/data/db.bak /opt/plane/data/db rm -rf /opt/plane/data/minio/uploads mv /opt/plane/data/minio/uploads.bak /opt/plane/data/minio/uploads prime-cli restart ``` ::: ## What's next * [Activate a paid plan license](/self-hosting/manage/manage-licenses/activate-pro-and-business). --- --- url: 'https://developers.plane.so/self-hosting/manage/community-to-airgapped.html' description: >- Deploy Plane in airgapped environment without internet access. Complete guide for offline Plane installation. --- # Upgrade from Community to Airgapped Edition This guide walks you through migrating your existing Plane Community Edition data to an air-gapped environment. You'll backup your current installation, transfer the data, and restore it in your air-gapped setup. ::: warning **Important**\ Make sure you already have Commercial Airgapped Edition installed on a fresh machine before starting this migration. If you haven't installed it yet, follow our [airgapped installation guide](/self-hosting/methods/airgapped-edition) first. ::: ## Prerequisites * Install the [Commercial Airgapped Edition](/self-hosting/methods/airgapped-edition) on a fresh machine, not the one running the Community Edition. * Be sure to log in as the root user or as a user with sudo access. The `/opt` folder requires sudo or root privileges. ## Backup data on Community instance 1. Download the latest version of `setup.sh`. ```bash curl -fsSL https://github.com/makeplane/plane/releases/latest/download/setup.sh -o setup.sh ``` 2. Run the setup.sh backup script to take the backup of the Community Edition instance. ```bash ./setup.sh backup ``` This will create a backup of the plane community instance in the `backup/` folder with the timestamp as the folder name. ```bash backup/ └── 20250605-0938 ├── pgdata.tar.gz ├── rabbitmq_data.tar.gz ├── redisdata.tar.gz └── uploads.tar.gz ``` ## Restore data on Airgapped instance 1. Download the latest version of `restore-airgapped.sh` ```bash curl -fsSL https://github.com/makeplane/plane/releases/latest/download/restore-airgapped.sh -o restore-airgapped.sh chmod +x restore-airgapped.sh ``` This allows you to restore the Community Edition data to the Commercial Airgapped instance. 2. Copy the `restore-airgapped.sh` script into your backup folder. 3. Move your entire backup folder to the server running the Commercial Airgapped Edition. 4. Open terminal, and execute the following command: ```bash sudo bash restore-airgapped.sh ./20250605-0938 ``` This will prompt you to enter the Commercial Airgapped Edition installation folder using whatever secure method works in your environment. 5. After the data restore is finished, start the instance. ```bash cd sudo docker compose -f docker-compose.yml --env-file plane.env up -d ``` You can now access the Commercial Airgapped instance at `http://` Once your migration is complete, verify that all your projects, issues, and team data have been successfully transferred to your air-gapped environment. --- --- url: 'https://developers.plane.so/self-hosting/manage/view-logs.html' description: >- View and debug container logs for self-hosted Plane. Monitor Docker and Kubernetes service logs to troubleshoot issues. --- # View container logs If you need to check the logs for troubleshooting or to monitor what’s happening in specific Plane services like the API or Worker, you can access them directly from the command line. To view logs, start by running the command ↓: ```bash sudo prime-cli monitor ``` This brings up a table where you can select which container logs you want to view. ![Container logs](/images/view-logs/container-logs.webp#hero) ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: ::: details Community Edition Here’s how to view logs for any service in Plane Community Edition, whether it’s the API, Worker, Redis, or others. This can be really helpful when troubleshooting or just getting insights into how each service is running. 1. Start by running the setup script: ```bash ./setup.sh ``` This will bring up the main menu with options. Select `6` to view logs. ``` Select a Action you want to perform: 1) Install (x86_64) 2) Start 3) Stop 4) Restart 5) Upgrade 6) View Logs 7) Backup Data 8) Exit Action [2]: 6 ``` 2. After choosing `6`, you’ll see a sub-menu listing all available services: ``` Select a Service you want to view the logs for: 1) Web 2) Space 3) API 4) Worker 5) Beat-Worker 6) Migrator 7) Proxy 8) Redis 9) Postgres 10) Minio 0) Back to Main Menu Service: ``` 3. Pick the service whose logs you’d like to check. For example, if you want to view the **API logs**, type `3`. After selecting a service, you’ll see the logs in real-time. Here’s an example of what API logs might look like: ``` api-1 | Waiting for database... api-1 | Database available! api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | Waiting for database migrations to complete... api-1 | No migrations Pending. Starting processes ... api-1 | Instance registered api-1 | ENABLE_SIGNUP loaded with value from environment variable. api-1 | ENABLE_EMAIL_PASSWORD loaded with value from environment variable. api-1 | ENABLE_MAGIC_LINK_LOGIN loaded with value from environment variable. api-1 | GOOGLE_CLIENT_ID loaded with value from environment variable. api-1 | GITHUB_CLIENT_ID loaded with value from environment variable. api-1 | GITHUB_CLIENT_SECRET loaded with value from environment variable. api-1 | EMAIL_HOST loaded with value from environment variable. api-1 | EMAIL_HOST_USER loaded with value from environment variable. api-1 | EMAIL_HOST_PASSWORD loaded with value from environment variable. api-1 | EMAIL_PORT loaded with value from environment variable. api-1 | EMAIL_FROM loaded with value from environment variable. api-1 | EMAIL_USE_TLS loaded with value from environment variable. api-1 | EMAIL_USE_SSL loaded with value from environment variable. api-1 | OPENAI_API_KEY loaded with value from environment variable. api-1 | GPT_ENGINE loaded with value from environment variable. api-1 | UNSPLASH_ACCESS_KEY loaded with value from environment variable. api-1 | Checking bucket... api-1 | Bucket 'uploads' does not exist. Creating bucket... api-1 | Bucket 'uploads' created successfully. api-1 | Public read access policy set for bucket 'uploads'. api-1 | Cache Cleared api-1 | [2024-05-02 03:56:01 +0000] [1] [INFO] Starting gunicorn 21.2.0 api-1 | [2024-05-02 03:56:01 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) api-1 | [2024-05-02 03:56:01 +0000] [1] [INFO] Using worker: uvicorn.workers.UvicornWorker api-1 | [2024-05-02 03:56:01 +0000] [25] [INFO] Booting worker with pid: 25 api-1 | [2024-05-02 03:56:03 +0000] [25] [INFO] Started server process [25] api-1 | [2024-05-02 03:56:03 +0000] [25] [INFO] Waiting for application startup. api-1 | [2024-05-02 03:56:03 +0000] [25] [INFO] ASGI 'lifespan' protocol appears unsupported. api-1 | [2024-05-02 03:56:03 +0000] [25] [INFO] Application startup complete. ``` 4. To exit the logs, use `CTRL+C`. This will take you back to the main menu where you can select another action or view logs from a different service. ::: --- --- url: 'https://developers.plane.so/self-hosting/manage/health-checks.html' description: >- Liveness, readiness, and health endpoints for self-hosted Plane services, and how to wire them into uptime monitors, load balancers, and Kubernetes or Docker Compose probes. --- # Health checks Self-hosted Plane services expose HTTP health endpoints so you can wire up uptime monitoring, load-balancer health checks, and container or orchestrator probes. This page documents every endpoint that ships with Plane, exactly what each one checks, and how to consume them from Kubernetes, Docker Compose, and external monitors. ::: info Edition availability The dedicated liveness, readiness, and detailed health probes documented here — `/api/live/`, `/api/ready/`, `/api/health/`, and the per-service endpoints — ship with **Commercial Edition** (Pro, Business, and Enterprise) deployments. **Community Edition** exposes only the basic root health check at `/` that returns `{ "status": "OK" }` (see [A note on the root endpoint](#a-note-on-the-root-endpoint)). ::: ## Liveness vs. readiness vs. health Plane follows the standard three-tier probe model. Knowing which one to point a given tool at matters: * **Liveness** — "is the process up?" A liveness probe answers a single question: is the service running and able to respond to HTTP at all. It performs no dependency checks. If a liveness probe fails, your orchestrator should **restart** the container. Liveness endpoints in Plane never return a failure body — if the process is alive, you get `200`; if it isn't, the request simply fails to connect. * **Readiness** — "should this instance receive traffic right now?" A readiness probe verifies that the service's critical dependencies (database, cache, Redis) are reachable. If a readiness probe fails, your orchestrator or load balancer should **stop routing traffic** to that instance until it recovers — but it should not restart it, since the process itself is fine. Readiness endpoints return `200` when ready and `503` when a dependency is unavailable. * **Health / detailed** — "what is the current state of this instance?" A detailed health endpoint returns the same up/down signal as readiness but with structured diagnostic detail (timestamps, uptime, per-dependency connection status). Use this for dashboards and debugging rather than as the gate for an orchestrator. ::: tip Why the distinction matters for self-hosting If you point a liveness probe at an endpoint that checks the database, a transient database blip will cause your orchestrator to **kill and restart healthy pods**, turning a small dependency hiccup into a restart storm. Always point liveness at the liveness endpoint and readiness at the readiness endpoint. ::: ## Primary API probes The Plane Django API is the main REST API and web application server. Its probes are the ones most operators need, and they are reachable externally through the reverse proxy under `/api/`. All three are unauthenticated `GET` requests, and all Django routes use a **trailing slash**. | Endpoint | Type | Checks | Healthy | Unhealthy | | -------------- | ----------------- | ------------------------------------- | ------- | ------------------ | | `/api/live/` | Liveness | Process running | `200` | (connection fails) | | `/api/ready/` | Readiness | Database + cache connectivity | `200` | `503` | | `/api/health/` | Health (detailed) | Database + cache connectivity, uptime | `200` | `503` | ### `/api/live/` Returns `200` as long as the process can serve HTTP. No dependency checks are performed. ```bash curl -i https://your-plane-domain.com/api/live/ ``` ```json { "alive": true } ``` ### `/api/ready/` Returns `200` when the database and cache are both reachable, and `503` if either is down. The failure response includes a sanitized `reason` field. ```bash curl -i https://your-plane-domain.com/api/ready/ ``` Healthy (`200`): ```json { "ready": true } ``` Not ready (`503`): ```json { "ready": false, "reason": "" } ``` The database check runs a `SELECT 1` query; the cache check sets and verifies a probe key. Failure reasons are **sanitized** in the response (the full exception is logged server-side only) so probes do not leak internal details. ### `/api/health/` Returns a detailed status payload. Use this for dashboards and diagnostics. It performs the same database and cache checks as readiness, but reports each dependency individually along with a timestamp and uptime. ```bash curl -i https://your-plane-domain.com/api/health/ ``` Healthy (`200`): ```json { "status": "ok", "timestamp": 1717346400000, "uptime": 12345, "database": { "connected": true }, "cache": { "connected": true } } ``` Degraded (`503`): ```json { "status": "degraded", "timestamp": 1717346400000, "uptime": 12345, "database": { "connected": false }, "cache": { "connected": false } } ``` ::: info 5-second result caching The `/api/ready/` and `/api/health/` endpoints cache their dependency-check results for **5 seconds per process** (`_RESULT_TTL_SECONDS=5`). This caps real database and cache hits at one per worker process per 5-second window, even under a flood of probe requests, so aggressive monitoring will not thrash your database or cache. The same 5-second per-worker caching applies to the Plane AI (pi) readiness check. ::: ### A note on the root endpoint The Django API also serves a minimal health indicator at the root path: | Endpoint | Type | Healthy body | | ------------- | ---------------- | ------------------------------- | | `/` | Health (minimal) | `{ "status": "OK" }` | | `/robots.txt` | Other | `User-agent: *` / `Disallow: /` | The root `/` and `/robots.txt` endpoints are mounted at the Django root level, while the detailed probes (`/api/live/`, `/api/ready/`, `/api/health/`) are mounted under `/api/`. This minimal root check is the only health endpoint present in **Community Edition** — the `/api/*` probes and the per-service endpoints below are Commercial Edition only. ::: warning Root path routing through the proxy In the reverse proxy (Caddyfile), `/` is a catch-all that routes to the web frontend (`web:3000`), not to the Django API. A request to `/` through the proxy will hit the frontend rather than the API's root health indicator. **Use the `/api/` probes for monitoring** — they route to the API service explicitly. See [Reverse proxy](/self-hosting/govern/reverse-proxy) for routing details. ::: ## All services and probe endpoints The table below lists every health-related endpoint across all Plane services. The **External path** column shows how the endpoint is reached through the reverse proxy; endpoints marked **internal only** are not exposed through the proxy and must not be made publicly reachable. | Service | Type | External path | Internal route | Checks | Success | Reachable | | ------------- | ------------------ | ------------------------- | -------------------- | ----------------------------- | -------------------- | ----------------------- | | API (Django) | Liveness | `/api/live/` | `/api/live/` | process running | `200` | External | | API (Django) | Readiness | `/api/ready/` | `/api/ready/` | database, cache | `200` (`503` fail) | External | | API (Django) | Health | `/api/health/` | `/api/health/` | database, cache, uptime | `200` (`503` fail) | External | | API (Django) | Health (min) | `/` | `/` | none | `200` | Via proxy hits frontend | | Plane AI (pi) | Liveness | `/pi/live/` | `/live/` | process running | `200` | External | | Plane AI (pi) | Readiness | `/pi/ready/` | `/ready/` | database | `200` (`503` fail) | External | | Plane AI (pi) | Health | `/pi/health/` | `/health/` | database, uptime | `200` (`503` fail) | External | | Plane AI (pi) | Other (deprecated) | `/pi/api/v1/health/` | `/api/v1/health/` | none | `200` | External | | Plane AI (pi) | Other (deprecated) | `/pi/api/v2/health/` | `/api/v2/health/` | none | `200` | External | | live | Health | `/live/health/` | `/health/` | process running, server agent | `200` | External | | live | Other (metrics) | `/live/health/memory` | `/health/memory` | memory, agent metrics | `200` (`401` no key) | External (secret-key) | | silo | Liveness | `/silo/health/` | `/health/` | process running | `201` (`500` fail) | External | | silo | Other | `/silo/health/check-hmac` | `/health/check-hmac` | API connectivity | `201` (`500` fail) | External | | silo | Readiness | `/silo/health/check-db` | `/health/check-db` | database | `200` (`500` fail) | External | | flux | Health | internal only | `/health` | redis, process running | `200` (`503` fail) | Internal | | flux | Readiness | internal only | `/ready` | redis | `200` (`503` fail) | Internal | | flux | Liveness | internal only | `/live` | process running | `200` | Internal | | node-runner | Health | internal only | `/health` | process running | `200` | Internal | | monitor | (prober) | internal only | — | watches other services | — | Internal | ::: warning Internal-only endpoints The **flux**, **node-runner**, and **monitor** services are not exposed through the reverse proxy. Their endpoints are reachable only on the internal Docker/Kubernetes network. Do not expose them publicly. The `live` memory-metrics endpoint (`/live/health/memory`) is reachable through the proxy but is secret-key protected — keep it that way. ::: ## Per-service details ### live (real-time collaboration) The `live` service is the real-time collaboration server for document editing (WebSocket via Hocuspocus), running on internal port `3000` and exposed under the `/live` proxy prefix. **Public health endpoint** — unauthenticated, suitable for probes: ```bash curl -i https://your-plane-domain.com/live/health/ ``` ```json { "status": "OK", "timestamp": "2026-06-02T00:00:00.000Z", "version": "1.0.0" } ``` This endpoint always returns `200` when the process is up; it has no failure state. Point readiness/liveness probes here. **Memory metrics endpoint** — for internal monitoring only, protected by a secret key: ```bash curl -i \ -H "live-server-secret-key: $LIVE_SERVER_SECRET_KEY" \ https://your-plane-domain.com/live/health/memory ``` ```json { "status": "ok", "timestamp": "2026-06-02T00:00:00.000Z", "memory": { "heapUsed": "120MB", "heapTotal": "256MB", "heapPercent": "47%", "rss": "300MB", "external": "5MB", "arrayBuffers": "2MB" }, "serverAgent": { "connections": 42, "recentActivity": [] }, "hocuspocus": {}, "uptime": "3600s" } ``` The `live-server-secret-key` header is checked against the `LIVE_SERVER_SECRET_KEY` environment variable; a missing or invalid key returns `401 Unauthorized`. Memory values are human-readable strings (e.g. `"120MB"`), `serverAgent.recentActivity` is truncated to the last 10 connections, and `uptime` is a string in seconds. ::: info Status-string inconsistency The `/live/health/` endpoint returns `"status": "OK"` (uppercase) while `/live/health/memory` returns `"status": "ok"` (lowercase). If you parse these programmatically, account for the case difference. ::: ### silo (integrations engine) The `silo` service is the integrations engine (ETL, OAuth, webhooks, and sync for GitHub, Jira, Linear, Asana, Slack, etc.). It runs on internal port `3000` and is exposed under the `/silo` proxy prefix. The base path is configurable via the `SILO_BASE_PATH` environment variable (default `/silo`). ```bash # Liveness — process running curl -i https://your-plane-domain.com/silo/health/ # API connectivity check (despite the name, this validates API connectivity, not HMAC) curl -i https://your-plane-domain.com/silo/health/check-hmac # Readiness — database (SELECT 1) curl -i https://your-plane-domain.com/silo/health/check-db ``` Success bodies: ```json { "message": "Welcome to Silo health check" } ``` ```json { "message": "Welcome to Silo API health check" } ``` ```json { "message": "Silo DB is up and running" } ``` A database failure on `check-db` returns `500`: ```json { "status": 500, "message": "Internal Server Error", "errors": { "message": "Database is not running" } } ``` ::: warning Non-standard status codes Silo's status codes are inconsistent with the other services. `/silo/health/` and `/silo/health/check-hmac` return **`201`** on success (not `200`), while `/silo/health/check-db` returns `200`. All three return `500` on failure. If you configure an external monitor that expects `2xx`, this works; if you expect exactly `200`, treat `201` as healthy. The `check-hmac` name is historical — it actually validates API connectivity to the Plane API, not HMAC. ::: ### Plane AI (pi) The Plane AI service (`pi`) is a FastAPI service for AI features (chat, embeddings, transcription, LLM proxy). It runs on internal port `8000` and is exposed under the `/pi` proxy prefix. Health routes are mounted at the app root; the prefix is controlled by `PI_BASE_PATH` (default empty), and the proxy adds the `/pi` prefix without stripping it. ```bash # Liveness — process running curl -i https://your-plane-domain.com/pi/live/ # Readiness — database connectivity curl -i https://your-plane-domain.com/pi/ready/ # Health (detailed) — database connectivity + uptime curl -i https://your-plane-domain.com/pi/health/ ``` Liveness (`200`): ```json { "alive": true } ``` Readiness — healthy (`200`) / not ready (`503`): ```json { "ready": true } ``` ```json { "ready": false, "reason": "database unavailable" } ``` Health — healthy (`200`) / degraded (`503`): ```json { "status": "ok", "timestamp": 1717346400000, "uptime": 12345, "database": { "connected": true } } ``` ```json { "status": "degraded", "timestamp": 1717346400000, "uptime": 12345, "database": { "connected": false } } ``` The database readiness check caches results for **5 seconds per worker** to avoid connection-pool exhaustion under probe floods. ::: info Deprecated pi endpoints `/pi/api/v1/health/` and `/pi/api/v2/health/` are **deprecated** in favor of the root-level probes above. They return a legacy payload `{ "status": "alive" }` and emit deprecation headers (per RFC 8594). Use `/pi/live/`, `/pi/ready/`, and `/pi/health/` instead. ::: ### flux (real-time event server) The `flux` service is a WebSocket server for real-time collaborative events, backed by Redis. It runs internally (default port `3004`, configurable via `PORT`) and is **internal-only** — it is not exposed through the reverse proxy. Its routes sit under the base path set by `FLUX_BASE_PATH` (default `/flux`), so they are reachable as `/flux/health`, `/flux/ready`, and `/flux/live` on the internal network: ```bash # From within the internal network / cluster (paths are relative to FLUX_BASE_PATH) curl -i http://flux:3004/flux/live # liveness — process running curl -i http://flux:3004/flux/ready # readiness — Redis connectivity curl -i http://flux:3004/flux/health # health (detailed) — Redis + connection counts ``` Liveness (`200`): ```json { "alive": true } ``` Readiness — healthy (`200`) / not ready (`503`): ```json { "ready": true } ``` ```json { "ready": false, "reason": "Redis not connected" } ``` Health — healthy (`200`) / degraded (`503`): ```json { "status": "ok", "timestamp": 1717346400000, "uptime": 12345, "connections": { "total": 5, "channels": 10 }, "redis": { "connected": true } } ``` ```json { "status": "degraded", "timestamp": 1717346400000, "uptime": 12345, "connections": { "total": 5, "channels": 10 }, "redis": { "connected": false } } ``` Liveness always returns `200`; readiness and health both gate on Redis connectivity. Note the degraded status string is `"degraded"`, not `"unhealthy"`. ### node-runner (automation execution) The `node-runner` service executes automation scripts (build, validate, sandboxed execution). It runs on internal port `3000` and is **internal-only** — not reachable through the reverse proxy. Its own health endpoint is reachable only on the internal network: ```bash curl -i http://node-runner:3000/health ``` ```json { "status": "ok" } ``` Runner health is also surfaced through the Django API at `/api/workspaces/{slug}/runnerctl/health/` (a `GET` that requires session authentication and workspace-admin permission). That endpoint checks the node-runner service and returns `{ "is_available": }` with status `200`. Use the Django-exposed endpoint if you need to observe runner health without internal network access. ### monitor (internal prober) The `monitor` service is a Go-based **prober** — it is not a service you probe. It runs internally on port `8080` (not exposed through the reverse proxy) and **does not expose any liveness/readiness/health endpoint of its own**. Instead, it periodically checks the other Plane services and reports their status. What it does: * **Health-check cron job** (default every 5 minutes, configurable via `--health-check-interval`): runs HTTP or TCP probes against the services you define via `SERVICE_*` environment variables. HTTP probes (`HTTP_TEST_METHOD`) issue `GET` requests and treat `200`–`399` as healthy; TCP probes (`TCP_TEST_METHOD`) attempt a raw connection. Each service is probed with `maxRetries=5`, `confirmTries=3`, a 5s timeout, and a 2s retry interval. Results are posted to the monitoring API at `HOST/api/service-status/`. * **Flag/license resync cron job** (default every 300 minutes, configurable via `--resync-flags-interval`): refreshes feature flags and licenses from the monitoring server. Services are declared as environment variables in the form `SERVICE__=hostname:port/path`. Examples: ```bash SERVICE_HTTP_WEB=web:3000 SERVICE_HTTP_API=api:8000 SERVICE_HTTP_LIVE=live:3000/live/health SERVICE_HTTP_PROXY=proxy:80 SERVICE_HTTP_MINIO=plane-minio:9090 SERVICE_TCP_REDIS=plane-redis:6379 SERVICE_TCP_POSTGRES=plane-db:5432 ``` For an unreachable service, monitor posts status code `500`; for a non-`2xx`/`3xx` HTTP response, it posts the actual status code. ## Using these in your infrastructure ### Kubernetes liveness and readiness probes Point `livenessProbe` at the liveness endpoint and `readinessProbe` at the readiness endpoint. The following mirrors the probe configuration Plane's own Helm charts use for the API service: ```yaml # api deployment livenessProbe: httpGet: path: /api/live/ port: 8000 initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /api/ready/ port: 8000 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 ``` The Plane AI (`pi-api`) service uses the same pattern against its own paths: ```yaml # pi-api deployment livenessProbe: httpGet: path: /pi/live/ # or {PI_BASE_PATH}/live/ port: 8000 initialDelaySeconds: 30 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /pi/ready/ # or {PI_BASE_PATH}/ready/ port: 8000 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 ``` The `live` and `silo` services use a readiness-only probe (no liveness) against their `/health` path, with a lenient `failureThreshold` to allow for slow startups: ```yaml # live deployment (silo is identical against {SILO_BASE_PATH}/health) readinessProbe: httpGet: path: /live/health # {LIVE_BASE_PATH}/health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 30 successThreshold: 1 ``` ::: info Timing implications With `initialDelaySeconds: 30` plus the start period, the API and pi-api pods take roughly 90 seconds before they are considered ready — this is intentional, giving migrations and warm-up time to complete. The `live`/`silo` readiness probe with `failureThreshold: 30` at a 10s period tolerates up to ~305 seconds of startup before marking the pod unhealthy. ::: ### Docker Compose healthcheck Plane's Compose deployments probe the API by calling its readiness endpoint from inside the container with Python (no extra tooling needed): ```yaml # api service healthcheck: test: [ "CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/ready/', timeout=5)", ] interval: 30s timeout: 10s retries: 5 start_period: 60s ``` The `pi-api` service follows the same approach, honoring its configurable base path: ```yaml # pi-api service healthcheck: test: [ "CMD", "python", "-c", 'import os,urllib.request; urllib.request.urlopen(f"http://localhost:8000{os.environ.get(''PI_BASE_PATH'','''')}/ready/", timeout=5)', ] interval: 30s timeout: 10s retries: 5 start_period: 60s ``` Stateful dependencies are probed with their native CLIs rather than HTTP: ```yaml # plane-db (PostgreSQL) healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 start_period: 10s # plane-mq (RabbitMQ) healthcheck: test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] interval: 10s timeout: 10s retries: 5 ``` Other services then declare `depends_on` with `condition: service_healthy` so they start only after their dependencies are healthy. ### External uptime monitors and load balancers For an external uptime monitor (UptimeRobot, Better Stack, Pingdom, etc.) or a load balancer health check, target the reverse-proxy URL of the **readiness** endpoint over HTTPS: ```bash # Load balancer / uptime monitor target GET https://your-plane-domain.com/api/ready/ # Healthy: HTTP 200 Unhealthy: HTTP 503 ``` Guidelines: * Use **`/api/ready/`** as the load-balancer health check so traffic is only routed to instances whose database and cache are reachable. Treat `200` as healthy and `503` as unhealthy. * Use **`/api/health/`** for richer dashboards where you want to display per-dependency status, timestamps, and uptime. * All API probes are **unauthenticated**, so no credentials are needed. The 5-second per-worker result cache means a frequent polling interval (e.g. every 15–30 seconds) will not stress your database or cache. * Mind the **trailing slash** — Django routes require it (`/api/ready/`, not `/api/ready`). * Do not point external monitors at internal-only services (`flux`, `node-runner`, `monitor`); they are not reachable through the proxy by design. ## Troubleshooting * **`503` from `/api/ready/` or `/api/health/`** — the database or cache is unreachable. Check that PostgreSQL and Redis/Valkey are running and reachable from the API container. The `reason` field on `/api/ready/` and the per-dependency `connected` flags on `/api/health/` tell you which dependency failed. Remember the full exception is logged server-side (the probe response is sanitized), so check the API logs for details. * **`503` from `/pi/ready/`** — the Plane AI service cannot reach its database (`"reason": "database unavailable"`). Verify database connectivity from the `pi` container. * **`503` from flux `/ready` or `/health` (`"status": "degraded"`)** — Redis is not connected (`"reason": "Redis not connected"`). Check Redis availability on the internal network. * **`401` from `/live/health/memory`** — the `live-server-secret-key` header is missing or does not match `LIVE_SERVER_SECRET_KEY`. Confirm the env var is set and the header value matches. * **`500` from `/silo/health/check-db`** — Silo cannot reach its database (`"message": "Database is not running"`). Note that silo's other health endpoints return `201` on success, not `200`. * **A pod restart loop in Kubernetes** — confirm the `livenessProbe` points at a *liveness* path (`/api/live/`, `/pi/live/`), not a readiness or health path. A liveness probe that checks the database will restart healthy pods during a transient dependency outage. * **Probes failing right after deploy** — the API and pi-api need ~90 seconds before they report ready (30s initial delay plus start period). Make sure your probe `initialDelaySeconds` and `start_period` allow for startup and migrations before marking the service down. --- --- url: 'https://developers.plane.so/self-hosting/manage/migrate-plane.html' description: >- Migrate Plane between servers or environments. Guide for moving your Plane installation to new infrastructure. --- # Move Plane instance to a new server Switching to another machine is straightforward on the Commercial Edition. ## Prerequisites Before we dive in, ensure: * You’re running Plane's Commercial Edition. * You have a different machine with our standard config to migrate to. * You understand the same domain will be used to host the app as the current machine. ::: warning If you need to change your domain during migration, contact our support team for assistance. ::: ## Steps 1. **Delink licenses**\ Log in to Plane on your current server. Head to each paid workspace like Pro or Business and [delink the licenses](/self-hosting/manage/manage-licenses/activate-pro-and-business#delink-license-key). This will free up the licenses for activation on your new server. Ideally, you have just one paid workspace. 2. **Backup data**\ Create a backup of your Plane instance with ↓: ```bash prime-cli backup ``` This command will generate a backup file in the path: `/opt/plane/backups`. ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: 3. **Set up Plane on the new server**\ Follow the [installation guide](/self-hosting/methods/docker-compose#install-plane) to deploy Plane on the new instance. 4. **Transfer backup files**\ Copy the `backups` folder from the old server, created in step 2, to the new server. Place the backup in the folder `/opt/plane`. 5. **Restore data**\ On the new server, restore your data with ↓: ```bash prime-cli restore ``` Follow the prompts during the restore process to make sure everything is set up correctly. 6. **Reactivate license**\ Finally, [reactivate your license keys](/self-hosting/manage/manage-licenses/activate-pro-and-business#activate-your-license) on the new instance. This should get your Plane instance up and running on the new server. --- --- url: 'https://developers.plane.so/self-hosting/manage/prime-cli.html' description: >- Use Plane Prime CLI for managing your self-hosted instance. Commands for setup, configuration, upgrades, and troubleshooting from the terminal. --- # Command line tools Our command-line tool is here to make managing your Plane instance simple. You can handle installs, upgrades, and general management without needing to be a Docker expert. ## Prime CLI The Prime CLI provides commands for common tasks like configuring services, monitoring health, managing backups, and upgrading your Plane instance. ::: warning **Prime CLI is for Docker installations only.** These commands only work on Plane instances originally installed using `prime-cli`. ::: Bring up the Prime CLI with `sudo prime-cli` from any directory on your machine. * The three operators you will use the most are: * `start` You will use this to start a service in the Docker network with the name of the service. * `stop` You will use this to stop a service in the Docker network with the name of the service. * `restart` You will use this to restart a service in the Docker network with the name of the service as a `{param or flag}`. * Often, you will want to monitor the health of your instance and see if some services are up or down. Use `monitor` to do that. * `healthcheck` is another useful utility that lets you see the status and errors, if any, of all running services * `repair` automatically diagnoses and fixes common errors in your Plane instance. This command also resets all configuration values in the plane.env file to their defaults. * `update-cli` downloads and installs the latest version of Prime CLI. ::: tip It is highly recommend to run this first before you download any Plane updates. The latest version of the CLI ensures your Plane upgrades happen smoothly. ::: For more advanced admins that want greater control over their instance, the list of additional commands available on Prime CLI follow. * `configure` Brings up a step form to let you specify the following. ::: details Steps to configure your instance * `Listening port`\ Specify the port that the built-in reverse proxy will use Default value: `80` * `Max file-upload size`\ Specify a size in MBs for how big each file uploaded to your Plane app can be Default value: `5 MB` * `External Postgres URL` Specify the URL of your own hosted Postgres if you would like to change the database your Plane app uses. Default database: Postgres 15.5 in the Docker container * `External Redis URL` Specify the URL of your own hosted REdis if you would like to change the default Redis Plane ships with. Default Redis: Redis 7.2.4 * `External storage` Specify your AWS S3 bucket's credentials in the format below to change storage from the default Plane ships with. * AWS Access Key ID * AWS Secret Access Key * AWS S3 Bucket Name Default storage: MinIO * Confirm your choices on the screen ↓. This restarts your instance with the new configs. ::: * `upgrade` checks your instance for available version upgrades and asks you for a confirmation before downloading the latest available version. 1. Typing `YES` lets the CLI automatically download's the latest version and installs it. Then it restarts the instance to load the latest app. 2. Typing `NO` cancels the upgrade. * `uninstall` uninstalls Plane. Before it goes through, it asks you for a confirmation. 1. Typing `YES` lets the CLI clean up the `/opt/plane` folder, leaving behind the `/opt/plane/data` and `/opt/plane/logs` folders. 2. Typing `NO` cancels the uninstall. ::: details Setup.sh script • Community Edition The setup script `setup.sh` provides a menu-driven interface to help you install and manage your Plane instance. #### Usage To run the setup.sh script, use the following command in your terminal from the directory where the script is located: ```bash ./setup.sh ``` This will launch an interactive menu with options to manage various aspects of your Plane instance. ```bash Select a Action you want to perform: 1) Install 2) Start 3) Stop 4) Restart 5) Upgrade 6) View Logs 7) Backup Data 8) Exit ``` #### Actions * **Install**\ Installs the Plane Community Edition on your machine. Choose this option if you are setting up Plane for the first time. * **Start**\ Starts the Plane server and all related services. * **Stop**\ Stops the Plane server and all services currently running on the machine. * **Restart**\ Restarts the Plane server and all associated services. * **Upgrade** Upgrades Plane to the latest available version. This will stop all services, update the necessary files, and then restart Plane with the latest configuration. See [Update Plane](/self-hosting/manage/upgrade-plane#prerequisites) for more info. > \[!WARNING] > It's recommended to create a backup before upgrading your instance. See [Backup and restore](/self-hosting/manage/backup-restore#backup-data). * **View Logs** Displays real-time logs of specific Plane services. See [View logs](/self-hosting/manage/view-logs) for more info. > \[!TIP] > Use **View Logs** to monitor service performance or troubleshoot issues. Press `CTRL+C` to exit the log view and return to the main menu. * **Backup Data** Creates a backup of your current Plane installation, including all data. See [Backup and restore data](/self-hosting/manage/backup-restore#backup-data) for more info. * **Exit**\ Closes the setup script and returns you to the command line. ::: ## Troubleshoot * [Failed to update Prime CLI](/self-hosting/troubleshoot/cli-errors#failed-to-update-prime-cli) --- --- url: 'https://developers.plane.so/self-hosting/manage/manage-instance-users.html' description: 'Manage instance users, invite instance admins, and control access to God Mode.' --- # Manage instance users User management lets instance admins view all users across the instance and manage their access. This is separate from workspace-level member management. ## View users Go to **God Mode → User Management** to see all users in the instance. ![User management](/images/instance-admin/user-management.webp#hero) The table displays: | Column | Description | | ------------ | ---------------------- | | Full Name | User's full name | | Display Name | User's display name | | Email | User's email address | | Account Type | User or Instance Admin | | Status | Active or Suspended | | Joining Date | When the user joined | Use the search bar to find specific users. ## Invite an instance admin Instance admins have access to God Mode but are not automatically added to any workspace. 1. Click **Invite members**. 2. Enter the user's email and password. 3. Optionally enable: * **Generate random password** — auto-create a password * **Prompt user to change password after onboarding** — require password reset on first login 4. Click **Invite**. ![Invite instance admin](/images/instance-admin/invite-instance-admin.webp#hero) :::warning No invitation email is sent. You must share the credentials with the user manually. ::: ## Manage user access Click **…** next to any user to: * **Grant admin access** — promote a user to Instance Admin. * **Remove admin access** — downgrade an Instance Admin to a regular user (loses God Mode access). * **Remove** — remove the user from the instance entirely. ![User actions](/images/instance-admin/user-actions.webp#hero) ## User status | Status | Description | | --------- | -------------------------------------------------- | | Active | User can access the instance | | Suspended | User account exists but cannot access the instance | --- --- url: 'https://developers.plane.so/self-hosting/troubleshoot/overview.html' description: Diagnose and resolve issues with your self-hosted Plane instance. --- # Troubleshooting When something goes wrong, start by identifying which service is affected, then check the relevant logs. ## Identify the service | Problem area | Service | Logs to check | | ------------------------------------------------------------ | ---------- | ------------------ | | API errors, data not saving, 500 errors | api | `plane-api` | | License activation or validation errors | monitor | `plane-monitor` | | GitHub, GitLab, Slack integrations, imports not working | silo | `plane-silo` | | SSL errors, 502/504 errors, routing issues | proxy | `plane-proxy` | | File uploads or attachments failing | minio | `plane-minio` | | Plane AI not working, AI chat errors | pi | `plane-pi` | | UI not loading, blank screens, page errors | web | `plane-web` | | Public pages or published views not working | space | `plane-space` | | Instance settings | admin | `plane-admin` | | Imports stuck, notifications delayed, file processing issues | worker | `plane-worker` | | Scheduled tasks or reminders not running | beat | `plane-beat` | | Upgrade failures, database schema errors | migrator | `plane-migrator` | | Real-time sync, live cursors, or presence not working | live | `plane-live` | | Intake Email not working | intake | `plane-intake` | | Search not returning results | opensearch | `plane-opensearch` | See [View logs](/self-hosting/manage/view-logs) for commands to access logs in Docker deployments. ## Reporting issues to support When [contacting support](https://docs.plane.so/support/get-help), include: * **Container logs** for the affected service (see table above) * **Browser Network logs** (open DevTools → Network tab → reproduce the issue → export as HAR file) This helps us diagnose the problem faster. ## Common issues * [Installation errors](/self-hosting/troubleshoot/installation-errors) * [License errors](/self-hosting/troubleshoot/license-errors) * [CLI errors](/self-hosting/troubleshoot/cli-errors) * [Storage errors](/self-hosting/troubleshoot/storage-errors) --- --- url: 'https://developers.plane.so/self-hosting/troubleshoot/installation-errors.html' description: >- Troubleshoot installation errors. Common issues, error messages, and solutions for self-hosted Plane. --- # Installation errors This guide is designed to help you resolve common issues encountered while installing Plane. Each section includes potential causes and step-by-step solutions for identified problems. ## Error during Docker Compose execution * This error typically occurs when the user doesn't have sudo or root privileges. To resolve this, ensure you're logged in as the root user or as a user with sudo access before attempting the installation again. * The issue may also be caused by using the older version of Docker Compose `docker-compose`. To fix this, install the latest version of Docker Compose `docker compose` and make sure the old version is removed. ## Migrator container exited This error typically occurs if you have configured an external database that is running on localhost. Since the connection is being attempted from inside the container, localhost won’t work, as the database is not running within the container. To resolve this issue, ensure that the database is hosted on a network-accessible server rather than localhost. Update the database URL to reflect the correct server address. See [how to configure external db](/self-hosting/govern/database-and-storage). --- --- url: 'https://developers.plane.so/self-hosting/troubleshoot/license-errors.html' description: >- Troubleshoot Plane license activation errors. Fix common issues with Pro, Business, Enterprise, and Airgapped license keys. --- # Errors related to licenses This guide is designed to help you resolve common issues encountered while activating the license key for a workspace. Each section includes potential causes and step-by-step solutions for identified problems. ## Before troubleshooting **Try syncing your plan first.** Many license issues resolve automatically when your workspace pulls the latest subscription information from the Prime server. You can sync your plan from the workspace settings in the Plane application. If the issue persists after syncing or you see other errors, continue with the specific error troubleshooting below. ## License is invalid * This issue usually occurs when your server has trouble connecting to ours to verify the license. Try running `prime-cli restart`, and it should resolve the problem. * If you migrated Plane to a new server without first [delinking the license key](/self-hosting/manage/manage-licenses/activate-pro-and-business#delink-license-key) from the old server, this error might happen. In that case, please reach out to the support team to have the license key delinked. For future reference, if you need to reinstall Plane or move to another server, follow [this guide](/self-hosting/manage/migrate-plane) to ensure a smooth transition. ## Something went wrong This error usually occurs when the license validation service is unavailable. Here's how you can troubleshoot it: 1. Confirm that your Plane instance is running the latest version. 2. If it's not up-to-date, [update to the latest version](/self-hosting/manage/upgrade-plane#prerequisites). Updating typically resolves this issue. If the problem persists, double-check your network connection and any firewall rules that might block access to the license validation service. ## Payment server is not configured This usually occurs when the environment confiuration is incorrect. The Env variable `payment_server_url` is missing in the setup. In this case, follow the below steps. 1. Backup the `plane.env` file. See [Backup plane.env](/self-hosting/manage/backup-restore#backup-plane-env). 2. Run `prime-cli repair` to allow Prime CLI to attempt automatic fixes to the `plane.env` file. 3. Try activating your workspace with the license key. 4. If needed, you can configure the instance in [God mode](/self-hosting/govern/instance-admin#settings) or adjust the environment variables directly in the new plane.env file. --- --- url: 'https://developers.plane.so/self-hosting/troubleshoot/cli-errors.html' description: >- Troubleshoot cli errors. Common issues, error messages, and solutions for self-hosted Plane. --- # CLI errors This page helps you troubleshoot common issues you might run into when using the Plane CLI tools. It covers potential causes of errors and provides straightforward steps to resolve them. ## Failed to update Prime CLI This error typically happens if you're using an older version of the Prime CLI. To fix it, follow these steps: 1. Start by taking a [data backup](/self-hosting/manage/backup-restore#backup-data) to be safe. 2. Run this command to remove the existing CLI: ```bash rm -rf /usr/bin/prime-cli ``` 3. Install the latest version of the CLI with: ```bash curl -fsSL https://prime.plane.so/install/ | sh ``` --- --- url: 'https://developers.plane.so/self-hosting/troubleshoot/storage-errors.html' description: >- Troubleshoot Plane storage and upload errors. Fix S3, MinIO, and file upload issues for self-hosted Plane instances. --- # Storage errors This guide is designed to help you resolve common issues encountered while configuring storage in Plane. Each section includes potential causes and step-by-step solutions for identified problems. ## Bucket policy exceeds size limit This error occurs when the bucket policy exceeds the 20KB size limit allowed by MinIO. It typically happens when trying to add complex policies or when unnecessary data bloats the policy size. To resolve this issue, you can define a streamlined bucket policy file and apply it correctly within the MinIO container. Follow these steps: 1. **Create a bucket policy JSON file** * On your local machine, create a file named `bucket-policy.json` with the following content: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": ["*"] }, "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::uploads/*"], "Condition": { "StringEquals": { "s3:ExistingObjectTag/publicAccess": ["true"] } } } ] } ``` * Save this file in an accessible location. 2. **Set up and apply the policy**\ ::: warning **IMPORTANT**\ Make sure to execute all the `mc` commands **within the MinIO container** (either by attaching to it or using `docker exec`). ::: * Configure MinIO alias: ```bash mc alias set myminio http://plane-plane-minio:9000 ``` Replace `plane-plane-minio:9000` with your MinIO server address. * Tag existing objects: ```bash mc find myminio/uploads --exec "mc tag set {} publicAccess=true" ``` * Copy the policy file to the MinIO container: ```bash docker cp bucket-policy.json :/tmp ``` Replace `` with the actual ID of your MinIO container. You can find the container ID by running `docker ps`. * Apply the policy to the bucket: ```bash mc anonymous set-json /tmp/bucket-policy.json myminio/uploads ``` 3. **Verification** * Verify that objects are correctly tagged: ```bash mc tag list myminio/uploads/ ``` * Test public access using: ```bash curl http://:9000/uploads/ ``` ### Notes * Verify that the `access-key` and `secret-key` used for setting up the alias have adequate permissions to manage the bucket. * If your MinIO server is hosted on a different machine or address, replace `plane-plane-minio:9000` with the appropriate server URL. * After applying the policy, test the setup to confirm it is working as expected. --- --- url: 'https://developers.plane.so/api-reference/project/add-project.html' description: >- Create a project via Plane API. HTTP request format, parameters, scopes, and example responses for create a project. --- # Create a project Create a new project in the workspace with default states and member assignments. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Project lead. Default assignee. Identifier. Icon prop. Emoji. Cover image. Module view. Cycle view. Issue views view. Page view. Intake view. Guest view all features. Archive in. Close in. * `Africa/Abidjan` - Africa/Abidjan * `Africa/Accra` - Africa/Accra * `Africa/Addis_Ababa` - Africa/Addis\_Ababa * `Africa/Algiers` - Africa/Algiers * `Africa/Asmara` - Africa/Asmara * `Africa/Bamako` - Africa/Bamako * `Africa/Bangui` - Africa/Bangui * `Africa/Banjul` - Africa/Banjul * `Africa/Bissau` - Africa/Bissau * `Africa/Blantyre` - Africa/Blantyre * `Africa/Brazzaville` - Africa/Brazzaville * `Africa/Bujumbura` - Africa/Bujumbura * `Africa/Cairo` - Africa/Cairo * `Africa/Casablanca` - Africa/Casablanca * `Africa/Ceuta` - Africa/Ceuta * `Africa/Conakry` - Africa/Conakry * `Africa/Dakar` - Africa/Dakar * `Africa/Dar_es_Salaam` - Africa/Dar\_es\_Salaam * `Africa/Djibouti` - Africa/Djibouti * `Africa/Douala` - Africa/Douala * `Africa/El_Aaiun` - Africa/El\_Aaiun * `Africa/Freetown` - Africa/Freetown * `Africa/Gaborone` - Africa/Gaborone * `Africa/Harare` - Africa/Harare * `Africa/Johannesburg` - Africa/Johannesburg * `Africa/Juba` - Africa/Juba * `Africa/Kampala` - Africa/Kampala * `Africa/Khartoum` - Africa/Khartoum * `Africa/Kigali` - Africa/Kigali * `Africa/Kinshasa` - Africa/Kinshasa * `Africa/Lagos` - Africa/Lagos * `Africa/Libreville` - Africa/Libreville * `Africa/Lome` - Africa/Lome * `Africa/Luanda` - Africa/Luanda * `Africa/Lubumbashi` - Africa/Lubumbashi * `Africa/Lusaka` - Africa/Lusaka * `Africa/Malabo` - Africa/Malabo * `Africa/Maputo` - Africa/Maputo * `Africa/Maseru` - Africa/Maseru * `Africa/Mbabane` - Africa/Mbabane * `Africa/Mogadishu` - Africa/Mogadishu * `Africa/Monrovia` - Africa/Monrovia * `Africa/Nairobi` - Africa/Nairobi * `Africa/Ndjamena` - Africa/Ndjamena * `Africa/Niamey` - Africa/Niamey * `Africa/Nouakchott` - Africa/Nouakchott * `Africa/Ouagadougou` - Africa/Ouagadougou * `Africa/Porto-Novo` - Africa/Porto-Novo * `Africa/Sao_Tome` - Africa/Sao\_Tome * `Africa/Tripoli` - Africa/Tripoli * `Africa/Tunis` - Africa/Tunis * `Africa/Windhoek` - Africa/Windhoek * `America/Adak` - America/Adak * `America/Anchorage` - America/Anchorage * `America/Anguilla` - America/Anguilla * `America/Antigua` - America/Antigua * `America/Araguaina` - America/Araguaina * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos\_Aires * `America/Argentina/Catamarca` - America/Argentina/Catamarca * `America/Argentina/Cordoba` - America/Argentina/Cordoba * `America/Argentina/Jujuy` - America/Argentina/Jujuy * `America/Argentina/La_Rioja` - America/Argentina/La\_Rioja * `America/Argentina/Mendoza` - America/Argentina/Mendoza * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio\_Gallegos * `America/Argentina/Salta` - America/Argentina/Salta * `America/Argentina/San_Juan` - America/Argentina/San\_Juan * `America/Argentina/San_Luis` - America/Argentina/San\_Luis * `America/Argentina/Tucuman` - America/Argentina/Tucuman * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia * `America/Aruba` - America/Aruba * `America/Asuncion` - America/Asuncion * `America/Atikokan` - America/Atikokan * `America/Bahia` - America/Bahia * `America/Bahia_Banderas` - America/Bahia\_Banderas * `America/Barbados` - America/Barbados * `America/Belem` - America/Belem * `America/Belize` - America/Belize * `America/Blanc-Sablon` - America/Blanc-Sablon * `America/Boa_Vista` - America/Boa\_Vista * `America/Bogota` - America/Bogota * `America/Boise` - America/Boise * `America/Cambridge_Bay` - America/Cambridge\_Bay * `America/Campo_Grande` - America/Campo\_Grande * `America/Cancun` - America/Cancun * `America/Caracas` - America/Caracas * `America/Cayenne` - America/Cayenne * `America/Cayman` - America/Cayman * `America/Chicago` - America/Chicago * `America/Chihuahua` - America/Chihuahua * `America/Ciudad_Juarez` - America/Ciudad\_Juarez * `America/Costa_Rica` - America/Costa\_Rica * `America/Creston` - America/Creston * `America/Cuiaba` - America/Cuiaba * `America/Curacao` - America/Curacao * `America/Danmarkshavn` - America/Danmarkshavn * `America/Dawson` - America/Dawson * `America/Dawson_Creek` - America/Dawson\_Creek * `America/Denver` - America/Denver * `America/Detroit` - America/Detroit * `America/Dominica` - America/Dominica * `America/Edmonton` - America/Edmonton * `America/Eirunepe` - America/Eirunepe * `America/El_Salvador` - America/El\_Salvador * `America/Fort_Nelson` - America/Fort\_Nelson * `America/Fortaleza` - America/Fortaleza * `America/Glace_Bay` - America/Glace\_Bay * `America/Goose_Bay` - America/Goose\_Bay * `America/Grand_Turk` - America/Grand\_Turk * `America/Grenada` - America/Grenada * `America/Guadeloupe` - America/Guadeloupe * `America/Guatemala` - America/Guatemala * `America/Guayaquil` - America/Guayaquil * `America/Guyana` - America/Guyana * `America/Halifax` - America/Halifax * `America/Havana` - America/Havana * `America/Hermosillo` - America/Hermosillo * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis * `America/Indiana/Knox` - America/Indiana/Knox * `America/Indiana/Marengo` - America/Indiana/Marengo * `America/Indiana/Petersburg` - America/Indiana/Petersburg * `America/Indiana/Tell_City` - America/Indiana/Tell\_City * `America/Indiana/Vevay` - America/Indiana/Vevay * `America/Indiana/Vincennes` - America/Indiana/Vincennes * `America/Indiana/Winamac` - America/Indiana/Winamac * `America/Inuvik` - America/Inuvik * `America/Iqaluit` - America/Iqaluit * `America/Jamaica` - America/Jamaica * `America/Juneau` - America/Juneau * `America/Kentucky/Louisville` - America/Kentucky/Louisville * `America/Kentucky/Monticello` - America/Kentucky/Monticello * `America/Kralendijk` - America/Kralendijk * `America/La_Paz` - America/La\_Paz * `America/Lima` - America/Lima * `America/Los_Angeles` - America/Los\_Angeles * `America/Lower_Princes` - America/Lower\_Princes * `America/Maceio` - America/Maceio * `America/Managua` - America/Managua * `America/Manaus` - America/Manaus * `America/Marigot` - America/Marigot * `America/Martinique` - America/Martinique * `America/Matamoros` - America/Matamoros * `America/Mazatlan` - America/Mazatlan * `America/Menominee` - America/Menominee * `America/Merida` - America/Merida * `America/Metlakatla` - America/Metlakatla * `America/Mexico_City` - America/Mexico\_City * `America/Miquelon` - America/Miquelon * `America/Moncton` - America/Moncton * `America/Monterrey` - America/Monterrey * `America/Montevideo` - America/Montevideo * `America/Montserrat` - America/Montserrat * `America/Nassau` - America/Nassau * `America/New_York` - America/New\_York * `America/Nome` - America/Nome * `America/Noronha` - America/Noronha * `America/North_Dakota/Beulah` - America/North\_Dakota/Beulah * `America/North_Dakota/Center` - America/North\_Dakota/Center * `America/North_Dakota/New_Salem` - America/North\_Dakota/New\_Salem * `America/Nuuk` - America/Nuuk * `America/Ojinaga` - America/Ojinaga * `America/Panama` - America/Panama * `America/Paramaribo` - America/Paramaribo * `America/Phoenix` - America/Phoenix * `America/Port-au-Prince` - America/Port-au-Prince * `America/Port_of_Spain` - America/Port\_of\_Spain * `America/Porto_Velho` - America/Porto\_Velho * `America/Puerto_Rico` - America/Puerto\_Rico * `America/Punta_Arenas` - America/Punta\_Arenas * `America/Rankin_Inlet` - America/Rankin\_Inlet * `America/Recife` - America/Recife * `America/Regina` - America/Regina * `America/Resolute` - America/Resolute * `America/Rio_Branco` - America/Rio\_Branco * `America/Santarem` - America/Santarem * `America/Santiago` - America/Santiago * `America/Santo_Domingo` - America/Santo\_Domingo * `America/Sao_Paulo` - America/Sao\_Paulo * `America/Scoresbysund` - America/Scoresbysund * `America/Sitka` - America/Sitka * `America/St_Barthelemy` - America/St\_Barthelemy * `America/St_Johns` - America/St\_Johns * `America/St_Kitts` - America/St\_Kitts * `America/St_Lucia` - America/St\_Lucia * `America/St_Thomas` - America/St\_Thomas * `America/St_Vincent` - America/St\_Vincent * `America/Swift_Current` - America/Swift\_Current * `America/Tegucigalpa` - America/Tegucigalpa * `America/Thule` - America/Thule * `America/Tijuana` - America/Tijuana * `America/Toronto` - America/Toronto * `America/Tortola` - America/Tortola * `America/Vancouver` - America/Vancouver * `America/Whitehorse` - America/Whitehorse * `America/Winnipeg` - America/Winnipeg * `America/Yakutat` - America/Yakutat * `Antarctica/Casey` - Antarctica/Casey * `Antarctica/Davis` - Antarctica/Davis * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville * `Antarctica/Macquarie` - Antarctica/Macquarie * `Antarctica/Mawson` - Antarctica/Mawson * `Antarctica/McMurdo` - Antarctica/McMurdo * `Antarctica/Palmer` - Antarctica/Palmer * `Antarctica/Rothera` - Antarctica/Rothera * `Antarctica/Syowa` - Antarctica/Syowa * `Antarctica/Troll` - Antarctica/Troll * `Antarctica/Vostok` - Antarctica/Vostok * `Arctic/Longyearbyen` - Arctic/Longyearbyen * `Asia/Aden` - Asia/Aden * `Asia/Almaty` - Asia/Almaty * `Asia/Amman` - Asia/Amman * `Asia/Anadyr` - Asia/Anadyr * `Asia/Aqtau` - Asia/Aqtau * `Asia/Aqtobe` - Asia/Aqtobe * `Asia/Ashgabat` - Asia/Ashgabat * `Asia/Atyrau` - Asia/Atyrau * `Asia/Baghdad` - Asia/Baghdad * `Asia/Bahrain` - Asia/Bahrain * `Asia/Baku` - Asia/Baku * `Asia/Bangkok` - Asia/Bangkok * `Asia/Barnaul` - Asia/Barnaul * `Asia/Beirut` - Asia/Beirut * `Asia/Bishkek` - Asia/Bishkek * `Asia/Brunei` - Asia/Brunei * `Asia/Chita` - Asia/Chita * `Asia/Choibalsan` - Asia/Choibalsan * `Asia/Colombo` - Asia/Colombo * `Asia/Damascus` - Asia/Damascus * `Asia/Dhaka` - Asia/Dhaka * `Asia/Dili` - Asia/Dili * `Asia/Dubai` - Asia/Dubai * `Asia/Dushanbe` - Asia/Dushanbe * `Asia/Famagusta` - Asia/Famagusta * `Asia/Gaza` - Asia/Gaza * `Asia/Hebron` - Asia/Hebron * `Asia/Ho_Chi_Minh` - Asia/Ho\_Chi\_Minh * `Asia/Hong_Kong` - Asia/Hong\_Kong * `Asia/Hovd` - Asia/Hovd * `Asia/Irkutsk` - Asia/Irkutsk * `Asia/Jakarta` - Asia/Jakarta * `Asia/Jayapura` - Asia/Jayapura * `Asia/Jerusalem` - Asia/Jerusalem * `Asia/Kabul` - Asia/Kabul * `Asia/Kamchatka` - Asia/Kamchatka * `Asia/Karachi` - Asia/Karachi * `Asia/Kathmandu` - Asia/Kathmandu * `Asia/Khandyga` - Asia/Khandyga * `Asia/Kolkata` - Asia/Kolkata * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk * `Asia/Kuala_Lumpur` - Asia/Kuala\_Lumpur * `Asia/Kuching` - Asia/Kuching * `Asia/Kuwait` - Asia/Kuwait * `Asia/Macau` - Asia/Macau * `Asia/Magadan` - Asia/Magadan * `Asia/Makassar` - Asia/Makassar * `Asia/Manila` - Asia/Manila * `Asia/Muscat` - Asia/Muscat * `Asia/Nicosia` - Asia/Nicosia * `Asia/Novokuznetsk` - Asia/Novokuznetsk * `Asia/Novosibirsk` - Asia/Novosibirsk * `Asia/Omsk` - Asia/Omsk * `Asia/Oral` - Asia/Oral * `Asia/Phnom_Penh` - Asia/Phnom\_Penh * `Asia/Pontianak` - Asia/Pontianak * `Asia/Pyongyang` - Asia/Pyongyang * `Asia/Qatar` - Asia/Qatar * `Asia/Qostanay` - Asia/Qostanay * `Asia/Qyzylorda` - Asia/Qyzylorda * `Asia/Riyadh` - Asia/Riyadh * `Asia/Sakhalin` - Asia/Sakhalin * `Asia/Samarkand` - Asia/Samarkand * `Asia/Seoul` - Asia/Seoul * `Asia/Shanghai` - Asia/Shanghai * `Asia/Singapore` - Asia/Singapore * `Asia/Srednekolymsk` - Asia/Srednekolymsk * `Asia/Taipei` - Asia/Taipei * `Asia/Tashkent` - Asia/Tashkent * `Asia/Tbilisi` - Asia/Tbilisi * `Asia/Tehran` - Asia/Tehran * `Asia/Thimphu` - Asia/Thimphu * `Asia/Tokyo` - Asia/Tokyo * `Asia/Tomsk` - Asia/Tomsk * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar * `Asia/Urumqi` - Asia/Urumqi * `Asia/Ust-Nera` - Asia/Ust-Nera * `Asia/Vientiane` - Asia/Vientiane * `Asia/Vladivostok` - Asia/Vladivostok * `Asia/Yakutsk` - Asia/Yakutsk * `Asia/Yangon` - Asia/Yangon * `Asia/Yekaterinburg` - Asia/Yekaterinburg * `Asia/Yerevan` - Asia/Yerevan * `Atlantic/Azores` - Atlantic/Azores * `Atlantic/Bermuda` - Atlantic/Bermuda * `Atlantic/Canary` - Atlantic/Canary * `Atlantic/Cape_Verde` - Atlantic/Cape\_Verde * `Atlantic/Faroe` - Atlantic/Faroe * `Atlantic/Madeira` - Atlantic/Madeira * `Atlantic/Reykjavik` - Atlantic/Reykjavik * `Atlantic/South_Georgia` - Atlantic/South\_Georgia * `Atlantic/St_Helena` - Atlantic/St\_Helena * `Atlantic/Stanley` - Atlantic/Stanley * `Australia/Adelaide` - Australia/Adelaide * `Australia/Brisbane` - Australia/Brisbane * `Australia/Broken_Hill` - Australia/Broken\_Hill * `Australia/Darwin` - Australia/Darwin * `Australia/Eucla` - Australia/Eucla * `Australia/Hobart` - Australia/Hobart * `Australia/Lindeman` - Australia/Lindeman * `Australia/Lord_Howe` - Australia/Lord\_Howe * `Australia/Melbourne` - Australia/Melbourne * `Australia/Perth` - Australia/Perth * `Australia/Sydney` - Australia/Sydney * `Canada/Atlantic` - Canada/Atlantic * `Canada/Central` - Canada/Central * `Canada/Eastern` - Canada/Eastern * `Canada/Mountain` - Canada/Mountain * `Canada/Newfoundland` - Canada/Newfoundland * `Canada/Pacific` - Canada/Pacific * `Europe/Amsterdam` - Europe/Amsterdam * `Europe/Andorra` - Europe/Andorra * `Europe/Astrakhan` - Europe/Astrakhan * `Europe/Athens` - Europe/Athens * `Europe/Belgrade` - Europe/Belgrade * `Europe/Berlin` - Europe/Berlin * `Europe/Bratislava` - Europe/Bratislava * `Europe/Brussels` - Europe/Brussels * `Europe/Bucharest` - Europe/Bucharest * `Europe/Budapest` - Europe/Budapest * `Europe/Busingen` - Europe/Busingen * `Europe/Chisinau` - Europe/Chisinau * `Europe/Copenhagen` - Europe/Copenhagen * `Europe/Dublin` - Europe/Dublin * `Europe/Gibraltar` - Europe/Gibraltar * `Europe/Guernsey` - Europe/Guernsey * `Europe/Helsinki` - Europe/Helsinki * `Europe/Isle_of_Man` - Europe/Isle\_of\_Man * `Europe/Istanbul` - Europe/Istanbul * `Europe/Jersey` - Europe/Jersey * `Europe/Kaliningrad` - Europe/Kaliningrad * `Europe/Kirov` - Europe/Kirov * `Europe/Kyiv` - Europe/Kyiv * `Europe/Lisbon` - Europe/Lisbon * `Europe/Ljubljana` - Europe/Ljubljana * `Europe/London` - Europe/London * `Europe/Luxembourg` - Europe/Luxembourg * `Europe/Madrid` - Europe/Madrid * `Europe/Malta` - Europe/Malta * `Europe/Mariehamn` - Europe/Mariehamn * `Europe/Minsk` - Europe/Minsk * `Europe/Monaco` - Europe/Monaco * `Europe/Moscow` - Europe/Moscow * `Europe/Oslo` - Europe/Oslo * `Europe/Paris` - Europe/Paris * `Europe/Podgorica` - Europe/Podgorica * `Europe/Prague` - Europe/Prague * `Europe/Riga` - Europe/Riga * `Europe/Rome` - Europe/Rome * `Europe/Samara` - Europe/Samara * `Europe/San_Marino` - Europe/San\_Marino * `Europe/Sarajevo` - Europe/Sarajevo * `Europe/Saratov` - Europe/Saratov * `Europe/Simferopol` - Europe/Simferopol * `Europe/Skopje` - Europe/Skopje * `Europe/Sofia` - Europe/Sofia * `Europe/Stockholm` - Europe/Stockholm * `Europe/Tallinn` - Europe/Tallinn * `Europe/Tirane` - Europe/Tirane * `Europe/Ulyanovsk` - Europe/Ulyanovsk * `Europe/Vaduz` - Europe/Vaduz * `Europe/Vatican` - Europe/Vatican * `Europe/Vienna` - Europe/Vienna * `Europe/Vilnius` - Europe/Vilnius * `Europe/Volgograd` - Europe/Volgograd * `Europe/Warsaw` - Europe/Warsaw * `Europe/Zagreb` - Europe/Zagreb * `Europe/Zurich` - Europe/Zurich * `GMT` - GMT * `Indian/Antananarivo` - Indian/Antananarivo * `Indian/Chagos` - Indian/Chagos * `Indian/Christmas` - Indian/Christmas * `Indian/Cocos` - Indian/Cocos * `Indian/Comoro` - Indian/Comoro * `Indian/Kerguelen` - Indian/Kerguelen * `Indian/Mahe` - Indian/Mahe * `Indian/Maldives` - Indian/Maldives * `Indian/Mauritius` - Indian/Mauritius * `Indian/Mayotte` - Indian/Mayotte * `Indian/Reunion` - Indian/Reunion * `Pacific/Apia` - Pacific/Apia * `Pacific/Auckland` - Pacific/Auckland * `Pacific/Bougainville` - Pacific/Bougainville * `Pacific/Chatham` - Pacific/Chatham * `Pacific/Chuuk` - Pacific/Chuuk * `Pacific/Easter` - Pacific/Easter * `Pacific/Efate` - Pacific/Efate * `Pacific/Fakaofo` - Pacific/Fakaofo * `Pacific/Fiji` - Pacific/Fiji * `Pacific/Funafuti` - Pacific/Funafuti * `Pacific/Galapagos` - Pacific/Galapagos * `Pacific/Gambier` - Pacific/Gambier * `Pacific/Guadalcanal` - Pacific/Guadalcanal * `Pacific/Guam` - Pacific/Guam * `Pacific/Honolulu` - Pacific/Honolulu * `Pacific/Kanton` - Pacific/Kanton * `Pacific/Kiritimati` - Pacific/Kiritimati * `Pacific/Kosrae` - Pacific/Kosrae * `Pacific/Kwajalein` - Pacific/Kwajalein * `Pacific/Majuro` - Pacific/Majuro * `Pacific/Marquesas` - Pacific/Marquesas * `Pacific/Midway` - Pacific/Midway * `Pacific/Nauru` - Pacific/Nauru * `Pacific/Niue` - Pacific/Niue * `Pacific/Norfolk` - Pacific/Norfolk * `Pacific/Noumea` - Pacific/Noumea * `Pacific/Pago_Pago` - Pacific/Pago\_Pago * `Pacific/Palau` - Pacific/Palau * `Pacific/Pitcairn` - Pacific/Pitcairn * `Pacific/Pohnpei` - Pacific/Pohnpei * `Pacific/Port_Moresby` - Pacific/Port\_Moresby * `Pacific/Rarotonga` - Pacific/Rarotonga * `Pacific/Saipan` - Pacific/Saipan * `Pacific/Tahiti` - Pacific/Tahiti * `Pacific/Tarawa` - Pacific/Tarawa * `Pacific/Tongatapu` - Pacific/Tongatapu * `Pacific/Wake` - Pacific/Wake * `Pacific/Wallis` - Pacific/Wallis * `US/Alaska` - US/Alaska * `US/Arizona` - US/Arizona * `US/Central` - US/Central * `US/Eastern` - US/Eastern * `US/Hawaii` - US/Hawaii * `US/Mountain` - US/Mountain * `US/Pacific` - US/Pacific * `UTC` - UTC External source. External id. Is issue type enabled. Is time tracking enabled. ### Scopes `projects:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "project_lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "project_lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", identifier: "PROJ-123", project_lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2, "project_lead": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/project/list-projects.html' description: >- List all projects via Plane API. HTTP request format, parameters, scopes, and example responses for list all projects. --- # List all projects Retrieve all projects in a workspace or get details of a specific project. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2 } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/project/get-project-detail.html' description: >- Retrieve a project via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a project. --- # Retrieve a project Retrieve details of a specific project. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2, "project_lead": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/project/update-project-detail.html' description: >- Update a project via Plane API. HTTP request format, parameters, scopes, and example responses for update a project. --- # Update a project Partially update an existing project's properties like name, description, or settings. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Project lead. Default assignee. Identifier. Icon prop. Emoji. Cover image. Module view. Cycle view. Issue views view. Page view. Intake view. Guest view all features. Archive in. Close in. * `Africa/Abidjan` - Africa/Abidjan * `Africa/Accra` - Africa/Accra * `Africa/Addis_Ababa` - Africa/Addis\_Ababa * `Africa/Algiers` - Africa/Algiers * `Africa/Asmara` - Africa/Asmara * `Africa/Bamako` - Africa/Bamako * `Africa/Bangui` - Africa/Bangui * `Africa/Banjul` - Africa/Banjul * `Africa/Bissau` - Africa/Bissau * `Africa/Blantyre` - Africa/Blantyre * `Africa/Brazzaville` - Africa/Brazzaville * `Africa/Bujumbura` - Africa/Bujumbura * `Africa/Cairo` - Africa/Cairo * `Africa/Casablanca` - Africa/Casablanca * `Africa/Ceuta` - Africa/Ceuta * `Africa/Conakry` - Africa/Conakry * `Africa/Dakar` - Africa/Dakar * `Africa/Dar_es_Salaam` - Africa/Dar\_es\_Salaam * `Africa/Djibouti` - Africa/Djibouti * `Africa/Douala` - Africa/Douala * `Africa/El_Aaiun` - Africa/El\_Aaiun * `Africa/Freetown` - Africa/Freetown * `Africa/Gaborone` - Africa/Gaborone * `Africa/Harare` - Africa/Harare * `Africa/Johannesburg` - Africa/Johannesburg * `Africa/Juba` - Africa/Juba * `Africa/Kampala` - Africa/Kampala * `Africa/Khartoum` - Africa/Khartoum * `Africa/Kigali` - Africa/Kigali * `Africa/Kinshasa` - Africa/Kinshasa * `Africa/Lagos` - Africa/Lagos * `Africa/Libreville` - Africa/Libreville * `Africa/Lome` - Africa/Lome * `Africa/Luanda` - Africa/Luanda * `Africa/Lubumbashi` - Africa/Lubumbashi * `Africa/Lusaka` - Africa/Lusaka * `Africa/Malabo` - Africa/Malabo * `Africa/Maputo` - Africa/Maputo * `Africa/Maseru` - Africa/Maseru * `Africa/Mbabane` - Africa/Mbabane * `Africa/Mogadishu` - Africa/Mogadishu * `Africa/Monrovia` - Africa/Monrovia * `Africa/Nairobi` - Africa/Nairobi * `Africa/Ndjamena` - Africa/Ndjamena * `Africa/Niamey` - Africa/Niamey * `Africa/Nouakchott` - Africa/Nouakchott * `Africa/Ouagadougou` - Africa/Ouagadougou * `Africa/Porto-Novo` - Africa/Porto-Novo * `Africa/Sao_Tome` - Africa/Sao\_Tome * `Africa/Tripoli` - Africa/Tripoli * `Africa/Tunis` - Africa/Tunis * `Africa/Windhoek` - Africa/Windhoek * `America/Adak` - America/Adak * `America/Anchorage` - America/Anchorage * `America/Anguilla` - America/Anguilla * `America/Antigua` - America/Antigua * `America/Araguaina` - America/Araguaina * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos\_Aires * `America/Argentina/Catamarca` - America/Argentina/Catamarca * `America/Argentina/Cordoba` - America/Argentina/Cordoba * `America/Argentina/Jujuy` - America/Argentina/Jujuy * `America/Argentina/La_Rioja` - America/Argentina/La\_Rioja * `America/Argentina/Mendoza` - America/Argentina/Mendoza * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio\_Gallegos * `America/Argentina/Salta` - America/Argentina/Salta * `America/Argentina/San_Juan` - America/Argentina/San\_Juan * `America/Argentina/San_Luis` - America/Argentina/San\_Luis * `America/Argentina/Tucuman` - America/Argentina/Tucuman * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia * `America/Aruba` - America/Aruba * `America/Asuncion` - America/Asuncion * `America/Atikokan` - America/Atikokan * `America/Bahia` - America/Bahia * `America/Bahia_Banderas` - America/Bahia\_Banderas * `America/Barbados` - America/Barbados * `America/Belem` - America/Belem * `America/Belize` - America/Belize * `America/Blanc-Sablon` - America/Blanc-Sablon * `America/Boa_Vista` - America/Boa\_Vista * `America/Bogota` - America/Bogota * `America/Boise` - America/Boise * `America/Cambridge_Bay` - America/Cambridge\_Bay * `America/Campo_Grande` - America/Campo\_Grande * `America/Cancun` - America/Cancun * `America/Caracas` - America/Caracas * `America/Cayenne` - America/Cayenne * `America/Cayman` - America/Cayman * `America/Chicago` - America/Chicago * `America/Chihuahua` - America/Chihuahua * `America/Ciudad_Juarez` - America/Ciudad\_Juarez * `America/Costa_Rica` - America/Costa\_Rica * `America/Creston` - America/Creston * `America/Cuiaba` - America/Cuiaba * `America/Curacao` - America/Curacao * `America/Danmarkshavn` - America/Danmarkshavn * `America/Dawson` - America/Dawson * `America/Dawson_Creek` - America/Dawson\_Creek * `America/Denver` - America/Denver * `America/Detroit` - America/Detroit * `America/Dominica` - America/Dominica * `America/Edmonton` - America/Edmonton * `America/Eirunepe` - America/Eirunepe * `America/El_Salvador` - America/El\_Salvador * `America/Fort_Nelson` - America/Fort\_Nelson * `America/Fortaleza` - America/Fortaleza * `America/Glace_Bay` - America/Glace\_Bay * `America/Goose_Bay` - America/Goose\_Bay * `America/Grand_Turk` - America/Grand\_Turk * `America/Grenada` - America/Grenada * `America/Guadeloupe` - America/Guadeloupe * `America/Guatemala` - America/Guatemala * `America/Guayaquil` - America/Guayaquil * `America/Guyana` - America/Guyana * `America/Halifax` - America/Halifax * `America/Havana` - America/Havana * `America/Hermosillo` - America/Hermosillo * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis * `America/Indiana/Knox` - America/Indiana/Knox * `America/Indiana/Marengo` - America/Indiana/Marengo * `America/Indiana/Petersburg` - America/Indiana/Petersburg * `America/Indiana/Tell_City` - America/Indiana/Tell\_City * `America/Indiana/Vevay` - America/Indiana/Vevay * `America/Indiana/Vincennes` - America/Indiana/Vincennes * `America/Indiana/Winamac` - America/Indiana/Winamac * `America/Inuvik` - America/Inuvik * `America/Iqaluit` - America/Iqaluit * `America/Jamaica` - America/Jamaica * `America/Juneau` - America/Juneau * `America/Kentucky/Louisville` - America/Kentucky/Louisville * `America/Kentucky/Monticello` - America/Kentucky/Monticello * `America/Kralendijk` - America/Kralendijk * `America/La_Paz` - America/La\_Paz * `America/Lima` - America/Lima * `America/Los_Angeles` - America/Los\_Angeles * `America/Lower_Princes` - America/Lower\_Princes * `America/Maceio` - America/Maceio * `America/Managua` - America/Managua * `America/Manaus` - America/Manaus * `America/Marigot` - America/Marigot * `America/Martinique` - America/Martinique * `America/Matamoros` - America/Matamoros * `America/Mazatlan` - America/Mazatlan * `America/Menominee` - America/Menominee * `America/Merida` - America/Merida * `America/Metlakatla` - America/Metlakatla * `America/Mexico_City` - America/Mexico\_City * `America/Miquelon` - America/Miquelon * `America/Moncton` - America/Moncton * `America/Monterrey` - America/Monterrey * `America/Montevideo` - America/Montevideo * `America/Montserrat` - America/Montserrat * `America/Nassau` - America/Nassau * `America/New_York` - America/New\_York * `America/Nome` - America/Nome * `America/Noronha` - America/Noronha * `America/North_Dakota/Beulah` - America/North\_Dakota/Beulah * `America/North_Dakota/Center` - America/North\_Dakota/Center * `America/North_Dakota/New_Salem` - America/North\_Dakota/New\_Salem * `America/Nuuk` - America/Nuuk * `America/Ojinaga` - America/Ojinaga * `America/Panama` - America/Panama * `America/Paramaribo` - America/Paramaribo * `America/Phoenix` - America/Phoenix * `America/Port-au-Prince` - America/Port-au-Prince * `America/Port_of_Spain` - America/Port\_of\_Spain * `America/Porto_Velho` - America/Porto\_Velho * `America/Puerto_Rico` - America/Puerto\_Rico * `America/Punta_Arenas` - America/Punta\_Arenas * `America/Rankin_Inlet` - America/Rankin\_Inlet * `America/Recife` - America/Recife * `America/Regina` - America/Regina * `America/Resolute` - America/Resolute * `America/Rio_Branco` - America/Rio\_Branco * `America/Santarem` - America/Santarem * `America/Santiago` - America/Santiago * `America/Santo_Domingo` - America/Santo\_Domingo * `America/Sao_Paulo` - America/Sao\_Paulo * `America/Scoresbysund` - America/Scoresbysund * `America/Sitka` - America/Sitka * `America/St_Barthelemy` - America/St\_Barthelemy * `America/St_Johns` - America/St\_Johns * `America/St_Kitts` - America/St\_Kitts * `America/St_Lucia` - America/St\_Lucia * `America/St_Thomas` - America/St\_Thomas * `America/St_Vincent` - America/St\_Vincent * `America/Swift_Current` - America/Swift\_Current * `America/Tegucigalpa` - America/Tegucigalpa * `America/Thule` - America/Thule * `America/Tijuana` - America/Tijuana * `America/Toronto` - America/Toronto * `America/Tortola` - America/Tortola * `America/Vancouver` - America/Vancouver * `America/Whitehorse` - America/Whitehorse * `America/Winnipeg` - America/Winnipeg * `America/Yakutat` - America/Yakutat * `Antarctica/Casey` - Antarctica/Casey * `Antarctica/Davis` - Antarctica/Davis * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville * `Antarctica/Macquarie` - Antarctica/Macquarie * `Antarctica/Mawson` - Antarctica/Mawson * `Antarctica/McMurdo` - Antarctica/McMurdo * `Antarctica/Palmer` - Antarctica/Palmer * `Antarctica/Rothera` - Antarctica/Rothera * `Antarctica/Syowa` - Antarctica/Syowa * `Antarctica/Troll` - Antarctica/Troll * `Antarctica/Vostok` - Antarctica/Vostok * `Arctic/Longyearbyen` - Arctic/Longyearbyen * `Asia/Aden` - Asia/Aden * `Asia/Almaty` - Asia/Almaty * `Asia/Amman` - Asia/Amman * `Asia/Anadyr` - Asia/Anadyr * `Asia/Aqtau` - Asia/Aqtau * `Asia/Aqtobe` - Asia/Aqtobe * `Asia/Ashgabat` - Asia/Ashgabat * `Asia/Atyrau` - Asia/Atyrau * `Asia/Baghdad` - Asia/Baghdad * `Asia/Bahrain` - Asia/Bahrain * `Asia/Baku` - Asia/Baku * `Asia/Bangkok` - Asia/Bangkok * `Asia/Barnaul` - Asia/Barnaul * `Asia/Beirut` - Asia/Beirut * `Asia/Bishkek` - Asia/Bishkek * `Asia/Brunei` - Asia/Brunei * `Asia/Chita` - Asia/Chita * `Asia/Choibalsan` - Asia/Choibalsan * `Asia/Colombo` - Asia/Colombo * `Asia/Damascus` - Asia/Damascus * `Asia/Dhaka` - Asia/Dhaka * `Asia/Dili` - Asia/Dili * `Asia/Dubai` - Asia/Dubai * `Asia/Dushanbe` - Asia/Dushanbe * `Asia/Famagusta` - Asia/Famagusta * `Asia/Gaza` - Asia/Gaza * `Asia/Hebron` - Asia/Hebron * `Asia/Ho_Chi_Minh` - Asia/Ho\_Chi\_Minh * `Asia/Hong_Kong` - Asia/Hong\_Kong * `Asia/Hovd` - Asia/Hovd * `Asia/Irkutsk` - Asia/Irkutsk * `Asia/Jakarta` - Asia/Jakarta * `Asia/Jayapura` - Asia/Jayapura * `Asia/Jerusalem` - Asia/Jerusalem * `Asia/Kabul` - Asia/Kabul * `Asia/Kamchatka` - Asia/Kamchatka * `Asia/Karachi` - Asia/Karachi * `Asia/Kathmandu` - Asia/Kathmandu * `Asia/Khandyga` - Asia/Khandyga * `Asia/Kolkata` - Asia/Kolkata * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk * `Asia/Kuala_Lumpur` - Asia/Kuala\_Lumpur * `Asia/Kuching` - Asia/Kuching * `Asia/Kuwait` - Asia/Kuwait * `Asia/Macau` - Asia/Macau * `Asia/Magadan` - Asia/Magadan * `Asia/Makassar` - Asia/Makassar * `Asia/Manila` - Asia/Manila * `Asia/Muscat` - Asia/Muscat * `Asia/Nicosia` - Asia/Nicosia * `Asia/Novokuznetsk` - Asia/Novokuznetsk * `Asia/Novosibirsk` - Asia/Novosibirsk * `Asia/Omsk` - Asia/Omsk * `Asia/Oral` - Asia/Oral * `Asia/Phnom_Penh` - Asia/Phnom\_Penh * `Asia/Pontianak` - Asia/Pontianak * `Asia/Pyongyang` - Asia/Pyongyang * `Asia/Qatar` - Asia/Qatar * `Asia/Qostanay` - Asia/Qostanay * `Asia/Qyzylorda` - Asia/Qyzylorda * `Asia/Riyadh` - Asia/Riyadh * `Asia/Sakhalin` - Asia/Sakhalin * `Asia/Samarkand` - Asia/Samarkand * `Asia/Seoul` - Asia/Seoul * `Asia/Shanghai` - Asia/Shanghai * `Asia/Singapore` - Asia/Singapore * `Asia/Srednekolymsk` - Asia/Srednekolymsk * `Asia/Taipei` - Asia/Taipei * `Asia/Tashkent` - Asia/Tashkent * `Asia/Tbilisi` - Asia/Tbilisi * `Asia/Tehran` - Asia/Tehran * `Asia/Thimphu` - Asia/Thimphu * `Asia/Tokyo` - Asia/Tokyo * `Asia/Tomsk` - Asia/Tomsk * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar * `Asia/Urumqi` - Asia/Urumqi * `Asia/Ust-Nera` - Asia/Ust-Nera * `Asia/Vientiane` - Asia/Vientiane * `Asia/Vladivostok` - Asia/Vladivostok * `Asia/Yakutsk` - Asia/Yakutsk * `Asia/Yangon` - Asia/Yangon * `Asia/Yekaterinburg` - Asia/Yekaterinburg * `Asia/Yerevan` - Asia/Yerevan * `Atlantic/Azores` - Atlantic/Azores * `Atlantic/Bermuda` - Atlantic/Bermuda * `Atlantic/Canary` - Atlantic/Canary * `Atlantic/Cape_Verde` - Atlantic/Cape\_Verde * `Atlantic/Faroe` - Atlantic/Faroe * `Atlantic/Madeira` - Atlantic/Madeira * `Atlantic/Reykjavik` - Atlantic/Reykjavik * `Atlantic/South_Georgia` - Atlantic/South\_Georgia * `Atlantic/St_Helena` - Atlantic/St\_Helena * `Atlantic/Stanley` - Atlantic/Stanley * `Australia/Adelaide` - Australia/Adelaide * `Australia/Brisbane` - Australia/Brisbane * `Australia/Broken_Hill` - Australia/Broken\_Hill * `Australia/Darwin` - Australia/Darwin * `Australia/Eucla` - Australia/Eucla * `Australia/Hobart` - Australia/Hobart * `Australia/Lindeman` - Australia/Lindeman * `Australia/Lord_Howe` - Australia/Lord\_Howe * `Australia/Melbourne` - Australia/Melbourne * `Australia/Perth` - Australia/Perth * `Australia/Sydney` - Australia/Sydney * `Canada/Atlantic` - Canada/Atlantic * `Canada/Central` - Canada/Central * `Canada/Eastern` - Canada/Eastern * `Canada/Mountain` - Canada/Mountain * `Canada/Newfoundland` - Canada/Newfoundland * `Canada/Pacific` - Canada/Pacific * `Europe/Amsterdam` - Europe/Amsterdam * `Europe/Andorra` - Europe/Andorra * `Europe/Astrakhan` - Europe/Astrakhan * `Europe/Athens` - Europe/Athens * `Europe/Belgrade` - Europe/Belgrade * `Europe/Berlin` - Europe/Berlin * `Europe/Bratislava` - Europe/Bratislava * `Europe/Brussels` - Europe/Brussels * `Europe/Bucharest` - Europe/Bucharest * `Europe/Budapest` - Europe/Budapest * `Europe/Busingen` - Europe/Busingen * `Europe/Chisinau` - Europe/Chisinau * `Europe/Copenhagen` - Europe/Copenhagen * `Europe/Dublin` - Europe/Dublin * `Europe/Gibraltar` - Europe/Gibraltar * `Europe/Guernsey` - Europe/Guernsey * `Europe/Helsinki` - Europe/Helsinki * `Europe/Isle_of_Man` - Europe/Isle\_of\_Man * `Europe/Istanbul` - Europe/Istanbul * `Europe/Jersey` - Europe/Jersey * `Europe/Kaliningrad` - Europe/Kaliningrad * `Europe/Kirov` - Europe/Kirov * `Europe/Kyiv` - Europe/Kyiv * `Europe/Lisbon` - Europe/Lisbon * `Europe/Ljubljana` - Europe/Ljubljana * `Europe/London` - Europe/London * `Europe/Luxembourg` - Europe/Luxembourg * `Europe/Madrid` - Europe/Madrid * `Europe/Malta` - Europe/Malta * `Europe/Mariehamn` - Europe/Mariehamn * `Europe/Minsk` - Europe/Minsk * `Europe/Monaco` - Europe/Monaco * `Europe/Moscow` - Europe/Moscow * `Europe/Oslo` - Europe/Oslo * `Europe/Paris` - Europe/Paris * `Europe/Podgorica` - Europe/Podgorica * `Europe/Prague` - Europe/Prague * `Europe/Riga` - Europe/Riga * `Europe/Rome` - Europe/Rome * `Europe/Samara` - Europe/Samara * `Europe/San_Marino` - Europe/San\_Marino * `Europe/Sarajevo` - Europe/Sarajevo * `Europe/Saratov` - Europe/Saratov * `Europe/Simferopol` - Europe/Simferopol * `Europe/Skopje` - Europe/Skopje * `Europe/Sofia` - Europe/Sofia * `Europe/Stockholm` - Europe/Stockholm * `Europe/Tallinn` - Europe/Tallinn * `Europe/Tirane` - Europe/Tirane * `Europe/Ulyanovsk` - Europe/Ulyanovsk * `Europe/Vaduz` - Europe/Vaduz * `Europe/Vatican` - Europe/Vatican * `Europe/Vienna` - Europe/Vienna * `Europe/Vilnius` - Europe/Vilnius * `Europe/Volgograd` - Europe/Volgograd * `Europe/Warsaw` - Europe/Warsaw * `Europe/Zagreb` - Europe/Zagreb * `Europe/Zurich` - Europe/Zurich * `GMT` - GMT * `Indian/Antananarivo` - Indian/Antananarivo * `Indian/Chagos` - Indian/Chagos * `Indian/Christmas` - Indian/Christmas * `Indian/Cocos` - Indian/Cocos * `Indian/Comoro` - Indian/Comoro * `Indian/Kerguelen` - Indian/Kerguelen * `Indian/Mahe` - Indian/Mahe * `Indian/Maldives` - Indian/Maldives * `Indian/Mauritius` - Indian/Mauritius * `Indian/Mayotte` - Indian/Mayotte * `Indian/Reunion` - Indian/Reunion * `Pacific/Apia` - Pacific/Apia * `Pacific/Auckland` - Pacific/Auckland * `Pacific/Bougainville` - Pacific/Bougainville * `Pacific/Chatham` - Pacific/Chatham * `Pacific/Chuuk` - Pacific/Chuuk * `Pacific/Easter` - Pacific/Easter * `Pacific/Efate` - Pacific/Efate * `Pacific/Fakaofo` - Pacific/Fakaofo * `Pacific/Fiji` - Pacific/Fiji * `Pacific/Funafuti` - Pacific/Funafuti * `Pacific/Galapagos` - Pacific/Galapagos * `Pacific/Gambier` - Pacific/Gambier * `Pacific/Guadalcanal` - Pacific/Guadalcanal * `Pacific/Guam` - Pacific/Guam * `Pacific/Honolulu` - Pacific/Honolulu * `Pacific/Kanton` - Pacific/Kanton * `Pacific/Kiritimati` - Pacific/Kiritimati * `Pacific/Kosrae` - Pacific/Kosrae * `Pacific/Kwajalein` - Pacific/Kwajalein * `Pacific/Majuro` - Pacific/Majuro * `Pacific/Marquesas` - Pacific/Marquesas * `Pacific/Midway` - Pacific/Midway * `Pacific/Nauru` - Pacific/Nauru * `Pacific/Niue` - Pacific/Niue * `Pacific/Norfolk` - Pacific/Norfolk * `Pacific/Noumea` - Pacific/Noumea * `Pacific/Pago_Pago` - Pacific/Pago\_Pago * `Pacific/Palau` - Pacific/Palau * `Pacific/Pitcairn` - Pacific/Pitcairn * `Pacific/Pohnpei` - Pacific/Pohnpei * `Pacific/Port_Moresby` - Pacific/Port\_Moresby * `Pacific/Rarotonga` - Pacific/Rarotonga * `Pacific/Saipan` - Pacific/Saipan * `Pacific/Tahiti` - Pacific/Tahiti * `Pacific/Tarawa` - Pacific/Tarawa * `Pacific/Tongatapu` - Pacific/Tongatapu * `Pacific/Wake` - Pacific/Wake * `Pacific/Wallis` - Pacific/Wallis * `US/Alaska` - US/Alaska * `US/Arizona` - US/Arizona * `US/Central` - US/Central * `US/Eastern` - US/Eastern * `US/Hawaii` - US/Hawaii * `US/Mountain` - US/Mountain * `US/Pacific` - US/Pacific * `UTC` - UTC External source. External id. Is issue type enabled. Is time tracking enabled. Default state. Estimate. ### Scopes `projects:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "project_lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "project_lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", identifier: "PROJ-123", project_lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2, "project_lead": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/project/archive-project.html' description: >- Archive project via Plane API. HTTP request format, parameters, scopes, and example responses for archive project. --- # Archive project Move a project to archived status, hiding it from active project lists. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/", { method: "POST", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/project/unarchive-project.html' description: >- Unarchive project via Plane API. HTTP request format, parameters, scopes, and example responses for unarchive project. --- # Unarchive project Restore an archived project to active status, making it available in regular workflows. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archive/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/project/delete-project.html' description: >- Delete a project via Plane API. HTTP request format, parameters, scopes, and example responses for delete a project. --- # Delete a project Permanently remove a project and all its associated data from the workspace. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/project-features/overview.html' description: >- Plane Project Features API overview. Learn how to inspect and update project feature flags with the Plane API. --- # Overview Project features control which project-level capabilities are enabled for an individual project. [Learn more about Projects](https://docs.plane.so/core-concepts/projects/overview) ## The Project Feature Object ### Attributes * `epics` *boolean* Epics. * `modules` *boolean* Modules. * `cycles` *boolean* Cycles. * `views` *boolean* Views. * `pages` *boolean* Pages. * `intakes` *boolean* Intakes. * `work_item_types` *boolean* Work item types. ```json { "epics": true, "modules": true, "cycles": true, "views": true, "pages": true, "intakes": true, "work_item_types": true } ``` --- --- url: >- https://developers.plane.so/api-reference/project-features/get-project-features.html description: >- Get project features via Plane API. HTTP request format, parameters, scopes, and example responses for get project features. --- # Get project features Get the features of a project ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.features:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "epics": true, "modules": true, "cycles": true, "views": true, "pages": true, "intakes": true, "work_item_types": true } ``` --- --- url: >- https://developers.plane.so/api-reference/project-features/update-project-features.html description: >- Update project features via Plane API. HTTP request format, parameters, scopes, and example responses for update project features. --- # Update project features Update the features of a project ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Epics. Modules. Cycles. Views. Pages. Intakes. Work item types. ### Scopes `projects.features:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "epics": true, "modules": true, "cycles": true, "views": true, "pages": true, "intakes": true, "work_item_types": true }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/", headers={"X-API-Key": "your-api-key"}, json={ "epics": true, "modules": true, "cycles": true, "views": true, "pages": true, "intakes": true, "work_item_types": true } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/features/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ epics: true, modules: true, cycles: true, views: true, pages: true, intakes: true, work_item_types: true, }), }); const data = await response.json(); ``` ```json { "epics": true, "modules": true, "cycles": true, "views": true, "pages": true, "intakes": true, "work_item_types": true } ``` --- --- url: 'https://developers.plane.so/api-reference/project-labels/overview.html' description: >- Plane Project Labels API overview. Learn how to manage workspace-level project labels with the Plane API. --- # Overview Project labels define reusable classifications that can be attached to projects across a workspace. [Learn more about Projects](https://developers.plane.so/api-reference/project/overview) ## The Project Label Object ### Attributes * `id` *string* Id. * `name` *string* Name. * `description` *string* Description. * `color` *string* Color. * `sort_order` *number* Sort order. * `workspace` *string* Workspace. * `created_at` *string* Created at. * `updated_at` *string* Updated at. * `created_by` *string* Created by. * `updated_by` *string* Updated by. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "description": "Example description", "sort_order": 65535, "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/project-labels/add-project-label.html description: >- Create project label via Plane API. HTTP request format, parameters, scopes, and example responses for create project label. --- # Create project label Create a new project label in the workspace with name, color, and description. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Color. Sort order. ### Scopes `projects.labels:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#ff0000", "description": "Example description" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#ff0000", "description": "Example description" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#ff0000", description: "Example description", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "description": "Example description", "sort_order": 65535, "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/project-labels/list-project-labels.html description: >- List project labels via Plane API. HTTP request format, parameters, scopes, and example responses for list project labels. --- # List project labels Retrieve all project labels in a workspace. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/project-labels/get-project-label-detail.html description: >- Get project label via Plane API. HTTP request format, parameters, scopes, and example responses for get project label. --- # Get project label Retrieve details of a specific project label. ### Path Parameters The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "description": "Example description", "sort_order": 65535, "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/project-labels/update-project-label-detail.html description: >- Update project label via Plane API. HTTP request format, parameters, scopes, and example responses for update project label. --- # Update project label Partially update an existing project label's properties like name, color, or description. ### Path Parameters The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Color. Sort order. ### Scopes `projects.labels:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#00ff00", "description": "Example description" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#00ff00", "description": "Example description" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#00ff00", description: "Example description", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "description": "Example description", "sort_order": 65535, "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/project-labels/delete-project-label.html description: >- Delete project label via Plane API. HTTP request format, parameters, scopes, and example responses for delete project label. --- # Delete project label Permanently delete an existing project label from the workspace. ### Path Parameters The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.labels:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/project-labels/label-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue/add-issue.html' description: >- Create a work item via Plane API. HTTP request format, parameters, scopes, and example responses for create a work item. --- # Create a work item Create a new work item in the specified project with the provided details. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Assignees. Labels. Type id. Parent. Deleted at. Point. Name. Description html. Description stripped. * `urgent` - Urgent * `high` - High * `medium` - Medium * `low` - Low * `none` - None Start date. Target date. Sequence id. Sort order. Completed at. Archived at. Last activity at. Is draft. External source. External id. Created by. State. Estimate point. Type. ### Scopes `projects.work_items:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "priority": "medium", "state": "550e8400-e29b-41d4-a716-446655440000", "assignees": [ "550e8400-e29b-41d4-a716-446655440000" ], "labels": [ "550e8400-e29b-41d4-a716-446655440000" ], "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "priority": "medium", "state": "550e8400-e29b-41d4-a716-446655440000", "assignees": [ "550e8400-e29b-41d4-a716-446655440000" ], "labels": [ "550e8400-e29b-41d4-a716-446655440000" ], "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", priority: "medium", state: "550e8400-e29b-41d4-a716-446655440000", assignees: ["550e8400-e29b-41d4-a716-446655440000"], labels: ["550e8400-e29b-41d4-a716-446655440000"], external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "sequence_id": 1, "priority": "high", "assignees": ["550e8400-e29b-41d4-a716-446655440000"], "labels": ["550e8400-e29b-41d4-a716-446655440000"], "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue/list-issues.html' description: >- List all work items via Plane API. HTTP request format, parameters, scopes, and example responses for list all work items. --- # List all work items Retrieve a paginated list of all work items in a project. Supports filtering, ordering, and field selection through query parameters. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response External system identifier for filtering or lookup External system source name for filtering or lookup Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "priority": "high", "sequence_id": 123, "state": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "group": "started" }, "assignees": [], "labels": [], "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/issue/get-issue-detail.html' description: >- Retrieve a work item by ID via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a work item by id. --- # Retrieve a work item by ID Retrieve details of a specific work item. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Comma-separated list of related fields to expand in response External system identifier for filtering or lookup External system source name for filtering or lookup Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order ### Scopes `projects.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/?expand=assignees&external_id=1234567890" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/?expand=assignees&external_id=1234567890", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/?expand=assignees&external_id=1234567890", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "sequence_id": 1, "priority": "high", "assignees": ["550e8400-e29b-41d4-a716-446655440000"], "labels": ["550e8400-e29b-41d4-a716-446655440000"], "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue/get-issue-sequence-id.html' description: >- Retrieve a work item by identifier via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a work item by identifier. --- # Retrieve a work item by identifier Retrieve a specific work item using workspace slug, project identifier, and issue identifier. ### Path Parameters The numeric issue identifier. The project identifier key. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/PROJ-123/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/PROJ-123/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/work-items/PROJ-123/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "sequence_id": 1, "priority": "high", "assignees": ["550e8400-e29b-41d4-a716-446655440000"], "labels": ["550e8400-e29b-41d4-a716-446655440000"], "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue/search-issues.html' description: >- Search work items via Plane API. HTTP request format, parameters, scopes, and example responses for search work items. --- # Search work items Perform semantic search across issue names, sequence IDs, and project identifiers. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Maximum number of results to return Project ID for filtering results within a specific project Search query to filter results by name, description, or identifier Whether to search across entire workspace or within specific project ### Scopes `projects.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/search/?limit=10&project_id=project-uuid" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/search/?limit=10&project_id=project-uuid", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/search/?limit=10&project_id=project-uuid", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "issues": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "sequence_id": 123, "project__identifier": "MAB", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace__slug": "my-workspace" }, { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "sequence_id": 124, "project__identifier": "MAB", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace__slug": "my-workspace" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/issue/advanced-search-work-items.html description: >- Advanced search work items via Plane API. HTTP request format, parameters, scopes, and example responses for advanced search work items. --- # Advanced search work items Search for work items with advanced filters and search query. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Search query string for text-based search across issue fields Filter JSON passed through to IssueFilterSet for validation and application Maximum number of results to return Whether to search across all projects in the workspace Optional project ID to filter results to a specific project ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/advanced-search/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": "login", "project_id": "550e8400-e29b-41d4-a716-446655440000", "limit": 10 }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/work-items/advanced-search/", headers={"X-API-Key": "your-api-key"}, json={ "query": "login", "project_id": "550e8400-e29b-41d4-a716-446655440000", "limit": 10 } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/work-items/advanced-search/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ query: "login", project_id: "550e8400-e29b-41d4-a716-446655440000", limit: 10, }), }); const data = await response.json(); ``` ```json [ [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "sequence_id": 102, "project_identifier": "WEB", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "type_id": "550e8400-e29b-41d4-a716-446655440000", "state_id": "550e8400-e29b-41d4-a716-446655440000", "priority": "high", "target_date": "2024-01-01", "start_date": "2024-01-01" }, { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "sequence_id": 245, "project_identifier": "API", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "type_id": "550e8400-e29b-41d4-a716-446655440000", "state_id": "550e8400-e29b-41d4-a716-446655440000", "priority": "medium", "target_date": null, "start_date": "2024-01-01" } ] ] ``` --- --- url: 'https://developers.plane.so/api-reference/issue/update-issue-detail.html' description: >- Update a work item via Plane API. HTTP request format, parameters, scopes, and example responses for update a work item. --- # Update a work item Partially update an existing work item with the provided fields. Supports external ID validation to prevent conflicts. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Assignees. Labels. Type id. Parent. Deleted at. Point. Name. Description html. Description stripped. * `urgent` - Urgent * `high` - High * `medium` - Medium * `low` - Low * `none` - None Start date. Target date. Sequence id. Sort order. Completed at. Archived at. Last activity at. Is draft. External source. External id. Created by. State. Estimate point. Type. ### Scopes `projects.work_items:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "priority": "medium", "state": "550e8400-e29b-41d4-a716-446655440000", "assignees": [ "550e8400-e29b-41d4-a716-446655440000" ], "labels": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "priority": "medium", "state": "550e8400-e29b-41d4-a716-446655440000", "assignees": [ "550e8400-e29b-41d4-a716-446655440000" ], "labels": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", priority: "medium", state: "550e8400-e29b-41d4-a716-446655440000", assignees: ["550e8400-e29b-41d4-a716-446655440000"], labels: ["550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "sequence_id": 1, "priority": "high", "assignees": ["550e8400-e29b-41d4-a716-446655440000"], "labels": ["550e8400-e29b-41d4-a716-446655440000"], "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue/delete-issue.html' description: >- Delete a work item via Plane API. HTTP request format, parameters, scopes, and example responses for delete a work item. --- # Delete a work item Permanently delete an existing work item from the project. Only admins or the item creator can perform this action. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/state/overview.html' description: >- Plane State API overview. Learn about endpoints, request/response format, and how to work with state via REST API. --- # Overview States represent the current status of a work item in your project workflow. [Learn more about States](https://docs.plane.so/core-concepts/work-items/states) ## The State Object ### Attributes * `name` *string* **( required )** Name of the state * `created_at` , `updated_at` *timestamp* Timestamp of the issue when it was created and when it was last updated * `description` *string* Description of the state * `color` *string* **(required)** String code of the color * `workspace_slug` *string* Slugified name of the state auto generated from the system * `sequence` *string* Auto generated sequence of the state for ordering. * `group` *string* **(required)** Group to which the state belongs can only take values * backlog * unstarted * started * completed * cancelled * `default` *boolean* Is it the default state in which if the issues are not assigned any states all the issues are created in this state. * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `project` *uuid* The project which the issue is part of auto generated from backend * `workspace` *uuid* The workspace which the issue is part of auto generated from backend ```json { "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", "created_at": "2023-11-19T17:41:45.478363Z", "updated_at": "2023-11-19T17:41:45.478383Z", "name": "Ideation", "description": "", "color": "#eb5757", "workspace_slug": "ideation", "sequence": 130000.0, "group": "unstarted", "default": false, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/state/add-state.html' description: >- Create a state via Plane API. HTTP request format, parameters, scopes, and example responses for create a state. --- # Create a state Create a new workflow state for a project with specified name, color, and group. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Color. Sequence. * `backlog` - Backlog * `unstarted` - Unstarted * `started` - Started * `completed` - Completed * `cancelled` - Cancelled * `triage` - Triage Is triage. Default. External source. External id. ### Scopes `projects.states:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#ff0000", "group": "backlog", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#ff0000", "group": "backlog", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#ff0000", group: "backlog", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "group": "started", "sequence": 2, "default": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/state/list-states.html' description: >- List all states via Plane API. HTTP request format, parameters, scopes, and example responses for list all states. --- # List all states Retrieve all workflow states for a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Number of results per page (default: 20, max: 100) ### Scopes `projects.states:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#ffa500", "group": "started", "sequence": 2 } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/state/get-state-detail.html' description: >- Retrieve a state via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a state. --- # Retrieve a state Retrieve details of a specific state. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the state. ### Scopes `projects.states:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "group": "started", "sequence": 2, "default": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/state/update-state-detail.html' description: >- Update a state via Plane API. HTTP request format, parameters, scopes, and example responses for update a state. --- # Update a state Partially update an existing workflow state's properties like name, color, or group. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the state. ### Body Parameters Name. Description. Color. Sequence. * `backlog` - Backlog * `unstarted` - Unstarted * `started` - Started * `completed` - Completed * `cancelled` - Cancelled * `triage` - Triage Is triage. Default. External source. External id. ### Scopes `projects.states:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#00ff00", "group": "backlog", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#00ff00", "group": "backlog", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#00ff00", group: "backlog", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#f39c12", "group": "started", "sequence": 2, "default": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/state/delete-state.html' description: >- Delete a state via Plane API. HTTP request format, parameters, scopes, and example responses for delete a state. --- # Delete a state Permanently remove a workflow state from a project. Default states and states with existing work items cannot be deleted. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the state. ### Scopes `projects.states:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/states/state-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/label/overview.html' description: >- Plane Label API overview. Learn about endpoints, request/response format, and how to work with label via REST API. --- # Overview Labels are tags that help you categorize and organize work items in your project. [Learn more about Labels](https://docs.plane.so/core-concepts/work-items/labels) ## The Label Object ### Attributes * `name` *string* **(required)** Name of the label * `created_at` , `updated_at` *timestamp* Timestamp of the issue when it was created and when it was last updated. * `description` *string* Description of the Label * `color` *string* Hex code of the color * `sort_order` *float* Sort order of the label used for sorting * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `project` *uuid* The project which the issue is part of auto generated from backend * `workspace` *uuid* The workspace which the issue is part of auto generated from backend * `parent` *uuid or null* Parent of the label which is also a Label ```json { "id": "c7146baf-7058-496b-aa3a-df6c25a7e929", "created_at": "2023-11-20T06:01:03.538675Z", "updated_at": "2023-11-20T06:01:03.538683Z", "name": "High", "description": "", "color": "", "sort_order": 72416.0, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "parent": null } ``` --- --- url: 'https://developers.plane.so/api-reference/label/add-label.html' description: >- Create a label via Plane API. HTTP request format, parameters, scopes, and example responses for create a label. --- # Create a label Create a new label in the specified project with name, color, and description. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Color. Description. External source. External id. Parent. Sort order. ### Scopes `projects.labels:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#ff0000", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#ff0000", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#ff0000", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#ff4444", "description": "Example description", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/label/list-labels.html' description: >- List all labels via Plane API. HTTP request format, parameters, scopes, and example responses for list all labels. --- # List all labels Retrieve all labels in a project. Supports filtering by name and color. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#ff4444", "description": "Example description" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/label/get-label-detail.html' description: >- Retrieve a label via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a label. --- # Retrieve a label Retrieve details of a specific label. ### Path Parameters The unique identifier of the label. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#ff4444", "description": "Example description", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/label/update-label-detail.html' description: >- Update a label via Plane API. HTTP request format, parameters, scopes, and example responses for update a label. --- # Update a label Partially update an existing label's properties like name, color, or description. ### Path Parameters The unique identifier of the label. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Color. Description. External source. External id. Parent. Sort order. ### Scopes `projects.labels:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "color": "#00ff00", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "color": "#00ff00", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", color: "#00ff00", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "color": "#ff4444", "description": "Example description", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/label/delete-label.html' description: >- Delete a label via Plane API. HTTP request format, parameters, scopes, and example responses for delete a label. --- # Delete a label Permanently remove a label from the project. This action cannot be undone. ### Path Parameters The unique identifier of the label. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.labels:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/labels/label-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-types/types/overview.html' description: >- Plane Types API overview. Learn about endpoints, request/response format, and how to work with types via REST API. --- # Overview Work item types categorize different kinds of work in your project (e.g., "Task", "Bug", "Feature"). [Learn more about Work Item Types](https://docs.plane.so/core-concepts/issues/work-item-types) ## The Work Item Type object ### Attributes * `id` *uuid* Unique identifier for the work item type * `name` *string* Name of the work item type * `description` *string* Description of the work item type * `logo_props` *object* Logo properties for the work item type * `is_epic` *boolean* Whether this work item type is an epic * `is_default` *boolean* Whether this is the default work item type * `is_active` *boolean* Whether this work item type is active * `level` *number* Level of the work item type * `workspace` *uuid* The workspace which the work item type is part of (auto generated from backend) * `project` *uuid* The project which the work item type is part of (auto generated from backend) * `created_at` *timestamp* Timestamp when the work item type was created * `updated_at` *timestamp* Timestamp when the work item type was last updated * `created_by` *uuid* ID of the user who created the work item type (auto saved) * `updated_by` *uuid* ID of the user who last updated the work item type (auto saved) * `deleted_at` *timestamp* Timestamp when the work item type was deleted (null if not deleted) * `external_id` *string* External ID for the work item type (auto saved) * `external_source` *string* External source for the work item type (auto saved) ```json { "id": "d6af3c13-3459-43ab-b91c-c33ef2fd7131", "name": "Postman work item type", "description": "Postman work item type description", "logo_props": {}, "is_epic": false, "is_default": false, "is_active": true, "level": 0, "workspace": "70b6599f-9313-4c0d-b5c0-406a13a05647", "project": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2024-10-23T06:54:46.169344Z", "updated_at": "2024-10-23T06:54:46.169390Z", "created_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "updated_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "deleted_at": null, "external_id": null, "external_source": null } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/add-issue-type.html description: >- Create a work item type via Plane API. HTTP request format, parameters, scopes, and example responses for create a work item type. --- # Create a work item type Create a new issue type for a project ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Is epic. Is active. External source. External id. ### Scopes `projects.work_item_types:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "project_ids": ["550e8400-e29b-41d4-a716-446655440000"], "logo_props": "example-value", "is_epic": true, "is_default": true, "is_active": true, "level": 1 } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/list-issue-types.html description: >- List all work item types via Plane API. HTTP request format, parameters, scopes, and example responses for list all work item types. --- # List all work item types List all issue types for a project ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_item_types:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "project_ids": ["550e8400-e29b-41d4-a716-446655440000"], "logo_props": "example-value", "is_epic": true, "is_default": true, "is_active": true, "level": 1 } ] ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/get-issue-type-details.html description: >- Retrieve a work item type via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a work item type. --- # Retrieve a work item type Retrieve an issue type by id ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Scopes `projects.work_item_types:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "project_ids": ["550e8400-e29b-41d4-a716-446655440000"], "logo_props": "example-value", "is_epic": true, "is_default": true, "is_active": true, "level": 1 } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/get-work-item-type-schema.html description: >- Get work item type schema via Plane API. HTTP request format, parameters, scopes, and example responses for get work item type schema. --- # Get work item type schema Returns the complete schema for a work item type including all standard fields and custom properties with their available options inline. This endpoint enables LLMs and MCP integrations to understand what fields are available when creating/updating work items. **Standard fields** are always included: * name, description\_html, priority, state\_id, assignee\_ids, label\_ids, start\_date, target\_date, parent\_id **Custom fields** are included when: * ISSUE\_TYPES feature is enabled AND * A type\_id is provided or a default type exists for the project **Options behavior:** * state\_id options are always included * priority options are always included * assignee\_ids and label\_ids options require `?include=members,labels` * estimate\_point\_id options are included when project has estimates configured ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Comma-separated list of additional options to include: members, labels Work item type ID. If not provided, returns schema for default type (when types enabled) or standard fields only. ### Scopes `projects.work_item_types:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/schema/?include=members&type_id=type-uuid" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/schema/?include=members&type_id=type-uuid", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/schema/?include=members&type_id=type-uuid", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "type_id": "550e8400-e29b-41d4-a716-446655440000", "type_name": "Bug", "type_description": "Example description", "type_logo_props": {}, "fields": { "name": { "type": "string", "required": true, "max_length": 255 }, "priority": { "type": "option", "required": false, "options": [ { "value": "urgent", "label": "Urgent" }, { "value": "high", "label": "High" } ] }, "state_id": { "type": "uuid", "required": false, "options": [ { "id": "...", "name": "Example Name", "group": "backlog" } ] } }, "custom_fields": { "custom_field_severity": { "id": "...", "type": "OPTION", "name": "Example Name", "display_name": "Example Name", "required": true, "is_multi": false, "options": [ { "id": "...", "name": "Example Name" } ] } } } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/update-issue-types.html description: >- Update a work item type via Plane API. HTTP request format, parameters, scopes, and example responses for update a work item type. --- # Update a work item type Update an issue type ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Body Parameters Name. Description. Is epic. Is active. External source. External id. ### Scopes `projects.work_item_types:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "project_ids": ["550e8400-e29b-41d4-a716-446655440000"], "logo_props": "example-value", "is_epic": true, "is_default": true, "is_active": true, "level": 1 } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/types/delete-issue-type.html description: >- Delete a work item type via Plane API. HTTP request format, parameters, scopes, and example responses for delete a work item type. --- # Delete a work item type Delete an issue type ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Scopes `projects.work_item_types:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-types/properties/overview.html' description: >- Plane Properties API overview. Learn about endpoints, request/response format, and how to work with properties via REST API. --- # Overview Custom properties allow you to extend work items with additional fields specific to your workflow and processes. [Learn more about Custom properties](https://docs.plane.so/core-concepts/issues/work-item-types#add-custom-properties) ## The Properties Object ### Attributes * `workspace` *uuid* The workspace which the issue is part of auto generated from backend * `project` *uuid* The project which the issue is part of auto generated from backend * `created_at` , `updated_at` timestamp Timestamp of the issue when it was created and when it was last updated. * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `external_id` & `external_source` This values are auto saved and represent the id of the user that created or the updated the project. ```json { "id": "f962febb-98bc-43ca-8bfb-8012e4d54dae", "created_at": "2024-10-23T07:38:58.231897Z", "updated_at": "2024-10-23T07:38:58.231920Z", "deleted_at": null, "name": "first-issue-property", "display_name": "first issue property", "description": "first issue property", "logo_props": {}, "sort_order": 75535.0, "property_type": "OPTION", "relation_type": null, "is_required": false, "default_value": [], "settings": {}, "is_active": false, "is_multi": false, "validation_rules": {}, "external_source": null, "external_id": null, "created_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "updated_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "workspace": "70b6599f-9313-4c0d-b5c0-406a13a05647", "project": "03a9bf56-84f4-4afe-b232-9400eb9b7b6b", "issue_type": "1800681a-a749-487b-9003-3279031fea35" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/properties/add-property.html description: >- Create a custom property via Plane API. HTTP request format, parameters, scopes, and example responses for create a custom property. --- # Create a custom property Create a new issue property ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Body Parameters * `ISSUE` - Issue * `USER` - User List of options to create when property\_type is OPTION. Each option should have 'name', optionally 'description', 'is\_default', 'external\_id', and 'external\_source'. Display name. Description. * `TEXT` - Text * `DATETIME` - Datetime * `DECIMAL` - Decimal * `BOOLEAN` - Boolean * `OPTION` - Option * `RELATION` - Relation * `URL` - URL * `EMAIL` - Email * `FILE` - File * `FORMULA` - Formula Is required. Default value. Settings. Is active. Is multi. Validation rules. External source. External id. Formula config. ### Scopes `projects.work_item_properties:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "property_type": "OPTION", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "property_type": "OPTION", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", property_type: "OPTION", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "relation_type": "ISSUE", "logo_props": "example-value", "sort_order": 1, "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/properties/list-properties.html description: >- List custom properties via Plane API. HTTP request format, parameters, scopes, and example responses for list custom properties. --- # List custom properties List issue properties ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Scopes `projects.work_item_properties:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "relation_type": "ISSUE", "logo_props": "example-value", "sort_order": 1, "is_required": true } ] ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/properties/get-property-details.html description: >- Retrieve a custom property via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a custom property. --- # Retrieve a custom property Get issue property by id ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Scopes `projects.work_item_properties:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "relation_type": "ISSUE", "logo_props": "example-value", "sort_order": 1, "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/properties/update-property.html description: >- Update a custom property via Plane API. HTTP request format, parameters, scopes, and example responses for update a custom property. --- # Update a custom property Update an issue property ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Body Parameters * `ISSUE` - Issue * `USER` - User List of options to create when property\_type is OPTION. Each option should have 'name', optionally 'description', 'is\_default', 'external\_id', and 'external\_source'. Display name. Description. * `TEXT` - Text * `DATETIME` - Datetime * `DECIMAL` - Decimal * `BOOLEAN` - Boolean * `OPTION` - Option * `RELATION` - Relation * `URL` - URL * `EMAIL` - Email * `FILE` - File * `FORMULA` - Formula Is required. Default value. Settings. Is active. Is multi. Validation rules. External source. External id. Formula config. ### Scopes `projects.work_item_properties:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "property_type": "OPTION", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "property_type": "OPTION", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", property_type: "OPTION", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "relation_type": "ISSUE", "logo_props": "example-value", "sort_order": 1, "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/properties/delete-property.html description: >- Delete a custom property via Plane API. HTTP request format, parameters, scopes, and example responses for delete a custom property. --- # Delete a custom property Delete an issue property ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item type. ### Scopes `projects.work_item_properties:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-types/type-uuid/work-item-properties/property-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-types/values/overview.html' description: >- Plane Values API overview. Learn about endpoints, request/response format, and how to work with values via REST API. --- # Overview Custom property values store the actual data entered into custom properties for specific work items. ## The Values Object ### Attributes * `workspace` *uuid* The workspace which the issue is part of auto generated from backend * `project` *uuid* The project which the issue is part of auto generated from backend * `created_at` , `updated_at` timestamp Timestamp of the issue when it was created and when it was last updated. * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `external_id` & `external_source` This values are auto saved and represent the id of the user that created or the updated the project. ```json { "id": "51a869d1-f612-4315-ac91-ffef3e96c20e", "created_at": "2024-10-23T07:44:42.883820Z", "updated_at": "2024-10-23T07:44:42.883855Z", "deleted_at": null, "name": "issue property option 3", "sort_order": 10000.0, "description": "issue property option 3 description", "logo_props": {}, "is_active": true, "is_default": false, "external_source": null, "external_id": null, "created_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "updated_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "workspace": "70b6599f-9313-4c0d-b5c0-406a13a05647", "project_ids": ["03a9bf56-84f4-4afe-b232-9400eb9b7b6b"], "property": "f962febb-98bc-43ca-8bfb-8012e4d54dae", "parent": null } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/values/add-property-values.html description: >- Add custom property values via Plane API. HTTP request format, parameters, scopes, and example responses for add custom property values. --- # Add custom property values Create or update the property value for a work item. Acts as an upsert operation since only one value is allowed per work item/property combination. ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Body Parameters The value to set for the property. Type depends on property type: string for text/url/email/file fields, string (UUID) or list of UUIDs for relations/options (list only when is\_multi=True), string (YYYY-MM-DD) for dates, number for decimals, boolean for booleans Optional external identifier for syncing with external systems Optional external source identifier (e.g., 'github', 'jira') ### Scopes `projects.work_item_property_values:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "value": "example text value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", headers={"X-API-Key": "your-api-key"}, json={ "value": "example text value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ value: "example text value", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "property_id": "550e8400-e29b-41d4-a716-446655440000", "issue_id": "550e8400-e29b-41d4-a716-446655440000", "value": "Example Name", "value_type": "Example Name", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/values/list-property-values.html description: >- List all custom property values via Plane API. HTTP request format, parameters, scopes, and example responses for list all custom property values. --- # List all custom property values Retrieve the property value(s) for a specific work item property. Returns a single value for non-multi properties, or a list for multi-value properties. ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Scopes `projects.work_item_property_values:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "property_id": "550e8400-e29b-41d4-a716-446655440000", "issue_id": "550e8400-e29b-41d4-a716-446655440000", "value": "Example Name", "value_type": "Example Name", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/values/get-property-value-detail.html description: >- Get property value via Plane API. HTTP request format, parameters, scopes, and example responses for get property value. --- # Get property value Retrieve the property value(s) for a specific work item property. Returns a single value for non-multi properties, or a list for multi-value properties. ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Scopes `projects.work_item_property_values:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "property_id": "550e8400-e29b-41d4-a716-446655440000", "issue_id": "550e8400-e29b-41d4-a716-446655440000", "value": "Example Name", "value_type": "Example Name", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/values/update-property-value.html description: >- Update property value via Plane API. HTTP request format, parameters, scopes, and example responses for update property value. --- # Update property value Update an existing property value for a work item (partial update) ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Body Parameters The value to set for the property. Type depends on property type: string for text/url/email/file fields, string (UUID) or list of UUIDs for relations/options (list only when is\_multi=True), string (YYYY-MM-DD) for dates, number for decimals, boolean for booleans Optional external identifier for syncing with external systems Optional external source identifier (e.g., 'github', 'jira') ### Scopes `projects.work_item_property_values:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "value": "updated text value" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", headers={"X-API-Key": "your-api-key"}, json={ "value": "updated text value" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ value: "updated text value", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "property_id": "550e8400-e29b-41d4-a716-446655440000", "issue_id": "550e8400-e29b-41d4-a716-446655440000", "value": "Example Name", "value_type": "Example Name", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/values/delete-property-value.html description: >- Delete property value via Plane API. HTTP request format, parameters, scopes, and example responses for delete property value. --- # Delete property value Delete the property value(s) for a work item. For multi-value properties, deletes all values. ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Scopes `projects.work_item_property_values:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/work-item-properties/property-uuid/values/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-types/options/overview.html' description: >- Plane Options API overview. Learn about endpoints, request/response format, and how to work with options via REST API. --- # Overview Custom property options define the available choices for dropdown-style custom properties on work items. ## The Options Object ### Attributes * `workspace` *uuid* The workspace which the issue is part of auto generated from backend * `project` *uuid* The project which the issue is part of auto generated from backend * `created_at` , `updated_at` timestamp Timestamp of the issue when it was created and when it was last updated. * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `external_id` & `external_source` This values are auto saved and represent the id of the user that created or the updated the project. ```json { "id": "51a869d1-f612-4315-ac91-ffef3e96c20e", "created_at": "2024-10-23T07:44:42.883820Z", "updated_at": "2024-10-23T07:44:42.883855Z", "deleted_at": null, "name": "issue property option 3", "sort_order": 10000.0, "description": "issue property option 3 description", "logo_props": {}, "is_active": true, "is_default": false, "external_source": null, "external_id": null, "created_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "updated_by": "9d6d1ecd-bf73-4169-80c8-7dee79b217f4", "workspace": "70b6599f-9313-4c0d-b5c0-406a13a05647", "project": "03a9bf56-84f4-4afe-b232-9400eb9b7b6b", "property": "f962febb-98bc-43ca-8bfb-8012e4d54dae", "parent": null } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/options/add-dropdown-options.html description: >- Add dropdown options via Plane API. HTTP request format, parameters, scopes, and example responses for add dropdown options. --- # Add dropdown options Create a new issue property option ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Is active. Is default. External source. External id. Parent. ### Scopes `projects.work_item_property_options:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "sort_order": 1, "logo_props": "example-value", "is_active": true, "is_default": true, "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/options/list-dropdown-options.html description: >- List all dropdown options via Plane API. HTTP request format, parameters, scopes, and example responses for list all dropdown options. --- # List all dropdown options List issue property options ### Path Parameters The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_item_property_options:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "sort_order": 1, "logo_props": "example-value", "is_active": true, "is_default": true, "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000" } ] ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/options/get-option-details.html description: >- Retrieve option details via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve option details. --- # Retrieve option details Get issue property option by id ### Path Parameters The unique identifier of the option. The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_item_property_options:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "sort_order": 1, "logo_props": "example-value", "is_active": true, "is_default": true, "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/options/update-dropdown-options.html description: >- Update dropdown options via Plane API. HTTP request format, parameters, scopes, and example responses for update dropdown options. --- # Update dropdown options Update an issue property option ### Path Parameters The unique identifier of the option. The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Is active. Is default. External source. External id. Parent. ### Scopes `projects.work_item_property_options:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "Example description", "deleted_at": "2024-01-01T00:00:00Z", "sort_order": 1, "logo_props": "example-value", "is_active": true, "is_default": true, "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-types/options/delete-dropdown-options.html description: >- Delete dropdown options via Plane API. HTTP request format, parameters, scopes, and example responses for delete dropdown options. --- # Delete dropdown options Delete an issue property option ### Path Parameters The unique identifier of the option. The unique identifier of the project. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_item_property_options:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-item-properties/property-uuid/options/option-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/link/overview.html' description: >- Plane Link API overview. Learn about endpoints, request/response format, and how to work with link via REST API. --- # Overview Links attach external resources to work items, allowing you to reference documentation, designs, or other relevant URLs. [Learn more about Links](https://docs.plane.so/core-concepts/work-items/overview#add-links-and-attachments) ## The Link Object ### Attributes * `title` *string* Title of the url * `url` *url* **(required)** Url of the external link * `metadata` *json* Metadata from the resource * `created_at` , `updated_at` *timestamp* Timestamp of the issue when it was created and when it was last updated. * `created_by` & `updated_by` This values are auto saved and represent the id of the user that created or the updated the project. * `project` *uuid* The project which the issue is part of auto generated from backend * `workspace` *uuid* The workspace which the issue is part of auto generated from backend * `issue` *uuid* The issue which the link is attached to ```json { "id": "662dd6b2-2b01-4315-955f-480eb51baa14", "created_at": "2023-11-20T06:23:10.270664Z", "updated_at": "2023-11-20T06:23:10.270689Z", "title": "Plane Website", "url": "https://plane.so", "metadata": {}, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "issue": "e1c25c66-5bb8-465e-a818-92a483423443" } ``` --- --- url: 'https://developers.plane.so/api-reference/link/add-link.html' description: >- Create a link via Plane API. HTTP request format, parameters, scopes, and example responses for create a link. --- # Create a link Add a new external link to a work item with URL, title, and metadata. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Title. Url. ### Scopes `projects.work_items.links:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/resource", "title": "Example Name" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/", headers={"X-API-Key": "your-api-key"}, json={ "url": "https://example.com/resource", "title": "Example Name" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com/resource", title: "Example Name", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/resource", "title": "Example Name", "metadata": { "title": "Example Name", "description": "Example description", "image": "https://example.com/assets/example-image.png" }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/link/list-links.html' description: >- List all links via Plane API. HTTP request format, parameters, scopes, and example responses for list all links. --- # List all links Retrieve all links associated with a work item. Supports filtering by URL, title, and metadata. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items.links:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/link/get-link-detail.html' description: >- Retrieve a link via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a link. --- # Retrieve a link Retrieve details of a specific work item link. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items.links:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/link/update-link-detail.html' description: >- Update a link via Plane API. HTTP request format, parameters, scopes, and example responses for update a link. --- # Update a link Modify the URL, title, or metadata of an existing issue link. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Title. Url. ### Scopes `projects.work_items.links:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/resource", "title": "Example Name" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "url": "https://example.com/resource", "title": "Example Name" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://example.com/resource", title: "Example Name", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/resource", "title": "Example Name", "metadata": { "title": "Example Name", "description": "Example description", "image": "https://example.com/assets/example-image.png" }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/link/delete-link.html' description: >- Delete a link via Plane API. HTTP request format, parameters, scopes, and example responses for delete a link. --- # Delete a link Permanently remove an external link from a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.links:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/links/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-activity/overview.html' description: >- Plane Issue-Activity API overview. Learn about endpoints, request/response format, and how to work with issue-activity via REST API. --- # Overview Work item activity represents the history of all changes made to a work item, including property changes, comments, and other modifications. ## The Activity Object ### Attributes * `id` *uuid* Unique identifier for the activity * `created_at` *timestamp* The timestamp of the time when the activity was created * `updated_at` *timestamp* The timestamp of the time when the activity was last updated * `deleted_at` *timestamp* or *null* The timestamp when the activity was deleted (if deleted) * `verb` *string* created or updated * `field` *string* or *null* The field that got changed null when created * `old_value` *string* Old value of the field * `new_value` *string* New value of the field * `comment` *string* Comment auto generated * `attachments` - *\[url,]* Url of all the attachments that are in the activity * `old_identifier` *uuid* Old identifier of the field * `new_identifier` *uuid* New identifier of the field * `epoch` *float* Epoch float field when the activity was created. * `project` uuid It contains projects uuid which is automatically saved. * `workspace` uuid It contains workspace uuid which is automatically saved * `issue` *uuid* The work item the activity is attached to * `issue_comment` *uuid or null* The comment uuid if the activity was created due to a comment * `actor` uuid The actor who triggered this activity ```json { "id": "35612f5b-3eff-4130-b91c-c976ff887a20", "created_at": "2023-11-19T11:56:55.452555Z", "updated_at": "2023-11-19T11:56:55.452561Z", "verb": "created", "field": null, "old_value": null, "new_value": null, "comment": "created the work item", "attachments": [], "old_identifier": null, "new_identifier": null, "epoch": 1700395015.0, "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "issue": "e1c25c66-5bb8-465e-a818-92a483423443", "issue_comment": null, "actor": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-activity/list-issue-activities.html description: >- List all work item activity via Plane API. HTTP request format, parameters, scopes, and example responses for list all work item activity. --- # List all work item activity Retrieve all activities for a work item. Supports filtering by activity type and date range. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items.activities:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-activity/get-issue-activity-detail.html description: >- Retrieve a work item activity via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a work item activity. --- # Retrieve a work item activity Retrieve details of a specific activity. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items.activities:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/resource-id-uuid/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/resource-id-uuid/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/activities/resource-id-uuid/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/issue-comment/overview.html' description: >- Plane Issue-Comment API overview. Learn about endpoints, request/response format, and how to work with issue-comment via REST API. --- # Overview Comments allow team members to discuss and collaborate on work items by adding text, mentions, and attachments. [Learn more about Work Item Comments](https://docs.plane.so/core-concepts/issues/overview#comment-on-work-items) ## The Comments Object ### Attributes * `id` *uuid* Unique identifier for the comment * `created_at` *timestamp* The timestamp when the comment was created * `updated_at` *timestamp* The timestamp when the comment was last updated * `deleted_at` *timestamp* or *null* The timestamp when the comment was deleted (if deleted) * `edited_at` *timestamp* or *null* The timestamp when the comment was last edited * `comment_html` *string* HTML string version of the comment * `comment_stripped` *string* Stripped string version of the comment * `comment_json` *object* JSON object version of the comment * `attachments` *string\[]* Array of attachment URLs * `access` *string* If the comment should be visible externally also if the project is published or not. Takes in two values * INTERNAL * EXTERNAL * `external_source` *string* External source identifier * `external_id` *string* External ID from the external source * `is_member` *boolean* Whether the current user is a member of the project * `created_by` , `updated_by` *uuid* These values are auto saved and represent the id of the user that created or updated the comment * `project` uuid It contains project uuid which is automatically saved. * `workspace` uuid It contains workspace uuid which is automatically saved * `issue` *uuid* The work item the comment is attached to * `actor` *uuid* UUID of the user who commented. ```json { "id": "f3e29f26-708d-40f0-9209-7e0de44abc49", "created_at": "2023-11-20T09:26:10.383129Z", "updated_at": "2023-11-20T09:26:10.383140Z", "comment_stripped": "Initialf ThoughtsaMy initial thoughts on this are very good", "comment_json": {}, "comment_html": "

Initialf Thoughts

a

My initial thoughts on this are very good

", "attachments": [], "access": "INTERNAL", "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "issue": "e1c25c66-5bb8-465e-a818-92a483423443", "actor": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue-comment/add-issue-comment.html' description: >- Create a work item comment via Plane API. HTTP request format, parameters, scopes, and example responses for create a work item comment. --- # Create a work item comment Add a new comment to a work item with HTML content. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Comment json. Comment html. * `INTERNAL` - INTERNAL * `EXTERNAL` - EXTERNAL External source. External id. Parent. ### Scopes `projects.work_items.comments:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "comment_html": "

Example content

", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/", headers={"X-API-Key": "your-api-key"}, json={ "comment_html": "

Example content

", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ comment_html: "

Example content

", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "comment_html": "

Example content

", "comment_json": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "This issue has been resolved by implementing OAuth 2.0 flow." } ] } ] }, "actor": { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "display_name": "Example Name", "avatar": "https://example.com/assets/example-image.png" }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-comment/list-issue-comments.html description: >- List all work item comments via Plane API. HTTP request format, parameters, scopes, and example responses for list all work item comments. --- # List all work item comments Retrieve all comments for a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items.comments:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-comment/get-issue-comment-detail.html description: >- Retrieve a work item comment via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a work item comment. --- # Retrieve a work item comment Retrieve details of a specific comment. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.comments:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "comment_html": "

Example content

", "comment_json": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "This issue has been resolved by implementing OAuth 2.0 flow." } ] } ] }, "actor": { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "display_name": "Example Name", "avatar": "https://example.com/assets/example-image.png" }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-comment/update-issue-comment-detail.html description: >- Update a work item comment via Plane API. HTTP request format, parameters, scopes, and example responses for update a work item comment. --- # Update a work item comment Modify the content of an existing comment on a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Comment json. Comment html. * `INTERNAL` - INTERNAL * `EXTERNAL` - EXTERNAL External source. External id. Parent. ### Scopes `projects.work_items.comments:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "comment_html": "

Example content

", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "comment_html": "

Example content

", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ comment_html: "

Example content

", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "comment_html": "

Example content

", "comment_json": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "This issue has been resolved by implementing OAuth 2.0 flow." } ] } ] }, "actor": { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "display_name": "Example Name", "avatar": "https://example.com/assets/example-image.png" }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-comment/delete-issue-comment.html description: >- Delete a work item comment via Plane API. HTTP request format, parameters, scopes, and example responses for delete a work item comment. --- # Delete a work item comment Permanently remove a comment from a work item. Records deletion activity for audit purposes. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.comments:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/comments/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/issue-attachments/overview.html' description: >- Plane Issue-Attachments API overview. Learn about endpoints, request/response format, and how to work with issue-attachments via REST API. --- # Overview Allows you to manage file attachments associated with work items and Intake work items. You can upload new attachments and retrieve existing attachments for a specific work item. [Learn more about Attachments](https://docs.plane.so/core-concepts/issues/overview#add-links-and-attachments) ## Upload process 1. Get the [upload credentials](/api-reference/issue-attachments/get-upload-credentials). 2. [Upload the file](/api-reference/issue-attachments/upload-file) to storage. 3. [Complete attachment upload](/api-reference/issue-attachments/complete-upload) to notify server. ## The Attachment Object ### Attributes * `id` *string* Unique identifier for the attachment * `created_at` , `updated_at`, `deleted_at` *timestamp* Timestamp when the attachment was created, when it was last modified or deleted * `attributes` *object* Contains file metadata: * `name` *string* Original filename of the attachment * `size` *integer* File size in bytes * `type` *string* MIME type of the file * `asset` *string* Storage path/identifier for the attachment file * `entity_type` *string* Always `ISSUE_ATTACHMENT` for work item attachments * `entity_identifier` *string* Entity identifier for the attachment * `is_deleted` *boolean* Whether the attachment has been deleted * `is_archived` *boolean* Whether the attachment has been archived * `external_id` *string* or *null* External identifier if the issue and its attachments are imported to Plane * `external_source` *string* or *null* Name of the source if the issue and its attachments are imported to Plane * `size` *integer* File size in bytes * `is_uploaded` *boolean* Whether the file has been successfully uploaded * `storage_metadata` *object* Cloud storage metadata: * `ETag` *string* Storage provider's entity tag * `Metadata` *object* Additional storage metadata * `ContentType` *object* MIME type of stored file * `LastModified` *timestamp* Last modification time in storage * `ContentLength` *integer* File size in bytes * `created_by` *string* ID of user who created the attachment * `updated_by` *string* ID of user who last modified the attachment * `deleted_by` *string* ID of user who deleted the attachment * `workspace` *string* ID of workspace containing the attachment * `project` *string* ID of project containing the work item * `issue` *string* ID of work item containing the attachment * `user` *string* ID of user associated with the attachment * `draft_issue` *string* ID of draft work item if applicable * `comment` *string* ID of comment if attachment is associated with a comment * `page` *string* ID of page if attachment is associated with a page ```json { "id": "8caf3ed5-4f57-9674-76c4fce146b2", "created_at": "2024-10-30T09:32:32.815273Z", "updated_at": "2024-10-30T09:32:35.533136Z", "deleted_at": null, "attributes": { "name": "plane-logo.png", "size": 135686, "type": "image/png" }, "asset": "9b8aab8a-9052-fc735350abe8/6893d862ecb740d4b7f9f6542cda539c-plane.png", "entity_type": "ISSUE_ATTACHMENT", "is_deleted": false, "is_archived": false, "external_id": null, "external_source": null, "size": 135686.0, "is_uploaded": true, "storage_metadata": { "ETag": "\"72d0d4be99999fe60c2fbc08c8b\"", "Metadata": {}, "ContentType": "image/png", "LastModified": "2024-10-30T09:32:34+00:00", "ContentLength": 135686 }, "created_by": "575de6bf-e120-43bb-9f6a-eae276210575", "updated_by": "575de6bf-e120-43bb-9f6a-eae276210575", "workspace": "9b8aab8a-9s6a-99ac-fc735350abe8", "project": "1790bd-5262-42fb-ac55-568c19a5", "issue": "7ba090-7702-4e26-a61e-aa6b866f7" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/get-attachments.html description: >- List all attachments via Plane API. HTTP request format, parameters, scopes, and example responses for list all attachments. --- # List all attachments Retrieve all attachments for a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.attachments:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "size": 1024000, "asset_url": "https://example.com/resource", "attributes": { "name": "Example Name", "type": "image/png", "size": 1024000 }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/get-attachment-detail.html description: >- Retrieve an attachment via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve an attachment. --- # Retrieve an attachment Download attachment file. Returns a redirect to the presigned download URL. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.attachments:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "detail": "Authentication credentials were not provided or are invalid." } ``` --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/get-upload-credentials.html description: >- Get upload credentials via Plane API. HTTP request format, parameters, scopes, and example responses for get upload credentials. --- # Get upload credentials Generate presigned URL for uploading file attachments to a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Original filename of the asset MIME type of the file File size in bytes External identifier for the asset (for integration tracking) External source system (for integration tracking) ### Scopes `projects.work_items.attachments:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "type": "application/pdf", "size": 1024000, "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "type": "application/pdf", "size": 1024000, "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", type: "application/pdf", size: 1024000, external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "detail": "Presigned download URL generated successfully" } ``` --- --- url: 'https://developers.plane.so/api-reference/issue-attachments/upload-file.html' description: >- Upload a file to the presigned storage URL returned by Plane. Includes the required multipart form fields for attachment uploads. --- # Upload file Use the presigned form fields returned by the attachment upload-credentials endpoint to upload the binary file directly to object storage. ### Body Parameters MIME type of the file being uploaded. Storage key returned by Plane for this upload. Base64-encoded upload policy returned by Plane. AWS signature returned by Plane. Binary file contents to upload. ```bash curl -X POST \ "https://planefs-uploads.s3.amazonaws.com/" \ -F "Content-Type=image/png" \ -F "key=attachments/550e8400-e29b-41d4-a716-446655440000/example-image.png" \ -F "policy=example-policy" \ -F "x-amz-signature=example-signature" \ -F "file=@./example-image.png" ``` ```python import requests with open("example-image.png", "rb") as file_handle: response = requests.post( "https://planefs-uploads.s3.amazonaws.com/", files={"file": file_handle}, data={ "Content-Type": "image/png", "key": "attachments/550e8400-e29b-41d4-a716-446655440000/example-image.png", "policy": "example-policy", "x-amz-signature": "example-signature", }, ) print(response.status_code) ``` ```javascript const formData = new FormData(); formData.append("Content-Type", "image/png"); formData.append("key", "attachments/550e8400-e29b-41d4-a716-446655440000/example-image.png"); formData.append("policy", "example-policy"); formData.append("x-amz-signature", "example-signature"); formData.append("file", fileInput.files[0]); const response = await fetch("https://planefs-uploads.s3.amazonaws.com/", { method: "POST", body: formData, }); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/complete-upload.html description: >- Complete upload via Plane API. HTTP request format, parameters, scopes, and example responses for complete upload. --- # Complete upload Mark an attachment as uploaded after successful file transfer to storage. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Mark attachment as uploaded ### Scopes `projects.work_items.attachments:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "is_uploaded": true }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "is_uploaded": true } ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ is_uploaded: true, }), } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/update-attachment.html description: >- Update an attachment via Plane API. HTTP request format, parameters, scopes, and example responses for update an attachment. --- # Update an attachment Mark an attachment as uploaded after successful file transfer to storage. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Mark attachment as uploaded ### Scopes `projects.work_items.attachments:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "is_uploaded": true }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "is_uploaded": true } ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ is_uploaded: true, }), } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/issue-attachments/delete-attachment.html description: >- Delete an attachment via Plane API. HTTP request format, parameters, scopes, and example responses for delete an attachment. --- # Delete an attachment Permanently remove an attachment from a work item. Records deletion activity for audit purposes. ### Path Parameters The unique identifier of the work item. The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.attachments:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/attachments/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/work-item-pages/overview.html' description: >- Plane Work Item Pages API overview. Learn how to link pages to work items using the Plane API. --- # Overview Work item pages create links between work items and wiki pages so related documentation stays close to execution. [Learn more about Work Items](https://docs.plane.so/core-concepts/issues) ## The Work Item Page Link Object ### Attributes * `id` *string* Id. * `page` *object* Lightweight page serializer for work item page links. Provides essential page information including identifiers, name, timestamps, and visual properties for work item page associations. * `issue` *string* Issue. * `project` *string* Project. * `workspace` *string* Workspace. * `created_at` *string* Created at. * `updated_at` *string* Updated at. * `created_by` *string* Created by. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "page": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "is_global": false, "logo_props": {} }, "issue": "550e8400-e29b-41d4-a716-446655440000", "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-pages/add-work-item-page.html description: >- Create work item page link via Plane API. HTTP request format, parameters, scopes, and example responses for create work item page link. --- # Create work item page link Link a page to a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Body Parameters ID of the page to link to the work item ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "page_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/", headers={"X-API-Key": "your-api-key"}, json={ "page_id": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ page_id: "550e8400-e29b-41d4-a716-446655440000", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "page": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "is_global": false, "logo_props": {} }, "issue": "550e8400-e29b-41d4-a716-446655440000", "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-pages/list-work-item-pages.html description: >- List work item pages via Plane API. HTTP request format, parameters, scopes, and example responses for list work item pages. --- # List work item pages Retrieve all page links associated with a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Query Parameters Pagination cursor for getting next set of results Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/?cursor=20:1:0&order_by=-created_at" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/?cursor=20:1:0&order_by=-created_at", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/?cursor=20:1:0&order_by=-created_at", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-pages/get-work-item-page-detail.html description: >- Get work item page link via Plane API. HTTP request format, parameters, scopes, and example responses for get work item page link. --- # Get work item page link Retrieve details of a specific page link for a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the page. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "page": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "is_global": false, "logo_props": {} }, "issue": "550e8400-e29b-41d4-a716-446655440000", "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-pages/delete-work-item-page.html description: >- Delete work item page link via Plane API. HTTP request format, parameters, scopes, and example responses for delete work item page link. --- # Delete work item page link Remove a page link from a work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the page. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the work item. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/pages/page-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/cycle/overview.html' description: >- Plane Cycle API overview. Learn about endpoints, request/response format, and how to work with cycle via REST API. --- # Overview Cycles are custom time periods in which a team works to complete items from their backlog. At the end of a cycle, the team typically has a new version of their project or product ready. [Learn more about Cycles](https://docs.plane.so/core-concepts/cycles) ## The Cycles Object ### Attributes * `name` string (required) Name of the cycle * `description` string Description of the cycle * `start_date` date Start date of the cycle * `end_date` date End date of the cycle * `created_at` *timestamp* The timestamp of the time when the project was created * `updated_at` *timestamp* The timestamp of the time when the project was last updated * `view_props` It store the filters and the display properties selected by the user to visualize the issues in the module * `sort_order` It gives the position of the module at which it should be displayed * `created_by` , `updated_by` *uuid* These values are auto saved and represent the id of the user that created or the updated the * `Project` uuid It contains projects uuid which is automatically saved. * `Workspace` uuid It contains workspace uuid which is automatically saved * `owned_by` uuid The user ID of the cycle owner * `archived_at` *timestamp* The timestamp when the cycle was archived (if archived) * `timezone` string The timezone for the cycle * `version` number Version number of the cycle ```json { "id": "50ebc791-65e4-4b4d-a164-3b4e529e55a5", "created_at": "2023-11-19T12:18:14.900078Z", "updated_at": "2023-11-19T12:18:14.900088Z", "name": "cycle testing", "description": "", "start_date": null, "end_date": null, "view_props": {}, "sort_order": 35535.0, "created_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "updated_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "project": "6436c4ae-fba7-45dc-ad4a-5440e17cb1b2", "workspace": "c467e125-59e3-44ec-b5ee-f9c1e138c611", "owned_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c" } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/add-cycle.html' description: >- Create a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for create a cycle. --- # Create a cycle Create a new development cycle with specified name, description, and date range. Supports external ID tracking for integration purposes. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Start date. End date. User who owns the cycle. If not provided, defaults to the current user. External source. External id. * `Africa/Abidjan` - Africa/Abidjan * `Africa/Accra` - Africa/Accra * `Africa/Addis_Ababa` - Africa/Addis\_Ababa * `Africa/Algiers` - Africa/Algiers * `Africa/Asmara` - Africa/Asmara * `Africa/Bamako` - Africa/Bamako * `Africa/Bangui` - Africa/Bangui * `Africa/Banjul` - Africa/Banjul * `Africa/Bissau` - Africa/Bissau * `Africa/Blantyre` - Africa/Blantyre * `Africa/Brazzaville` - Africa/Brazzaville * `Africa/Bujumbura` - Africa/Bujumbura * `Africa/Cairo` - Africa/Cairo * `Africa/Casablanca` - Africa/Casablanca * `Africa/Ceuta` - Africa/Ceuta * `Africa/Conakry` - Africa/Conakry * `Africa/Dakar` - Africa/Dakar * `Africa/Dar_es_Salaam` - Africa/Dar\_es\_Salaam * `Africa/Djibouti` - Africa/Djibouti * `Africa/Douala` - Africa/Douala * `Africa/El_Aaiun` - Africa/El\_Aaiun * `Africa/Freetown` - Africa/Freetown * `Africa/Gaborone` - Africa/Gaborone * `Africa/Harare` - Africa/Harare * `Africa/Johannesburg` - Africa/Johannesburg * `Africa/Juba` - Africa/Juba * `Africa/Kampala` - Africa/Kampala * `Africa/Khartoum` - Africa/Khartoum * `Africa/Kigali` - Africa/Kigali * `Africa/Kinshasa` - Africa/Kinshasa * `Africa/Lagos` - Africa/Lagos * `Africa/Libreville` - Africa/Libreville * `Africa/Lome` - Africa/Lome * `Africa/Luanda` - Africa/Luanda * `Africa/Lubumbashi` - Africa/Lubumbashi * `Africa/Lusaka` - Africa/Lusaka * `Africa/Malabo` - Africa/Malabo * `Africa/Maputo` - Africa/Maputo * `Africa/Maseru` - Africa/Maseru * `Africa/Mbabane` - Africa/Mbabane * `Africa/Mogadishu` - Africa/Mogadishu * `Africa/Monrovia` - Africa/Monrovia * `Africa/Nairobi` - Africa/Nairobi * `Africa/Ndjamena` - Africa/Ndjamena * `Africa/Niamey` - Africa/Niamey * `Africa/Nouakchott` - Africa/Nouakchott * `Africa/Ouagadougou` - Africa/Ouagadougou * `Africa/Porto-Novo` - Africa/Porto-Novo * `Africa/Sao_Tome` - Africa/Sao\_Tome * `Africa/Tripoli` - Africa/Tripoli * `Africa/Tunis` - Africa/Tunis * `Africa/Windhoek` - Africa/Windhoek * `America/Adak` - America/Adak * `America/Anchorage` - America/Anchorage * `America/Anguilla` - America/Anguilla * `America/Antigua` - America/Antigua * `America/Araguaina` - America/Araguaina * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos\_Aires * `America/Argentina/Catamarca` - America/Argentina/Catamarca * `America/Argentina/Cordoba` - America/Argentina/Cordoba * `America/Argentina/Jujuy` - America/Argentina/Jujuy * `America/Argentina/La_Rioja` - America/Argentina/La\_Rioja * `America/Argentina/Mendoza` - America/Argentina/Mendoza * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio\_Gallegos * `America/Argentina/Salta` - America/Argentina/Salta * `America/Argentina/San_Juan` - America/Argentina/San\_Juan * `America/Argentina/San_Luis` - America/Argentina/San\_Luis * `America/Argentina/Tucuman` - America/Argentina/Tucuman * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia * `America/Aruba` - America/Aruba * `America/Asuncion` - America/Asuncion * `America/Atikokan` - America/Atikokan * `America/Bahia` - America/Bahia * `America/Bahia_Banderas` - America/Bahia\_Banderas * `America/Barbados` - America/Barbados * `America/Belem` - America/Belem * `America/Belize` - America/Belize * `America/Blanc-Sablon` - America/Blanc-Sablon * `America/Boa_Vista` - America/Boa\_Vista * `America/Bogota` - America/Bogota * `America/Boise` - America/Boise * `America/Cambridge_Bay` - America/Cambridge\_Bay * `America/Campo_Grande` - America/Campo\_Grande * `America/Cancun` - America/Cancun * `America/Caracas` - America/Caracas * `America/Cayenne` - America/Cayenne * `America/Cayman` - America/Cayman * `America/Chicago` - America/Chicago * `America/Chihuahua` - America/Chihuahua * `America/Ciudad_Juarez` - America/Ciudad\_Juarez * `America/Costa_Rica` - America/Costa\_Rica * `America/Creston` - America/Creston * `America/Cuiaba` - America/Cuiaba * `America/Curacao` - America/Curacao * `America/Danmarkshavn` - America/Danmarkshavn * `America/Dawson` - America/Dawson * `America/Dawson_Creek` - America/Dawson\_Creek * `America/Denver` - America/Denver * `America/Detroit` - America/Detroit * `America/Dominica` - America/Dominica * `America/Edmonton` - America/Edmonton * `America/Eirunepe` - America/Eirunepe * `America/El_Salvador` - America/El\_Salvador * `America/Fort_Nelson` - America/Fort\_Nelson * `America/Fortaleza` - America/Fortaleza * `America/Glace_Bay` - America/Glace\_Bay * `America/Goose_Bay` - America/Goose\_Bay * `America/Grand_Turk` - America/Grand\_Turk * `America/Grenada` - America/Grenada * `America/Guadeloupe` - America/Guadeloupe * `America/Guatemala` - America/Guatemala * `America/Guayaquil` - America/Guayaquil * `America/Guyana` - America/Guyana * `America/Halifax` - America/Halifax * `America/Havana` - America/Havana * `America/Hermosillo` - America/Hermosillo * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis * `America/Indiana/Knox` - America/Indiana/Knox * `America/Indiana/Marengo` - America/Indiana/Marengo * `America/Indiana/Petersburg` - America/Indiana/Petersburg * `America/Indiana/Tell_City` - America/Indiana/Tell\_City * `America/Indiana/Vevay` - America/Indiana/Vevay * `America/Indiana/Vincennes` - America/Indiana/Vincennes * `America/Indiana/Winamac` - America/Indiana/Winamac * `America/Inuvik` - America/Inuvik * `America/Iqaluit` - America/Iqaluit * `America/Jamaica` - America/Jamaica * `America/Juneau` - America/Juneau * `America/Kentucky/Louisville` - America/Kentucky/Louisville * `America/Kentucky/Monticello` - America/Kentucky/Monticello * `America/Kralendijk` - America/Kralendijk * `America/La_Paz` - America/La\_Paz * `America/Lima` - America/Lima * `America/Los_Angeles` - America/Los\_Angeles * `America/Lower_Princes` - America/Lower\_Princes * `America/Maceio` - America/Maceio * `America/Managua` - America/Managua * `America/Manaus` - America/Manaus * `America/Marigot` - America/Marigot * `America/Martinique` - America/Martinique * `America/Matamoros` - America/Matamoros * `America/Mazatlan` - America/Mazatlan * `America/Menominee` - America/Menominee * `America/Merida` - America/Merida * `America/Metlakatla` - America/Metlakatla * `America/Mexico_City` - America/Mexico\_City * `America/Miquelon` - America/Miquelon * `America/Moncton` - America/Moncton * `America/Monterrey` - America/Monterrey * `America/Montevideo` - America/Montevideo * `America/Montserrat` - America/Montserrat * `America/Nassau` - America/Nassau * `America/New_York` - America/New\_York * `America/Nome` - America/Nome * `America/Noronha` - America/Noronha * `America/North_Dakota/Beulah` - America/North\_Dakota/Beulah * `America/North_Dakota/Center` - America/North\_Dakota/Center * `America/North_Dakota/New_Salem` - America/North\_Dakota/New\_Salem * `America/Nuuk` - America/Nuuk * `America/Ojinaga` - America/Ojinaga * `America/Panama` - America/Panama * `America/Paramaribo` - America/Paramaribo * `America/Phoenix` - America/Phoenix * `America/Port-au-Prince` - America/Port-au-Prince * `America/Port_of_Spain` - America/Port\_of\_Spain * `America/Porto_Velho` - America/Porto\_Velho * `America/Puerto_Rico` - America/Puerto\_Rico * `America/Punta_Arenas` - America/Punta\_Arenas * `America/Rankin_Inlet` - America/Rankin\_Inlet * `America/Recife` - America/Recife * `America/Regina` - America/Regina * `America/Resolute` - America/Resolute * `America/Rio_Branco` - America/Rio\_Branco * `America/Santarem` - America/Santarem * `America/Santiago` - America/Santiago * `America/Santo_Domingo` - America/Santo\_Domingo * `America/Sao_Paulo` - America/Sao\_Paulo * `America/Scoresbysund` - America/Scoresbysund * `America/Sitka` - America/Sitka * `America/St_Barthelemy` - America/St\_Barthelemy * `America/St_Johns` - America/St\_Johns * `America/St_Kitts` - America/St\_Kitts * `America/St_Lucia` - America/St\_Lucia * `America/St_Thomas` - America/St\_Thomas * `America/St_Vincent` - America/St\_Vincent * `America/Swift_Current` - America/Swift\_Current * `America/Tegucigalpa` - America/Tegucigalpa * `America/Thule` - America/Thule * `America/Tijuana` - America/Tijuana * `America/Toronto` - America/Toronto * `America/Tortola` - America/Tortola * `America/Vancouver` - America/Vancouver * `America/Whitehorse` - America/Whitehorse * `America/Winnipeg` - America/Winnipeg * `America/Yakutat` - America/Yakutat * `Antarctica/Casey` - Antarctica/Casey * `Antarctica/Davis` - Antarctica/Davis * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville * `Antarctica/Macquarie` - Antarctica/Macquarie * `Antarctica/Mawson` - Antarctica/Mawson * `Antarctica/McMurdo` - Antarctica/McMurdo * `Antarctica/Palmer` - Antarctica/Palmer * `Antarctica/Rothera` - Antarctica/Rothera * `Antarctica/Syowa` - Antarctica/Syowa * `Antarctica/Troll` - Antarctica/Troll * `Antarctica/Vostok` - Antarctica/Vostok * `Arctic/Longyearbyen` - Arctic/Longyearbyen * `Asia/Aden` - Asia/Aden * `Asia/Almaty` - Asia/Almaty * `Asia/Amman` - Asia/Amman * `Asia/Anadyr` - Asia/Anadyr * `Asia/Aqtau` - Asia/Aqtau * `Asia/Aqtobe` - Asia/Aqtobe * `Asia/Ashgabat` - Asia/Ashgabat * `Asia/Atyrau` - Asia/Atyrau * `Asia/Baghdad` - Asia/Baghdad * `Asia/Bahrain` - Asia/Bahrain * `Asia/Baku` - Asia/Baku * `Asia/Bangkok` - Asia/Bangkok * `Asia/Barnaul` - Asia/Barnaul * `Asia/Beirut` - Asia/Beirut * `Asia/Bishkek` - Asia/Bishkek * `Asia/Brunei` - Asia/Brunei * `Asia/Chita` - Asia/Chita * `Asia/Choibalsan` - Asia/Choibalsan * `Asia/Colombo` - Asia/Colombo * `Asia/Damascus` - Asia/Damascus * `Asia/Dhaka` - Asia/Dhaka * `Asia/Dili` - Asia/Dili * `Asia/Dubai` - Asia/Dubai * `Asia/Dushanbe` - Asia/Dushanbe * `Asia/Famagusta` - Asia/Famagusta * `Asia/Gaza` - Asia/Gaza * `Asia/Hebron` - Asia/Hebron * `Asia/Ho_Chi_Minh` - Asia/Ho\_Chi\_Minh * `Asia/Hong_Kong` - Asia/Hong\_Kong * `Asia/Hovd` - Asia/Hovd * `Asia/Irkutsk` - Asia/Irkutsk * `Asia/Jakarta` - Asia/Jakarta * `Asia/Jayapura` - Asia/Jayapura * `Asia/Jerusalem` - Asia/Jerusalem * `Asia/Kabul` - Asia/Kabul * `Asia/Kamchatka` - Asia/Kamchatka * `Asia/Karachi` - Asia/Karachi * `Asia/Kathmandu` - Asia/Kathmandu * `Asia/Khandyga` - Asia/Khandyga * `Asia/Kolkata` - Asia/Kolkata * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk * `Asia/Kuala_Lumpur` - Asia/Kuala\_Lumpur * `Asia/Kuching` - Asia/Kuching * `Asia/Kuwait` - Asia/Kuwait * `Asia/Macau` - Asia/Macau * `Asia/Magadan` - Asia/Magadan * `Asia/Makassar` - Asia/Makassar * `Asia/Manila` - Asia/Manila * `Asia/Muscat` - Asia/Muscat * `Asia/Nicosia` - Asia/Nicosia * `Asia/Novokuznetsk` - Asia/Novokuznetsk * `Asia/Novosibirsk` - Asia/Novosibirsk * `Asia/Omsk` - Asia/Omsk * `Asia/Oral` - Asia/Oral * `Asia/Phnom_Penh` - Asia/Phnom\_Penh * `Asia/Pontianak` - Asia/Pontianak * `Asia/Pyongyang` - Asia/Pyongyang * `Asia/Qatar` - Asia/Qatar * `Asia/Qostanay` - Asia/Qostanay * `Asia/Qyzylorda` - Asia/Qyzylorda * `Asia/Riyadh` - Asia/Riyadh * `Asia/Sakhalin` - Asia/Sakhalin * `Asia/Samarkand` - Asia/Samarkand * `Asia/Seoul` - Asia/Seoul * `Asia/Shanghai` - Asia/Shanghai * `Asia/Singapore` - Asia/Singapore * `Asia/Srednekolymsk` - Asia/Srednekolymsk * `Asia/Taipei` - Asia/Taipei * `Asia/Tashkent` - Asia/Tashkent * `Asia/Tbilisi` - Asia/Tbilisi * `Asia/Tehran` - Asia/Tehran * `Asia/Thimphu` - Asia/Thimphu * `Asia/Tokyo` - Asia/Tokyo * `Asia/Tomsk` - Asia/Tomsk * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar * `Asia/Urumqi` - Asia/Urumqi * `Asia/Ust-Nera` - Asia/Ust-Nera * `Asia/Vientiane` - Asia/Vientiane * `Asia/Vladivostok` - Asia/Vladivostok * `Asia/Yakutsk` - Asia/Yakutsk * `Asia/Yangon` - Asia/Yangon * `Asia/Yekaterinburg` - Asia/Yekaterinburg * `Asia/Yerevan` - Asia/Yerevan * `Atlantic/Azores` - Atlantic/Azores * `Atlantic/Bermuda` - Atlantic/Bermuda * `Atlantic/Canary` - Atlantic/Canary * `Atlantic/Cape_Verde` - Atlantic/Cape\_Verde * `Atlantic/Faroe` - Atlantic/Faroe * `Atlantic/Madeira` - Atlantic/Madeira * `Atlantic/Reykjavik` - Atlantic/Reykjavik * `Atlantic/South_Georgia` - Atlantic/South\_Georgia * `Atlantic/St_Helena` - Atlantic/St\_Helena * `Atlantic/Stanley` - Atlantic/Stanley * `Australia/Adelaide` - Australia/Adelaide * `Australia/Brisbane` - Australia/Brisbane * `Australia/Broken_Hill` - Australia/Broken\_Hill * `Australia/Darwin` - Australia/Darwin * `Australia/Eucla` - Australia/Eucla * `Australia/Hobart` - Australia/Hobart * `Australia/Lindeman` - Australia/Lindeman * `Australia/Lord_Howe` - Australia/Lord\_Howe * `Australia/Melbourne` - Australia/Melbourne * `Australia/Perth` - Australia/Perth * `Australia/Sydney` - Australia/Sydney * `Canada/Atlantic` - Canada/Atlantic * `Canada/Central` - Canada/Central * `Canada/Eastern` - Canada/Eastern * `Canada/Mountain` - Canada/Mountain * `Canada/Newfoundland` - Canada/Newfoundland * `Canada/Pacific` - Canada/Pacific * `Europe/Amsterdam` - Europe/Amsterdam * `Europe/Andorra` - Europe/Andorra * `Europe/Astrakhan` - Europe/Astrakhan * `Europe/Athens` - Europe/Athens * `Europe/Belgrade` - Europe/Belgrade * `Europe/Berlin` - Europe/Berlin * `Europe/Bratislava` - Europe/Bratislava * `Europe/Brussels` - Europe/Brussels * `Europe/Bucharest` - Europe/Bucharest * `Europe/Budapest` - Europe/Budapest * `Europe/Busingen` - Europe/Busingen * `Europe/Chisinau` - Europe/Chisinau * `Europe/Copenhagen` - Europe/Copenhagen * `Europe/Dublin` - Europe/Dublin * `Europe/Gibraltar` - Europe/Gibraltar * `Europe/Guernsey` - Europe/Guernsey * `Europe/Helsinki` - Europe/Helsinki * `Europe/Isle_of_Man` - Europe/Isle\_of\_Man * `Europe/Istanbul` - Europe/Istanbul * `Europe/Jersey` - Europe/Jersey * `Europe/Kaliningrad` - Europe/Kaliningrad * `Europe/Kirov` - Europe/Kirov * `Europe/Kyiv` - Europe/Kyiv * `Europe/Lisbon` - Europe/Lisbon * `Europe/Ljubljana` - Europe/Ljubljana * `Europe/London` - Europe/London * `Europe/Luxembourg` - Europe/Luxembourg * `Europe/Madrid` - Europe/Madrid * `Europe/Malta` - Europe/Malta * `Europe/Mariehamn` - Europe/Mariehamn * `Europe/Minsk` - Europe/Minsk * `Europe/Monaco` - Europe/Monaco * `Europe/Moscow` - Europe/Moscow * `Europe/Oslo` - Europe/Oslo * `Europe/Paris` - Europe/Paris * `Europe/Podgorica` - Europe/Podgorica * `Europe/Prague` - Europe/Prague * `Europe/Riga` - Europe/Riga * `Europe/Rome` - Europe/Rome * `Europe/Samara` - Europe/Samara * `Europe/San_Marino` - Europe/San\_Marino * `Europe/Sarajevo` - Europe/Sarajevo * `Europe/Saratov` - Europe/Saratov * `Europe/Simferopol` - Europe/Simferopol * `Europe/Skopje` - Europe/Skopje * `Europe/Sofia` - Europe/Sofia * `Europe/Stockholm` - Europe/Stockholm * `Europe/Tallinn` - Europe/Tallinn * `Europe/Tirane` - Europe/Tirane * `Europe/Ulyanovsk` - Europe/Ulyanovsk * `Europe/Vaduz` - Europe/Vaduz * `Europe/Vatican` - Europe/Vatican * `Europe/Vienna` - Europe/Vienna * `Europe/Vilnius` - Europe/Vilnius * `Europe/Volgograd` - Europe/Volgograd * `Europe/Warsaw` - Europe/Warsaw * `Europe/Zagreb` - Europe/Zagreb * `Europe/Zurich` - Europe/Zurich * `GMT` - GMT * `Indian/Antananarivo` - Indian/Antananarivo * `Indian/Chagos` - Indian/Chagos * `Indian/Christmas` - Indian/Christmas * `Indian/Cocos` - Indian/Cocos * `Indian/Comoro` - Indian/Comoro * `Indian/Kerguelen` - Indian/Kerguelen * `Indian/Mahe` - Indian/Mahe * `Indian/Maldives` - Indian/Maldives * `Indian/Mauritius` - Indian/Mauritius * `Indian/Mayotte` - Indian/Mayotte * `Indian/Reunion` - Indian/Reunion * `Pacific/Apia` - Pacific/Apia * `Pacific/Auckland` - Pacific/Auckland * `Pacific/Bougainville` - Pacific/Bougainville * `Pacific/Chatham` - Pacific/Chatham * `Pacific/Chuuk` - Pacific/Chuuk * `Pacific/Easter` - Pacific/Easter * `Pacific/Efate` - Pacific/Efate * `Pacific/Fakaofo` - Pacific/Fakaofo * `Pacific/Fiji` - Pacific/Fiji * `Pacific/Funafuti` - Pacific/Funafuti * `Pacific/Galapagos` - Pacific/Galapagos * `Pacific/Gambier` - Pacific/Gambier * `Pacific/Guadalcanal` - Pacific/Guadalcanal * `Pacific/Guam` - Pacific/Guam * `Pacific/Honolulu` - Pacific/Honolulu * `Pacific/Kanton` - Pacific/Kanton * `Pacific/Kiritimati` - Pacific/Kiritimati * `Pacific/Kosrae` - Pacific/Kosrae * `Pacific/Kwajalein` - Pacific/Kwajalein * `Pacific/Majuro` - Pacific/Majuro * `Pacific/Marquesas` - Pacific/Marquesas * `Pacific/Midway` - Pacific/Midway * `Pacific/Nauru` - Pacific/Nauru * `Pacific/Niue` - Pacific/Niue * `Pacific/Norfolk` - Pacific/Norfolk * `Pacific/Noumea` - Pacific/Noumea * `Pacific/Pago_Pago` - Pacific/Pago\_Pago * `Pacific/Palau` - Pacific/Palau * `Pacific/Pitcairn` - Pacific/Pitcairn * `Pacific/Pohnpei` - Pacific/Pohnpei * `Pacific/Port_Moresby` - Pacific/Port\_Moresby * `Pacific/Rarotonga` - Pacific/Rarotonga * `Pacific/Saipan` - Pacific/Saipan * `Pacific/Tahiti` - Pacific/Tahiti * `Pacific/Tarawa` - Pacific/Tarawa * `Pacific/Tongatapu` - Pacific/Tongatapu * `Pacific/Wake` - Pacific/Wake * `Pacific/Wallis` - Pacific/Wallis * `US/Alaska` - US/Alaska * `US/Arizona` - US/Arizona * `US/Central` - US/Central * `US/Eastern` - US/Eastern * `US/Hawaii` - US/Hawaii * `US/Mountain` - US/Mountain * `US/Pacific` - US/Pacific * `UTC` - UTC ### Scopes `projects.cycles:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", start_date: "2024-01-01T00:00:00Z", end_date: "2024-01-01T00:00:00Z", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "status": "current", "total_issues": 15, "completed_issues": 8, "cancelled_issues": 1, "started_issues": 4, "unstarted_issues": 2, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/add-cycle-work-items.html' description: >- Add work items to cycle via Plane API. HTTP request format, parameters, scopes, and example responses for add work items to cycle. --- # Add work items to cycle Assign multiple work items to a cycle. Automatically handles bulk creation and updates with activity tracking. ### Path Parameters The unique identifier of the cycle. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters List of issue IDs to add to the cycle ### Scopes `projects.cycles:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/", headers={"X-API-Key": "your-api-key"}, json={ "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issues: ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "cycle": "550e8400-e29b-41d4-a716-446655440000", "issue": "550e8400-e29b-41d4-a716-446655440000", "sub_issues_count": 3, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/transfer-cycle-work-items.html' description: >- Transfer cycle work items via Plane API. HTTP request format, parameters, scopes, and example responses for transfer cycle work items. --- # Transfer cycle work items Move incomplete work items from the current cycle to a new target cycle. Captures progress snapshot and transfers only unfinished work items. ### Path Parameters The unique identifier of the cycle. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters ID of the target cycle to transfer issues to ### Scopes `projects.cycles:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/transfer-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "new_cycle_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/transfer-issues/", headers={"X-API-Key": "your-api-key"}, json={ "new_cycle_id": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/transfer-issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ new_cycle_id: "550e8400-e29b-41d4-a716-446655440000", }), } ); const data = await response.json(); ``` ```json { "message": "Success" } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/archive-cycle.html' description: >- Archive a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for archive a cycle. --- # Archive a cycle Move a completed cycle to archived status for historical tracking. Only cycles that have ended can be archived. ### Path Parameters The unique identifier of the cycle. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.cycles:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/archive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/archive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/archive/", { method: "POST", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/cycle/list-cycles.html' description: >- List all cycles via Plane API. HTTP request format, parameters, scopes, and example responses for list all cycles. --- # List all cycles Retrieve all cycles in a project. Supports filtering by cycle status like current, upcoming, completed, or draft. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Filter cycles by status Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.cycles:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/?cursor=20:1:0&cycle_view=all" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/?cursor=20:1:0&cycle_view=all", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/?cursor=20:1:0&cycle_view=all", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "status": "current" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/get-cycle-detail.html' description: >- Retrieve a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a cycle. --- # Retrieve a cycle Retrieve details of a specific cycle by its ID. Supports cycle status filtering. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.cycles:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "status": "current", "total_issues": 15, "completed_issues": 8, "cancelled_issues": 1, "started_issues": 4, "unstarted_issues": 2, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/list-cycle-work-items.html' description: >- List all work items in a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for list all work items in a cycle. --- # List all work items in a cycle Retrieve all work items assigned to a cycle. ### Path Parameters The unique identifier of the cycle. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `projects.cycles:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "cycle": "550e8400-e29b-41d4-a716-446655440000", "issue": "550e8400-e29b-41d4-a716-446655440000", "sub_issues_count": 3, "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/list-archived-cycles.html' description: >- List all archived cycles via Plane API. HTTP request format, parameters, scopes, and example responses for list all archived cycles. --- # List all archived cycles Retrieve all cycles that have been archived in the project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `projects.cycles:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/update-cycle-detail.html' description: >- Update a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for update a cycle. --- # Update a cycle Modify an existing cycle's properties like name, description, or date range. Completed cycles can only have their sort order changed. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Start date. End date. User who owns the cycle. If not provided, defaults to the current user. External source. External id. * `Africa/Abidjan` - Africa/Abidjan * `Africa/Accra` - Africa/Accra * `Africa/Addis_Ababa` - Africa/Addis\_Ababa * `Africa/Algiers` - Africa/Algiers * `Africa/Asmara` - Africa/Asmara * `Africa/Bamako` - Africa/Bamako * `Africa/Bangui` - Africa/Bangui * `Africa/Banjul` - Africa/Banjul * `Africa/Bissau` - Africa/Bissau * `Africa/Blantyre` - Africa/Blantyre * `Africa/Brazzaville` - Africa/Brazzaville * `Africa/Bujumbura` - Africa/Bujumbura * `Africa/Cairo` - Africa/Cairo * `Africa/Casablanca` - Africa/Casablanca * `Africa/Ceuta` - Africa/Ceuta * `Africa/Conakry` - Africa/Conakry * `Africa/Dakar` - Africa/Dakar * `Africa/Dar_es_Salaam` - Africa/Dar\_es\_Salaam * `Africa/Djibouti` - Africa/Djibouti * `Africa/Douala` - Africa/Douala * `Africa/El_Aaiun` - Africa/El\_Aaiun * `Africa/Freetown` - Africa/Freetown * `Africa/Gaborone` - Africa/Gaborone * `Africa/Harare` - Africa/Harare * `Africa/Johannesburg` - Africa/Johannesburg * `Africa/Juba` - Africa/Juba * `Africa/Kampala` - Africa/Kampala * `Africa/Khartoum` - Africa/Khartoum * `Africa/Kigali` - Africa/Kigali * `Africa/Kinshasa` - Africa/Kinshasa * `Africa/Lagos` - Africa/Lagos * `Africa/Libreville` - Africa/Libreville * `Africa/Lome` - Africa/Lome * `Africa/Luanda` - Africa/Luanda * `Africa/Lubumbashi` - Africa/Lubumbashi * `Africa/Lusaka` - Africa/Lusaka * `Africa/Malabo` - Africa/Malabo * `Africa/Maputo` - Africa/Maputo * `Africa/Maseru` - Africa/Maseru * `Africa/Mbabane` - Africa/Mbabane * `Africa/Mogadishu` - Africa/Mogadishu * `Africa/Monrovia` - Africa/Monrovia * `Africa/Nairobi` - Africa/Nairobi * `Africa/Ndjamena` - Africa/Ndjamena * `Africa/Niamey` - Africa/Niamey * `Africa/Nouakchott` - Africa/Nouakchott * `Africa/Ouagadougou` - Africa/Ouagadougou * `Africa/Porto-Novo` - Africa/Porto-Novo * `Africa/Sao_Tome` - Africa/Sao\_Tome * `Africa/Tripoli` - Africa/Tripoli * `Africa/Tunis` - Africa/Tunis * `Africa/Windhoek` - Africa/Windhoek * `America/Adak` - America/Adak * `America/Anchorage` - America/Anchorage * `America/Anguilla` - America/Anguilla * `America/Antigua` - America/Antigua * `America/Araguaina` - America/Araguaina * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos\_Aires * `America/Argentina/Catamarca` - America/Argentina/Catamarca * `America/Argentina/Cordoba` - America/Argentina/Cordoba * `America/Argentina/Jujuy` - America/Argentina/Jujuy * `America/Argentina/La_Rioja` - America/Argentina/La\_Rioja * `America/Argentina/Mendoza` - America/Argentina/Mendoza * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio\_Gallegos * `America/Argentina/Salta` - America/Argentina/Salta * `America/Argentina/San_Juan` - America/Argentina/San\_Juan * `America/Argentina/San_Luis` - America/Argentina/San\_Luis * `America/Argentina/Tucuman` - America/Argentina/Tucuman * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia * `America/Aruba` - America/Aruba * `America/Asuncion` - America/Asuncion * `America/Atikokan` - America/Atikokan * `America/Bahia` - America/Bahia * `America/Bahia_Banderas` - America/Bahia\_Banderas * `America/Barbados` - America/Barbados * `America/Belem` - America/Belem * `America/Belize` - America/Belize * `America/Blanc-Sablon` - America/Blanc-Sablon * `America/Boa_Vista` - America/Boa\_Vista * `America/Bogota` - America/Bogota * `America/Boise` - America/Boise * `America/Cambridge_Bay` - America/Cambridge\_Bay * `America/Campo_Grande` - America/Campo\_Grande * `America/Cancun` - America/Cancun * `America/Caracas` - America/Caracas * `America/Cayenne` - America/Cayenne * `America/Cayman` - America/Cayman * `America/Chicago` - America/Chicago * `America/Chihuahua` - America/Chihuahua * `America/Ciudad_Juarez` - America/Ciudad\_Juarez * `America/Costa_Rica` - America/Costa\_Rica * `America/Creston` - America/Creston * `America/Cuiaba` - America/Cuiaba * `America/Curacao` - America/Curacao * `America/Danmarkshavn` - America/Danmarkshavn * `America/Dawson` - America/Dawson * `America/Dawson_Creek` - America/Dawson\_Creek * `America/Denver` - America/Denver * `America/Detroit` - America/Detroit * `America/Dominica` - America/Dominica * `America/Edmonton` - America/Edmonton * `America/Eirunepe` - America/Eirunepe * `America/El_Salvador` - America/El\_Salvador * `America/Fort_Nelson` - America/Fort\_Nelson * `America/Fortaleza` - America/Fortaleza * `America/Glace_Bay` - America/Glace\_Bay * `America/Goose_Bay` - America/Goose\_Bay * `America/Grand_Turk` - America/Grand\_Turk * `America/Grenada` - America/Grenada * `America/Guadeloupe` - America/Guadeloupe * `America/Guatemala` - America/Guatemala * `America/Guayaquil` - America/Guayaquil * `America/Guyana` - America/Guyana * `America/Halifax` - America/Halifax * `America/Havana` - America/Havana * `America/Hermosillo` - America/Hermosillo * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis * `America/Indiana/Knox` - America/Indiana/Knox * `America/Indiana/Marengo` - America/Indiana/Marengo * `America/Indiana/Petersburg` - America/Indiana/Petersburg * `America/Indiana/Tell_City` - America/Indiana/Tell\_City * `America/Indiana/Vevay` - America/Indiana/Vevay * `America/Indiana/Vincennes` - America/Indiana/Vincennes * `America/Indiana/Winamac` - America/Indiana/Winamac * `America/Inuvik` - America/Inuvik * `America/Iqaluit` - America/Iqaluit * `America/Jamaica` - America/Jamaica * `America/Juneau` - America/Juneau * `America/Kentucky/Louisville` - America/Kentucky/Louisville * `America/Kentucky/Monticello` - America/Kentucky/Monticello * `America/Kralendijk` - America/Kralendijk * `America/La_Paz` - America/La\_Paz * `America/Lima` - America/Lima * `America/Los_Angeles` - America/Los\_Angeles * `America/Lower_Princes` - America/Lower\_Princes * `America/Maceio` - America/Maceio * `America/Managua` - America/Managua * `America/Manaus` - America/Manaus * `America/Marigot` - America/Marigot * `America/Martinique` - America/Martinique * `America/Matamoros` - America/Matamoros * `America/Mazatlan` - America/Mazatlan * `America/Menominee` - America/Menominee * `America/Merida` - America/Merida * `America/Metlakatla` - America/Metlakatla * `America/Mexico_City` - America/Mexico\_City * `America/Miquelon` - America/Miquelon * `America/Moncton` - America/Moncton * `America/Monterrey` - America/Monterrey * `America/Montevideo` - America/Montevideo * `America/Montserrat` - America/Montserrat * `America/Nassau` - America/Nassau * `America/New_York` - America/New\_York * `America/Nome` - America/Nome * `America/Noronha` - America/Noronha * `America/North_Dakota/Beulah` - America/North\_Dakota/Beulah * `America/North_Dakota/Center` - America/North\_Dakota/Center * `America/North_Dakota/New_Salem` - America/North\_Dakota/New\_Salem * `America/Nuuk` - America/Nuuk * `America/Ojinaga` - America/Ojinaga * `America/Panama` - America/Panama * `America/Paramaribo` - America/Paramaribo * `America/Phoenix` - America/Phoenix * `America/Port-au-Prince` - America/Port-au-Prince * `America/Port_of_Spain` - America/Port\_of\_Spain * `America/Porto_Velho` - America/Porto\_Velho * `America/Puerto_Rico` - America/Puerto\_Rico * `America/Punta_Arenas` - America/Punta\_Arenas * `America/Rankin_Inlet` - America/Rankin\_Inlet * `America/Recife` - America/Recife * `America/Regina` - America/Regina * `America/Resolute` - America/Resolute * `America/Rio_Branco` - America/Rio\_Branco * `America/Santarem` - America/Santarem * `America/Santiago` - America/Santiago * `America/Santo_Domingo` - America/Santo\_Domingo * `America/Sao_Paulo` - America/Sao\_Paulo * `America/Scoresbysund` - America/Scoresbysund * `America/Sitka` - America/Sitka * `America/St_Barthelemy` - America/St\_Barthelemy * `America/St_Johns` - America/St\_Johns * `America/St_Kitts` - America/St\_Kitts * `America/St_Lucia` - America/St\_Lucia * `America/St_Thomas` - America/St\_Thomas * `America/St_Vincent` - America/St\_Vincent * `America/Swift_Current` - America/Swift\_Current * `America/Tegucigalpa` - America/Tegucigalpa * `America/Thule` - America/Thule * `America/Tijuana` - America/Tijuana * `America/Toronto` - America/Toronto * `America/Tortola` - America/Tortola * `America/Vancouver` - America/Vancouver * `America/Whitehorse` - America/Whitehorse * `America/Winnipeg` - America/Winnipeg * `America/Yakutat` - America/Yakutat * `Antarctica/Casey` - Antarctica/Casey * `Antarctica/Davis` - Antarctica/Davis * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville * `Antarctica/Macquarie` - Antarctica/Macquarie * `Antarctica/Mawson` - Antarctica/Mawson * `Antarctica/McMurdo` - Antarctica/McMurdo * `Antarctica/Palmer` - Antarctica/Palmer * `Antarctica/Rothera` - Antarctica/Rothera * `Antarctica/Syowa` - Antarctica/Syowa * `Antarctica/Troll` - Antarctica/Troll * `Antarctica/Vostok` - Antarctica/Vostok * `Arctic/Longyearbyen` - Arctic/Longyearbyen * `Asia/Aden` - Asia/Aden * `Asia/Almaty` - Asia/Almaty * `Asia/Amman` - Asia/Amman * `Asia/Anadyr` - Asia/Anadyr * `Asia/Aqtau` - Asia/Aqtau * `Asia/Aqtobe` - Asia/Aqtobe * `Asia/Ashgabat` - Asia/Ashgabat * `Asia/Atyrau` - Asia/Atyrau * `Asia/Baghdad` - Asia/Baghdad * `Asia/Bahrain` - Asia/Bahrain * `Asia/Baku` - Asia/Baku * `Asia/Bangkok` - Asia/Bangkok * `Asia/Barnaul` - Asia/Barnaul * `Asia/Beirut` - Asia/Beirut * `Asia/Bishkek` - Asia/Bishkek * `Asia/Brunei` - Asia/Brunei * `Asia/Chita` - Asia/Chita * `Asia/Choibalsan` - Asia/Choibalsan * `Asia/Colombo` - Asia/Colombo * `Asia/Damascus` - Asia/Damascus * `Asia/Dhaka` - Asia/Dhaka * `Asia/Dili` - Asia/Dili * `Asia/Dubai` - Asia/Dubai * `Asia/Dushanbe` - Asia/Dushanbe * `Asia/Famagusta` - Asia/Famagusta * `Asia/Gaza` - Asia/Gaza * `Asia/Hebron` - Asia/Hebron * `Asia/Ho_Chi_Minh` - Asia/Ho\_Chi\_Minh * `Asia/Hong_Kong` - Asia/Hong\_Kong * `Asia/Hovd` - Asia/Hovd * `Asia/Irkutsk` - Asia/Irkutsk * `Asia/Jakarta` - Asia/Jakarta * `Asia/Jayapura` - Asia/Jayapura * `Asia/Jerusalem` - Asia/Jerusalem * `Asia/Kabul` - Asia/Kabul * `Asia/Kamchatka` - Asia/Kamchatka * `Asia/Karachi` - Asia/Karachi * `Asia/Kathmandu` - Asia/Kathmandu * `Asia/Khandyga` - Asia/Khandyga * `Asia/Kolkata` - Asia/Kolkata * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk * `Asia/Kuala_Lumpur` - Asia/Kuala\_Lumpur * `Asia/Kuching` - Asia/Kuching * `Asia/Kuwait` - Asia/Kuwait * `Asia/Macau` - Asia/Macau * `Asia/Magadan` - Asia/Magadan * `Asia/Makassar` - Asia/Makassar * `Asia/Manila` - Asia/Manila * `Asia/Muscat` - Asia/Muscat * `Asia/Nicosia` - Asia/Nicosia * `Asia/Novokuznetsk` - Asia/Novokuznetsk * `Asia/Novosibirsk` - Asia/Novosibirsk * `Asia/Omsk` - Asia/Omsk * `Asia/Oral` - Asia/Oral * `Asia/Phnom_Penh` - Asia/Phnom\_Penh * `Asia/Pontianak` - Asia/Pontianak * `Asia/Pyongyang` - Asia/Pyongyang * `Asia/Qatar` - Asia/Qatar * `Asia/Qostanay` - Asia/Qostanay * `Asia/Qyzylorda` - Asia/Qyzylorda * `Asia/Riyadh` - Asia/Riyadh * `Asia/Sakhalin` - Asia/Sakhalin * `Asia/Samarkand` - Asia/Samarkand * `Asia/Seoul` - Asia/Seoul * `Asia/Shanghai` - Asia/Shanghai * `Asia/Singapore` - Asia/Singapore * `Asia/Srednekolymsk` - Asia/Srednekolymsk * `Asia/Taipei` - Asia/Taipei * `Asia/Tashkent` - Asia/Tashkent * `Asia/Tbilisi` - Asia/Tbilisi * `Asia/Tehran` - Asia/Tehran * `Asia/Thimphu` - Asia/Thimphu * `Asia/Tokyo` - Asia/Tokyo * `Asia/Tomsk` - Asia/Tomsk * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar * `Asia/Urumqi` - Asia/Urumqi * `Asia/Ust-Nera` - Asia/Ust-Nera * `Asia/Vientiane` - Asia/Vientiane * `Asia/Vladivostok` - Asia/Vladivostok * `Asia/Yakutsk` - Asia/Yakutsk * `Asia/Yangon` - Asia/Yangon * `Asia/Yekaterinburg` - Asia/Yekaterinburg * `Asia/Yerevan` - Asia/Yerevan * `Atlantic/Azores` - Atlantic/Azores * `Atlantic/Bermuda` - Atlantic/Bermuda * `Atlantic/Canary` - Atlantic/Canary * `Atlantic/Cape_Verde` - Atlantic/Cape\_Verde * `Atlantic/Faroe` - Atlantic/Faroe * `Atlantic/Madeira` - Atlantic/Madeira * `Atlantic/Reykjavik` - Atlantic/Reykjavik * `Atlantic/South_Georgia` - Atlantic/South\_Georgia * `Atlantic/St_Helena` - Atlantic/St\_Helena * `Atlantic/Stanley` - Atlantic/Stanley * `Australia/Adelaide` - Australia/Adelaide * `Australia/Brisbane` - Australia/Brisbane * `Australia/Broken_Hill` - Australia/Broken\_Hill * `Australia/Darwin` - Australia/Darwin * `Australia/Eucla` - Australia/Eucla * `Australia/Hobart` - Australia/Hobart * `Australia/Lindeman` - Australia/Lindeman * `Australia/Lord_Howe` - Australia/Lord\_Howe * `Australia/Melbourne` - Australia/Melbourne * `Australia/Perth` - Australia/Perth * `Australia/Sydney` - Australia/Sydney * `Canada/Atlantic` - Canada/Atlantic * `Canada/Central` - Canada/Central * `Canada/Eastern` - Canada/Eastern * `Canada/Mountain` - Canada/Mountain * `Canada/Newfoundland` - Canada/Newfoundland * `Canada/Pacific` - Canada/Pacific * `Europe/Amsterdam` - Europe/Amsterdam * `Europe/Andorra` - Europe/Andorra * `Europe/Astrakhan` - Europe/Astrakhan * `Europe/Athens` - Europe/Athens * `Europe/Belgrade` - Europe/Belgrade * `Europe/Berlin` - Europe/Berlin * `Europe/Bratislava` - Europe/Bratislava * `Europe/Brussels` - Europe/Brussels * `Europe/Bucharest` - Europe/Bucharest * `Europe/Budapest` - Europe/Budapest * `Europe/Busingen` - Europe/Busingen * `Europe/Chisinau` - Europe/Chisinau * `Europe/Copenhagen` - Europe/Copenhagen * `Europe/Dublin` - Europe/Dublin * `Europe/Gibraltar` - Europe/Gibraltar * `Europe/Guernsey` - Europe/Guernsey * `Europe/Helsinki` - Europe/Helsinki * `Europe/Isle_of_Man` - Europe/Isle\_of\_Man * `Europe/Istanbul` - Europe/Istanbul * `Europe/Jersey` - Europe/Jersey * `Europe/Kaliningrad` - Europe/Kaliningrad * `Europe/Kirov` - Europe/Kirov * `Europe/Kyiv` - Europe/Kyiv * `Europe/Lisbon` - Europe/Lisbon * `Europe/Ljubljana` - Europe/Ljubljana * `Europe/London` - Europe/London * `Europe/Luxembourg` - Europe/Luxembourg * `Europe/Madrid` - Europe/Madrid * `Europe/Malta` - Europe/Malta * `Europe/Mariehamn` - Europe/Mariehamn * `Europe/Minsk` - Europe/Minsk * `Europe/Monaco` - Europe/Monaco * `Europe/Moscow` - Europe/Moscow * `Europe/Oslo` - Europe/Oslo * `Europe/Paris` - Europe/Paris * `Europe/Podgorica` - Europe/Podgorica * `Europe/Prague` - Europe/Prague * `Europe/Riga` - Europe/Riga * `Europe/Rome` - Europe/Rome * `Europe/Samara` - Europe/Samara * `Europe/San_Marino` - Europe/San\_Marino * `Europe/Sarajevo` - Europe/Sarajevo * `Europe/Saratov` - Europe/Saratov * `Europe/Simferopol` - Europe/Simferopol * `Europe/Skopje` - Europe/Skopje * `Europe/Sofia` - Europe/Sofia * `Europe/Stockholm` - Europe/Stockholm * `Europe/Tallinn` - Europe/Tallinn * `Europe/Tirane` - Europe/Tirane * `Europe/Ulyanovsk` - Europe/Ulyanovsk * `Europe/Vaduz` - Europe/Vaduz * `Europe/Vatican` - Europe/Vatican * `Europe/Vienna` - Europe/Vienna * `Europe/Vilnius` - Europe/Vilnius * `Europe/Volgograd` - Europe/Volgograd * `Europe/Warsaw` - Europe/Warsaw * `Europe/Zagreb` - Europe/Zagreb * `Europe/Zurich` - Europe/Zurich * `GMT` - GMT * `Indian/Antananarivo` - Indian/Antananarivo * `Indian/Chagos` - Indian/Chagos * `Indian/Christmas` - Indian/Christmas * `Indian/Cocos` - Indian/Cocos * `Indian/Comoro` - Indian/Comoro * `Indian/Kerguelen` - Indian/Kerguelen * `Indian/Mahe` - Indian/Mahe * `Indian/Maldives` - Indian/Maldives * `Indian/Mauritius` - Indian/Mauritius * `Indian/Mayotte` - Indian/Mayotte * `Indian/Reunion` - Indian/Reunion * `Pacific/Apia` - Pacific/Apia * `Pacific/Auckland` - Pacific/Auckland * `Pacific/Bougainville` - Pacific/Bougainville * `Pacific/Chatham` - Pacific/Chatham * `Pacific/Chuuk` - Pacific/Chuuk * `Pacific/Easter` - Pacific/Easter * `Pacific/Efate` - Pacific/Efate * `Pacific/Fakaofo` - Pacific/Fakaofo * `Pacific/Fiji` - Pacific/Fiji * `Pacific/Funafuti` - Pacific/Funafuti * `Pacific/Galapagos` - Pacific/Galapagos * `Pacific/Gambier` - Pacific/Gambier * `Pacific/Guadalcanal` - Pacific/Guadalcanal * `Pacific/Guam` - Pacific/Guam * `Pacific/Honolulu` - Pacific/Honolulu * `Pacific/Kanton` - Pacific/Kanton * `Pacific/Kiritimati` - Pacific/Kiritimati * `Pacific/Kosrae` - Pacific/Kosrae * `Pacific/Kwajalein` - Pacific/Kwajalein * `Pacific/Majuro` - Pacific/Majuro * `Pacific/Marquesas` - Pacific/Marquesas * `Pacific/Midway` - Pacific/Midway * `Pacific/Nauru` - Pacific/Nauru * `Pacific/Niue` - Pacific/Niue * `Pacific/Norfolk` - Pacific/Norfolk * `Pacific/Noumea` - Pacific/Noumea * `Pacific/Pago_Pago` - Pacific/Pago\_Pago * `Pacific/Palau` - Pacific/Palau * `Pacific/Pitcairn` - Pacific/Pitcairn * `Pacific/Pohnpei` - Pacific/Pohnpei * `Pacific/Port_Moresby` - Pacific/Port\_Moresby * `Pacific/Rarotonga` - Pacific/Rarotonga * `Pacific/Saipan` - Pacific/Saipan * `Pacific/Tahiti` - Pacific/Tahiti * `Pacific/Tarawa` - Pacific/Tarawa * `Pacific/Tongatapu` - Pacific/Tongatapu * `Pacific/Wake` - Pacific/Wake * `Pacific/Wallis` - Pacific/Wallis * `US/Alaska` - US/Alaska * `US/Arizona` - US/Arizona * `US/Central` - US/Central * `US/Eastern` - US/Eastern * `US/Hawaii` - US/Hawaii * `US/Mountain` - US/Mountain * `US/Pacific` - US/Pacific * `UTC` - UTC ### Scopes `projects.cycles:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", start_date: "2024-01-01T00:00:00Z", end_date: "2024-01-01T00:00:00Z", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "status": "current", "total_issues": 15, "completed_issues": 8, "cancelled_issues": 1, "started_issues": 4, "unstarted_issues": 2, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/cycle/unarchive-cycle.html' description: >- Restore a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for restore a cycle. --- # Restore a cycle Restore an archived cycle to active status, making it available for regular use. ### Path Parameters The unique identifier of the cycle. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.cycles:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/cycle-uuid/unarchive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/cycle-uuid/unarchive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-cycles/cycle-uuid/unarchive/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/cycle/remove-cycle-work-item.html' description: >- Remove work item from cycle via Plane API. HTTP request format, parameters, scopes, and example responses for remove work item from cycle. --- # Remove work item from cycle Remove a work item from a cycle while keeping the work item in the project. ### Path Parameters The unique identifier of the cycle. The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.cycles:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/cycle-uuid/cycle-issues/work-item-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/cycle/delete-cycle.html' description: >- Delete a cycle via Plane API. HTTP request format, parameters, scopes, and example responses for delete a cycle. --- # Delete a cycle Permanently remove a cycle and all its associated issue relationships ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.cycles:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/cycles/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/module/overview.html' description: >- Plane Module API overview. Learn about endpoints, request/response format, and how to work with module via REST API. --- # Overview Modules are smaller, focused projects that help you group and organize issues within a specific time frame. They allow you to break down your work into manageable chunks and track progress towards specific goals or objectives. [Learn more about Modules](https://docs.plane.so/core-concepts/modules) ## The Module Object ### Attributes * `name` string(required) Name of the module * `description` string Description of the module * `description_html` string Description in HTML format * `start_date` date Start date of the module * `target_date` date Estimated date to complete the module * `created_at` *timestamp* The timestamp of the time when the project was created * `updated_at` *timestamp* The timestamp of the time when the project was last updated * `status` It describes the status of the module The status can be * backlog * planned * in-progress * paused * completed * cancelled * `view_props` It store the filters and the display properties selected by the user to visualize the issues in the module * `sort_order` It gives the position of the module at which it should be displayed * `created_by` , `updated_by` *uuid* These values are auto saved and represent the id of the user that created or updated the module * `Project` uuid It contains projects uuid which is automatically saved. * `Workspace` uuid It contains workspace uuid which is automatically saved * `lead` uuid Lead of the module * `members` string\[] List of member user IDs assigned to the module * `archived_at` *timestamp* The timestamp when the module was archived (if archived) * `logo_props` Logo properties for the module * `description_text` Description in plain text format ```json { "id": "b69b19ae-261f-428c-899f-dd58efaa36c0", "created_at": "2023-11-19T11:48:21.130161Z", "updated_at": "2023-11-19T11:48:21.130168Z", "name": "module stesting", "description": "", "description_text": null, "description_html": null, "start_date": null, "target_date": null, "status": "planned", "view_props": {}, "sort_order": 55535.0, "created_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "updated_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "project": "6436c4ae-fba7-45dc-ad4a-5440e17cb1b2", "workspace": "c467e125-59e3-44ec-b5ee-f9c1e138c611", "lead": null, "members": [] } ``` --- --- url: 'https://developers.plane.so/api-reference/module/add-module.html' description: >- Create a module via Plane API. HTTP request format, parameters, scopes, and example responses for create a module. --- # Create a module Create a new project module with specified name, description, and timeline. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Start date. Target date. * `backlog` - Backlog * `planned` - Planned * `in-progress` - In Progress * `paused` - Paused * `completed` - Completed * `cancelled` - Cancelled Lead. Members. External source. External id. ### Scopes `projects.modules:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "end_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "end_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", start_date: "2024-01-01", end_date: "2024-01-01", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "target_date": "2024-01-01", "status": "in-progress", "total_issues": 12, "completed_issues": 5, "cancelled_issues": 0, "started_issues": 4, "unstarted_issues": 3, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/module/add-module-work-items.html' description: >- Add work items to module via Plane API. HTTP request format, parameters, scopes, and example responses for add work items to module. --- # Add work items to module Assign multiple work items to a module or move them from another module. Automatically handles bulk creation and updates with activity tracking. ### Path Parameters The unique identifier of the module. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters List of issue IDs to add to the module ### Scopes `projects.modules:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/", headers={"X-API-Key": "your-api-key"}, json={ "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issues: ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "module": "550e8400-e29b-41d4-a716-446655440000", "issue": "550e8400-e29b-41d4-a716-446655440000", "sub_issues_count": 2, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/module/archive-module.html' description: >- Archive a module via Plane API. HTTP request format, parameters, scopes, and example responses for archive a module. --- # Archive a module Move a module to archived status for historical tracking. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.modules:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/archive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/archive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/archive/", { method: "POST", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/module/list-modules.html' description: >- List all modules via Plane API. HTTP request format, parameters, scopes, and example responses for list all modules. --- # List all modules Retrieve all modules in a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.modules:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "target_date": "2024-01-01", "status": "in_progress" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/module/get-module-detail.html' description: >- Retrieve a module via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a module. --- # Retrieve a module Retrieve details of a specific module. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.modules:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "target_date": "2024-01-01", "status": "in-progress", "total_issues": 12, "completed_issues": 5, "cancelled_issues": 0, "started_issues": 4, "unstarted_issues": 3, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/module/list-module-work-items.html' description: >- List all work items in a module via Plane API. HTTP request format, parameters, scopes, and example responses for list all work items in a module. --- # List all work items in a module Retrieve all work items assigned to a module with detailed information. ### Path Parameters The unique identifier of the module. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.modules:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/module/list-archived-modules.html' description: >- List all archived modules via Plane API. HTTP request format, parameters, scopes, and example responses for list all archived modules. --- # List all archived modules Retrieve all modules that have been archived in the project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.modules:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/module/update-module-detail.html' description: >- Update module details via Plane API. HTTP request format, parameters, scopes, and example responses for update module details. --- # Update module details Modify an existing module's properties like name, description, status, or timeline. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Start date. Target date. * `backlog` - Backlog * `planned` - Planned * `in-progress` - In Progress * `paused` - Paused * `completed` - Completed * `cancelled` - Cancelled Lead. Members. External source. External id. ### Scopes `projects.modules:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "end_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "end_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", start_date: "2024-01-01", end_date: "2024-01-01", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "start_date": "2024-01-01", "target_date": "2024-01-01", "status": "in-progress", "total_issues": 12, "completed_issues": 5, "cancelled_issues": 0, "started_issues": 4, "unstarted_issues": 3, "backlog_issues": 0, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/module/unarchive-module.html' description: >- Restore a module via Plane API. HTTP request format, parameters, scopes, and example responses for restore a module. --- # Restore a module Restore an archived module to active status, making it available for regular use. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.modules:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/resource-id-uuid/unarchive/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/resource-id-uuid/unarchive/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/archived-modules/resource-id-uuid/unarchive/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/module/remove-module-work-item.html' description: >- Remove work item from module via Plane API. HTTP request format, parameters, scopes, and example responses for remove work item from module. --- # Remove work item from module Remove a work item from a module while keeping the work item in the project. ### Path Parameters The unique identifier of the work item. The unique identifier of the module. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.modules:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/module-uuid/module-issues/work-item-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/module/delete-module.html' description: >- Delete a module via Plane API. HTTP request format, parameters, scopes, and example responses for delete a module. --- # Delete a module Permanently remove a module and all its associated issue relationships. ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.modules:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/modules/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/page/overview.html' description: >- Plane Page API overview. Learn about endpoints, request/response format, and how to work with page via REST API. --- # Overview Pages allow you to create and manage documentation at both workspace and project levels. Workspace pages are accessible across all projects, while project pages are specific to individual projects. **Documentation**: [Wiki](https://docs.plane.so/core-concepts/pages/wiki), [Pages](https://docs.plane.so/core-concepts/pages/overview) ## The Pages Object ### Attributes * `id` *uuid* Unique identifier for the page * `name` *string* Name of the page * `description_html` *string* HTML description/content of the page * `created_at` *timestamp* The timestamp when the page was created * `updated_at` *timestamp* The timestamp when the page was last updated * `created_by` *uuid* ID of the user who created the page * `updated_by` *uuid* ID of the user who last updated the page ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "name": "Getting Started", "description_html": "

Welcome

This is a getting started guide.

", "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" } ``` --- --- url: 'https://developers.plane.so/api-reference/page/list-workspace-pages.html' description: >- List workspace wiki pages via Plane API. HTTP request format, parameters, scopes, and example responses for listing workspace wiki pages. --- # List workspace wiki pages List all wiki pages in a workspace with optional filtering and search. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Filter pages by scope. Defaults to `all`. * `all` — all pages the user has access to * `public` — pages with public access, excluding archived * `private` — pages owned by the user and not shared, excluding archived * `shared` — private pages explicitly shared with the user * `archived` — pages that have been archived Case-insensitive search on page title. Number of results per page. Defaults to `20`, maximum `100`. Pagination cursor for getting the next or previous set of results. ### Scopes `wiki.pages:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/pages/?type=public&search=welcome&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/pages/", headers={"X-API-Key": "your-api-key"}, params={"type": "public", "search": "welcome", "per_page": 20}, ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/pages/?type=public&search=welcome&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": null, "sub_grouped_by": null, "total_count": 4, "next_cursor": "20:1:0", "prev_cursor": "20:-1:1", "next_page_results": false, "prev_page_results": false, "count": 4, "total_pages": 1, "total_results": 4, "extra_stats": null, "results": [ { "id": "b3478c56-31f6-4f7e-b445-8392a4b26621", "name": "welcome 3 b", "owned_by": "5b0af4aa-e310-408a-a480-868429af5701", "access": 0, "is_locked": false, "archived_at": null, "workspace": "8725ddfa-c181-49f6-9173-97b8d0b7d599", "created_at": "2026-04-01T15:41:19.062280Z", "updated_at": "2026-04-07T19:30:39.274060Z", "logo_props": {}, "parent_id": "a2819c8b-f7ac-4cbd-b971-682726c4f8cc" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/page/add-workspace-page.html' description: >- Create a wiki page via Plane API. HTTP request format, parameters, scopes, and example responses for create a wiki page. --- # Create a wiki page Create a workspace page ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. * `0` - Public * `1` - Private Color. Is locked. Archived at. View props. Logo props. External id. External source. Description html. ### Scopes `wiki.pages:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/pages/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "view_props": "example-value", "logo_props": "example-value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "description_html": "

Example content

" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/pages/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "view_props": "example-value", "logo_props": "example-value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "description_html": "

Example content

" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/pages/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", access: 0, color: "Example Name", is_locked: true, archived_at: "2024-01-01", view_props: "example-value", logo_props: "example-value", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", description_html: "

Example content

", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "description_html": "

Example content

", "owned_by": "550e8400-e29b-41d4-a716-446655440000", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: 'https://developers.plane.so/api-reference/page/list-project-pages.html' description: >- List project pages via Plane API. HTTP request format, parameters, scopes, and example responses for listing project pages. --- # List project pages List all pages in a project with optional filtering and search. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the project. ### Query Parameters Filter pages by scope. Defaults to `all`. * `all` — all pages the user has access to * `public` — pages with public access, excluding archived * `private` — pages owned by the user and not shared, excluding archived * `shared` — private pages explicitly shared with the user * `archived` — pages that have been archived Case-insensitive search on page title. Number of results per page. Defaults to `20`, maximum `100`. Pagination cursor for getting the next or previous set of results. ### Scopes `projects.pages:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-id/pages/?type=public&search=welcome&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-id/pages/", headers={"X-API-Key": "your-api-key"}, params={"type": "public", "search": "welcome", "per_page": 20}, ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-id/pages/?type=public&search=welcome&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": null, "sub_grouped_by": null, "total_count": 4, "next_cursor": "20:1:0", "prev_cursor": "20:-1:1", "next_page_results": false, "prev_page_results": false, "count": 4, "total_pages": 1, "total_results": 4, "extra_stats": null, "results": [ { "id": "b3478c56-31f6-4f7e-b445-8392a4b26621", "name": "welcome 3 b", "owned_by": "5b0af4aa-e310-408a-a480-868429af5701", "access": 0, "is_locked": false, "archived_at": null, "workspace": "8725ddfa-c181-49f6-9173-97b8d0b7d599", "created_at": "2026-04-01T15:41:19.062280Z", "updated_at": "2026-04-07T19:30:39.274060Z", "logo_props": {}, "parent_id": "a2819c8b-f7ac-4cbd-b971-682726c4f8cc" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/page/add-project-page.html' description: >- Create a project page via Plane API. HTTP request format, parameters, scopes, and example responses for create a project page. --- # Create a project page Create a project page ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. * `0` - Public * `1` - Private Color. Is locked. Archived at. View props. Logo props. External id. External source. Description html. ### Scopes `projects.pages:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "view_props": "example-value", "logo_props": "example-value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "description_html": "

Example content

" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "view_props": "example-value", "logo_props": "example-value", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "description_html": "

Example content

" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", access: 0, color: "Example Name", is_locked: true, archived_at: "2024-01-01", view_props: "example-value", logo_props: "example-value", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", description_html: "

Example content

", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "description_html": "

Example content

", "owned_by": "550e8400-e29b-41d4-a716-446655440000", "access": 0, "color": "Example Name", "is_locked": true, "archived_at": "2024-01-01", "workspace": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: 'https://developers.plane.so/api-reference/page/get-workspace-page.html' description: >- Retrieve a wiki page via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a wiki page. --- # Retrieve a wiki page Get a workspace page by ID ### Path Parameters The unique identifier of the page. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `wiki.pages:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/pages/page-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/pages/page-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/pages/page-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/page/get-project-page.html' description: >- Retrieve a project page via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a project page. --- # Retrieve a project page Get a project page by ID ### Path Parameters The unique identifier of the page. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.pages:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/page-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/page-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/pages/page-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/intake-issue/overview.html' description: >- Plane Intake-Issue API overview. Learn about endpoints, request/response format, and how to work with intake-issue via REST API. --- # Overview Intake allows guests to create work items that admins and members can review and move into a project. [Learn more about Intake](https://docs.plane.so/core-concepts/intake) ## Enable Intake To enable the Intake feature, the user can hit a PATCH request on the project api with the body as ``` { intake_view:true, } ``` To create an Intake work item, the payload should be sent in the below format ```json { "issue": { "name": "Snoozed task 2", "priority": "high" } } ``` ### The Intake Object **Attribute** * `created_at` *timestamp* The timestamp of the time when the project was created * `updated_at` *timestamp* The timestamp of the time when the project was last updated * `status` the status of the work item can be in above mentioned status * -2 - Pending * -1 - Rejected * 0 - Snoozed * 1 - Accepted * 2 - Duplicate * `snoozed_till` The time untill the work item is snoozed. * `source` The source describes the type of intake from * `created_by` , `updated_by` *uuid* These values are auto saved and represent the id of the user that created or updated the module * `Project` uuid It contains projects uuid which is automatically saved. * `Workspace` uuid It contains workspace uuid which is automatically saved. * `inbox` intake id of the work item * `issue` work item id of the work item * `duplicate_to` Id of the work item of which the current work item is duplicate of. ```json { "id": "0de4d6d1-fdc7-4849-8080-dc379ab210e3", "pending_issue_count": 0, "created_at": "2023-11-21T07:32:26.072634Z", "updated_at": "2023-11-21T07:32:26.072648Z", "name": "a dummy project with Intake", "description": "", "is_default": true, "view_props": {}, "created_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "updated_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "project": "6436c4ae-fba7-45dc-ad4a-5440e17cb1b2", "workspace": "c467e125-59e3-44ec-b5ee-f9c1e138c611" } ``` --- --- url: 'https://developers.plane.so/api-reference/intake-issue/add-intake-issue.html' description: >- Create an intake work item via Plane API. HTTP request format, parameters, scopes, and example responses for create an intake work item. --- # Create an intake work item Submit a new work item to the project's intake queue for review and triage. Automatically creates the work item with default triage state and tracks activity. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Issue data for the intake issue ### Scopes `projects.intakes:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issue": { "name": "Example Name", "description": "Example description", "priority": "medium" } }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/", headers={"X-API-Key": "your-api-key"}, json={ "issue": { "name": "Example Name", "description": "Example description", "priority": "medium" } } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issue: { name: "Example Name", description: "Example description", priority: "medium", }, }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "status": 0, "source": "in_app", "issue": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "priority": "medium", "sequence_id": 124 }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/intake-issue/list-intake-issues.html' description: >- List all intake work items via Plane API. HTTP request format, parameters, scopes, and example responses for list all intake work items. --- # List all intake work items Retrieve all work items in the project's intake queue. Returns paginated results when listing all intake work items. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Number of results per page (default: 20, max: 100) ### Scopes `projects.intakes:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/intake-issue/get-intake-issue-detail.html description: >- Retrieve an intake work item via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve an intake work item. --- # Retrieve an intake work item Retrieve details of a specific intake work item. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.intakes:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "status": 0, "source": "in_app", "issue": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "priority": "medium", "sequence_id": 124 }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/intake-issue/update-intake-issue-detail.html description: >- Update an intake work item via Plane API. HTTP request format, parameters, scopes, and example responses for update an intake work item. --- # Update an intake work item Modify an existing intake work item's properties or status for triage processing. Supports status changes like accept, reject, or mark as duplicate. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters * `-2` - Pending * `-1` - Rejected * `0` - Snoozed * `1` - Accepted * `2` - Duplicate Snoozed till. Duplicate to. Source. Source email. Issue data to update in the intake issue ### Scopes `projects.intakes:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": 1, "issue": { "name": "Example Name", "description": "Example description", "priority": "high" } }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "status": 1, "issue": { "name": "Example Name", "description": "Example description", "priority": "high" } } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ status: 1, issue: { name: "Example Name", description: "Example description", priority: "high", }, }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "status": 0, "source": "in_app", "issue": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "priority": "medium", "sequence_id": 124 }, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/intake-issue/delete-intake-issue.html description: >- Delete an intake work item via Plane API. HTTP request format, parameters, scopes, and example responses for delete an intake work item. --- # Delete an intake work item Permanently remove an intake work item from the triage queue. Also deletes the underlying work item if it hasn't been accepted yet. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.intakes:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/intake-issues/work-item-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/assets/overview.html' description: >- Plane Assets API overview. Learn how user and workspace asset uploads work through the Plane API. --- # Overview Assets let you upload and manage files used across user profiles and workspaces, including images and other binary resources. [Learn more about using the Plane API](https://developers.plane.so/api-reference/introduction) ## The Asset Object ### Attributes ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "design-spec.pdf", "type": "application/pdf", "asset_url": "https://cdn.example.com/workspace-assets/design-spec.pdf", "attributes": { "entity_type": "WORKSPACE" } } ``` --- --- url: 'https://developers.plane.so/api-reference/assets/create-user-asset-upload.html' description: >- Create user asset upload via Plane API. HTTP request format, parameters, scopes, and example responses for create user asset upload. --- # Create user asset upload Generate presigned URL for user asset upload ### Body Parameters Original filename of the asset MIME type of the file * `image/jpeg` - JPEG * `image/png` - PNG * `image/webp` - WebP * `image/jpg` - JPG * `image/gif` - GIF File size in bytes Type of user asset * `USER_AVATAR` - User Avatar * `USER_COVER` - User Cover ### Scopes `assets:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/assets/user-assets/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "type": "image/jpeg", "size": 1024000, "entity_type": "USER_AVATAR" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/assets/user-assets/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "type": "image/jpeg", "size": 1024000, "entity_type": "USER_AVATAR" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/assets/user-assets/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", type: "image/jpeg", size: 1024000, entity_type: "USER_AVATAR", }), }); const data = await response.json(); ``` ```json { "asset_id": "550e8400-e29b-41d4-a716-446655440000", "asset_url": "/api/assets/v2/static/550e8400-e29b-41d4-a716-446655440000/", "upload_data": { "url": "https://uploads.example.com/plane-bucket", "fields": { "Content-Type": "image/png", "key": "user-assets/550e8400-e29b-41d4-a716-446655440000/profile-image.png", "x-amz-algorithm": "AWS4-HMAC-SHA256", "x-amz-credential": "example/20240101/us-east-1/s3/aws4_request", "x-amz-date": "20240101T000000Z", "policy": "example-policy", "x-amz-signature": "example-signature" } } } ``` --- --- url: 'https://developers.plane.so/api-reference/assets/update-user-asset.html' description: >- Update user asset via Plane API. HTTP request format, parameters, scopes, and example responses for update user asset. --- # Update user asset Mark user asset as uploaded ### Path Parameters Asset ID ### Body Parameters Additional attributes to update for the asset ### Scopes `assets:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/assets/user-assets/asset-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "attributes": { "name": "Example Name", "type": "image/jpeg", "size": 1024000 }, "entity_type": "USER_AVATAR" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/assets/user-assets/asset-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "attributes": { "name": "Example Name", "type": "image/jpeg", "size": 1024000 }, "entity_type": "USER_AVATAR" } ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/assets/user-assets/asset-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ attributes: { name: "Example Name", type: "image/jpeg", size: 1024000, }, entity_type: "USER_AVATAR", }), }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/assets/delete-user-asset.html' description: >- Delete user asset via Plane API. HTTP request format, parameters, scopes, and example responses for delete user asset. --- # Delete user asset Delete user asset. Delete a user profile asset (avatar or cover image) and remove its reference from the user profile. This performs a soft delete by marking the asset as deleted and updating the user's profile. ### Path Parameters Asset ID ### Scopes `assets:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/assets/user-assets/asset-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/assets/user-assets/asset-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/assets/user-assets/asset-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/assets/create-workspace-asset-upload.html description: >- Create workspace asset upload via Plane API. HTTP request format, parameters, scopes, and example responses for create workspace asset upload. --- # Create workspace asset upload Generate presigned URL for generic asset upload ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Original filename of the asset MIME type of the file File size in bytes UUID of the project to associate with the asset External identifier for the asset (for integration tracking) External source system (for integration tracking) ### Scopes `assets:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/assets/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "type": "image/jpeg", "size": 1024000, "project_id": "550e8400-e29b-41d4-a716-446655440000", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/assets/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "type": "image/jpeg", "size": 1024000, "project_id": "550e8400-e29b-41d4-a716-446655440000", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/assets/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", type: "image/jpeg", size: 1024000, project_id: "550e8400-e29b-41d4-a716-446655440000", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "asset_id": "550e8400-e29b-41d4-a716-446655440000", "asset_url": "/api/assets/v2/workspaces/my-workspace/projects/None/issues/None/attachments/550e8400-e29b-41d4-a716-446655440000/", "upload_data": { "url": "https://uploads.example.com/plane-bucket", "fields": { "Content-Type": "image/png", "key": "workspace-assets/550e8400-e29b-41d4-a716-446655440000/workspace-image.png", "x-amz-algorithm": "AWS4-HMAC-SHA256", "x-amz-credential": "example/20240101/us-east-1/s3/aws4_request", "x-amz-date": "20240101T000000Z", "policy": "example-policy", "x-amz-signature": "example-signature" } } } ``` --- --- url: 'https://developers.plane.so/api-reference/assets/get-workspace-asset.html' description: >- Get workspace asset via Plane API. HTTP request format, parameters, scopes, and example responses for get workspace asset. --- # Get workspace asset Get presigned URL for asset download ### Path Parameters The unique identifier of the asset. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `assets:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "design-spec.pdf", "type": "application/pdf", "asset_url": "https://cdn.example.com/workspace-assets/design-spec.pdf", "attributes": { "entity_type": "WORKSPACE" } } ``` --- --- url: 'https://developers.plane.so/api-reference/assets/update-workspace-asset.html' description: >- Update workspace asset via Plane API. HTTP request format, parameters, scopes, and example responses for update workspace asset. --- # Update workspace asset Update generic asset after upload completion ### Path Parameters The unique identifier of the asset. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Whether the asset has been successfully uploaded ### Scopes `assets:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "is_uploaded": true }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "is_uploaded": true } ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/assets/asset-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ is_uploaded: true, }), }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/milestones/overview.html' description: >- Plane Milestones API overview. Learn how to manage milestones and milestone work items through the Plane API. --- # Overview Milestones help teams group work items around important dates, releases, and delivery checkpoints inside a project. [Learn more about Projects](https://docs.plane.so/core-concepts/projects/overview) ## The Milestone Object ### Attributes * `id` *string* Id. * `title` *string* Title. * `target_date` *string* Target date. * `external_id` *string* External id. * `external_source` *string* External source. * `created_at` *string* Created at. * `updated_at` *string* Updated at. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/milestones/add-milestone.html' description: >- Create milestone via Plane API. HTTP request format, parameters, scopes, and example responses for create milestone. --- # Create milestone Create a new milestone in a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Title. Target date. External id. External source. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/", headers={"X-API-Key": "your-api-key"}, json={ "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ title: "Example Name", target_date: "2024-01-01", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/milestones/list-milestones.html' description: >- List milestones via Plane API. HTTP request format, parameters, scopes, and example responses for list milestones. --- # List milestones List all milestones in a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/milestones/get-milestone-detail.html' description: >- Get milestone via Plane API. HTTP request format, parameters, scopes, and example responses for get milestone. --- # Get milestone Retrieve a specific milestone by its ID. ### Path Parameters The unique identifier of the milestone. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/milestones/list-milestone-work-items.html description: >- List milestone work items via Plane API. HTTP request format, parameters, scopes, and example responses for list milestone work items. --- # List milestone work items List all work items for a milestone. ### Path Parameters The unique identifier of the milestone. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/work-items/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/work-items/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/work-items/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "issue": "550e8400-e29b-41d4-a716-446655440000", "milestone": "550e8400-e29b-41d4-a716-446655440000" } ] ``` --- --- url: >- https://developers.plane.so/api-reference/milestones/update-milestone-detail.html description: >- Update milestone via Plane API. HTTP request format, parameters, scopes, and example responses for update milestone. --- # Update milestone Update a specific milestone by its ID. ### Path Parameters The unique identifier of the milestone. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Title. Target date. External id. External source. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ title: "Example Name", target_date: "2024-01-01", external_id: "550e8400-e29b-41d4-a716-446655440000", external_source: "github", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Example Name", "target_date": "2024-01-01", "external_id": "550e8400-e29b-41d4-a716-446655440000", "external_source": "github", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/milestones/delete-milestone.html' description: >- Delete milestone via Plane API. HTTP request format, parameters, scopes, and example responses for delete milestone. --- # Delete milestone Delete a specific milestone by its ID. ### Path Parameters The unique identifier of the milestone. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes API key authentication or an OAuth token with equivalent access. ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/milestones/milestone-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/estimate/overview.html' description: >- Plane Estimate API overview. Learn about estimate objects, estimate points, and how to work with estimates via REST API. --- # Overview Estimates define how work is sized in a project. An estimate represents the scale (categories, points, or time), and estimate points represent the allowed values on work items. ## The Estimate Object ### Attributes * `name` *string* **(required)** Name of the estimate * `description` *string* Description of the estimate * `type` *string* Type of estimate. Possible values: `categories`, `points`, `time` * `last_used` *boolean* Whether this estimate is the most recently used estimate for the project * `external_id` *string* or *null* External ID from an external system * `external_source` *string* or *null* External source identifier * `created_at`, `updated_at` *timestamp* Timestamps when the estimate was created and last updated * `created_by`, `updated_by` *uuid* IDs of the users who created and last updated the estimate * `project` *uuid* Project ID associated with the estimate * `workspace` *uuid* Workspace ID associated with the estimate ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T12:10:00Z", "name": "Story Points", "description": "Standard story point scale", "type": "points", "last_used": true, "external_id": "sp-001", "external_source": "jira", "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` ## The Estimate Point Object ### Attributes * `estimate` *uuid* **(required)** Estimate ID this point belongs to * `key` *integer* Numeric key used for ordering and display * `value` *string* **(required)** Display value for the estimate point (max 20 characters) * `description` *string* Description of the estimate point * `external_id` *string* or *null* External ID from an external system * `external_source` *string* or *null* External source identifier * `created_at`, `updated_at` *timestamp* Timestamps when the estimate point was created and last updated * `created_by`, `updated_by` *uuid* IDs of the users who created and last updated the estimate point * `project` *uuid* Project ID associated with the estimate point * `workspace` *uuid* Workspace ID associated with the estimate point ```json { "id": "550e8400-e29b-41d4-a716-446655440010", "created_at": "2024-01-15T10:40:00Z", "updated_at": "2024-01-20T12:15:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 3, "value": "3", "description": "Small", "external_id": "sp-3", "external_source": "jira", "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/add-estimate.html' description: >- Create an estimate via Plane API. HTTP request format, parameters, scopes, and example responses for create an estimate. --- # Create an estimate Create a new estimate for the project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name of the estimate. Description of the estimate. Type of estimate. Possible values: `categories`, `points`, `time`. Whether this estimate is the most recently used estimate for the project. External ID from an external system. External source identifier. ### Scopes `projects.estimates:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Story Points", "description": "Standard story point scale", "type": "points" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Story Points", "description": "Standard story point scale", "type": "points", }, ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Story Points", description: "Standard story point scale", type: "points", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T12:10:00Z", "name": "Story Points", "description": "Standard story point scale", "type": "points", "last_used": true, "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/get-estimate.html' description: >- Get an estimate via Plane API. HTTP request format, parameters, scopes, and example responses for get an estimate. --- # Get an estimate Retrieve the estimate configured for a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.estimates:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T12:10:00Z", "name": "Story Points", "description": "Standard story point scale", "type": "points", "last_used": true, "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/update-estimate.html' description: >- Update an estimate via Plane API. HTTP request format, parameters, scopes, and example responses for update an estimate. --- # Update an estimate Update the estimate for a project. Only fields provided in the request will be updated. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name of the estimate. Description of the estimate. External ID from an external system. External source identifier. ### Scopes `projects.estimates:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "description": "Updated story point scale" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", headers={"X-API-Key": "your-api-key"}, json={"description": "Updated story point scale"}, ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ description: "Updated story point scale", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-21T09:30:00Z", "name": "Story Points", "description": "Updated story point scale", "type": "points", "last_used": true, "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/delete-estimate.html' description: >- Delete an estimate via Plane API. HTTP request format, parameters, scopes, and example responses for delete an estimate. --- # Delete an estimate Delete the estimate configured for a project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.estimates:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/estimate/list-estimate-points.html' description: >- List estimate points via Plane API. HTTP request format, parameters, scopes, and example responses for list estimate points. --- # List estimate points Retrieve all estimate points for a project estimate. ### Path Parameters The unique identifier of the estimate. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.estimates:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440010", "created_at": "2024-01-15T10:40:00Z", "updated_at": "2024-01-20T12:15:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 1, "value": "1", "description": "Tiny", "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" }, { "id": "550e8400-e29b-41d4-a716-446655440011", "created_at": "2024-01-15T10:41:00Z", "updated_at": "2024-01-20T12:16:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 2, "value": "2", "description": "Small", "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/add-estimate-points.html' description: >- Create estimate points via Plane API. HTTP request format, parameters, scopes, and example responses for create estimate points. --- # Create estimate points Create estimate points for a project estimate. You can send a JSON array directly or wrap it inside `estimate_points`. ### Path Parameters The unique identifier of the estimate. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Array of estimate point objects. Each object can include: `value` (string, required, max 20 chars), `key` (integer), `description` (string), `external_id` (string), and `external_source` (string). ### Scopes `projects.estimates:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '[ { "key": 1, "value": "1", "description": "Tiny" }, { "key": 2, "value": "2", "description": "Small" } ]' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/", headers={"X-API-Key": "your-api-key"}, json=[ {"key": 1, "value": "1", "description": "Tiny"}, {"key": 2, "value": "2", "description": "Small"}, ], ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify([ { key: 1, value: "1", description: "Tiny" }, { key: 2, value: "2", description: "Small" }, ]), } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440010", "created_at": "2024-01-15T10:40:00Z", "updated_at": "2024-01-20T12:15:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 1, "value": "1", "description": "Tiny", "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" }, { "id": "550e8400-e29b-41d4-a716-446655440011", "created_at": "2024-01-15T10:41:00Z", "updated_at": "2024-01-20T12:16:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 2, "value": "2", "description": "Small", "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/update-estimate-point.html' description: >- Update an estimate point via Plane API. HTTP request format, parameters, scopes, and example responses for update an estimate point. --- # Update an estimate point Update a single estimate point for a project estimate. ### Path Parameters The unique identifier of the estimate point. The unique identifier of the estimate. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Numeric key used for ordering and display. Display value for the estimate point (max 20 characters). Description of the estimate point. External ID from an external system. External source identifier. ### Scopes `projects.estimates:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "value": "3", "description": "Small" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/", headers={"X-API-Key": "your-api-key"}, json={"value": "3", "description": "Small"}, ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ value: "3", description: "Small", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440010", "created_at": "2024-01-15T10:40:00Z", "updated_at": "2024-01-21T09:45:00Z", "estimate": "550e8400-e29b-41d4-a716-446655440000", "key": 3, "value": "3", "description": "Small", "external_id": null, "external_source": null, "created_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "updated_by": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "project": "4af68566-94a4-4eb3-94aa-50dc9427067b", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/estimate/delete-estimate-point.html' description: >- Delete an estimate point via Plane API. HTTP request format, parameters, scopes, and example responses for delete an estimate point. --- # Delete an estimate point Delete a single estimate point from a project estimate. ### Path Parameters The unique identifier of the estimate point. The unique identifier of the estimate. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.estimates:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/estimates/estimate-uuid/estimate-points/estimate-point-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/worklogs/overview.html' description: >- Plane Worklogs API overview. Learn about endpoints, request/response format, and how to work with worklogs via REST API. --- # Overview Worklogs enable time tracking for work items within a project, recording time spent in minutes along with descriptions and user information. [Learn more about Time tracking](https://docs.plane.so/core-concepts/issues/time-tracking) ## The Worklogs Object ### Attributes * `id` *string* Unique identifier for the worklog * `created_at` *timestamp* Timestamp when the worklog was created * `updated_at` *timestamp* Timestamp when the worklog was last modified * `deleted_at` *timestamp* Timestamp when the worklog was deleted * `description` *string* Description of the work done during the worklog * `duration` *integer* Time spent on the issue, recorded in minutes * `created_by` *string* ID of user who created the worklog * `updated_by` *string* ID of user who last modified the worklog * `project_id` *string* ID of project associated with the worklog * `workspace_id` *string* ID of workspace associated with the worklog * `logged_by` *string* ID of the user who logged the work ```json { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "created_at": "2025-01-29T21:27:54.197306+05:30", "updated_at": "2025-01-29T21:27:54.197320+05:30", "description": "", "duration": 1, "created_by": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "updated_by": null, "project_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "logged_by": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } ``` --- --- url: 'https://developers.plane.so/api-reference/worklogs/create-worklog.html' description: >- Create a worklog via Plane API. HTTP request format, parameters, scopes, and example responses for create a worklog. --- # Create a worklog Create a new worklog entry ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Description. Duration. Created by. Updated by. ### Scopes `projects.work_items.worklogs:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/", headers={"X-API-Key": "your-api-key"}, json={ "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ description: "Example description", duration: 1, created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "logged_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: 'https://developers.plane.so/api-reference/worklogs/get-worklogs-for-issue.html' description: >- List all worklogs for a work item via Plane API. HTTP request format, parameters, scopes, and example responses for list all worklogs for a work item. --- # List all worklogs for a work item List worklog entries ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.worklogs:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "logged_by": "550e8400-e29b-41d4-a716-446655440000" } ] ``` --- --- url: 'https://developers.plane.so/api-reference/worklogs/get-total-time.html' description: >- Get total time for each work item via Plane API. HTTP request format, parameters, scopes, and example responses for get total time for each work item. --- # Get total time for each work item Get project worklog summary ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.worklogs:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/total-worklogs/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/total-worklogs/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/total-worklogs/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "issue_id": "550e8400-e29b-41d4-a716-446655440000", "duration": 1 } ] ``` --- --- url: 'https://developers.plane.so/api-reference/worklogs/update-worklog.html' description: >- Update a worklog via Plane API. HTTP request format, parameters, scopes, and example responses for update a worklog. --- # Update a worklog Update a worklog entry ### Path Parameters The unique identifier of the work item. The unique identifier of the worklog. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Description. Duration. Created by. Updated by. ### Scopes `projects.work_items.worklogs:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ description: "Example description", duration: 1, created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "description": "Example description", "duration": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "project_id": "550e8400-e29b-41d4-a716-446655440000", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "logged_by": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: 'https://developers.plane.so/api-reference/worklogs/delete-worklog.html' description: >- Delete a worklog via Plane API. HTTP request format, parameters, scopes, and example responses for delete a worklog. --- # Delete a worklog Delete a worklog entry ### Path Parameters The unique identifier of the work item. The unique identifier of the worklog. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.work_items.worklogs:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/worklogs/worklog-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/epics/overview.html' description: >- Plane Epics API overview. Learn about endpoints, request/response format, and how to work with epics via REST API. --- # Overview Epics help you group related tasks into a larger work item, providing a hierarchical structure for managing complex projects. Use epics to break down major objectives into smaller, manageable pieces while keeping everything organized. [Learn more about Epics](https://docs.plane.so/core-concepts/issues/epics). ## The Epics Object ### Attributes * `id` string Unique identifier for the epic. * `name` string Name of the epic. * `description` object JSON representation of the epic description. * `description_html` string HTML-formatted description of the epic. * `description_stripped` string Plain text version of the description. * `description_binary` string Binary representation of the description. * `state` string ID of the state (status) of the epic. * `priority` string Priority level. Possible values: `none`, `urgent`, `high`, `medium`, `low`. * `assignees` array Array of user IDs assigned to the epic. * `labels` array Array of label IDs applied to the epic. * `type` string ID of the work item type for the epic. * `estimate_point` string ID of the estimate point, or null if not estimated. * `point` integer Point value for the epic, or null. * `start_date` string Start date of the epic in YYYY-MM-DD format. * `target_date` string Target completion date in YYYY-MM-DD format. * `parent` string ID of the parent work item, or null if no parent. * `sequence_id` integer Auto-generated sequential identifier for the epic within the project. * `sort_order` number Auto-generated sort order for display purposes. * `is_draft` boolean Whether the epic is a draft. * `completed_at` timestamp Time at which the epic was completed, or null if not completed. * `archived_at` timestamp Time at which the epic was archived, or null if not archived. * `project` string ID of the project containing this epic. * `workspace` string ID of the workspace containing this epic. * `external_id` string External identifier if imported from another system, or null. * `external_source` string Name of the source system if imported, or null. * `deleted_at` timestamp Time at which the epic was deleted, or null if not deleted. * `created_at` timestamp Time at which the epic was created. * `updated_at` timestamp Time at which the epic was last updated. * `created_by` string ID of the user who created the epic. * `updated_by` string ID of the user who last updated the epic. ```json { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "name": "Develop Mobile Application Framework", "description": {}, "description_html": "

Create a cross-platform mobile application framework that supports all core system functionalities with native-like performance and user experience

", "description_stripped": "Create a cross-platform mobile application framework that supports all core system functionalities with native-like performance and user experience", "description_binary": null, "state": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "priority": "medium", "assignees": [], "labels": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "type": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "estimate_point": null, "point": null, "start_date": "2025-02-28", "target_date": "2025-06-20", "parent": null, "sequence_id": 57, "sort_order": 605535.0, "is_draft": false, "completed_at": null, "archived_at": null, "project": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "workspace": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "external_id": null, "external_source": null, "deleted_at": null, "created_at": "2025-03-01T21:23:54.645263+05:30", "updated_at": "2025-03-03T10:38:44.667276+05:30", "created_by": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "updated_by": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } ``` --- --- url: 'https://developers.plane.so/api-reference/epics/create-epic.html' description: >- Create an epic via Plane API. HTTP request format, parameters, scopes, and example responses for create an epic. --- # Create an epic Create a new epic in the specified project with the provided details. ### Path Parameters Project ID Workspace slug ### Body Parameters Name of the epic. HTML-formatted description of the epic. ID of the state (status) to assign to the epic. ID of the parent work item. Priority level. Possible values: `none`, `urgent`, `high`, `medium`, `low`. Start date of the epic in YYYY-MM-DD format. Target completion date in YYYY-MM-DD format. List of user IDs to assign to the epic. List of label IDs to apply to the epic. ID of the estimate point. Name of the source system if importing from another tool. External identifier from the source system. ### Scopes `projects.epics:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Develop Mobile Application Framework", "description_html": "

Create a cross-platform mobile application framework

", "priority": "high", "start_date": "2025-03-01", "target_date": "2025-06-30" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Develop Mobile Application Framework", "description_html": "

Create a cross-platform mobile application framework

", "priority": "high", "start_date": "2025-03-01", "target_date": "2025-06-30" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Develop Mobile Application Framework", description_html: "

Create a cross-platform mobile application framework

", priority: "high", start_date: "2025-03-01", target_date: "2025-06-30", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Develop Mobile Application Framework", "description": {}, "description_html": "

Create a cross-platform mobile application framework

", "description_stripped": "Create a cross-platform mobile application framework", "description_binary": null, "state": "550e8400-e29b-41d4-a716-446655440001", "priority": "high", "assignees": [], "labels": [], "type": "550e8400-e29b-41d4-a716-446655440002", "estimate_point": null, "point": null, "start_date": "2025-03-01", "target_date": "2025-06-30", "parent": null, "sequence_id": 57, "sort_order": 605535.0, "is_draft": false, "completed_at": null, "archived_at": null, "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440003", "external_id": null, "external_source": null, "deleted_at": null, "created_at": "2025-03-01T21:23:54.645263Z", "updated_at": "2025-03-01T21:23:54.645263Z", "created_by": "550e8400-e29b-41d4-a716-446655440004", "updated_by": null } ``` --- --- url: 'https://developers.plane.so/api-reference/epics/list-epics.html' description: >- List all epics via Plane API. HTTP request format, parameters, scopes, and example responses for list all epics. --- # List all epics List epics ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `projects.epics:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/epics/get-epic-detail.html' description: >- Retrieve an epic via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve an epic. --- # Retrieve an epic Retrieve an epic by id ### Path Parameters The unique identifier of the resource. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Comma-separated list of fields to include in response ### Scopes `projects.epics:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/resource-id-uuid/?fields=id,name,description" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/resource-id-uuid/?fields=id,name,description", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/epics/resource-id-uuid/?fields=id,name,description", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/epics/update-epic.html' description: >- Update an epic via Plane API. HTTP request format, parameters, scopes, and example responses for update an epic. --- # Update an epic Partially update an existing epic with the provided fields. Supports external ID validation to prevent conflicts. ### Path Parameters Epic ID Project ID Workspace slug ### Body Parameters Name of the epic. HTML-formatted description of the epic. ID of the state (status) to assign to the epic. ID of the parent work item. Priority level. Possible values: `none`, `urgent`, `high`, `medium`, `low`. Start date of the epic in YYYY-MM-DD format. Target completion date in YYYY-MM-DD format. List of user IDs to assign to the epic. List of label IDs to apply to the epic. ID of the estimate point. Name of the source system if importing from another tool. External identifier from the source system. ### Scopes `projects.epics:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Epic Name", "priority": "medium", "target_date": "2025-09-30" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Updated Epic Name", "priority": "medium", "target_date": "2025-09-30" } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Updated Epic Name", priority: "medium", target_date: "2025-09-30", }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Updated Epic Name", "description": {}, "description_html": "

Create a cross-platform mobile application framework

", "description_stripped": "Create a cross-platform mobile application framework", "description_binary": null, "state": "550e8400-e29b-41d4-a716-446655440002", "priority": "medium", "assignees": [], "labels": [], "type": "550e8400-e29b-41d4-a716-446655440003", "estimate_point": null, "point": null, "start_date": "2025-03-01", "target_date": "2025-09-30", "parent": null, "sequence_id": 57, "sort_order": 605535.0, "is_draft": false, "completed_at": null, "archived_at": null, "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440004", "external_id": null, "external_source": null, "deleted_at": null, "created_at": "2025-03-01T21:23:54.645263Z", "updated_at": "2025-03-05T14:12:00.123456Z", "created_by": "550e8400-e29b-41d4-a716-446655440005", "updated_by": "550e8400-e29b-41d4-a716-446655440005" } ``` --- --- url: 'https://developers.plane.so/api-reference/epics/delete-epic.html' description: >- Delete an epic via Plane API. HTTP request format, parameters, scopes, and example responses for delete an epic. --- # Delete an epic Permanently delete an existing epic from the project. Child work items will have their parent unset. ### Path Parameters Epic ID Project ID Workspace slug ### Scopes `projects.epics:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/epics/add-epic-work-items.html' description: >- Add work items to epic via Plane API. HTTP request format, parameters, scopes, and example responses for add work items to epic. --- # Add work items to epic Add multiple work items as sub-issues under an epic. Validates type hierarchy before assignment. ### Path Parameters Epic ID Project ID Workspace slug ### Body Parameters List of work item IDs to add to the epic ### Scopes `projects.epics:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440010", "550e8400-e29b-41d4-a716-446655440011" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/", headers={"X-API-Key": "your-api-key"}, json={ "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440010", "550e8400-e29b-41d4-a716-446655440011" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ work_item_ids: ["550e8400-e29b-41d4-a716-446655440010", "550e8400-e29b-41d4-a716-446655440011"], }), } ); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440010", "name": "Implement login screen", "description_html": "

Build the login screen UI

", "description_stripped": "Build the login screen UI", "description_binary": null, "state": "550e8400-e29b-41d4-a716-446655440002", "priority": "high", "assignees": [], "labels": [], "type": null, "type_id": null, "estimate_point": null, "point": null, "start_date": null, "target_date": null, "parent": "550e8400-e29b-41d4-a716-446655440001", "sequence_id": 12, "sort_order": 65535.0, "is_draft": false, "completed_at": null, "archived_at": null, "last_activity_at": "2025-03-15T10:00:00Z", "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440003", "external_id": null, "external_source": null, "deleted_at": null, "created_at": "2025-03-10T09:00:00Z", "updated_at": "2025-03-15T10:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440005", "updated_by": null } ] ``` --- --- url: 'https://developers.plane.so/api-reference/epics/list-epic-work-items.html' description: >- List epic work items via Plane API. HTTP request format, parameters, scopes, and example responses for list epic work items. --- # List epic work items Retrieve all work items under an epic. ### Path Parameters Epic ID Project ID Workspace slug ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `projects.epics:read` `projects.work-items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/?per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/?per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/550e8400-e29b-41d4-a716-446655440000/epics/550e8400-e29b-41d4-a716-446655440001/issues/?per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": null, "sub_grouped_by": null, "total_count": 5, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": false, "prev_page_results": false, "count": 5, "total_pages": 1, "total_results": 5, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440010", "name": "Implement login screen", "description_html": "

Build the login screen UI

", "description_stripped": "Build the login screen UI", "description_binary": null, "state": "550e8400-e29b-41d4-a716-446655440002", "priority": "high", "assignees": ["550e8400-e29b-41d4-a716-446655440005"], "labels": [], "type": null, "type_id": null, "estimate_point": null, "point": null, "start_date": "2025-03-10", "target_date": "2025-03-20", "parent": "550e8400-e29b-41d4-a716-446655440001", "sequence_id": 12, "sort_order": 65535.0, "is_draft": false, "completed_at": null, "archived_at": null, "last_activity_at": "2025-03-15T10:00:00Z", "project": "550e8400-e29b-41d4-a716-446655440000", "workspace": "550e8400-e29b-41d4-a716-446655440003", "external_id": null, "external_source": null, "deleted_at": null, "created_at": "2025-03-10T09:00:00Z", "updated_at": "2025-03-15T10:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440005", "updated_by": null } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/initiative/overview.html' description: >- Plane Initiative API overview. Learn about endpoints, request/response format, and how to work with initiative via REST API. --- # Overview Initiatives are high-level strategic goals that help organize and track work across multiple projects, epics, and work items, providing a way to group related work and measure progress toward larger objectives. [Learn more about Initiatives](https://docs.plane.so/core-concepts/projects/initiatives) ## The Initiatives Object ### Attributes * `id` *uuid* Unique identifier for the initiative * `name` *string* **(required)** Name of the initiative * `description` *string* Plain text description of the initiative * `description_html` *string* HTML description of the initiative * `description_stripped` *string* Stripped version of the HTML description * `description_binary` *string* Binary description of the initiative * `lead` *uuid* User ID of the initiative lead * `start_date` *date* Start date of the initiative in YYYY-MM-DD format * `end_date` *date* End date of the initiative in YYYY-MM-DD format * `logo_props` *object* Logo properties for the initiative * `state` *string* State of the initiative. Can be: DRAFT, PLANNED, ACTIVE, COMPLETED, CLOSED * `workspace` *uuid* Workspace UUID which is automatically saved * `created_at` *timestamp* The timestamp when the initiative was created * `updated_at` *timestamp* The timestamp when the initiative was last updated * `created_by` *uuid* ID of the user who created the initiative * `updated_by` *uuid* ID of the user who last updated the initiative ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "name": "Q1 Product Launch", "description": "Launch new product features in Q1", "description_html": "

Launch new product features in Q1

", "lead": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "start_date": "2024-01-01", "end_date": "2024-03-31", "state": "ACTIVE", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/initiative/add-initiative.html' description: >- Create an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for create an initiative. --- # Create an initiative Create a new initiative in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Description stripped. Start date. End date. Logo props. * `DRAFT` - Draft * `PLANNED` - Planned * `ACTIVE` - Active * `COMPLETED` - Completed * `CLOSED` - Closed Archived at. Created by. Updated by. Lead. ### Scopes `initiatives:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "description_html": "

Example content

", "description_stripped": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "logo_props": "example-value", "state": "DRAFT", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "description_html": "

Example content

", "description_stripped": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "logo_props": "example-value", "state": "DRAFT", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", description_html: "

Example content

", description_stripped: "Example description", start_date: "2024-01-01T00:00:00Z", end_date: "2024-01-01T00:00:00Z", logo_props: "example-value", state: "DRAFT", archived_at: "2024-01-01T00:00:00Z", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: 'https://developers.plane.so/api-reference/initiative/list-initiatives.html' description: >- List all initiatives via Plane API. HTTP request format, parameters, scopes, and example responses for list all initiatives. --- # List all initiatives List all initiatives in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `initiatives:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ] } ] ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/get-initiative-detail.html description: >- Retrieve an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve an initiative. --- # Retrieve an initiative Retrieve an initiative by its ID ### Path Parameters The unique identifier of the initiative. The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/update-initiative-detail.html description: >- Update an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for update an initiative. --- # Update an initiative Update an initiative by its ID ### Path Parameters The unique identifier of the initiative. The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Description stripped. Start date. End date. Logo props. * `DRAFT` - Draft * `PLANNED` - Planned * `ACTIVE` - Active * `COMPLETED` - Completed * `CLOSED` - Closed Archived at. Created by. Updated by. Lead. ### Scopes `initiatives:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "description_html": "

Example content

", "description_stripped": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "logo_props": "example-value", "state": "DRAFT", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "description_html": "

Example content

", "description_stripped": "Example description", "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-01T00:00:00Z", "logo_props": "example-value", "state": "DRAFT", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", description_html: "

Example content

", description_stripped: "Example description", start_date: "2024-01-01T00:00:00Z", end_date: "2024-01-01T00:00:00Z", logo_props: "example-value", state: "DRAFT", archived_at: "2024-01-01T00:00:00Z", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: 'https://developers.plane.so/api-reference/initiative/delete-initiative.html' description: >- Delete an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for delete an initiative. --- # Delete an initiative Delete an initiative by its ID ### Path Parameters The unique identifier of the initiative. The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/initiative/add-initiative-label.html' description: >- Create an initiative label via Plane API. HTTP request format, parameters, scopes, and example responses for create an initiative label. --- # Create an initiative label Create a new initiative label in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Color. Sort order. ### Scopes `initiatives.labels:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "color": "Example Name", "sort_order": 1 }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "color": "Example Name", "sort_order": 1 } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", color: "Example Name", sort_order: 1, }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/add-labels-to-initiative.html description: >- Add labels to initiative via Plane API. HTTP request format, parameters, scopes, and example responses for add labels to initiative. --- # Add labels to initiative Add labels to an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Label ids. ### Scopes `initiatives.labels:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "label_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/", headers={"X-API-Key": "your-api-key"}, json={ "label_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ label_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/list-initiative-labels.html description: >- List all initiative labels via Plane API. HTTP request format, parameters, scopes, and example responses for list all initiative labels. --- # List all initiative labels List all initiative labels in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `initiatives.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ] } ] ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/get-initiative-label-detail.html description: >- Retrieve an initiative label via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve an initiative label. --- # Retrieve an initiative label Retrieve an initiative label by its ID ### Path Parameters The unique identifier of the initiative label. The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/list-initiative-labels-for-initiative.html description: >- List all labels for an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for list all labels for an initiative. --- # List all labels for an initiative List all labels associated with an initiative ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `initiatives.labels:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/update-initiative-label-detail.html description: >- Update an initiative label via Plane API. HTTP request format, parameters, scopes, and example responses for update an initiative label. --- # Update an initiative label Update an initiative label by its ID ### Path Parameters The unique identifier of the initiative label. The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Color. Sort order. ### Scopes `initiatives.labels:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "Example description", "color": "Example Name", "sort_order": 1 }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "Example description", "color": "Example Name", "sort_order": 1 } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "Example description", color: "Example Name", sort_order: 1, }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/remove-labels-from-initiative.html description: >- Remove labels from initiative via Plane API. HTTP request format, parameters, scopes, and example responses for remove labels from initiative. --- # Remove labels from initiative Remove labels from an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives.labels:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/labels/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/initiative/delete-initiative-label.html description: >- Delete an initiative label via Plane API. HTTP request format, parameters, scopes, and example responses for delete an initiative label. --- # Delete an initiative label Delete an initiative label by its ID ### Path Parameters The unique identifier of the initiative label. The unique identifier of the label. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives.labels:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/labels/label-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/initiative/add-projects-to-initiative.html description: >- Add projects to initiative via Plane API. HTTP request format, parameters, scopes, and example responses for add projects to initiative. --- # Add projects to initiative Add projects to an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Project ids. ### Scopes `initiatives.projects:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "project_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/", headers={"X-API-Key": "your-api-key"}, json={ "project_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ project_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2, "project_lead": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/list-initiative-projects.html description: >- List all projects in an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for list all projects in an initiative. --- # List all projects in an initiative List all projects associated with an initiative ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `initiatives.projects:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2 } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/remove-projects-from-initiative.html description: >- Remove projects from initiative via Plane API. HTTP request format, parameters, scopes, and example responses for remove projects from initiative. --- # Remove projects from initiative Remove projects from an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives.projects:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/projects/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/initiative/add-epics-to-initiative.html description: >- Add epics to initiative via Plane API. HTTP request format, parameters, scopes, and example responses for add epics to initiative. --- # Add epics to initiative Add epics to an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Epic ids. ### Scopes `initiatives.epics:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "epic_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/", headers={"X-API-Key": "your-api-key"}, json={ "epic_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ epic_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/list-initiative-epics.html description: >- List all epics for an initiative via Plane API. HTTP request format, parameters, scopes, and example responses for list all epics for an initiative. --- # List all epics for an initiative List all epics associated with an initiative ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `initiatives.epics:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "priority": "high", "sequence_id": 123, "state": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "group": "started" }, "assignees": [], "labels": [], "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/initiative/remove-epics-from-initiative.html description: >- Remove epics from initiative via Plane API. HTTP request format, parameters, scopes, and example responses for remove epics from initiative. --- # Remove epics from initiative Remove epics from an initiative by its ID ### Path Parameters The unique identifier of the initiative. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `initiatives.epics:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/initiatives/initiative-uuid/epics/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/customer/overview.html' description: >- Plane Customer API overview. Learn about endpoints, request/response format, and how to work with customer via REST API. --- # Overview Customers allow you to manage customer relationships and track customer-related work items, requests, and custom properties within a workspace. [Learn more about Customers](https://docs.plane.so/customers) ## The Customer Object ### Attributes * `id` *uuid* Unique identifier for the customer * `name` *string* **(required)** Name of the customer * `email` *string* Email address of the customer * `workspace` *uuid* Workspace UUID which is automatically saved * `created_at` *timestamp* The timestamp when the customer was created * `updated_at` *timestamp* The timestamp when the customer was last updated * `created_by` *uuid* ID of the user who created the customer * `updated_by` *uuid* ID of the user who last updated the customer ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "name": "Acme Corporation", "email": "contact@acme.com", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/customer/add-customer.html' description: >- Create a customer via Plane API. HTTP request format, parameters, scopes, and example responses for create a customer. --- # Create a customer Create a new customer in the specified workspace. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Description stripped. Email. Website url. Logo props. Domain. Employees. Stage. Contract status. Revenue. Archived at. Created by. Updated by. Logo asset. ### Scopes `customers:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "example-value", description_html: "

Example content

", description_stripped: "Example description", email: "Example Name", website_url: "https://example.com/resource", logo_props: "example-value", domain: "Example Name", employees: 1, stage: "Example Name", contract_status: "Example Name", revenue: "Example Name", archived_at: "2024-01-01T00:00:00Z", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", logo_asset: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "deleted_at": "2024-01-01T00:00:00Z", "customer_request_count": 1, "logo_url": "Example Name", "description_html": "

Example content

", "description_stripped": "Example description", "description_binary": "Example description", "email": "Example Name" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/link-work-items-to-customer.html description: >- Link work items to customer via Plane API. HTTP request format, parameters, scopes, and example responses for link work items to customer. --- # Link work items to customer Link one or more issues to a customer, optionally within a specific customer request. ### Path Parameters The unique identifier of the customer. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Array of issue IDs to link ### Scopes `customers.work_items:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issue_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/", headers={"X-API-Key": "your-api-key"}, json={ "issue_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issue_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), }); const data = await response.json(); ``` ```json { "detail": "Issues linked successfully" } ``` --- --- url: 'https://developers.plane.so/api-reference/customer/list-customers.html' description: >- List all customers via Plane API. HTTP request format, parameters, scopes, and example responses for list all customers. --- # List all customers List all customers in a workspace with optional search filtering ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/customer/get-customer-detail.html' description: >- Retrieve a customer via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a customer. --- # Retrieve a customer Get a specific customer by ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "deleted_at": "2024-01-01T00:00:00Z", "customer_request_count": 1, "logo_url": "Example Name", "description_html": "

Example content

", "description_stripped": "Example description", "description_binary": "Example description", "email": "Example Name" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/list-customer-work-items.html description: >- List all customer work items via Plane API. HTTP request format, parameters, scopes, and example responses for list all customer work items. --- # List all customer work items List all issues linked to a customer, with filtering by request ### Path Parameters The unique identifier of the customer. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "detail": "Customer issues retrieved successfully" } ``` --- --- url: 'https://developers.plane.so/api-reference/customer/update-customer-detail.html' description: >- Update a customer via Plane API. HTTP request format, parameters, scopes, and example responses for update a customer. --- # Update a customer Update an existing customer with the provided fields. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Description stripped. Email. Website url. Logo props. Domain. Employees. Stage. Contract status. Revenue. Archived at. Created by. Updated by. Logo asset. ### Scopes `customers:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "example-value", description_html: "

Example content

", description_stripped: "Example description", email: "Example Name", website_url: "https://example.com/resource", logo_props: "example-value", domain: "Example Name", employees: 1, stage: "Example Name", contract_status: "Example Name", revenue: "Example Name", archived_at: "2024-01-01T00:00:00Z", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", logo_asset: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "deleted_at": "2024-01-01T00:00:00Z", "customer_request_count": 1, "logo_url": "Example Name", "description_html": "

Example content

", "description_stripped": "Example description", "description_binary": "Example description", "email": "Example Name" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/unlink-work-item-from-customer.html description: >- Unlink work item from customer via Plane API. HTTP request format, parameters, scopes, and example responses for unlink work item from customer. --- # Unlink work item from customer Remove the link between an issue and a customer/customer request. ### Path Parameters The unique identifier of the customer. The unique identifier of the work item. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.work_items:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/work-item-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/work-item-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/issues/work-item-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/customer/delete-customer.html' description: >- Delete a customer via Plane API. HTTP request format, parameters, scopes, and example responses for delete a customer. --- # Delete a customer Permanently delete a customer from the workspace. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/customer/add-customer-property.html' description: >- Create a customer property via Plane API. HTTP request format, parameters, scopes, and example responses for create a customer property. --- # Create a customer property Create a new customer property in the specified workspace. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Display name. Description. Logo props. Sort order. * `TEXT` - Text * `DATETIME` - Datetime * `DECIMAL` - Decimal * `BOOLEAN` - Boolean * `OPTION` - Option * `RELATION` - Relation * `URL` - URL * `EMAIL` - Email * `FILE` - File - `ISSUE` - Issue - `USER` - User Is required. Default value. Settings. Is active. Is multi. Validation rules. External source. External id. Created by. Updated by. ### Scopes `customers.properties:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "display_name": "Example Name", "description": "Example description", "logo_props": "example-value", "sort_order": 1, "property_type": "TEXT", "relation_type": "ISSUE", "is_required": true, "default_value": [ "Example Name" ], "settings": "example-value", "is_active": true, "is_multi": true, "validation_rules": "example-value", "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/", headers={"X-API-Key": "your-api-key"}, json={ "display_name": "Example Name", "description": "Example description", "logo_props": "example-value", "sort_order": 1, "property_type": "TEXT", "relation_type": "ISSUE", "is_required": true, "default_value": [ "Example Name" ], "settings": "example-value", "is_active": true, "is_multi": true, "validation_rules": "example-value", "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ display_name: "Example Name", description: "Example description", logo_props: "example-value", sort_order: 1, property_type: "TEXT", relation_type: "ISSUE", is_required: true, default_value: ["Example Name"], settings: "example-value", is_active: true, is_multi: true, validation_rules: "example-value", external_source: "github", external_id: "550e8400-e29b-41d4-a716-446655440000", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "logo_props": "example-value", "sort_order": 1, "relation_type": "ISSUE", "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/list-customer-properties.html description: >- List all customer properties via Plane API. HTTP request format, parameters, scopes, and example responses for list all customer properties. --- # List all customer properties List all customer properties in a workspace. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.properties:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/get-customer-property-detail.html description: >- Retrieve a customer property via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a customer property. --- # Retrieve a customer property Retrieve a specific customer property by ID. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.properties:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "logo_props": "example-value", "sort_order": 1, "relation_type": "ISSUE", "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/list-customer-property-values.html description: >- List all customer property values via Plane API. HTTP request format, parameters, scopes, and example responses for list all customer property values. --- # List all customer property values Retrieve all property values for a specific customer. ### Path Parameters The unique identifier of the customer. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.property_values:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "detail": "Customer property values retrieved successfully" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/get-customer-property-value.html description: >- Retrieve a customer property value via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a customer property value. --- # Retrieve a customer property value Retrieve values for a specific property of a customer. ### Path Parameters The unique identifier of the customer. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.property_values:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "detail": "Property values retrieved successfully" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/update-customer-property-detail.html description: >- Update a customer property via Plane API. HTTP request format, parameters, scopes, and example responses for update a customer property. --- # Update a customer property Update an existing customer property with the provided fields. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Display name. Description. Logo props. Sort order. * `TEXT` - Text * `DATETIME` - Datetime * `DECIMAL` - Decimal * `BOOLEAN` - Boolean * `OPTION` - Option * `RELATION` - Relation * `URL` - URL * `EMAIL` - Email * `FILE` - File - `ISSUE` - Issue - `USER` - User Is required. Default value. Settings. Is active. Is multi. Validation rules. External source. External id. Created by. Updated by. ### Scopes `customers.properties:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "display_name": "Example Name", "description": "Example description", "logo_props": "example-value", "sort_order": 1, "property_type": "TEXT", "relation_type": "ISSUE", "is_required": true, "default_value": [ "Example Name" ], "settings": "example-value", "is_active": true, "is_multi": true, "validation_rules": "example-value", "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "display_name": "Example Name", "description": "Example description", "logo_props": "example-value", "sort_order": 1, "property_type": "TEXT", "relation_type": "ISSUE", "is_required": true, "default_value": [ "Example Name" ], "settings": "example-value", "is_active": true, "is_multi": true, "validation_rules": "example-value", "external_source": "github", "external_id": "550e8400-e29b-41d4-a716-446655440000", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ display_name: "Example Name", description: "Example description", logo_props: "example-value", sort_order: 1, property_type: "TEXT", relation_type: "ISSUE", is_required: true, default_value: ["Example Name"], settings: "example-value", is_active: true, is_multi: true, validation_rules: "example-value", external_source: "github", external_id: "550e8400-e29b-41d4-a716-446655440000", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "name": "Example Name", "display_name": "Example Name", "description": "Example description", "property_type": "TEXT", "deleted_at": "2024-01-01T00:00:00Z", "logo_props": "example-value", "sort_order": 1, "relation_type": "ISSUE", "is_required": true } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/update-customer-property-value.html description: >- Update a customer property value via Plane API. HTTP request format, parameters, scopes, and example responses for update a customer property value. --- # Update a customer property value Update values for a specific property of a customer. ### Path Parameters The unique identifier of the customer. The unique identifier of the property. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Array of values for the property ### Scopes `customers.property_values:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "values": [ "Example Name" ] }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "values": [ "Example Name" ] } ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/property-values/property-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ values: ["Example Name"], }), } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/customer/delete-customer-property.html description: >- Delete a customer property via Plane API. HTTP request format, parameters, scopes, and example responses for delete a customer property. --- # Delete a customer property Permanently delete a customer property from the workspace. ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.properties:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customer-properties/property-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/customer/add-customer-request.html' description: >- Create a customer request via Plane API. HTTP request format, parameters, scopes, and example responses for create a customer request. --- # Create a customer request Create a new request for the specified customer. ### Path Parameters The unique identifier of the customer. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Link. Work item ids. ### Scopes `customers.requests:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "link": "https://example.com/resource", "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "link": "https://example.com/resource", "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "example-value", description_html: "

Example content

", link: "https://example.com/resource", work_item_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), }); const data = await response.json(); ``` ```json { "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: 'https://developers.plane.so/api-reference/customer/list-customer-requests.html' description: >- List all customer requests via Plane API. HTTP request format, parameters, scopes, and example responses for list all customer requests. --- # List all customer requests List all requests for a customer with optional search filtering ### Path Parameters The unique identifier of the customer. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.requests:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/get-customer-request-detail.html description: >- Retrieve a customer request via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a customer request. --- # Retrieve a customer request Get a specific customer request by ID ### Path Parameters The unique identifier of the customer. The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.requests:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/update-customer-request-detail.html description: >- Update a customer request via Plane API. HTTP request format, parameters, scopes, and example responses for update a customer request. --- # Update a customer request Update an existing customer request with the provided fields. ### Path Parameters The unique identifier of the customer. The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Name. Description. Description html. Link. Work item ids. ### Scopes `customers.requests:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "link": "https://example.com/resource", "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "link": "https://example.com/resource", "work_item_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Example Name", description: "example-value", description_html: "

Example content

", link: "https://example.com/resource", work_item_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json { "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "email": "Example Name", "website_url": "https://example.com/resource", "logo_props": "example-value", "domain": "Example Name", "employees": 1, "stage": "Example Name", "contract_status": "Example Name", "revenue": "Example Name", "archived_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "logo_asset": "550e8400-e29b-41d4-a716-446655440000" } ``` --- --- url: >- https://developers.plane.so/api-reference/customer/delete-customer-request.html description: >- Delete a customer request via Plane API. HTTP request format, parameters, scopes, and example responses for delete a customer request. --- # Delete a customer request Permanently delete a customer request and unlink any linked issue ### Path Parameters The unique identifier of the customer. The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `customers.requests:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/customers/customer-uuid/requests/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/teamspace/overview.html' description: >- Plane Teamspace API overview. Learn about endpoints, request/response format, and how to work with teamspace via REST API. --- # Overview Teamspaces allow you to organize teams, projects, and members within a workspace, providing a way to group related work and manage access at a team level. [Learn more about Teamspaces](https://docs.plane.so/core-concepts/workspaces/teamspaces) ## The Teamspace Object ### Attributes * `id` *uuid* Unique identifier for the teamspace. * `name` *string* **(required)** Name of the teamspace. * `description_json` *object* JSON representation of the teamspace description. * `description_html` *string* HTML-formatted description of the teamspace. * `description_stripped` *string* Stripped version of the HTML description. * `description_binary` *string* Binary representation of the description. * `logo_props` *object* Logo properties for the teamspace. * `lead` *uuid* ID of the user who leads the teamspace. * `workspace` *uuid* ID of the workspace containing the teamspace. * `created_at` *timestamp* Time at which the teamspace was created. * `updated_at` *timestamp* Time at which the teamspace was last updated. * `created_by` *uuid* ID of the user who created the teamspace. * `updated_by` *uuid* ID of the user who last updated the teamspace. ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "name": "Engineering Team", "description_html": "

Engineering team workspace

", "lead": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4" } ``` --- --- url: 'https://developers.plane.so/api-reference/teamspace/add-teamspace.html' description: >- Create a teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for create a teamspace. --- # Create a teamspace Create a new teamspace in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Logo props. Name. Description json. Description html. Description stripped. Created by. Updated by. Lead. ### Scopes `teamspaces:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "logo_props": "example-value", "name": "Example Name", "description_json": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/", headers={"X-API-Key": "your-api-key"}, json={ "logo_props": "example-value", "name": "Example Name", "description_json": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ logo_props: "example-value", name: "Example Name", description_json: "example-value", description_html: "

Example content

", description_stripped: "Example description", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: 'https://developers.plane.so/api-reference/teamspace/list-teamspaces.html' description: >- List all teamspaces via Plane API. HTTP request format, parameters, scopes, and example responses for list all teamspaces. --- # List all teamspaces List all teamspaces in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `teamspaces:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "created_at": "2024-01-01T00:00:00Z" } ] } ] ``` --- --- url: 'https://developers.plane.so/api-reference/teamspace/get-teamspace-detail.html' description: >- Retrieve a teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a teamspace. --- # Retrieve a teamspace Retrieve a teamspace by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Scopes `teamspaces:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: >- https://developers.plane.so/api-reference/teamspace/update-teamspace-detail.html description: >- Update a teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for update a teamspace. --- # Update a teamspace Update a teamspace by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Body Parameters Logo props. Name. Description json. Description html. Description stripped. Created by. Updated by. Lead. ### Scopes `teamspaces:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "logo_props": "example-value", "name": "Example Name", "description_json": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "logo_props": "example-value", "name": "Example Name", "description_json": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000", "lead": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ logo_props: "example-value", name: "Example Name", description_json: "example-value", description_html: "

Example content

", description_stripped: "Example description", created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", lead: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description" } ``` --- --- url: 'https://developers.plane.so/api-reference/teamspace/delete-teamspace.html' description: >- Delete a teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for delete a teamspace. --- # Delete a teamspace Delete a teamspace by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Scopes `teamspaces:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/teamspace/list-teamspace-members.html description: >- List all teamspace members via Plane API. HTTP request format, parameters, scopes, and example responses for list all teamspace members. --- # List all teamspace members List all members in a teamspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Query Parameters Pagination cursor for getting next set of results Number of results per page (default: 20, max: 100) ### Scopes `teamspaces.members:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/?cursor=20:1:0&per_page=20" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/?cursor=20:1:0&per_page=20", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/?cursor=20:1:0&per_page=20", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "created_at": "2024-01-01T00:00:00Z" } ] } ``` --- --- url: 'https://developers.plane.so/api-reference/teamspace/add-teamspace-members.html' description: >- Add members to teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for add members to teamspace. --- # Add members to teamspace Add members to a teamspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Body Parameters Member ids. ### Scopes `teamspaces.members:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "member_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/", headers={"X-API-Key": "your-api-key"}, json={ "member_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ member_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png", "avatar_url": "https://example.com/assets/example-image.png", "display_name": "Example Name" } ``` --- --- url: >- https://developers.plane.so/api-reference/teamspace/remove-teamspace-members.html description: >- Remove members from teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for remove members from teamspace. --- # Remove members from teamspace Delete members from a teamspace by its ID ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Scopes `teamspaces.members:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/members/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/teamspace/list-teamspace-projects.html description: >- List teamspace projects via Plane API. HTTP request format, parameters, scopes, and example responses for list teamspace projects. --- # List teamspace projects List all projects in a teamspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Scopes `teamspaces.projects:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2 } ] } ``` --- --- url: >- https://developers.plane.so/api-reference/teamspace/add-projects-to-teamspace.html description: >- Add projects to teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for add projects to teamspace. --- # Add projects to teamspace Add projects to a teamspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Body Parameters Project ids. ### Scopes `teamspaces.projects:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "project_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", headers={"X-API-Key": "your-api-key"}, json={ "project_ids": [ "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ project_ids: ["550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description": "Example description", "identifier": "PROJ-123", "network": 2, "project_lead": "550e8400-e29b-41d4-a716-446655440000", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/teamspace/remove-projects-from-teamspace.html description: >- Remove projects from teamspace via Plane API. HTTP request format, parameters, scopes, and example responses for remove projects from teamspace. --- # Remove projects from teamspace Remove projects from a teamspace by its ID ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. The unique identifier of the teamspace. ### Scopes `teamspaces.projects:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/teamspaces/teamspace-uuid/projects/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/sticky/overview.html' description: >- Plane Sticky API overview. Learn about endpoints, request/response format, and how to work with sticky via REST API. --- # Overview Stickies are workspace-level notes that allow you to capture quick thoughts, ideas, or important reminders. [Learn more about Stickies](https://docs.plane.so/core-concepts/stickies) ## The Sticky Object ### Attributes * `id` *uuid* Unique identifier for the sticky * `name` *string* Name of the sticky * `description` *object* JSON description of the sticky * `description_html` *string* HTML description of the sticky * `description_stripped` *string* Stripped version of the HTML description * `description_binary` *string* Binary description of the sticky * `logo_props` *object* Logo properties for the sticky * `color` *string* Color of the sticky * `background_color` *string* Background color of the sticky * `workspace` *uuid* Workspace UUID which is automatically saved * `owner` *uuid* User ID of the sticky owner * `sort_order` *number* Sort order for the sticky * `created_at` *timestamp* The timestamp when the sticky was created * `updated_at` *timestamp* The timestamp when the sticky was last updated * `created_by` *uuid* ID of the user who created the sticky * `updated_by` *uuid* ID of the user who last updated the sticky ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "created_at": "2023-11-19T11:56:55.176802Z", "updated_at": "2023-11-19T11:56:55.176809Z", "name": "Important Note", "description": {}, "description_html": "

This is an important note

", "color": "#FF5733", "background_color": "#FFF9E6", "sort_order": 1000.0, "workspace": "cd4ab5a2-1a5f-4516-a6c6-8da1a9fa5be4", "owner": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" } ``` --- --- url: 'https://developers.plane.so/api-reference/sticky/add-sticky.html' description: >- Create a sticky via Plane API. HTTP request format, parameters, scopes, and example responses for create a sticky. --- # Create a sticky Create a new sticky in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Deleted at. Name. Description. Description html. Description stripped. Logo props. Color. Background color. Sort order. Created by. Updated by. ### Scopes `stickies:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "deleted_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "logo_props": "example-value", "color": "Example Name", "background_color": "Example Name", "sort_order": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/", headers={"X-API-Key": "your-api-key"}, json={ "deleted_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "logo_props": "example-value", "color": "Example Name", "background_color": "Example Name", "sort_order": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/stickies/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ deleted_at: "2024-01-01T00:00:00Z", name: "Example Name", description: "example-value", description_html: "

Example content

", description_stripped: "Example description", logo_props: "example-value", color: "Example Name", background_color: "Example Name", sort_order: 1, created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/sticky/list-stickies.html' description: >- List all stickies via Plane API. HTTP request format, parameters, scopes, and example responses for list all stickies. --- # List all stickies List all stickies in the workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `stickies:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/stickies/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json [ { "grouped_by": "state", "sub_grouped_by": "priority", "total_count": 150, "next_cursor": "20:1:0", "prev_cursor": "20:0:0", "next_page_results": true, "prev_page_results": false, "count": 20, "total_pages": 8, "total_results": 150, "extra_stats": null, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z" } ] } ] ``` --- --- url: 'https://developers.plane.so/api-reference/sticky/get-sticky-detail.html' description: >- Retrieve a sticky via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve a sticky. --- # Retrieve a sticky Retrieve a sticky by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `stickies:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/sticky/update-sticky-detail.html' description: >- Update a sticky via Plane API. HTTP request format, parameters, scopes, and example responses for update a sticky. --- # Update a sticky Update a sticky by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Deleted at. Name. Description. Description html. Description stripped. Logo props. Color. Background color. Sort order. Created by. Updated by. ### Scopes `stickies:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "deleted_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "logo_props": "example-value", "color": "Example Name", "background_color": "Example Name", "sort_order": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "deleted_at": "2024-01-01T00:00:00Z", "name": "Example Name", "description": "example-value", "description_html": "

Example content

", "description_stripped": "Example description", "logo_props": "example-value", "color": "Example Name", "background_color": "Example Name", "sort_order": 1, "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ deleted_at: "2024-01-01T00:00:00Z", name: "Example Name", description: "example-value", description_html: "

Example content

", description_stripped: "Example description", logo_props: "example-value", color: "Example Name", background_color: "Example Name", sort_order: 1, created_by: "550e8400-e29b-41d4-a716-446655440000", updated_by: "550e8400-e29b-41d4-a716-446655440000", }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "description_html": "

Example content

", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/sticky/delete-sticky.html' description: >- Delete a sticky via Plane API. HTTP request format, parameters, scopes, and example responses for delete a sticky. --- # Delete a sticky Delete a sticky by its ID ### Path Parameters The unique identifier of the resource. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `stickies:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/stickies/resource-id-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/workspace-features/overview.html' description: >- Plane Workspace Features API overview. Learn how to inspect and update workspace feature flags with the Plane API. --- # Overview Workspace features control which major Plane capabilities are enabled for a workspace. [Learn more about using the Plane API](https://developers.plane.so/api-reference/introduction) ## The Workspace Feature Object ### Attributes * `project_grouping` *boolean* Project grouping. * `initiatives` *boolean* Initiatives. * `teams` *boolean* Teams. * `customers` *boolean* Customers. * `wiki` *boolean* Wiki. * `pi` *boolean* Pi. ```json { "project_grouping": true, "initiatives": true, "teams": true, "customers": true, "wiki": true, "pi": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-features/get-workspace-features.html description: >- Get workspace features via Plane API. HTTP request format, parameters, scopes, and example responses for get workspace features. --- # Get workspace features Get the features of a workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `workspaces.features:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/features/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/features/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/features/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "project_grouping": true, "initiatives": true, "teams": true, "customers": true, "wiki": true, "pi": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-features/update-workspace-features.html description: >- Update workspace features via Plane API. HTTP request format, parameters, scopes, and example responses for update workspace features. --- # Update workspace features Update the features of a workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Project grouping. Initiatives. Teams. Customers. Wiki. Pi. ### Scopes `workspaces.features:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/features/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "project_grouping": true, "initiatives": true, "teams": true, "customers": true, "wiki": true, "pi": true }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/features/", headers={"X-API-Key": "your-api-key"}, json={ "project_grouping": true, "initiatives": true, "teams": true, "customers": true, "wiki": true, "pi": true } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/features/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ project_grouping: true, initiatives: true, teams: true, customers: true, wiki: true, pi: true, }), }); const data = await response.json(); ``` ```json { "project_grouping": true, "initiatives": true, "teams": true, "customers": true, "wiki": true, "pi": true } ``` --- --- url: 'https://developers.plane.so/api-reference/workspace-invitations/overview.html' description: >- Plane Workspace Invitations API overview. Learn how to create and manage workspace invitations with the Plane API. --- # Overview Workspace invitations let admins invite users to join a workspace with specific access settings. [Learn more about Members](https://developers.plane.so/api-reference/members/overview) ## The Workspace Invitation Object ### Attributes * `id` *string* Id. * `email` *string* Email. * `role` *integer* * `20` - Admin - `15` - Member - `5` - Guest * `created_at` *string* Created at. * `updated_at` *string* Updated at. * `responded_at` *string* Responded at. * `accepted` *boolean* Accepted. ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "Example Name", "role": 20, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "responded_at": "2024-01-01T00:00:00Z", "accepted": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-invitations/add-workspace-invitation.html description: >- Create workspace invitation via Plane API. HTTP request format, parameters, scopes, and example responses for create workspace invitation. --- # Create workspace invitation Create a workspace invite ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Email. * `20` - Admin * `15` - Member * `5` - Guest ### Scopes Workspace admin or owner permission required. ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "Example Name", "role": 20 }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/", headers={"X-API-Key": "your-api-key"}, json={ "email": "Example Name", "role": 20 } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/invitations/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ email: "Example Name", role: 20, }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "Example Name", "role": 20, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "responded_at": "2024-01-01T00:00:00Z", "accepted": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-invitations/list-workspace-invitations.html description: >- List workspace invitations via Plane API. HTTP request format, parameters, scopes, and example responses for list workspace invitations. --- # List workspace invitations List all workspace invites for a workspace ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes Workspace admin or owner permission required. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/invitations/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "Example Name", "role": 20, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "responded_at": "2024-01-01T00:00:00Z", "accepted": true } ] ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-invitations/get-workspace-invitation-detail.html description: >- Get workspace invitation via Plane API. HTTP request format, parameters, scopes, and example responses for get workspace invitation. --- # Get workspace invitation Get a workspace invite by ID ### Path Parameters The unique identifier of the invitation. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes Workspace admin or owner permission required. ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "Example Name", "role": 20, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "responded_at": "2024-01-01T00:00:00Z", "accepted": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-invitations/update-workspace-invitation.html description: >- Update workspace invitation via Plane API. HTTP request format, parameters, scopes, and example responses for update workspace invitation. --- # Update workspace invitation Update a workspace invite ### Path Parameters The unique identifier of the invitation. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Email. * `20` - Admin * `15` - Member * `5` - Guest ### Scopes Workspace admin or owner permission required. ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "Example Name", "role": 20 }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "email": "Example Name", "role": 20 } ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ email: "Example Name", role: 20, }), }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "Example Name", "role": 20, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "responded_at": "2024-01-01T00:00:00Z", "accepted": true } ``` --- --- url: >- https://developers.plane.so/api-reference/workspace-invitations/delete-workspace-invitation.html description: >- Delete workspace invitation via Plane API. HTTP request format, parameters, scopes, and example responses for delete workspace invitation. --- # Delete workspace invitation Delete a workspace invite ### Path Parameters The unique identifier of the invitation. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes Workspace admin or owner permission required. ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/invitations/invitation-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/members/overview.html' description: >- Plane Members API overview. Learn about endpoints, request/response format, and how to work with members via REST API. --- # Overview Members represent users who belong to a workspace or project. The Members API allows you to retrieve information about workspace and project members. [Learn more about Members](https://docs.plane.so/core-concepts/workspaces/members) ## The Members Object ### Attributes * `id` *string* Unique identifier for the Member * `first_name` *string* First name of the Member * `last_name` *string* Last name of the Member * `email` *string* Email address of the Member * `avatar` *string* Optional avatar image file reference * `avatar_url` *string* Publicly accessible URL for the avatar image * `display_name` *string* Display name shown across the application * `role` *integer* Role of the Member in the Workspace or Project ```json { "id": "00000000-0000-0000-0000-000000000001", "first_name": "User", "last_name": "One", "email": "user1@example.com", "avatar": "", "avatar_url": null, "display_name": "user1", "role": 15 } ``` --- --- url: 'https://developers.plane.so/api-reference/members/get-workspace-members.html' description: >- Get all workspace members via Plane API. HTTP request format, parameters, scopes, and example responses for get all workspace members. --- # Get all workspace members Retrieve all users who are members of the specified workspace. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `workspaces.members:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/members/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/members/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/members/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "display_name": "Example Name", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png", "role": 20 }, { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "Jane", "last_name": "Smith", "display_name": "Example Name", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png", "role": 15 } ] ``` --- --- url: 'https://developers.plane.so/api-reference/members/get-project-members.html' description: >- List all project members via Plane API. HTTP request format, parameters, scopes, and example responses for list all project members. --- # List all project members Retrieve all users who are members of the specified project. ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.members:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json [ [ { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "display_name": "Example Name", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png" }, { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "Jane", "last_name": "Smith", "display_name": "Example Name", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png" } ] ] ``` --- --- url: 'https://developers.plane.so/api-reference/members/add-project-member.html' description: >- Create project member via Plane API. HTTP request format, parameters, scopes, and example responses for create project member. --- # Create project member Create a new project member ### Path Parameters The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Member. * `20` - Admin * `15` - Member * `5` - Guest ### Scopes `projects.members:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/", headers={"X-API-Key": "your-api-key"}, json={ "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ member: "550e8400-e29b-41d4-a716-446655440000", role: 20, }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 } ``` --- --- url: >- https://developers.plane.so/api-reference/members/get-project-member-detail.html description: >- Get project member via Plane API. HTTP request format, parameters, scopes, and example responses for get project member. --- # Get project member Retrieve a project member by ID. ### Path Parameters The unique identifier of the member. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.members:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 } ``` --- --- url: 'https://developers.plane.so/api-reference/members/update-project-member.html' description: >- Update project member via Plane API. HTTP request format, parameters, scopes, and example responses for update project member. --- # Update project member Update a project member ### Path Parameters The unique identifier of the member. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Member. * `20` - Admin * `15` - Member * `5` - Guest ### Scopes `projects.members:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", headers={"X-API-Key": "your-api-key"}, json={ "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ member: "550e8400-e29b-41d4-a716-446655440000", role: 20, }), } ); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "member": "550e8400-e29b-41d4-a716-446655440000", "role": 20 } ``` --- --- url: 'https://developers.plane.so/api-reference/members/delete-project-member.html' description: >- Delete project member via Plane API. HTTP request format, parameters, scopes, and example responses for delete project member. --- # Delete project member Delete a project member ### Path Parameters The unique identifier of the member. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `projects.members:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/project-members/member-uuid/", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/members/remove-workspace-member.html' description: >- Remove workspace member via Plane API. HTTP request format, parameters, scopes, and example responses for remove workspace member. --- # Remove workspace member Remove a member from the workspace, deactivate them from all projects, and reduce the seat count. ### Path Parameters The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Scopes `workspaces.members:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/members/remove/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/members/remove/", headers={"X-API-Key": "your-api-key"} ) print(response.status_code) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/workspaces/my-workspace/members/remove/", { method: "POST", headers: { "X-API-Key": "your-api-key", }, }); console.log(response.status); ``` No response body. --- --- url: 'https://developers.plane.so/api-reference/user/overview.html' description: >- Plane User API overview. Learn about endpoints, request/response format, and how to work with user via REST API. --- # Overview Users represent the people who use Plane. The Users API allows you to retrieve information about the current authenticated user. ## The User Object ### Attributes * `id` *uuid* Unique identifier for the user * `first_name` *string* First name of the user * `last_name` *string* Last name of the user * `email` *string* Email address of the user * `avatar` *string* Avatar identifier for the user * `avatar_url` *string* URL of the user's avatar image * `display_name` *string* Display name of the user ```json { "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "avatar": "avatar-123", "avatar_url": "https://example.com/avatars/avatar-123.png", "display_name": "John Doe" } ``` --- --- url: 'https://developers.plane.so/api-reference/user/get-current-user.html' description: >- Retrieve current user via Plane API. HTTP request format, parameters, scopes, and example responses for retrieve current user. --- # Retrieve current user Retrieve the authenticated user's profile information including basic details. ### Scopes `profile:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/users/me/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/users/me/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch("https://api.plane.so/api/v1/users/me/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, }); const data = await response.json(); ``` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "email": "user@example.com", "avatar": "https://example.com/assets/example-image.png", "avatar_url": "https://example.com/assets/example-image.png", "display_name": "Example Name" } ``` --- --- url: 'https://developers.plane.so/dev-tools/agents/building-an-agent.html' description: >- Step-by-step guide to creating a Plane agent, including OAuth setup, webhook handling, and activity creation. --- # Building an agent ::: info Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. ::: ## Prerequisites Before building an agent, make sure you have completed the following: 1. **Build a Plane app** — Follow the [Build a Plane App](/dev-tools/build-plane-app/overview) guide to understand OAuth flows, deployment, and webhook handling. 2. **Get your bot token** — Complete the [Bot Token Flow](/dev-tools/build-plane-app/choose-token-flow) to obtain a `bot_token` for your agent. This token is used for all API calls. 3. **Set up webhook handling** — Ensure your server can [receive and verify webhooks](/dev-tools/build-plane-app/webhooks) from Plane. ::: info This guide assumes you have a working OAuth app with webhook handling. If not, complete the [Build a Plane App](/dev-tools/build-plane-app/overview) guide first. ::: ## Creating an agent Building a Plane agent involves three main steps: 1. Create an OAuth application with agent capabilities enabled 2. Implement the OAuth flow to install your agent in workspaces 3. Handle webhooks and create activities to respond to users ### OAuth app creation To create an agent, you first need to [register an OAuth application](/dev-tools/build-plane-app/create-oauth-application) with the **Enable App Mentions** checkbox enabled. 1. Navigate to `https://app.plane.so//settings/integrations/` 2. Click on **Build your own** button 3. Fill out the required details: * **Setup URL**: The URL users are redirected to when installing your app * **Redirect URIs**: Where Plane sends the authorization code after consent * **Webhook URL Endpoint**: Your service's webhook endpoint for receiving events 4. **Enable the "Enable App Mentions" checkbox** — This is required for agents 5. **Choose the "Agent Run" scopes** — This is required for agents to be able to create run activities and get run details. See [OAuth Scopes](/dev-tools/build-plane-app/oauth-scopes#agent-run-scopes) for more information on the available scopes. 6. Save and securely store your **Client ID** and **Client Secret** ::: info The "Enable App Mentions" checkbox is what transforms a regular OAuth app into an agent that can be @mentioned in work items. ::: ### Setting is mentionable When you enable app mentions during OAuth app creation, your application becomes mentionable in work item comments. This means: * Users will see your agent in the mention picker when typing `@` * Your agent can be assigned or delegated work items * Webhooks will be triggered when users interact with your agent After installation, your agent appears alongside workspace members in the mention autocomplete. ## Agent interaction Once your agent is installed via the [OAuth consent flow](/dev-tools/build-plane-app/choose-token-flow) and users start mentioning it, you need to handle the interactions through Agent Runs and Activities. ### AgentRun An **AgentRun** tracks a complete interaction session between a user and your agent. #### Key fields | Field | Type | Description | | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------ | | `id` | UUID | Unique identifier for the agent run | | `agent_user` | UUID | The bot user ID representing your agent | | `issue` | UUID | The work item where the interaction started | | `project` | UUID | The project containing the work item | | `workspace` | UUID | The workspace where the agent is installed | | `comment` | UUID | The comment thread for this run | | `source_comment` | UUID | The original comment that triggered the run | | `creator` | UUID | The user who initiated the run | | `status` | String | Current status (`created`, `in_progress`, `awaiting`, `completed`, `stopping`, `stopped`, `failed`, `stale`) | | `started_at` | DateTime | When the run started | | `ended_at` | DateTime | When the run ended (if applicable) | | `stopped_at` | DateTime | When a stop was requested | | `stopped_by` | UUID | User who requested the stop | | `external_link` | URL | Optional link to external dashboard/logs | | `error_metadata` | JSON | Error details if the run failed | | `type` | String | Type of run (currently `comment_thread`) | ### AgentRunActivity An **AgentRunActivity** represents a single message or action within an Agent Run. #### Key fields | Field | Type | Description | | ------------------ | ------- | ------------------------------------------------------------------------------------ | | `id` | UUID | Unique identifier for the activity | | `agent_run` | UUID | The parent Agent Run | | `type` | String | Activity type (`prompt`, `thought`, `action`, `response`, `elicitation`, `error`) | | `content` | JSON | The activity content (structure varies by type) | | `content_metadata` | JSON | Additional metadata about the content | | `ephemeral` | Boolean | If true, the activity is temporary and won't create a comment | | `signal` | String | Signal for how to handle the activity (`continue`, `stop`, `auth_request`, `select`) | | `signal_metadata` | JSON | Additional signal data | | `actor` | UUID | The user or bot that created the activity | | `comment` | UUID | Associated comment (for non-ephemeral activities) | ### Creating activities Your agent communicates back to users by creating activities. We recommend using the official SDKs which provide typed helpers and insulate your code from API changes. #### Install the SDK :::tabs key:language \== Node.js {#nodejs} ```bash npm install @makeplane/plane-node-sdk ``` \== Python {#python} ```bash pip install plane-sdk ``` ::: #### Activity examples :::tabs key:language \== TypeScript {#typescript} ```typescript import { PlaneClient } from "@makeplane/plane-node-sdk"; // Initialize the client with your bot token const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: botToken, }); // Create a thought activity (ephemeral - shows agent's reasoning) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "thought", content: { type: "thought", body: "Analyzing the user's request about weather data...", }, }); // Create an action activity (ephemeral - shows tool usage) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "action", content: { type: "action", action: "getWeather", parameters: { location: "San Francisco" }, }, }); // Create a response activity (creates a visible comment) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "response", content: { type: "response", body: "The weather in San Francisco is currently 68°F with partly cloudy skies.", }, signal: "continue", }); // Create an elicitation activity (asks user for input) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "elicitation", content: { type: "elicitation", body: "Which city would you like me to check the weather for?", }, }); // Create an error activity await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "error", content: { type: "error", body: "Unable to fetch weather data. Please try again later.", }, }); ``` \== Python {#python} ```python from plane import PlaneClient from plane.models.agent_runs import CreateAgentRunActivity # Initialize the client with your bot token plane_client = PlaneClient( base_url=os.getenv("PLANE_API_URL", "https://api.plane.so"), access_token=bot_token, ) # Create a thought activity (ephemeral - shows agent's reasoning) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="thought", content={ "type": "thought", "body": "Analyzing the user's request about weather data...", }, ), ) # Create an action activity (ephemeral - shows tool usage) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="action", content={ "type": "action", "action": "getWeather", "parameters": {"location": "San Francisco"}, }, ), ) # Create a response activity (creates a visible comment) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={ "type": "response", "body": "The weather in San Francisco is currently 68°F with partly cloudy skies.", }, signal="continue", ), ) # Create an elicitation activity (asks user for input) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="elicitation", content={ "type": "elicitation", "body": "Which city would you like me to check the weather for?", }, ), ) # Create an error activity plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="error", content={ "type": "error", "body": "Unable to fetch weather data. Please try again later.", }, ), ) ``` ::: ### Content payload types The `content` field structure varies based on the activity type: ::: details Thought Internal reasoning from the agent. Automatically marked as ephemeral. ```json { "type": "thought", "body": "The user is asking about weather data for their location." } ``` ::: ::: details Action A tool invocation. Automatically marked as ephemeral. You can include results after execution. ```json { "type": "action", "action": "searchDatabase", "parameters": { "query": "weather API", "limit": "10" } } ``` With result: ```json { "type": "action", "action": "searchDatabase", "parameters": { "query": "weather API", "result": "Found 3 matching records" } } ``` ::: ::: details Response A final response to the user. Creates a comment reply. ```json { "type": "response", "body": "Here's the weather forecast for San Francisco..." } ``` ::: ::: details Elicitation A question requesting user input. Creates a comment and sets run to `awaiting`. ```json { "type": "elicitation", "body": "Could you please specify which date range you're interested in?" } ``` ::: ::: details Error An error message. Creates a comment and sets run to `failed`. ```json { "type": "error", "body": "I encountered an error while processing your request." } ``` ::: ### Signals Signals provide additional context about how an activity should be interpreted: | Signal | Description | | -------------- | --------------------------------------------------------- | | `continue` | Default signal, indicates the conversation can continue | | `stop` | User requested to stop the agent run | | `auth_request` | Agent needs user to authenticate with an external service | | `select` | Agent is presenting options for user to select from | See [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for detailed information. ### Ephemeral activities Activities with `ephemeral: true` are temporary and don't create comments. They're useful for showing agent progress without cluttering the conversation. The following activity types are automatically marked as ephemeral: * `thought` * `action` * `error` Ephemeral activities are displayed temporarily in the UI and replaced when the next activity arrives. ## AgentRun webhooks Your agent receives webhooks when users interact with it. There are two main webhook events: ### AgentRun create webhook Triggered when a new Agent Run is created (user first mentions your agent). **Event:** `agent_run_create` **Payload:** ```json { "action": "created", "agent_run": { "id": "uuid", "agent_user": "uuid", "issue": "uuid", "project": "uuid", "workspace": "uuid", "status": "created", "type": "comment_thread", "started_at": "2025-01-15T10:30:00Z" }, "agent_user_id": "uuid", "app_client_id": "your-client-id", "issue_id": "uuid", "project_id": "uuid", "workspace_id": "uuid", "comment_id": "uuid", "type": "agent_run" } ``` ### AgentRun activity webhook Triggered when a user sends a prompt to your agent (initial mention or follow-up). **Event:** `agent_run_user_prompt` **Payload:** ```json { "action": "prompted", "agent_run_activity": { "id": "uuid", "agent_run": "uuid", "type": "prompt", "content": { "type": "prompt", "body": "What's the weather like in San Francisco?" }, "ephemeral": false, "signal": "continue", "actor": "uuid", "workspace": "uuid" }, "agent_run": { "id": "uuid", "agent_user": "uuid", "issue": "uuid", "project": "uuid", "workspace": "uuid", "status": "in_progress" }, "agent_user_id": "uuid", "app_client_id": "your-client-id", "comment_id": "uuid", "issue_id": "uuid", "project_id": "uuid", "workspace_id": "uuid", "type": "agent_run_activity" } ``` ### Handling webhooks Here's a complete example of handling agent webhooks using the SDKs: :::tabs key:language \== TypeScript {#typescript} ```typescript import { PlaneClient } from "@makeplane/plane-node-sdk"; interface AgentRunActivityWebhook { action: string; agent_run_activity: { id: string; content: { type: string; body?: string }; signal: string; }; agent_run: { id: string; status: string; }; workspace_id: string; project_id: string; type: string; } async function handleWebhook( webhook: AgentRunActivityWebhook, credentials: { bot_token: string; workspace_slug: string } ) { // Only handle agent_run_activity webhooks if (webhook.type !== "agent_run_activity") { return; } const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: credentials.bot_token, }); const agentRunId = webhook.agent_run.id; const userPrompt = webhook.agent_run_activity.content.body || ""; const signal = webhook.agent_run_activity.signal; // Check for stop signal if (signal === "stop") { await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "response", content: { type: "response", body: "Stopping as requested." }, }); return; } // Send initial thought (ephemeral - shows processing status) await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "thought", content: { type: "thought", body: "Processing your request..." }, }); // Process the request (implement your logic here) const response = await processUserRequest(userPrompt); // Send the response (creates a visible comment) await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "response", content: { type: "response", body: response }, }); } ``` \== Python {#python} ```python from plane import PlaneClient from plane.models.agent_runs import CreateAgentRunActivity def handle_webhook(webhook: dict, credentials: dict): """Handle incoming agent webhook.""" # Only handle agent_run_activity webhooks if webhook.get("type") != "agent_run_activity": return plane_client = PlaneClient( base_url=os.getenv("PLANE_API_URL", "https://api.plane.so"), access_token=credentials["bot_token"], ) agent_run_id = webhook["agent_run"]["id"] user_prompt = webhook["agent_run_activity"]["content"].get("body", "") signal = webhook["agent_run_activity"]["signal"] # Check for stop signal if signal == "stop": plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={"type": "response", "body": "Stopping as requested."}, ), ) return # Send initial thought (ephemeral - shows processing status) plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="thought", content={"type": "thought", "body": "Processing your request..."}, ), ) # Process the request (implement your logic here) response = process_user_request(user_prompt) # Send the response (creates a visible comment) plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={"type": "response", "body": response}, ), ) ``` ::: ## Next steps * Learn about [Best Practices](/dev-tools/agents/best-practices) for building responsive agents * Explore [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for advanced activity handling --- --- url: 'https://developers.plane.so/dev-tools/agents/best-practices.html' description: >- Guidelines for building responsive, user-friendly Plane agents that provide a seamless experience. Covers error handling, response patterns, and user communication. --- # Best practices ::: info Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. ::: ## Overview Building a great agent experience requires thoughtful design around responsiveness, error handling, and user communication. This guide covers best practices to ensure your agent feels native to Plane and provides a seamless experience for users. ## Sending immediate thought activity When your agent receives a webhook, users are waiting for a response. The most important best practice is to **acknowledge the request immediately**. ### Why immediate acknowledgment matters * Users see that your agent is active and processing their request * Prevents the Agent Run from being marked as `stale` (5-minute timeout) * Builds trust that the agent received and understood the request * Provides visual feedback during potentially long processing times ### Implementation Send a `thought` activity within the first few seconds of receiving a webhook: :::tabs key:language \== TypeScript {#typescript} ```typescript import { PlaneClient } from "@makeplane/plane-node-sdk"; async function handleWebhook( webhook: AgentRunActivityWebhook, credentials: { bot_token: string; workspace_slug: string } ) { const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: credentials.bot_token, }); const agentRunId = webhook.agent_run.id; // IMMEDIATELY acknowledge receipt await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "thought", content: { type: "thought", body: "Received your request. Analyzing..." }, }); // Now proceed with actual processing // This can take longer since user knows agent is working const result = await processRequest(webhook); // ... rest of the logic } ``` \== Python {#python} ```python from plane import PlaneClient from plane.models.agent_runs import CreateAgentRunActivity def handle_webhook(webhook: dict, credentials: dict): plane_client = PlaneClient( base_url=os.getenv("PLANE_API_URL", "https://api.plane.so"), access_token=credentials["bot_token"], ) agent_run_id = webhook["agent_run"]["id"] # IMMEDIATELY acknowledge receipt plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="thought", content={"type": "thought", "body": "Received your request. Analyzing..."}, ), ) # Now proceed with actual processing result = process_request(webhook) # ... rest of the logic ``` ::: ### Thought activity best practices * Keep thoughts concise but informative * Update thoughts as you progress through different stages * Use thoughts to explain what the agent is doing, not technical details **Good examples:** * "Analyzing your question about project timelines..." * "Searching for relevant work items..." * "Preparing response with the requested data..." **Avoid:** * "Initializing LLM context with temperature 0.7..." * "Executing database query SELECT \* FROM..." * Generic messages like "Working..." repeated multiple times ## Acknowledging important signals Signals communicate user intent beyond the message content. Your agent **must** handle the `stop` signal appropriately. ### The stop signal When a user wants to stop an agent run, Plane sends a `stop` signal with the activity. Your agent should: 1. **Recognize the signal immediately** 2. **Stop any ongoing processing** 3. **Send a confirmation response** :::tabs key:language \== TypeScript {#typescript} ```typescript async function handleWebhook( webhook: AgentRunActivityWebhook, credentials: { bot_token: string; workspace_slug: string } ) { const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: credentials.bot_token, }); const signal = webhook.agent_run_activity.signal; const agentRunId = webhook.agent_run.id; // ALWAYS check for stop signal first if (signal === "stop") { // Cancel any ongoing work cancelOngoingTasks(agentRunId); // Acknowledge the stop await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "response", content: { type: "response", body: "Understood. I've stopped processing your previous request.", }, }); return; // Exit early } // Continue with normal processing... } ``` \== Python {#python} ```python def handle_webhook(webhook: dict, credentials: dict): plane_client = PlaneClient( base_url=os.getenv("PLANE_API_URL", "https://api.plane.so"), access_token=credentials["bot_token"], ) signal = webhook["agent_run_activity"]["signal"] agent_run_id = webhook["agent_run"]["id"] # ALWAYS check for stop signal first if signal == "stop": # Cancel any ongoing work cancel_ongoing_tasks(agent_run_id) # Acknowledge the stop plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={ "type": "response", "body": "Understood. I've stopped processing your previous request.", }, ), ) return # Exit early # Continue with normal processing... ``` ::: ### Signal considerations | Signal | How to Handle | | ---------- | ----------------------------------------- | | `continue` | Default behavior, proceed with processing | | `stop` | Immediately halt and confirm | ## Progress communication For long-running tasks, keep users informed with progress updates. ### Multi-step operations When your agent performs multiple steps, send thought activities for each: ```typescript // Step 1: Acknowledge await createThought("Understanding your request..."); // Step 2: First action await createAction("searchDocuments", { query: userQuery }); const searchResults = await searchDocuments(userQuery); // Step 3: Processing await createThought("Found relevant information. Analyzing..."); // Step 4: Additional work await createAction("generateSummary", { data: searchResults }); const summary = await generateSummary(searchResults); // Step 5: Final response await createResponse(`Here's what I found: ${summary}`); ``` ### Avoiding information overload While progress updates are important, too many can be overwhelming: * **Don't** send a thought for every internal function call * **Do** send thoughts for user-meaningful milestones * **Don't** expose technical implementation details * **Do** explain what value is being created for the user ## Error handling Graceful error handling is crucial for a good user experience. ### Always catch and report errors ```typescript async function handleWebhook( webhook: AgentRunActivityWebhook, credentials: { bot_token: string; workspace_slug: string } ) { const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: credentials.bot_token, }); const agentRunId = webhook.agent_run.id; try { await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "thought", content: { type: "thought", body: "Processing your request..." }, }); // Your logic here... const result = await processRequest(webhook); await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "response", content: { type: "response", body: result }, }); } catch (error) { // ALWAYS inform the user about errors await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "error", content: { type: "error", body: getUserFriendlyErrorMessage(error), }, }); } } function getUserFriendlyErrorMessage(error: Error): string { // Map technical errors to user-friendly messages if (error.message.includes("rate limit")) { return "I'm receiving too many requests right now. Please try again in a few minutes."; } if (error.message.includes("timeout")) { return "The operation took too long. Please try a simpler request or try again later."; } // Generic fallback return "I encountered an unexpected error. Please try again or contact support if the issue persists."; } ``` ### Error message guidelines **Do:** * Use clear, non-technical language * Suggest next steps when possible * Be honest about what went wrong (at a high level) **Don't:** * Expose stack traces or technical details * Blame the user for errors * Leave users without any feedback ## Handling conversation context For multi-turn conversations, maintain context from previous activities. ### Fetching previous activities ```typescript // Get all activities for context const activities = await planeClient.agentRuns.activities.list(credentials.workspace_slug, agentRunId); // Build conversation history const history = activities.results .filter((a) => a.type === "prompt" || a.type === "response") .map((a) => ({ role: a.type === "prompt" ? "user" : "assistant", content: a.content.body, })); // Use history in your LLM call or logic const response = await processWithContext(newPrompt, history); ``` ### Context best practices * Retrieve relevant history, not every single activity * Filter to meaningful exchanges (prompts and responses) * Consider summarizing long histories to save tokens/processing * Don't assume infinite context availability ## Rate limiting and timeouts Be mindful of Plane's API limits and your own processing time. ### Stale run prevention Agent Runs are marked as `stale` after 5 minutes of inactivity. For long operations: ```typescript async function longRunningTask(agentRunId: string) { const HEARTBEAT_INTERVAL = 60000; // 1 minute const heartbeat = setInterval(async () => { await createThought("Still working on your request..."); }, HEARTBEAT_INTERVAL); try { const result = await performLongOperation(); return result; } finally { clearInterval(heartbeat); } } ``` ### Webhook response time * Return HTTP 200 from your webhook handler quickly (within seconds) * Process the actual agent logic asynchronously * Don't block the webhook response waiting for LLM calls ```typescript // Good: Respond immediately, process async app.post("/webhook", async (req, res) => { res.status(200).json({ received: true }); // Process in background processWebhookAsync(req.body).catch(console.error); }); ``` ## Summary checklist **Responsiveness** * Send thought within seconds of webhook * Return webhook response quickly * Send heartbeats for long operations **Signal handling** * Always check for `stop` signal first * Handle all signal types appropriately * Confirm when stopping **Error handling** * Wrap processing in try/catch * Always send error activity on failure * Use friendly error messages **User experience** * Progress updates for long tasks * Clear, non-technical communication * Maintain conversation context ## Next steps * Learn about [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for advanced activity handling * Review the [Building an Agent](/dev-tools/agents/building-an-agent) guide for implementation details --- --- url: 'https://developers.plane.so/dev-tools/agents/signals-content-payload.html' description: >- Detailed reference for activity signals and content payload structures in Plane agents. Includes signal types, payload formats, and content examples. --- # Signals and content payload ::: info Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. ::: ## Overview Agent activities consist of two key components: 1. **Content** — The message or action being communicated 2. **Signal** — Metadata indicating how the activity should be interpreted Understanding these components is essential for building agents that communicate effectively with users. ## Signals Signals are metadata that modify how an activity should be interpreted or handled. They provide additional context about the sender's intent—guiding how the activity should be processed or responded to. ### Available signals | Signal | Description | Use Case | | -------------- | -------------------------------------- | ---------------------------------------- | | `continue` | Default signal, indicates normal flow | Standard responses, ongoing conversation | | `stop` | User requested to stop the agent | Cancellation, abort operations | | `auth_request` | Agent needs external authentication | OAuth flows, API key collection | | `select` | Agent presenting options for selection | Multiple choice questions | ### Signal: `continue` The default signal for most activities. Indicates normal conversation flow where the agent can continue processing. ```json { "type": "response", "content": { "type": "response", "body": "Here's the information you requested." }, "signal": "continue" } ``` ### Signal: `stop` Sent by Plane when a user requests to stop the agent. Your agent should: 1. Immediately halt any ongoing processing 2. Clean up resources if needed 3. Send a confirmation response **Incoming webhook with stop signal:** ```json { "action": "prompted", "agent_run_activity": { "type": "prompt", "content": { "type": "prompt", "body": "Stop" }, "signal": "stop" } } ``` **Handling the stop signal:** ```typescript if (webhook.agent_run_activity.signal === "stop") { // Cancel ongoing work await cancelAllPendingTasks(); // Acknowledge the stop - this transitions run to "stopped" status await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "response", content: { type: "response", body: "I've stopped working on your request.", }, }); return; } ``` ### Signal: `auth_request` Used when your agent needs the user to authenticate with an external service. Requires a URL in the `signal_metadata`. **Creating an auth request:** ```typescript await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "elicitation", content: { type: "elicitation", body: "I need access to your GitHub account to proceed. Please authenticate using the link below.", }, signal: "auth_request", signal_metadata: { url: "https://your-agent.com/auth/github?session=abc123", }, }); ``` **Requirements:** * The URL must start with `https://` * The URL should be a secure endpoint on your agent's server * After authentication, redirect the user back or notify completion ### Signal: `select` Used when presenting options for the user to choose from. Useful for disambiguation or multi-choice scenarios. ```typescript await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "elicitation", content: { type: "elicitation", body: "Which project would you like me to search?\n\n1. Frontend App\n2. Backend API\n3. Mobile App", }, signal: "select", signal_metadata: { options: [ { id: "frontend", label: "Frontend App" }, { id: "backend", label: "Backend API" }, { id: "mobile", label: "Mobile App" }, ], }, }); ``` ## Content payload types The `content` field contains the actual message or action. Its structure varies based on the activity type. ### Type: `thought` Internal reasoning or progress updates from the agent. Automatically marked as ephemeral (won't create a comment). **Structure:** ```typescript interface ThoughtContent { type: "thought"; body: string; // The thought message } ``` **Example:** ```json { "type": "thought", "body": "The user is asking about deployment status. I'll check the CI/CD pipeline." } ``` **Creating a thought activity:** :::tabs key:language \== TypeScript {#typescript} ```typescript await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "thought", content: { type: "thought", body: "Analyzing the codebase for potential issues...", }, }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="thought", content={ "type": "thought", "body": "Analyzing the codebase for potential issues...", }, ), ) ``` ::: **Best practices for thoughts:** * Keep them concise and user-meaningful * Use to show progress, not internal implementation * Update as you move through stages of processing ### Type: `action` Describes a tool invocation or external action. Automatically marked as ephemeral. **Structure:** ```typescript interface ActionContent { type: "action"; action: string; // Name of the tool/action parameters: { // Key-value pairs of parameters [key: string]: string; }; } ``` **Example - Starting an action:** ```json { "type": "action", "action": "searchDatabase", "parameters": { "query": "bug reports", "status": "open" } } ``` **Example - Action with result:** ```json { "type": "action", "action": "searchDatabase", "parameters": { "query": "bug reports", "status": "open", "result": "Found 12 matching work items" } } ``` **Creating action activities:** :::tabs key:language \== TypeScript {#typescript} ```typescript // Before executing the action await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "action", content: { type: "action", action: "fetchWeather", parameters: { location: "San Francisco", }, }, }); // Execute the actual action const weatherData = await fetchWeather("San Francisco"); // After execution, report the result await planeClient.agentRuns.activities.create(credentials.workspace_slug, agentRunId, { type: "action", content: { type: "action", action: "fetchWeather", parameters: { location: "San Francisco", result: `Temperature: ${weatherData.temp}°F, Conditions: ${weatherData.conditions}`, }, }, content_metadata: { result: weatherData, // Store full result in metadata }, }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity # Before executing the action plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="action", content={ "type": "action", "action": "fetchWeather", "parameters": {"location": "San Francisco"}, }, ), ) # Execute the actual action weather_data = fetch_weather("San Francisco") # After execution, report the result plane_client.agent_runs.activities.create( workspace_slug=credentials["workspace_slug"], run_id=agent_run_id, data=CreateAgentRunActivity( type="action", content={ "type": "action", "action": "fetchWeather", "parameters": { "location": "San Francisco", "result": f"Temperature: {weather_data['temp']}°F, Conditions: {weather_data['conditions']}", }, }, content_metadata={"result": weather_data}, ), ) ``` ::: **Parameter requirements:** * All parameter keys must be strings * All parameter values must be strings * Use `content_metadata` to store complex/structured data ### Type: `response` A final response to the user. Creates a comment reply visible to users. **Structure:** ```typescript interface ResponseContent { type: "response"; body: string; // The response message (supports Markdown) } ``` **Example:** ```json { "type": "response", "body": "Based on my analysis, here are the top 3 issues affecting your sprint:\n\n1. **AUTH-123**: Login timeout affecting 15% of users\n2. **API-456**: Rate limiting too aggressive\n3. **UI-789**: Dashboard loading slowly\n\nWould you like me to provide more details on any of these?" } ``` **Creating a response:** :::tabs key:language \== TypeScript {#typescript} ```typescript await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "response", content: { type: "response", body: "Here's the weather in San Francisco:\n\n**68°F** - Partly Cloudy\n\nExpect mild conditions throughout the day.", }, signal: "continue", }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={ "type": "response", "body": "Here's the weather in San Francisco:\n\n**68°F** - Partly Cloudy\n\nExpect mild conditions throughout the day.", }, signal="continue", ), ) ``` ::: **Response best practices:** * Use Markdown for formatting * Be clear and concise * Include relevant context * End with a call-to-action if appropriate ### Type: `elicitation` Requests clarification or input from the user. Creates a comment and sets the Agent Run status to `awaiting`. **Structure:** ```typescript interface ElicitationContent { type: "elicitation"; body: string; // The question or request (supports Markdown) } ``` **Example:** ```json { "type": "elicitation", "body": "I found multiple projects matching your query. Which one would you like me to focus on?\n\n1. Project Alpha (12 open work items)\n2. Project Beta (8 open work items)\n3. Project Gamma (23 open work items)" } ``` **Creating an elicitation:** :::tabs key:language \== TypeScript {#typescript} ```typescript await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "elicitation", content: { type: "elicitation", body: "To generate the report, I need a few details:\n\n- What date range should I cover?\n- Should I include completed work items or only open ones?", }, }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="elicitation", content={ "type": "elicitation", "body": "To generate the report, I need a few details:\n\n- What date range should I cover?\n- Should I include completed work items or only open ones?", }, ), ) ``` ::: **Elicitation best practices:** * Ask specific, answerable questions * Provide options when possible * Don't ask too many questions at once * Consider using `select` signal for multiple choice ### Type: `error` Reports an error or failure. Creates a comment and sets the Agent Run status to `failed`. **Structure:** ```typescript interface ErrorContent { type: "error"; body: string; // The error message (supports Markdown) } ``` **Example:** ```json { "type": "error", "body": "I couldn't complete your request due to a connection issue with the external service. Please try again in a few minutes." } ``` **Creating an error activity:** :::tabs key:language \== TypeScript {#typescript} ```typescript await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "error", content: { type: "error", body: "I was unable to access the GitHub repository. Please ensure the integration is properly configured.", }, signal_metadata: { error_code: "GITHUB_ACCESS_DENIED", retryable: true, }, }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="error", content={ "type": "error", "body": "I was unable to access the GitHub repository. Please ensure the integration is properly configured.", }, signal_metadata={ "error_code": "GITHUB_ACCESS_DENIED", "retryable": True, }, ), ) ``` ::: **Error best practices:** * Use friendly, non-technical language * Suggest next steps when possible * Store detailed error info in `signal_metadata` * Don't expose stack traces or sensitive information ### Type: `prompt` This type is **user-generated only**. Your agent cannot create prompt activities—they're created by Plane when a user sends a message. **Structure:** ```typescript interface PromptContent { type: "prompt"; body: string; // The user's message } ``` **Example received in webhook:** ```json { "type": "prompt", "body": "Can you check the status of our deployment pipeline?" } ``` ## Ephemeral activities Ephemeral activities are temporary and won't create comment replies. They're useful for showing agent progress without cluttering the conversation thread. ### Automatically ephemeral types The following activity types are automatically marked as ephemeral: * `thought` * `action` * `error` ### Ephemeral behavior * Ephemeral activities appear temporarily in the Agent UI * They're replaced when the next activity arrives * They don't create permanent comment replies * Useful for real-time progress updates ### Visual example ``` User: @WeatherBot What's the weather in Tokyo? [Ephemeral - disappears when next activity arrives] Analyzing your request... [Ephemeral - disappears when next activity arrives] getCoordinates("Tokyo") [Ephemeral - disappears when next activity arrives] getWeather(35.6762, 139.6503) → 72°F, Clear [Permanent - stays as comment] The weather in Tokyo is currently 72°F with clear skies. ``` ## Content metadata Use `content_metadata` to store additional structured data about an activity: :::tabs key:language \== TypeScript {#typescript} ```typescript await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "action", content: { type: "action", action: "analyzeCode", parameters: { file: "src/index.ts", result: "Found 3 potential issues", }, }, content_metadata: { analysis_results: { issues: [ { line: 42, severity: "warning", message: "Unused variable" }, { line: 78, severity: "error", message: "Type mismatch" }, { line: 156, severity: "info", message: "Consider refactoring" }, ], processing_time_ms: 1250, }, }, }); ``` \== Python {#python} ```python from plane.models.agent_runs import CreateAgentRunActivity plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="action", content={ "type": "action", "action": "analyzeCode", "parameters": { "file": "src/index.ts", "result": "Found 3 potential issues", }, }, content_metadata={ "analysis_results": { "issues": [ {"line": 42, "severity": "warning", "message": "Unused variable"}, {"line": 78, "severity": "error", "message": "Type mismatch"}, {"line": 156, "severity": "info", "message": "Consider refactoring"}, ], "processing_time_ms": 1250, }, }, ), ) ``` ::: ## Signal metadata Use `signal_metadata` to provide additional context for signals: ```typescript // Auth request with URL { signal: "auth_request", signal_metadata: { url: "https://your-agent.com/auth/connect?session=xyz", provider: "github", scopes: ["repo", "read:user"], } } // Select with options { signal: "select", signal_metadata: { options: [ { id: "opt1", label: "Option 1", description: "First choice" }, { id: "opt2", label: "Option 2", description: "Second choice" }, ], allow_multiple: false, } } // Error with details { signal: "continue", signal_metadata: { error_code: "RATE_LIMIT_EXCEEDED", retry_after: 60, retryable: true, } } ``` ## Complete activity creation reference Here's a comprehensive example showing all activity types: :::tabs key:language \== TypeScript {#typescript} ```typescript import { PlaneClient } from "@makeplane/plane-node-sdk"; const planeClient = new PlaneClient({ baseUrl: process.env.PLANE_API_URL || "https://api.plane.so", accessToken: botToken, }); // 1. Thought - Show reasoning (ephemeral) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "thought", content: { type: "thought", body: "Analyzing the request..." }, }); // 2. Action - Tool invocation (ephemeral) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "action", content: { type: "action", action: "searchWorkItems", parameters: { query: "bug", status: "open" }, }, }); // 3. Response - Final answer (creates comment) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "response", content: { type: "response", body: "Found 5 open bugs." }, signal: "continue", }); // 4. Elicitation - Ask for input (creates comment, awaits response) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "elicitation", content: { type: "elicitation", body: "Which bug should I prioritize?" }, signal: "select", signal_metadata: { options: [ { id: "bug-1", label: "AUTH-123: Login timeout" }, { id: "bug-2", label: "API-456: Rate limiting" }, ], }, }); // 5. Error - Report failure (creates comment) await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { type: "error", content: { type: "error", body: "Unable to access the database." }, signal_metadata: { error_code: "DB_CONNECTION_FAILED" }, }); ``` \== Python {#python} ```python from plane import PlaneClient from plane.models.agent_runs import CreateAgentRunActivity plane_client = PlaneClient( base_url=os.getenv("PLANE_API_URL", "https://api.plane.so"), access_token=bot_token, ) # 1. Thought - Show reasoning (ephemeral) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="thought", content={"type": "thought", "body": "Analyzing the request..."}, ), ) # 2. Action - Tool invocation (ephemeral) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="action", content={ "type": "action", "action": "searchWorkItems", "parameters": {"query": "bug", "status": "open"}, }, ), ) # 3. Response - Final answer (creates comment) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="response", content={"type": "response", "body": "Found 5 open bugs."}, signal="continue", ), ) # 4. Elicitation - Ask for input (creates comment, awaits response) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="elicitation", content={"type": "elicitation", "body": "Which bug should I prioritize?"}, signal="select", signal_metadata={ "options": [ {"id": "bug-1", "label": "AUTH-123: Login timeout"}, {"id": "bug-2", "label": "API-456: Rate limiting"}, ], }, ), ) # 5. Error - Report failure (creates comment) plane_client.agent_runs.activities.create( workspace_slug=workspace_slug, run_id=agent_run_id, data=CreateAgentRunActivity( type="error", content={"type": "error", "body": "Unable to access the database."}, signal_metadata={"error_code": "DB_CONNECTION_FAILED"}, ), ) ``` ::: ## Next steps * Review [Best Practices](/dev-tools/agents/best-practices) for building responsive agents * See [Building an Agent](/dev-tools/agents/building-an-agent) for implementation examples * Check the [Overview](/dev-tools/agents/overview) for Agent Run lifecycle details --- --- url: >- https://developers.plane.so/dev-tools/build-plane-app/create-oauth-application.html description: >- Register your Plane OAuth application to get Client ID and Secret. Configure setup URL, redirect URI, and webhook endpoints for your integration. --- # Create an OAuth Application 1. Navigate to **Workspace Settings** → **Integrations**. ```text https://app.plane.so//settings/integrations/ ``` 2. Click **Build your own**. 3. Fill in the required details: | Field | Description | | ---------------- | ------------------------------------------------------------------------------------------------------ | | **App Name** | Display name shown to users | | **Setup URL** | Entry point when users install your app. Your app redirects users to Plane's consent screen from here. | | **Redirect URI** | Callback URL where Plane sends users after they approve access, along with the authorization code. | | **Webhook URL** | Endpoint for receiving event notifications | 4. For agents that respond to @mentions, enable **"Enable App Mentions"**. 5. Save and store your **Client ID** and **Client Secret** securely. 6. Select the scopes you need for your app from the **Scopes & Permissions** section. See [OAuth Scopes](/dev-tools/build-plane-app/oauth-scopes) for more information on the available scopes. ::: warning Never expose your Client Secret in client-side code or commit it to version control. ::: --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/choose-token-flow.html' description: >- Decide between Bot Token and User Token flows for your Plane app. Compare client credentials and authorization code grant types for different use cases. --- # Choose token flow Plane supports two OAuth flows: | Flow | Use When | Token Type | | ----------------------------------- | ---------------------------------------------- | -------------- | | **Bot Token** (Client Credentials) | Agents, webhooks, automation, background tasks | `bot_token` | | **User Token** (Authorization Code) | Actions on behalf of a specific user | `access_token` | ::: info Most integrations should use the **Bot Token flow**. Use User Token only when you need to perform actions as a specific user. ::: *** ## Bot token flow Use this flow for agents, webhook handlers, and automation that acts autonomously. ```mermaid sequenceDiagram participant User participant Plane participant YourApp User->>YourApp: Clicks "Install" YourApp->>Plane: Redirects to consent screen Plane->>User: Shows consent screen User->>Plane: Approves Plane->>YourApp: Redirects with app_installation_id YourApp->>Plane: POST /auth/o/token/ (client_credentials) Plane->>YourApp: Returns bot_token YourApp->>YourApp: Store credentials ``` ### 1. Redirect to authorization When a user clicks "Install", redirect them to Plane's consent screen: ``` GET https://api.plane.so/auth/o/authorize-app/ ?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=https://your-app.com/callback &scope=scopeA scopeB scopeC ``` ### 2. Handle the callback After the user approves, Plane redirects to your Redirect URI with: | Parameter | Description | | --------------------- | ----------------------------------------- | | `app_installation_id` | Unique identifier for this installation | | `code` | Authorization code (not used in bot flow) | ### 3. Exchange for bot token ``` POST https://api.plane.so/auth/o/token/ Content-Type: application/x-www-form-urlencoded Authorization: Basic base64(client_id:client_secret) grant_type=client_credentials &app_installation_id=APP_INSTALLATION_ID &scope=scopeA scopeB scopeC ``` **Response:** ```json { "access_token": "pln_bot_xxxxxxxxxxxx", "token_type": "Bearer", "expires_in": 86400, "scope": "scopeA scopeB scopeC" } ``` ### 4. Get workspace details ``` GET https://api.plane.so/auth/o/app-installation/?id=APP_INSTALLATION_ID Authorization: Bearer YOUR_BOT_TOKEN ``` **Response:** ```json [ { "id": "installation-uuid", "workspace": "workspace-uuid", "workspace_detail": { "name": "My Workspace", "slug": "my-workspace" }, "app_bot": "bot-user-uuid", "status": "installed" } ] ``` Store the `workspace_detail.slug` for API calls and `app_installation_id` for token refresh. ### 5. Refresh bot token Bot tokens expire. Request a new one using the stored `app_installation_id`: ``` POST https://api.plane.so/auth/o/token/ Content-Type: application/x-www-form-urlencoded Authorization: Basic base64(client_id:client_secret) grant_type=client_credentials &app_installation_id=APP_INSTALLATION_ID &scope=scopeA scopeB scopeC ``` *** ## User token flow Use this flow when your app needs to act on behalf of a specific user. ```mermaid sequenceDiagram participant User participant Plane participant YourApp User->>YourApp: Clicks "Connect" YourApp->>Plane: Redirects to consent screen Plane->>User: Shows consent screen User->>Plane: Approves Plane->>YourApp: Redirects with code YourApp->>Plane: POST /auth/o/token/ (authorization_code) Plane->>YourApp: Returns access_token + refresh_token YourApp->>YourApp: Store tokens for user ``` ### 1. Redirect to authorization ``` GET https://api.plane.so/auth/o/authorize-app/ ?client_id=YOUR_CLIENT_ID &response_type=code &redirect_uri=https://your-app.com/callback &state=RANDOM_STATE_VALUE &scope=scopeA scopeB scopeC ``` ::: info Include a random `state` parameter to prevent CSRF attacks. Verify it matches when handling the callback. ::: ### 2. Handle the callback After approval, Plane redirects to your Redirect URI with: | Parameter | Description | | --------- | ------------------------------------------ | | `code` | Authorization code to exchange for tokens | | `state` | Your state parameter (verify this matches) | ### 3. Exchange code for tokens ``` POST https://api.plane.so/auth/o/token/ Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTHORIZATION_CODE &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET &redirect_uri=https://your-app.com/callback ``` **Response:** ```json { "access_token": "pln_xxxxxxxxxxxx", "refresh_token": "pln_refresh_xxxxxxxxxxxx", "token_type": "Bearer", "expires_in": 86400, "scope": "scopeA scopeB scopeC" } ``` ### 4. Refresh user token ``` POST https://api.plane.so/auth/o/token/ Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=YOUR_REFRESH_TOKEN &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ``` *** ## Making API requests Include the token in the `Authorization` header: ``` GET https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/ Authorization: Bearer YOUR_TOKEN ``` See the [API Reference](/api-reference/introduction) for available endpoints. --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/webhooks.html' description: >- Receive and verify webhooks from Plane in your application. Learn about webhook headers, signature verification, event types, and payload handling. --- # Handling webhooks When events occur in Plane, webhooks are sent to your Webhook URL. ## Webhook headers | Header | Description | | ------------------- | ------------------------------------------- | | `X-Plane-Delivery` | Unique delivery ID | | `X-Plane-Event` | Event type (e.g., `issue`, `issue_comment`) | | `X-Plane-Signature` | HMAC-SHA256 signature for verification | ## Verify signature Always verify the `X-Plane-Signature` header: :::tabs key:language \== Python {#python} ```python import hmac import hashlib def verify_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) ``` \== TypeScript {#typescript} ```typescript import crypto from "crypto"; function verifySignature(payload: string, signature: string, secret: string): boolean { const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } ``` ::: ## Webhook payload ```json { "event": "issue", "action": "created", "webhook_id": "webhook-uuid", "workspace_id": "workspace-uuid", "data": { ... }, "activity": { "actor": { "id": "user-uuid", "display_name": "John Doe" } } } ``` See [Webhook Events](/dev-tools/intro-webhooks) for all event types. --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/oauth-scopes.html' description: >- Complete reference of all OAuth scopes available when building a Plane app. Includes read and write permissions for projects, work items, cycles, modules, and more. --- # OAuth scopes This document lists all OAuth scopes available when building a Plane app. Request only the scopes your app needs. ## Project scopes | Scope | Description | | ------------------------------------------- | -------------------------------------------- | | `projects:read` | Read projects | | `projects:write` | Create and update projects | | `projects.features:read` | Read project features | | `projects.features:write` | Create and update project features | | `projects.members:read` | Read project members | | `projects.members:write` | Manage project members | | `projects.states:read` | Read project states | | `projects.states:write` | Create and update project states | | `projects.labels:read` | Read project labels | | `projects.labels:write` | Create and update project labels | | `projects.intakes:read` | Read project intakes | | `projects.intakes:write` | Create and update project intakes | | `projects.epics:read` | Read project epics | | `projects.epics:write` | Create and update project epics | | `projects.cycles:read` | Read project cycles | | `projects.cycles:write` | Create and update project cycles | | `projects.pages:read` | Read project pages | | `projects.pages:write` | Create and update project pages | | `projects.modules:read` | Read project modules | | `projects.modules:write` | Create and update project modules | | `projects.work_items:read` | Read project work items | | `projects.work_items:write` | Create and update project work items | | `projects.work_items.comments:read` | Read work item comments | | `projects.work_items.comments:write` | Create and update work item comments | | `projects.work_items.attachments:read` | Read work item attachments | | `projects.work_items.attachments:write` | Create and update work item attachments | | `projects.work_items.links:read` | Read work item links | | `projects.work_items.links:write` | Create and update work item links | | `projects.work_items.relations:read` | Read work item relations | | `projects.work_items.relations:write` | Create and update work item relations | | `projects.work_items.activities:read` | Read work item activities | | `projects.work_items.activities:write` | Create and update work item activities | | `projects.work_items.worklogs:read` | Read work item worklogs | | `projects.work_items.worklogs:write` | Create and update work item worklogs | | `projects.work_item_types:read` | Read work item types | | `projects.work_item_types:write` | Create and update work item types | | `projects.work_item_properties:read` | Read work item properties | | `projects.work_item_properties:write` | Create and update work item properties | | `projects.work_item_property_options:read` | Read work item property options | | `projects.work_item_property_options:write` | Create and update work item property options | | `projects.work_item_property_values:read` | Read work item property values | | `projects.work_item_property_values:write` | Create and update work item property values | | `projects.milestones:read` | Read project milestones | | `projects.milestones:write` | Create and update project milestones | ## Wiki scopes | Scope | Description | | ------------------ | ---------------------------- | | `wiki.pages:read` | Read wiki pages | | `wiki.pages:write` | Create and update wiki pages | ## Customer scopes | Scope | Description | | --------------------------------- | ------------------------------------------ | | `customers:read` | Read customers | | `customers:write` | Create and update customers | | `customers.requests:read` | Read customer requests | | `customers.requests:write` | Create and update customer requests | | `customers.properties:read` | Read customer properties | | `customers.properties:write` | Create and update customer properties | | `customers.property_values:read` | Read customer property values | | `customers.property_values:write` | Create and update customer property values | | `customers.work_items:read` | Read customer work items | | `customers.work_items:write` | Create and update customer work items | ## Initiatives scopes | Scope | Description | | ---------------------------- | ------------------------------------- | | `initiatives:read` | Read initiatives | | `initiatives:write` | Create and update initiatives | | `initiatives.projects:read` | Read initiative projects | | `initiatives.projects:write` | Create and update initiative projects | | `initiatives.epics:read` | Read initiative epics | | `initiatives.epics:write` | Create and update initiative epics | | `initiatives.labels:read` | Read initiative labels | | `initiatives.labels:write` | Create and update initiative labels | ## Workspace scopes | Scope | Description | | --------------------------- | ------------------------------------ | | `workspaces.members:read` | Read workspace members | | `workspaces.members:write` | Manage workspace members | | `workspaces.features:read` | Read workspace features | | `workspaces.features:write` | Create and update workspace features | ## Stickies scopes | Scope | Description | | ---------------- | -------------------------- | | `stickies:read` | Read stickies | | `stickies:write` | Create and update stickies | ## Teamspaces scopes | Scope | Description | | --------------------------- | ------------------------------------ | | `teamspaces:read` | Read teamspaces | | `teamspaces:write` | Create and update teamspaces | | `teamspaces.projects:read` | Read teamspace projects | | `teamspaces.projects:write` | Create and update teamspace projects | | `teamspaces.members:read` | Read teamspace members | | `teamspaces.members:write` | Create and update teamspace members | ## Profile scopes | Scope | Description | | -------------- | ----------------- | | `profile:read` | Read user profile | ## Assets scopes | Scope | Description | | -------------- | ------------------------ | | `assets:read` | Read assets | | `assets:write` | Create and update assets | ## Agent Run scopes | Scope | Description | | ----------------------------- | -------------------------------------- | | `agents.runs:read` | Read agent runs | | `agents.runs:write` | Create and update agent runs | | `agents.run_activities:read` | Read agent run activities | | `agents.run_activities:write` | Create and update agent run activities | --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/sdks.html' description: >- Official Plane SDKs for Node.js and Python. Get typed API clients and OAuth helpers to build Plane integrations faster. --- # SDKs Official SDKs provide OAuth helpers and typed API clients: | Language | Package | | -------- | ------------------------------------------------------------------------------------ | | Node.js | [@makeplane/plane-node-sdk](https://www.npmjs.com/package/@makeplane/plane-node-sdk) | | Python | [plane-sdk](https://pypi.org/project/plane-sdk/) | ```bash npm install @makeplane/plane-node-sdk # or pip install plane-sdk ``` #### OAuth helper methods ::: code-group ```typescript [Node.js] import { OAuthClient } from "@makeplane/plane-node-sdk"; const oauth = new OAuthClient({ clientId: "your_client_id", clientSecret: "your_client_secret", redirectUri: "https://your-app.com/callback", }); // Generate authorization URL const authUrl = oauth.getAuthorizationUrl("code", "state"); // Exchange for bot token const token = await oauth.getBotToken(appInstallationId); // Exchange code for user token const userToken = await oauth.exchangeCodeForToken(code); // Refresh user token const newToken = await oauth.getRefreshToken(refreshToken); ``` ```python [Python] from plane.client import OAuthClient oauth = OAuthClient( client_id="your_client_id", client_secret="your_client_secret", ) # Generate authorization URL auth_url = oauth.get_authorization_url(redirect_uri="...", state="state") # Exchange for bot token token = oauth.get_client_credentials_token(app_installation_id=app_installation_id) # Exchange code for user token user_token = oauth.exchange_code(code=code, redirect_uri=redirect_uri) # Refresh user token new_token = oauth.refresh_token(refresh_token) ``` ::: --- --- url: 'https://developers.plane.so/dev-tools/build-plane-app/examples.html' description: >- Full code examples for building Plane OAuth apps with Node.js (Express) and Python (Flask). Includes bot token flow, user token flow, and webhook handling. --- # Complete examples ::: code-group ```typescript [TypeScript (Express)] import express from "express"; import axios from "axios"; import crypto from "crypto"; const app = express(); const CLIENT_ID = process.env.PLANE_CLIENT_ID!; const CLIENT_SECRET = process.env.PLANE_CLIENT_SECRET!; const REDIRECT_URI = process.env.PLANE_REDIRECT_URI!; const WEBHOOK_SECRET = process.env.PLANE_WEBHOOK_SECRET!; const PLANE_API_URL = process.env.PLANE_API_URL || "https://api.plane.so"; // In-memory storage (use a database in production) const installations = new Map< string, { botToken: string; workspaceSlug: string; appInstallationId: string; } >(); // Setup URL - redirect to Plane's consent screen app.get("/oauth/setup", (req, res) => { const params = new URLSearchParams({ client_id: CLIENT_ID, response_type: "code", redirect_uri: REDIRECT_URI, }); res.redirect(`${PLANE_API_URL}/auth/o/authorize-app/?${params}`); }); // OAuth callback - exchange app_installation_id for bot token app.get("/oauth/callback", async (req, res) => { const appInstallationId = req.query.app_installation_id as string; if (!appInstallationId) { return res.status(400).send("Missing app_installation_id"); } try { const basicAuth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"); // Exchange for bot token const tokenRes = await axios.post( `${PLANE_API_URL}/auth/o/token/`, new URLSearchParams({ grant_type: "client_credentials", app_installation_id: appInstallationId, }).toString(), { headers: { Authorization: `Basic ${basicAuth}`, "Content-Type": "application/x-www-form-urlencoded", }, } ); const botToken = tokenRes.data.access_token; // Get workspace details const installRes = await axios.get(`${PLANE_API_URL}/auth/o/app-installation/?id=${appInstallationId}`, { headers: { Authorization: `Bearer ${botToken}` }, }); const installation = installRes.data[0]; const workspaceId = installation.workspace; const workspaceSlug = installation.workspace_detail.slug; // Store credentials installations.set(workspaceId, { botToken, workspaceSlug, appInstallationId }); console.log(`Installed in workspace: ${workspaceSlug}`); res.send("Installation successful! You can close this window."); } catch (error) { console.error("OAuth error:", error); res.status(500).send("Installation failed"); } }); // Webhook handler app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-plane-signature"] as string; const payload = req.body.toString(); // Verify signature const expected = crypto.createHmac("sha256", WEBHOOK_SECRET).update(payload).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(signature || ""), Buffer.from(expected))) { return res.status(403).send("Invalid signature"); } const event = JSON.parse(payload); console.log(`Received: ${event.event} ${event.action}`); // Get credentials for this workspace const creds = installations.get(event.workspace_id); if (creds) { // Process the event with creds.botToken } res.status(200).send("OK"); }); app.listen(3000, () => console.log("Server running on http://localhost:3000")); ``` ```python [Python (Flask)] import os import hmac import hashlib import base64 import requests as http_requests from flask import Flask, request, redirect from urllib.parse import urlencode app = Flask(__name__) CLIENT_ID = os.getenv("PLANE_CLIENT_ID") CLIENT_SECRET = os.getenv("PLANE_CLIENT_SECRET") REDIRECT_URI = os.getenv("PLANE_REDIRECT_URI") WEBHOOK_SECRET = os.getenv("PLANE_WEBHOOK_SECRET") PLANE_API_URL = os.getenv("PLANE_API_URL", "https://api.plane.so") # In-memory storage (use a database in production) installations = {} @app.route("/oauth/setup") def oauth_setup(): """Redirect to Plane's consent screen.""" params = urlencode({ "client_id": CLIENT_ID, "response_type": "code", "redirect_uri": REDIRECT_URI, }) return redirect(f"{PLANE_API_URL}/auth/o/authorize-app/?{params}") @app.route("/oauth/callback") def oauth_callback(): """Exchange app_installation_id for bot token.""" app_installation_id = request.args.get("app_installation_id") if not app_installation_id: return "Missing app_installation_id", 400 try: # Exchange for bot token credentials = f"{CLIENT_ID}:{CLIENT_SECRET}" basic_auth = base64.b64encode(credentials.encode()).decode() token_response = http_requests.post( f"{PLANE_API_URL}/auth/o/token/", data={ "grant_type": "client_credentials", "app_installation_id": app_installation_id, }, headers={ "Authorization": f"Basic {basic_auth}", "Content-Type": "application/x-www-form-urlencoded", }, ) token_response.raise_for_status() bot_token = token_response.json()["access_token"] # Get workspace details install_response = http_requests.get( f"{PLANE_API_URL}/auth/o/app-installation/", params={"id": app_installation_id}, headers={"Authorization": f"Bearer {bot_token}"}, ) install_response.raise_for_status() installation = install_response.json()[0] workspace_id = installation["workspace"] workspace_slug = installation["workspace_detail"]["slug"] # Store credentials installations[workspace_id] = { "bot_token": bot_token, "workspace_slug": workspace_slug, "app_installation_id": app_installation_id, } print(f"Installed in workspace: {workspace_slug}") return "Installation successful! You can close this window." except Exception as e: print(f"OAuth error: {e}") return "Installation failed", 500 @app.route("/webhook", methods=["POST"]) def webhook(): """Handle incoming webhooks.""" signature = request.headers.get("X-Plane-Signature", "") payload = request.get_data() # Verify signature expected = hmac.new( WEBHOOK_SECRET.encode(), payload, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, signature): return "Invalid signature", 403 event = request.get_json() print(f"Received: {event['event']} {event['action']}") # Get credentials for this workspace creds = installations.get(event["workspace_id"]) if creds: # Process the event with creds["bot_token"] pass return "OK", 200 if __name__ == "__main__": app.run(port=3000) ``` ::: ## Next Steps * [Build an Agent](/dev-tools/agents/overview) - Create AI agents that respond to @mentions * [API Reference](/api-reference/introduction) - Explore the full Plane API * [Webhook Events](/dev-tools/intro-webhooks) - All webhook event types * [Example: PRD Agent](https://github.com/makeplane/prd-agent) - Complete agent implementation ## Publish to Marketplace Apps can be listed on the [Plane Marketplace](https://plane.so/marketplace/integrations). Contact to list your app. --- --- url: 'https://developers.plane.so/dev-tools/mcp-server-claude-code.html' description: >- Install and use the Plane MCP Server with Claude Code CLI. Manage work items, projects, cycles, and modules from your terminal with AI. --- # MCP Server for Claude Code [Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's CLI tool for agentic coding. By connecting the Plane MCP Server to Claude Code, you can manage your Plane workspace directly from the terminal — create work items, plan cycles, search across projects, and more, all through natural language. ## Prerequisites * **Claude Code**: [Install Claude Code](https://docs.anthropic.com/en/docs/claude-code/getting-started) if you haven't already. * **Node.js 22+**: Required for HTTP transports. Verify with `node --version`. * **Python 3.10+** and **uvx**: Required only for the local Stdio transport. Verify with `python --version` and `uvx --version`. * **Plane API Key**: For PAT or Stdio transports, generate one from **Workspace Settings > API Tokens** in Plane. ## Setup methods Choose the transport method that fits your setup. | Method | Best for | Auth | | ------------------------------------- | ---------------------------- | --------------------- | | [HTTP with OAuth](#http-with-oauth) | Plane Cloud, interactive use | Browser-based OAuth | | [HTTP with PAT](#http-with-pat-token) | CI/CD, automated workflows | API key in headers | | [Local Stdio](#local-stdio) | Self-hosted Plane instances | Environment variables | ## HTTP with OAuth The simplest way to connect to Plane Cloud. Run this in your terminal: ```bash claude mcp add --transport http plane https://mcp.plane.so/http/mcp ``` After adding the server, start Claude Code and run `/mcp` to authenticate via your browser. ## HTTP with PAT token For automated workflows or when you prefer token-based auth: ```bash claude mcp add-json plane '{ "type": "http", "url": "https://mcp.plane.so/http/api-key/mcp", "headers": { "Authorization": "Bearer ", "X-Workspace-slug": "" } }' ``` Replace `` and `` with your actual values. ## Local Stdio For self-hosted Plane instances, use the Stdio transport which runs locally: ```bash claude mcp add-json plane '{ "type": "stdio", "command": "uvx", "args": ["plane-mcp-server", "stdio"], "env": { "PLANE_API_KEY": "", "PLANE_WORKSPACE_SLUG": "", "PLANE_BASE_URL": "https://your-plane-instance.com" } }' ``` ### Environment variables | Variable | Required | Description | | ---------------------- | -------- | --------------------------------------------------------------------- | | `PLANE_API_KEY` | Yes | API key from your workspace settings | | `PLANE_WORKSPACE_SLUG` | Yes | Your workspace slug (found in your Plane URL) | | `PLANE_BASE_URL` | No | API URL for self-hosted instances. Defaults to `https://api.plane.so` | ## Scope options Control where the MCP server configuration is stored by adding a `--scope` flag: ```bash # Available only to you in the current project (default) claude mcp add --scope local --transport http plane https://mcp.plane.so/http/mcp # Shared via .mcp.json, committed to version control claude mcp add --scope project --transport http plane https://mcp.plane.so/http/mcp # Available across all your projects claude mcp add --scope user --transport http plane https://mcp.plane.so/http/mcp ``` ## Verify the connection After setup, verify the Plane MCP Server is connected: ```bash # List all configured MCP servers claude mcp list # Get details for the Plane server claude mcp get plane ``` Inside Claude Code, run `/mcp` to check the server status and see the available tools. ## Available tools The Plane MCP Server exposes 55+ tools across these categories: | Category | Tools | Examples | | ------------------------ | ----- | --------------------------------------------------------------- | | **Projects** | 9 | List, create, update, delete projects; get members and features | | **Work Items** | 7 | Create, list, search, update, delete work items | | **Cycles** | 12 | Manage cycles, add/remove work items, transfer, archive | | **Modules** | 11 | Manage modules, add/remove work items, archive | | **Initiatives** | 5 | Create and manage workspace-level initiatives | | **Intake** | 5 | Manage intake work items for triage | | **Work Item Properties** | 5 | Manage custom properties on work items | | **Users** | 1 | Get current authenticated user info | ## Examples Here are common tasks you can perform by chatting with Claude Code after connecting the Plane MCP Server. ### List projects in your workspace **Prompt:** ``` List all projects in my workspace. ``` Claude Code calls `list_projects` and returns a summary of all projects including their identifiers, lead, and status. ### Create a work item **Prompt:** ``` Create a bug in project WEB titled "Fix login redirect loop" and assign it to me. ``` Claude Code calls `get_me` to find your user ID, then `create_work_item` with the project ID, name, and assignee. ### Search across work items **Prompt:** ``` Search for work items related to "authentication" across the workspace. ``` Claude Code calls `search_work_items` with the query string and returns matching results from all projects. ### Plan a cycle **Prompt:** ``` Create a new cycle called "Sprint 24" in project WEB starting today and ending in 2 weeks. Add work items WEB-102, WEB-115, and WEB-118 to it. ``` Claude Code calls `create_cycle` with the name and dates, then `add_work_items_to_cycle` to attach the specified items. ### Triage intake items **Prompt:** ``` Show me all intake items in project MOBILE and accept the ones related to crash reports. ``` Claude Code calls `list_intake_work_items` to retrieve pending items, then `update_intake_work_item` to accept the relevant ones. ### Get a project overview **Prompt:** ``` Give me a summary of project BACKEND — what cycles are active, how many open work items are there, and who are the members? ``` Claude Code calls `retrieve_project`, `list_cycles`, `list_work_items`, and `get_project_members` to assemble a full overview. ### Manage modules **Prompt:** ``` Create a module called "Auth Revamp" in project WEB and add all work items tagged with the "auth" label to it. ``` Claude Code calls `create_module`, then `list_work_items` with label filtering, and finally `add_work_items_to_module` to associate the items. ### Move work items between cycles **Prompt:** ``` Transfer all incomplete work items from "Sprint 23" to "Sprint 24" in project WEB. ``` Claude Code calls `list_cycles` to find both cycle IDs, then `transfer_cycle_work_items` to move unfinished items. ## Managing the server ```bash # Remove the Plane MCP server claude mcp remove plane # Re-add with a different transport claude mcp add --transport http plane https://mcp.plane.so/http/mcp ``` ## Troubleshooting ### MCP server not connecting If the server fails to start, launch Claude Code with the debug flag: ```bash claude --mcp-debug ``` This shows detailed logs during MCP server initialization. ### Authentication errors (OAuth) Clear saved OAuth tokens and re-authenticate: ```bash rm -rf ~/.mcp-auth ``` Then restart Claude Code and run `/mcp` to authenticate again. ### Server timeout on startup If the MCP server times out during initialization, increase the timeout: ```bash MCP_TIMEOUT=10000 claude ``` This sets a 10-second startup timeout (default is lower). ### Python or Node.js not found Ensure the required runtime is installed and available on your `PATH`: ```bash # For HTTP transports node --version # Should be 22+ # For Stdio transport python --version # Should be 3.10+ uvx --version ``` ### Getting help If issues persist: 1. Check your credentials and workspace slug 2. Run `claude mcp list` to verify the configuration 3. Contact support at support@plane.so for Plane-specific issues 4. Visit the [Plane MCP Server repository](https://github.com/makeplane/plane-mcp-server) for known issues --- --- url: 'https://developers.plane.so/dev-tools/plane-compose.html' description: >- Define Plane projects, workflows, and work items in YAML files. Version control your project structure and sync bidirectionally with Plane using the command line. --- # Plane Compose Plane Compose is a command-line tool that lets you define and manage Plane projects using YAML configuration files. Think of it as "project as code", you write your project structure, schema, and work items in files, version control them with Git, and sync them with Plane. ## Prerequisites * Python 3.10 or later. Verify with `python3 --version`. * pipx. Plane Compose is distributed as a Python package and pipx is the recommended installer. If you do not have pipx: ```bash # macOS brew install pipx pipx ensurepath # Linux / Windows (via pip) pip install --user pipx pipx ensurepath ``` * A Plane account with access to at least one workspace. * An API token. You will need it during authentication. ## Install Plane Compose ```bash pipx install plane-compose ``` The package is published at . To upgrade to the latest version: ```bash pipx upgrade plane-compose ``` ## Authenticate ```bash plane auth login ``` You will be prompted for: * **Server URL** - leave blank for `https://api.plane.so`; enter your instance URL if self-hosted * **Auth type** - `pat` for a Personal Access Token, `workspace` for a workspace-scoped token * **Token** - your API key, generated at `https://app.plane.so//settings/account/api-tokens/` * **Workspace** - your workspace slug (the URL segment after `app.plane.so/`) * **Connection name** - a label for this connection (e.g. `personal`, `work`, `staging`), leave blank for default; used to identify it in `plane auth list-connections` Verify it worked: ```bash plane auth list-connections ``` To add credentials for a second workspace or a different Plane instance, run `plane auth login` again. Each login creates a separate connection. Link a workspace to a specific connection: ```bash plane auth connect-workspace --connection ``` Remove a connection: ```bash plane auth logout ``` ## Project operations ### Start a new project ```bash plane init --workspace ``` This creates the project directory with the given key and generates the full file structure inside it. To start from a template instead of the default schema: ```bash plane init --workspace --template ./templates/standard # or a Git URL: plane init --workspace \ --template https://github.com///templates/ ``` Before pushing, edit your schema files to define your project's structure. See the **Define your project schema** guide below. Then push the schema to create the project in Plane: ```bash cd plane schema push ``` `plane.yaml` is updated with the project UUID after this runs. ### Define your project schema After `plane init`, the `schema/` directory contains default files. Edit them to match your project's actual structure before pushing. **Edit the files in this order** - each file can only reference names defined in the files before it. 1. **Define your states** Open `schema/states.yaml`. Every state belongs to one of five groups: `backlog`, `unstarted`, `started`, `completed`, `cancelled`. Exactly one state should have `is_default: true`. ```yaml states: Backlog: group: backlog color: "#858585" is_default: true allow_issue_creation: true In Progress: group: started color: "#f59e0b" Done: group: completed color: "#22c55e" Cancelled: group: cancelled color: "#ef4444" ``` See the `schema/states.yaml` reference for all fields. 2. **Define your labels** Open `schema/labels.yaml`. Labels are flat - no nesting. ```yaml labels: - name: backend color: "#3b82f6" - name: frontend color: "#8b5cf6" ``` 3. **Define your workflows** Open `schema/workflows.yaml`. A workflow ties a set of states together and optionally restricts which transitions are allowed. Use only state names defined in `schema/states.yaml`. ```yaml workflows: default: is_active: true work_item_types: - Story - Bug states: - Backlog - In Progress - Done - Cancelled ``` Without a `transitions` block, any state change is permitted. See the `schema/workflows.yaml` reference for transition and approval syntax. 4. **Define your work item types** Open `schema/types.yaml`. Each type references a workflow by name. Use only workflow names defined in `schema/workflows.yaml`. ```yaml work_item_types: Story: description: A unit of user-facing work workflow: default is_epic: false Bug: description: A defect requiring correction workflow: default ``` To add custom properties to a type, see the `schema/types.yaml` reference. 5. **Set your default type in `plane.yaml`** Open `plane.yaml` and set `defaults.type` to the type name used when a work item does not specify one: ```yaml defaults: type: Story workflow: default ``` 6. **Toggle features** Open `schema/features.yaml` and disable features your project does not need. Disabling a feature hides it from the Plane UI and causes Plane Compose to skip its corresponding work file on push and pull. ```yaml features: cycles: true modules: true pages: false ``` 7. **Validate** Check for errors without making any API calls: ```bash plane schema validate ``` Fix any reported errors before proceeding. 8. **Push the schema** Then push the schema to create the project in Plane: ```bash cd my-project plane schema push ``` `plane.yaml` is updated with the project UUID after this runs. This creates the project in Plane if it does not exist yet and pushes your types, states, labels, and workflows. `plane.yaml` is updated with the project UUID after this runs. ### Clone an existing project Use this when the project already exists in Plane and you want to manage it locally. ```bash plane clone --workspace ``` :::info Use `--connection ` if the workspace slug is common across multiple connections. ::: This downloads `plane.yaml`, all schema files, and all work files into a new local directory. The schema files will reflect what is currently in Plane, you can edit them and push changes back. ### Push changes to Plane `plane push` pushes everything - schema and work files - in the correct order. `plane schema push` pushes schema only and is equivalent to `plane push --schema-only`. Use `plane schema push` when you are setting up a project for the first time and have no work items yet, or when you want to push schema changes without touching work items. Use `plane push` for all other cases. From inside the project directory: ```bash plane push ``` Preview what will change before pushing: ```bash plane push --dry-run ``` If you have only changed schema files: ```bash plane push --schema-only ``` If you have only changed work items: ```bash plane push --work-only ``` ### Pull remote changes from Plane Use this when changes have been made in Plane (via the UI or by other users) and you want to bring them into your local files. ```bash plane pull ``` To keep local additions and apply remote changes without losing local-only items: ```bash plane pull --merge ``` To overwrite local files entirely with what is in Plane: ```bash plane pull --force ``` ### Import schema changes made in Plane Use this when someone has modified work item types, states, labels, or workflows directly in the Plane UI and your local schema files are now out of sync. To reconnect local names to their remote IDs without changing any YAML (safe, no file changes): ```bash plane schema import ``` To add items that exist in Plane but are absent from your local files, without touching what you already have: ```bash plane schema import --merge ``` To replace your local schema files entirely with whatever is in Plane: ```bash plane schema import --force ``` ### Upgrade a project to a new template version Use this when your team has updated the standard template and you want to bring an existing project in line with it. Preview the changes first: ```bash plane upgrade --template https://github.com///templates/ --dry-run ``` Apply the upgrade: ```bash plane upgrade --template https://github.com///templates/ ``` The template merges over your local schema. Items unique to your project are preserved; conflicts are resolved in favour of the template. ### Run Plane Compose in CI/CD Authenticate non-interactively using flags: ```bash plane auth login \ --server-url https://api.plane.so \ --auth-type pat \ --token "$PLANE_TOKEN" \ --workspace ``` Push without prompts and with a machine-readable exit code: ```bash plane push --force --no-conflict-check --exit-code ``` Exit code values: `0` - no changes needed, `1` - error, `2` - changes were applied. ## Workspace operations ### Manage workspace configuration Clone workspace-level configuration to a local directory: ```bash plane ws clone -c ``` After making changes locally, push them to Plane: ```bash plane ws push ``` To pull the latest workspace state from Plane: ```bash plane ws pull ``` To pull and preserve local additions: ```bash plane ws pull --merge ``` ### Work with multiple projects From a directory containing multiple project subdirectories: ```bash plane push --all plane pull --all plane status --all ``` To limit to a specific workspace: ```bash plane push --all --workspace ``` To filter by project name pattern: ```bash plane push --all --filter "" ``` ## Recover from sync problems **State file deleted or corrupted:** ```bash plane schema import # reconnects schema names to remote IDs plane pull # restores work item entries ``` **A specific item is stuck or needs to be re-created:** ```bash plane state remove types. plane state remove work_items. ``` **All work items need to be re-pushed:** ```bash plane state clear-items plane push ``` **A push was interrupted and some items failed:** ```bash plane push --resume ``` **Diagnosing what went wrong:** ```bash plane --debug push tail -f ~/.config/plane-compose/plane.log ``` *** ## Reference ### CLI commands *** #### `plane init` Initialises a new project directory with the standard file structure: `plane.yaml`, `schema/`, `work/`, and `.plane/state.json`. If a template is specified, schema and work files are pre-populated from the template source. If called without arguments, the command runs interactively and prompts for workspace and project values. ``` plane init [PROJECT] [--workspace WS] [--connection CONN] [--path PATH] [--template TEMPLATE] ``` | Option | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PROJECT` | Name of the directory to create and the project key written into `plane.yaml` under `project.key`. | | `--workspace WS` | Slug of the Plane workspace this project belongs to. Written into `plane.yaml`. | | `--connection CONN` | ID of the connection to use for API calls during initialisation. Defaults to the connection linked to the workspace. | | `--path PATH` | Parent directory in which to create the project folder. Defaults to the current working directory. | | `--template TEMPLATE` | Source for pre-populating the schema and work files. Accepts a built-in name (e.g. `default`), a local filesystem path, a Git HTTPS URL, or a Git SSH URL. The resolved value is written to `plane.yaml` under `template` so that `plane upgrade` can reference it later. | *** #### `plane auth login` Stores a new set of credentials as a connection in `~/.config/plane-compose/config.json` and links it to a workspace. When all flags are provided, the command runs non-interactively, making it suitable for CI/CD pipelines. On success, prints the generated connection ID. ``` plane auth login [--connection CONN] [--server-url URL] [--auth-type TYPE] [--token TOKEN] [--workspace WS] ``` | Option | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--connection CONN` | Name or ID to assign to this connection. Used to identify it in `plane auth list-connections` and to reference it with `--connection` in other commands. If omitted, a name is prompted interactively. | | `--server-url URL` | Base URL of the Plane API. Defaults to `https://api.plane.so`. Set this to your instance URL when using a self-hosted deployment. | | `--auth-type TYPE` | Token type. `pat` for a Personal Access Token scoped to a user. `workspace` for a workspace-scoped token. | | `--token TOKEN` | The API token value. | | `--workspace WS` | Workspace slug to associate with this connection. The association is stored so that commands can resolve credentials from `plane.yaml` automatically. | *** #### `plane auth logout` Removes a stored connection and all its associated workspace links from `~/.config/plane-compose/config.json`. This operation is irreversible without re-authenticating. ``` plane auth logout CONNECTION_ID [--force] ``` | Option | Description | | --------------- | -------------------------------------------------------------------------- | | `CONNECTION_ID` | ID of the connection to remove, as shown by `plane auth list-connections`. | | `--force` | Skips the confirmation prompt before deletion. | *** #### `plane auth list-connections` Prints all stored connections with their server URL, auth type, and linked workspaces. The default workspace is marked with ★. Aliases: `whoami`, `connections`, `list`. ``` plane auth list-connections ``` *** #### `plane auth connect-workspace` Associates a workspace slug with an existing connection. After this, any command targeting that workspace will use the specified connection's credentials without requiring an explicit `--connection` flag. ``` plane auth connect-workspace WORKSPACE_SLUG --connection CONN_ID ``` | Option | Description | | ---------------------- | --------------------------------------------- | | `WORKSPACE_SLUG` | The Plane workspace slug to link. | | `--connection CONN_ID` | ID of the connection to link it to. Required. | *** #### `plane auth disconnect-workspace` Removes the association between a workspace slug and a connection. After this, commands targeting that workspace will require an explicit `--connection` flag or re-authentication. ``` plane auth disconnect-workspace WORKSPACE_SLUG [--connection CONN_ID] ``` | Option | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `WORKSPACE_SLUG` | The workspace slug to unlink. | | `--connection CONN_ID` | ID of the connection to unlink from. If omitted and the workspace has only one associated connection, that connection is unlinked automatically. | *** #### `plane schema validate` Checks all schema files in the project for structural errors, unknown field types, missing required fields, and invalid references. Runs entirely offline - no API connection is made. Exits with a non-zero code if any errors are found. ``` plane schema validate [PROJECT] [--path PATH] ``` | Option | Description | | ------------- | ------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | *** #### `plane schema push` Pushes local schema files to Plane. Creates the project in Plane if it does not already exist. Applies changes to work item types, states, workflows, and labels in the order required by the Plane API. On first push, writes the project UUID back into `plane.yaml`. Updates `.plane/state.json` with remote ID mappings for all pushed schema items. ``` plane schema push [PROJECT] [--path PATH] [--dry-run] [--force] ``` | Option | Description | | ------------- | ----------------------------------------------------------------------------------------------- | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--dry-run` | Computes and displays the full change plan without making any API calls or modifying any files. | | `--force` | Skips the interactive confirmation prompt before applying changes. | *** #### `plane schema import` Reads the current schema from the Plane remote and reconciles it with local files. Without flags, only `.plane/state.json` is updated - no YAML files are modified. This is the safe mode for reconnecting local names to remote IDs after out-of-band changes. ``` plane schema import [PROJECT] [--path PATH] [--merge] [--force] ``` | Option | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--merge` | Writes remote schema items that are absent from local files into the appropriate YAML files. Existing local items are not modified. Additive only. | | `--force` | Replaces the contents of local schema files entirely with what is returned from the Plane API. Any local-only items are lost. | *** #### `plane schema diff` Fetches the current schema from Plane and compares it to local schema files. Prints a structured diff showing which types, states, workflows, and labels differ. Makes no changes to local files or the remote. ``` plane schema diff [PROJECT] [--path PATH] ``` | Option | Description | | ------------- | ------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | *** #### `plane push` Pushes schema and work data to Plane in dependency order: schema first (types, states, workflows, labels), then work items, then cycles and modules, then milestones. Skips items whose content hash matches the hash stored in `.plane/state.json`. Updates state after each successful push. If the schema push fails, work data push does not proceed. ``` plane push [PROJECT] [--path PATH] [--connection CONN] [--dry-run] [--force] [--schema-only] [--work-only] [--skip SECTION] [--all] [--workspace WS] [--filter PATTERN] [--no-conflict-check] [--exit-code] [--resume] ``` | Option | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--connection CONN` | Overrides the connection resolved from `plane.yaml` for this invocation only. | | `--dry-run` | Computes and displays the full change plan without making any API calls or writing to state. | | `--force` | Skips the interactive confirmation prompt before applying changes. | | `--schema-only` | Pushes only schema files. Equivalent to running `plane schema push`. Skips `workitems`, `cycles`, `modules`, and `milestones`. | | `--work-only` | Skips the schema push phase and pushes only work files. Requires schema to already be in sync. | | `--skip SECTION` | Excludes a specific section from the push. Repeatable. Valid values: `workitems`, `cycles`, `modules`, `milestones`. | | `--all` | Discovers all project directories under the current directory and pushes each one. | | `--workspace WS` | When used with `--all`, restricts discovery to projects belonging to this workspace. | | `--filter PATTERN` | When used with `--all`, applies a glob pattern to filter project directory names. | | `--no-conflict-check` | Skips the pre-push API call that detects remote conflicts. Reduces API usage. Recommended for CI/CD pipelines where conflicts are not expected. | | `--exit-code` | Returns a differentiated exit code: `0` if no changes were needed, `1` on error, `2` if changes were successfully applied. Useful for scripting and CI gate logic. | | `--resume` | Reads the failure log from the previous push and retries only the items that failed. Items that succeeded in the previous run are not re-pushed. | *** #### `plane pull` Fetches schema and work data from Plane and writes it to local files. Without flags, overwrites local files with remote content after prompting for confirmation. Updates `.plane/state.json` with the latest remote IDs and content hashes. ``` plane pull [PROJECT] [--path PATH] [--connection CONN] [--merge] [--force] [--schema-only] [--work-only] [--skip SECTION] [--with-properties] [--no-properties] [--all] [--workspace WS] [--filter PATTERN] ``` | Option | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--connection CONN` | Overrides the connection resolved from `plane.yaml` for this invocation only. | | `--merge` | Applies remote changes to local files while preserving items that exist locally but not remotely. Local-only items are not deleted. | | `--force` | Overwrites local files with remote content without prompting. Local-only items are lost. | | `--schema-only` | Pulls only schema files. Skips `workitems`, `cycles`, `modules`, and `milestones`. | | `--work-only` | Pulls only work files. Skips schema. | | `--skip SECTION` | Excludes a specific section from the pull. Repeatable. Valid values: `workitems`, `cycles`, `modules`, `milestones`. | | `--with-properties` | Includes custom property values in the pulled work items. Enabled by default. | | `--no-properties` | Excludes custom property values from pulled work items. The `properties` map is omitted from each work item in the output file. | | `--all` | Discovers all project directories under the current directory and pulls each one. | | `--workspace WS` | When used with `--all`, restricts discovery to projects belonging to this workspace. | | `--filter PATTERN` | When used with `--all`, applies a glob pattern to filter project directory names. | *** #### `plane clone` Downloads a complete Plane project - including `plane.yaml`, all schema files, and all work files - into a new local directory. Initialises `.plane/state.json` with the remote IDs of all cloned items. The project must already exist in Plane. ``` plane clone PROJECT [--directory DIR] [--path PATH] [--workspace WS] [--connection CONN] [--schema-only] [--skip SECTION] [--with-properties] ``` | Option | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PROJECT` | Project identifier. Accepts three formats: `workspace/KEY` shorthand (e.g. `myteam/API`), a project key with `--workspace` flag, or a UUID with `--workspace` flag. | | `--directory DIR` | Name of the local directory to create. Defaults to the project key. | | `--path PATH` | Parent directory in which to create the project folder. Defaults to the current working directory. | | `--workspace WS` | Workspace slug. Required when `PROJECT` is a key or UUID rather than a shorthand. | | `--connection CONN` | Overrides the connection resolved from the workspace for this invocation only. | | `--schema-only` | Downloads schema files only. Skips `workitems`, `cycles`, `modules`, and `milestones`. | | `--skip SECTION` | Excludes a specific section from the clone. Repeatable. Valid values: `workitems`, `cycles`, `modules`, `milestones`. | | `--with-properties` | Includes custom property values in the cloned work items. Enabled by default. | *** #### `plane diff` Fetches work items from Plane and compares them to local work files. Classifies each item into one of six categories and prints a structured report. Makes no changes to local files or the remote. ``` plane diff [PROJECT] [--path PATH] [--connection CONN] ``` | Option | Description | | ------------------- | ----------------------------------------------------------------------------- | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--connection CONN` | Overrides the connection resolved from `plane.yaml` for this invocation only. | **Output categories:** | Category | Meaning | | ----------------- | ------------------------------------------------------------------------------------------------ | | `new_local` | Item exists in local files but has no corresponding remote record. | | `modified_local` | Item exists both locally and remotely, and the local version differs from the remote. | | `modified_remote` | Item exists both locally and remotely, and the remote version differs from what was last synced. | | `conflicts` | Item has been modified both locally and remotely since the last sync. | | `in_sync` | Local and remote versions are identical. | | `deleted_remote` | Item has been deleted from Plane but still exists in local files. | *** #### `plane validate` Validates work item files against the project schema. Checks for unknown type names, unknown state names, unknown label names, invalid priority values, duplicate `id` values, malformed dates, and missing required fields. By default, fetches the current schema from Plane to validate against. Exits with a non-zero code if any errors are found. ``` plane validate [PATH] [--offline] [--json] ``` | Option | Description | | ----------- | ----------------------------------------------------------------------------------------------------------- | | `PATH` | Filesystem path to the project root. Defaults to the current directory. | | `--offline` | Skips the API call to fetch the remote schema. Validates only against local schema files. | | `--json` | Outputs validation errors as a JSON array instead of formatted text. Useful for scripting and CI pipelines. | *** #### `plane status` Reads `.plane/state.json` and the local work files to produce a summary of the project's sync state: schema sync status, number of work items pending push, number of items in sync, and the timestamp of the last successful push. Does not make any API calls. ``` plane status [PATH] [--all] [--workspace WS] [--filter PATTERN] [--json] ``` | Option | Description | | ------------------ | ------------------------------------------------------------------------------------------ | | `PATH` | Filesystem path to the project root. Defaults to the current directory. | | `--all` | Discovers all project directories under the current directory and reports status for each. | | `--workspace WS` | When used with `--all`, restricts discovery to projects belonging to this workspace. | | `--filter PATTERN` | When used with `--all`, applies a glob pattern to filter project directory names. | | `--json` | Outputs the status report as JSON. | *** #### `plane upgrade` Applies a template to an existing project's schema. Pulls the latest schema from Plane first (unless `--skip-pull` is set), then computes a three-way merge between the current local schema, the current remote schema, and the template. Items present only in the template are added. Items in conflict between template and local are resolved in favour of the template. Items present only locally are preserved. Presents a plan before applying. ``` plane upgrade [PROJECT] --template TEMPLATE [--path PATH] [--include-data] [--dry-run] [--force] [--skip-pull] [--schema-only] ``` | Option | Description | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--template TEMPLATE` | Template source. Required. Accepts a built-in name, a local path, a Git HTTPS URL, or a Git SSH URL. | | `--path PATH` | Explicit filesystem path to the project root. | | `--include-data` | Also copies cycles, modules, and work items from the template into the local work files. | | `--dry-run` | Computes and displays the upgrade plan without modifying any files or making any API calls. | | `--force` | Skips the interactive confirmation prompt before applying the upgrade. | | `--skip-pull` | Skips pulling the latest schema from Plane before computing the merge. Uses the current local schema as the base. | | `--schema-only` | Applies only schema changes from the template. Does not copy cycles, modules, or work items even if `--include-data` is set. | *** #### `plane state show` Prints the contents of `.plane/state.json` as a structured report showing remote ID mappings and content hashes for schema items and work items. Makes no API calls. ``` plane state show [PROJECT] [--path PATH] [--json] ``` | Option | Description | | ------------- | ------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--json` | Outputs the state as raw JSON. | *** #### `plane state reset` Clears all entries from `.plane/state.json`. After a reset, the next `plane push` treats every local item as new and attempts to create it in Plane. Use with caution - this can result in duplicate remote items if the project already exists in Plane. ``` plane state reset [PROJECT] [--path PATH] [--force] ``` | Option | Description | | ------------- | ------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--force` | Skips the confirmation prompt. | *** #### `plane state clear-items` Removes only the `work_items` section of `.plane/state.json`, leaving schema state intact. The next `plane push` re-pushes all work items as if they are new, but schema items are not affected. ``` plane state clear-items [PROJECT] [--path PATH] [--force] ``` | Option | Description | | ------------- | ------------------------------------------------------------ | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | | `--force` | Skips the confirmation prompt. | *** #### `plane state remove` Removes a single entry from `.plane/state.json` identified by a dot-separated path. On the next push, the removed item is treated as new. ``` plane state remove PATH_STR [PROJECT] [--path PATH] ``` | Option | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `PATH_STR` | Dot-separated path into the state object. For example: `types.Story` removes the Story type mapping; `states.Done` removes the Done state mapping; `work_items.AUTH-1` removes a specific work item mapping. | | `PROJECT` | Project key or directory. Defaults to the current directory. | | `--path PATH` | Explicit filesystem path to the project root. | *** #### `plane ws clone` Downloads workspace-level configuration - including `workspace.yaml`, workspace schema files, and member data - into a new local directory. Initialises `.plane/state.json` with remote ID mappings. ``` plane ws clone WORKSPACE [--directory DIR] [--path PATH] [--connection CONN] [--force] [--skip SECTION] ``` | Option | Description | | ------------------- | ------------------------------------------------------------------------------------- | | `WORKSPACE` | Workspace slug to clone. | | `--directory DIR` | Name of the local directory to create. Defaults to the workspace slug. | | `--path PATH` | Parent directory for the workspace folder. Defaults to the current working directory. | | `--connection CONN` | Overrides the connection resolved from the workspace slug for this invocation only. | | `--force` | Overwrites an existing local directory without prompting. | | `--skip SECTION` | Excludes a section from the clone. Valid values: `releases`. | *** #### `plane ws pull` Fetches workspace-level configuration from Plane and writes it to local workspace files. Behaviour with respect to local content is controlled by `--merge` and `--force`, identical in semantics to `plane pull`. ``` plane ws pull [WORKSPACE] [--path PATH] [--merge] [--force] [--skip SECTION] ``` | Option | Description | | ---------------- | ---------------------------------------------------------------------------------------- | | `WORKSPACE` | Workspace slug. Defaults to the value in `workspace.yaml`. | | `--path PATH` | Explicit filesystem path to the workspace root. | | `--merge` | Preserves local-only items while applying remote changes. | | `--force` | Overwrites local files with remote content without prompting. Local-only items are lost. | | `--skip SECTION` | Excludes a section from the pull. Valid values: `releases`. | *** #### `plane ws push` Pushes local workspace configuration files to Plane. Updates `.plane/state.json` with remote ID mappings for all pushed items. ``` plane ws push [WORKSPACE] [--path PATH] [--dry-run] [--force] ``` | Option | Description | | ------------- | ------------------------------------------------------------------- | | `WORKSPACE` | Workspace slug. Defaults to the value in `workspace.yaml`. | | `--path PATH` | Explicit filesystem path to the workspace root. | | `--dry-run` | Computes and displays the change plan without making any API calls. | | `--force` | Skips the interactive confirmation prompt. | *** #### `plane ws diff` Fetches workspace configuration from Plane and compares it to local workspace files. Prints a structured diff. Makes no changes. ``` plane ws diff [WORKSPACE] [--path PATH] ``` | Option | Description | | ------------- | ---------------------------------------------------------- | | `WORKSPACE` | Workspace slug. Defaults to the value in `workspace.yaml`. | | `--path PATH` | Explicit filesystem path to the workspace root. | *** #### `plane ws upgrade` Applies a template to an existing workspace's schema. Follows the same merge logic as `plane upgrade`. ``` plane ws upgrade [WORKSPACE] [--path PATH] [--template TEMPLATE] [--dry-run] [--force] [--skip-pull] [--schema-only] ``` | Option | Description | | --------------------- | ------------------------------------------------------------------------------------ | | `WORKSPACE` | Workspace slug. Defaults to the value in `workspace.yaml`. | | `--path PATH` | Explicit filesystem path to the workspace root. | | `--template TEMPLATE` | Template source. Accepts a built-in name, local path, Git HTTPS URL, or Git SSH URL. | | `--dry-run` | Displays the upgrade plan without applying it. | | `--force` | Skips the confirmation prompt. | | `--skip-pull` | Skips pulling the latest workspace schema from Plane before computing the merge. | | `--schema-only` | Applies only schema changes. Does not copy data from the template. | *** #### `plane ws state show / reset / clear / remove` Manage workspace sync state in `.plane/state.json` within the workspace directory. Semantics are identical to their project-level equivalents. ``` plane ws state show [WORKSPACE] [--path PATH] [--json] plane ws state reset [WORKSPACE] [--path PATH] [--force] plane ws state clear [WORKSPACE] [--path PATH] [--force] plane ws state remove PATH_STR [WORKSPACE] [--path PATH] ``` `PATH_STR` examples for workspace state: `workitemtypes.types.Task`, `members.dev@example.com`, `releases.tags.v1.0`. *** #### `plane rate stats` Prints the current rate limit window statistics: total requests made, requests remaining, and the time until the window resets. Reads from local counters; does not make an API call. ``` plane rate stats ``` *** #### `plane rate reset` Resets the local rate limit counters to zero. Does not affect Plane's server-side rate limiting. ``` plane rate reset ``` *** #### Global options These options are accepted by every `plane` command. | Option | Description | | ----------------- | -------------------------------------------------------------------------------------------- | | `--version`, `-V` | Prints the installed version of Plane Compose and exits. | | `--verbose`, `-v` | Enables verbose output. Prints additional detail about each operation as it runs. | | `--debug` | Enables debug-level logging. Writes a structured log to `~/.config/plane-compose/plane.log`. | *** ### Configuration files #### Project directory structure ``` / ├── plane.yaml ├── schema/ │ ├── types.yaml │ ├── states.yaml │ ├── workflows.yaml │ ├── labels.yaml │ ├── features.yaml │ ├── members.yaml # populated on pull; read-only │ ├── workitem_templates.yaml # populated on pull │ └── page_templates.yaml # populated on pull ├── work/ │ ├── workitems.yaml │ ├── cycles.yaml │ ├── modules.yaml │ └── milestones.yaml ├── .plane/ │ ├── state.json # sync state; do not edit manually │ └── .state.lock # held during active sync operations └── .gitignore ``` Workspace directory (created by `plane ws clone`): ``` / ├── workspace.yaml ├── schema/ │ └── workitem_types.yaml # workspace-level WIT definitions (Enterprise) └── .plane/ └── state.json ``` *** #### `plane.yaml` The primary configuration file for a project. Identifies the project, specifies the workspace and connection, and sets defaults used when work item fields are omitted. | Field | Type | Description | | --------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | string | Always `project`. Identifies this directory as a project as opposed to a workspace. | | `workspace` | string | Slug of the Plane workspace this project belongs to. Used to resolve the correct connection from `~/.config/plane-compose/config.json`. | | `connection` | string | Optional. ID of a specific connection to use for all operations on this project. Overrides the workspace-to-connection mapping. | | `project.key` | string | Short project identifier, maximum 10 uppercase characters. Used as the prefix in work item sequence IDs (e.g. `API-42`). | | `project.name` | string | Human-readable project name as displayed in Plane. | | `project.uuid` | string | Remote UUID of the project. Populated automatically after the first `plane schema push`. Do not set manually. | | `project.description` | string | Optional project description as displayed in Plane. | | `project.network` | string | Visibility setting. `public` makes the project visible to all workspace members. `private` restricts visibility. Defaults to `public`. | | `project.timezone` | string | IANA timezone string (e.g. `UTC`, `America/New_York`). Affects due date display and cycle date calculations in Plane. | | `defaults.type` | string | Default work item type applied when a work item in `work/workitems.yaml` does not specify a `type` field. Must match a key in `schema/types.yaml`. | | `defaults.workflow` | string | Default workflow applied when a work item type does not specify one. Must match a key in `schema/workflows.yaml`. | | `template` | string | Source of the template used during `plane init` or `plane upgrade`. Written automatically; used by `plane upgrade` to know where to pull the template from. | ```yaml type: project workspace: myteam connection: conn-1 project: key: API name: API Project uuid: abc-123-def-456 description: "" network: public timezone: UTC defaults: type: Story workflow: default template: default ``` *** #### `workspace.yaml` The configuration file for a workspace directory created by `plane ws clone`. Identifies the workspace and connection, and contains workspace-level member data. | Field | Type | Description | | ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- | | `workspace` | string | Slug of the Plane workspace. Used to resolve the correct connection from `~/.config/plane-compose/config.json`. | | `connection` | string | Optional. ID of a specific connection to pin to this workspace directory. | | `workspace_features` | map | Optional. Workspace-level feature flag overrides. Keys are feature names; values are `true` or `false`. | | `members` | list | Workspace members. Populated automatically on `plane ws pull`. Read-only - do not edit manually. | | `members[].id` | string | Remote UUID of the member. | | `members[].email` | string | Email address of the member. | | `members[].display_name` | string | Display name of the member as shown in Plane. | ```yaml workspace: myteam connection: conn-1 workspace_features: epics: true members: - id: abc-123 email: dev@example.com display_name: Dev User ``` *** #### `schema/types.yaml` Defines the work item types available in the project. Each key is the type name. Types control which workflow applies, whether the type can act as an parent, its icon in the Plane UI, and which custom properties are attached. | Field | Type | Description | | ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `` | map | Top-level key is the type name as referenced in work items and workflows. | | `description` | string | Human-readable description of the type, displayed in the Plane UI. | | `workflow` | string | Name of the workflow from `schema/workflows.yaml` that governs state transitions for this type. | | `is_epic` | boolean | When `true`, work items of this type can act as parents for other work items, enabling hierarchy. Defaults to `false`. Requires `epics: true` in `schema/features.yaml`. | | `logo_props.icon` | string | Name of the icon displayed for this type in the Plane UI. | | `logo_props.background_color` | string | Hex colour string for the icon background (e.g. `#6366f1`). | | `properties` | list | List of custom property definitions attached to this type. | | `properties[].name` | string | Property name as displayed in the Plane UI and as the key in work item `properties` maps. | | `properties[].type` | string | Data type of the property. See property type table below. | | `properties[].required` | boolean | When `true`, the property must have a value before a work item of this type can be marked as done. | | `properties[].options` | list | List of option strings. Required when `type` is `option`. | | `properties[].is_multi` | boolean | When `true` and `type` is `option`, the property accepts multiple selected values. Defaults to `false`. | **Property types:** | Type | Description | Notes | | ---------------- | ------------------------------------------------- | ------------------------------------------------------------------------------ | | `text` | Single or multi-line text input. | Alias: `string`. | | `number` | Integer numeric value. | | | `decimal` | Floating-point numeric value. | | | `date` | Calendar date in `YYYY-MM-DD` format. | | | `datetime` | Date and time in ISO 8601 format. | | | `option` | Dropdown selector, single or multi-select. | Alias: `enum`. Requires `options` list. Add `is_multi: true` for multi-select. | | `boolean` | True/false checkbox. | | | `url` | URL string with validation. | | | `email` | Email address string with validation. | | | `member_picker` | Reference to one or more Plane workspace members. | | | `relation` | Reference to another work item or user. | Set `relation_type: user` or `relation_type: issue`. | | `release_picker` | Reference to a Plane release tag. | Populated on pull; push is blocked by the Plane API. Read-only in practice. | | `file` | File attachment reference. | | | `formula` | Computed value derived from other fields. | Push not yet supported. | ```yaml work_item_types: Story: description: A unit of user-facing work workflow: default is_epic: false logo_props: icon: bookmark background_color: "#6366f1" properties: - name: Severity type: option required: false options: - Minor - Major - Critical is_multi: false Bug: description: A defect requiring correction workflow: default properties: - name: Reproducible type: boolean required: true ``` *** #### `schema/states.yaml` Defines the states available in the project. Each key is the state name as referenced in work items and workflows. States are grouped into one of five standard Plane groups that determine how Plane treats them in reporting and cycle calculations. | Field | Type | Description | | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `` | map | Top-level key is the state name, referenced in `work/workitems.yaml` and `schema/workflows.yaml`. | | `group` | string | Functional category of the state. One of: `backlog`, `unstarted`, `started`, `completed`, `cancelled`. Determines how Plane aggregates and reports on work items in this state. | | `color` | string | Hex colour string used to represent this state in the Plane UI (e.g. `#22c55e`). | | `allow_issue_creation` | boolean | When `true`, new work items can be created directly in this state. Defaults to `true`. | | `is_default` | boolean | When `true`, this state is assigned to new work items that do not specify a state. Only one state per project should have `is_default: true`. | ```yaml states: Backlog: group: backlog color: "#858585" allow_issue_creation: true is_default: true Todo: group: unstarted color: "#d1d5db" In Progress: group: started color: "#f59e0b" Done: group: completed color: "#22c55e" Cancelled: group: cancelled color: "#ef4444" ``` *** #### `schema/workflows.yaml` Defines the workflows available in the project. Each workflow associates a set of states with a set of work item types and optionally restricts which state transitions are permitted. When no transitions are defined, any state change is allowed. | Field | Type | Description | | ------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `` | map | Top-level key is the workflow name, referenced from `schema/types.yaml` and `plane.yaml`. | | `description` | string | Human-readable description of the workflow. | | `is_active` | boolean | When `false`, the workflow is defined but not enforced by Plane. | | `work_item_types` | list | Names of work item types from `schema/types.yaml` that this workflow governs. | | `states` | list | Names of states from `schema/states.yaml` that are valid within this workflow. | | `transitions` | map | Optional. Defines which state transitions are permitted. Keys are source state names; values are lists of allowed target transitions. When absent, all transitions between the workflow's states are permitted. | | `transitions.[].to` | string | Name of the target state for this transition. Must be in the workflow's `states` list. | | `transitions.[].type` | string | `transition` for a direct state change. `approval` for a change that requires approval before completing. | | `transitions.[].required_approvals` | integer | For `approval` type only. Number of approvers required. `null` means all listed approvers must approve. | | `transitions.[].approvers` | list | For `approval` type only. Email addresses of Plane members who can approve the transition. | ```yaml workflows: default: description: Standard engineering workflow is_active: true work_item_types: - Story - Bug states: - Backlog - Todo - In Progress - Done transitions: Todo: - to: In Progress type: transition In Progress: - to: Done type: approval required_approvals: 1 approvers: - lead@example.com - to: Todo type: transition ``` *** #### `schema/labels.yaml` Defines the labels available in the project. Labels are flat - no nesting. Each entry in the list defines one label. | Field | Type | Description | | ------- | ------ | -------------------------------------------------------------------------------------------- | | `name` | string | Label name as referenced in work item `labels` lists. | | `color` | string | Hex colour string used to render the label chip in the Plane UI. | | `id` | string | Remote UUID of the label. Populated automatically after the first push. Do not set manually. | ```yaml labels: - name: backend color: "#3b82f6" - name: frontend color: "#8b5cf6" - name: infrastructure color: "#10b981" ``` *** #### `schema/features.yaml` Controls which Plane features are enabled for the project. Disabling a feature hides it from the Plane UI and prevents Plane Compose from pushing or pulling data for that section. | Field | Type | Description | | ----------------- | ------- | ---------------------------------------------------------------------------------------------------- | | `cycles` | boolean | Enables time-boxed sprint cycles. When `false`, `work/cycles.yaml` is ignored on push and pull. | | `modules` | boolean | Enables modules for grouping work by feature area. When `false`, `work/modules.yaml` is ignored. | | `pages` | boolean | Enables wiki-style pages within the project. | | `views` | boolean | Enables saved filtered views. | | `intakes` | boolean | Enables a public intake form for submitting work items from outside the workspace. | | `epics` | boolean | Enables work item hierarchy. Requires at least one type with `is_epic: true` in `schema/types.yaml`. | | `work_item_types` | boolean | Enables custom work item types. When `false`, Plane uses only the default type. | | `workflows` | boolean | Enables custom workflow enforcement. When `false`, state transitions are unrestricted. | | `parallel_cycles` | boolean | Allows multiple active cycles to run simultaneously. | | `project_updates` | boolean | Enables the project updates feed. | ```yaml features: cycles: true modules: true pages: true views: true intakes: false epics: true work_item_types: true workflows: true parallel_cycles: false project_updates: false ``` *** #### `work/workitems.yaml` Defines the work items to be synced to Plane. Contains a single top-level `workitems` key whose value is a list. Each list entry represents one work item. | Field | Type | Required | Description | | -------------- | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | string | Recommended | Stable local identifier chosen by the author. Used as the tracking key in `.plane/state.json`. If omitted, Plane Compose derives the key from a hash of the item's content - title or description changes will then generate a new key and cause a duplicate to be created instead of an update. | | `title` | string | Yes | Work item title as displayed in Plane. | | `type` | string | No | Name of the work item type from `schema/types.yaml`. Defaults to `defaults.type` in `plane.yaml`. | | `state` | string | No | Name of the state from `schema/states.yaml`. Defaults to the state with `is_default: true`. | | `priority` | string | No | Priority level. One of: `urgent`, `high`, `medium`, `low`, `none`. Defaults to `none`. | | `labels` | list | No | List of label names from `schema/labels.yaml`. | | `assignees` | list | No | List of email addresses of Plane workspace members to assign to this work item. | | `watchers` | list | No | List of email addresses of Plane workspace members who receive notifications for this work item. | | `start_date` | string | No | Planned start date in `YYYY-MM-DD` format. | | `due_date` | string | No | Planned due date in `YYYY-MM-DD` format. | | `description` | string | No | Full description in Markdown format. | | `parent` | string | No | Sequence ID of the parent work item (e.g. `API-5`). Requires `epics: true` in `schema/features.yaml` and a type with `is_epic: true`. | | `blocked_by` | list | No | List of sequence IDs of work items that must be completed before this one can begin. | | `blocking` | list | No | List of sequence IDs of work items that cannot begin until this one is completed. | | `duplicate_of` | string | No | Sequence ID of the work item this one duplicates. | | `relates_to` | list | No | List of sequence IDs of work items related to this one without a specific dependency relationship. | | `properties` | map | No | Custom property values. Keys are property names as defined in the type's `properties` list in `schema/types.yaml`. Values must match the property type. | ```yaml workitems: - id: "auth-oauth" title: Implement OAuth2 login type: Story state: Backlog priority: high labels: - backend assignees: - dev@example.com watchers: - pm@example.com start_date: "2026-06-01" due_date: "2026-06-15" description: | Add OAuth2 authentication using the provider SDK. parent: "API-5" blocked_by: - "API-3" blocking: - "API-9" properties: Severity: Major ``` *** #### `work/cycles.yaml` Defines time-boxed sprint cycles. The `status` field is computed by Plane based on dates relative to the current time and is read-only - do not set it manually. The `id` field is populated automatically after the first push. | Field | Type | Description | | ------------- | ------ | ---------------------------------------------------------------------------------- | | `name` | string | Cycle name as displayed in Plane. Used as the tracking key in state. | | `description` | string | Optional description of the cycle's goal or scope. | | `start_date` | string | Cycle start date in `YYYY-MM-DD` format. | | `end_date` | string | Cycle end date in `YYYY-MM-DD` format. | | `id` | string | Remote UUID of the cycle. Populated automatically after push. Do not set manually. | ```yaml cycles: - name: Sprint 1 description: Foundation sprint start_date: "2026-06-01" end_date: "2026-06-14" id: abc-123 ``` *** #### `work/modules.yaml` Defines modules that group work by feature or initiative. The `status` field is computed by Plane and is read-only. The `id` field is populated automatically after the first push. | Field | Type | Description | | ------------- | ------ | ----------------------------------------------------------------------------------- | | `name` | string | Module name as displayed in Plane. Used as the tracking key in state. | | `description` | string | Optional description of the module's scope. | | `start_date` | string | Module start date in `YYYY-MM-DD` format. | | `end_date` | string | Module end date in `YYYY-MM-DD` format. | | `id` | string | Remote UUID of the module. Populated automatically after push. Do not set manually. | ```yaml modules: - name: Authentication description: All auth-related work items start_date: "2026-06-01" end_date: "2026-07-01" id: abc-123 ``` *** #### `work/milestones.yaml` Defines milestones that mark significant points in the project timeline. The `id` field is populated automatically after the first push. | Field | Type | Description | | ------------- | ------ | -------------------------------------------------------------------------------------------------------------------- | | `name` | string | Milestone name as displayed in Plane. Used as the tracking key in state. Maps to the `title` field in the Plane API. | | `target_date` | string | Target completion date in `YYYY-MM-DD` format. | | `work_items` | list | List of work item sequence IDs (e.g. `API-1`) to associate with this milestone. | | `id` | string | Remote UUID of the milestone. Populated automatically after push. Do not set manually. | ```yaml milestones: - name: v1.0 Release target_date: "2026-08-01" work_items: - "API-1" - "API-5" id: abc-123 ``` ### Troubleshooting #### Authentication failed (401) The stored token is invalid, expired, or has been revoked. Remove the connection and re-authenticate: ```bash plane auth logout plane auth login ``` #### Permission denied (403) The authenticated user does not have access to the requested workspace or project. Verify workspace membership with `plane auth list-connections`. Contact the workspace administrator to request access. #### Project not found (404) The `project.uuid` in `plane.yaml` does not correspond to an existing project. This occurs when the project has been deleted from Plane or when `plane.yaml` from one environment is used in another. Remove the `uuid` field from `plane.yaml` and run `plane schema push` to create a new project and update the UUID. #### Rate limit exceeded (429) Plane Compose has exceeded the API request limit for the current time window. Run `plane rate stats` to see how many requests remain and when the window resets. To reduce the request rate: ```bash PLANE_RATE_LIMIT_PER_MINUTE=30 plane push ``` #### Duplicate work items Work items were pushed without a stable `id` field. When the title or description was subsequently changed, the content hash changed, causing Plane Compose to treat the item as new rather than an update. A second item was created in Plane. To fix: add a stable `id` to the item in the YAML file, remove the old state entry with `plane state remove work_items.`, delete the duplicate from Plane manually, then push. #### State file out of sync or deleted ```bash plane schema import # rebuilds schema entries from remote IDs plane pull # rebuilds work item entries from remote data ``` #### Push fails partway through If a push is interrupted before completion, the next `plane push --resume` reads the failure log written during the interrupted run and retries only the items that did not succeed. Items that pushed successfully are not re-pushed. *** ## Explanation ### Why project as code Project management tools like Plane are rich and powerful, but they have a structural problem: the configuration of a project - its work item types, its workflows, its state definitions - lives exclusively inside the tool. There is no file you can open, no diff you can review, no commit history you can trace. When a workflow changes, you cannot see who changed it, when, or why. When a project template drifts across teams, you have no way to detect it. When you want to spin up a new project that mirrors an existing one, you configure it manually from memory. Plane Compose addresses this by treating the project as an artifact that lives in your repository. The schema and work items are YAML files. Changes go through pull requests. History is in Git. ### The local-first model In a bidirectional sync tool, neither side is fully in control - changes can originate anywhere and the tool tries to merge them. This creates ambiguity: if a state is renamed both locally and in the UI at the same time, which one wins? Who is responsible for the project structure? Plane Compose takes a deliberate position: local files are the source of truth. The Plane remote is the target. You declare what you want; Plane Compose makes it so. Remote changes do not flow back automatically - you pull them deliberately when you choose to accept them, review the diff, and commit. This asymmetry is a feature, not a limitation. It means the project schema has a single authoritative home: your repository. It means changes are proposed through pull requests, reviewed by teammates, and tracked in Git history. It means a new team member can understand the entire project structure by reading YAML files rather than navigating a UI. The tradeoff is that Plane Compose requires discipline. If your team routinely reconfigures projects through the Plane UI and rarely pulls those changes back into local files, the local files drift out of date and lose their value as the source of truth. The model works best when local files are treated as the real project definition and the UI is used for day-to-day work on individual items, not for structural changes. ### The connection model The simplest possible authentication design would be a single API key stored somewhere on disk. Plane Compose uses a more structured model - connections - because a single key assumption breaks quickly in practice. Different workspaces may require different credentials. A developer might have access to a `myteam` workspace with a personal token and a `client-project` workspace under a separate account. A CI/CD system may use a workspace-scoped service token. A self-hosted Plane instance has a different server URL entirely. A connection bundles three things: a server URL, an auth type, and a token. Each connection gets a name. Workspaces are then linked to connections, so that when a command reads `workspace: myteam` from `plane.yaml`, it knows which set of credentials to use without you specifying it each time. This design also separates identity from configuration. `plane.yaml` contains the workspace slug - a human-readable project identity - but not the credentials. The credentials live in `~/.config/plane-compose/config.json`, separate from the repository. You can commit `plane.yaml` to Git without leaking tokens. ### Schema and work as separate concerns Plane Compose separates project content into two categories with different natures and different lifecycles. Schema files define *what is possible*: the types of work items that exist, the states they can move through, the labels available, the features enabled. Schema changes infrequently, is owned by leads or architects, and has consequences across the entire project. Work files define *what is happening*: the actual items, sprints, modules, and milestones. Work changes constantly - every day, by everyone on the team. This distinction shapes how you use Plane Compose. Schema is the part of the project you want to version-control rigorously, review carefully, and propagate from a template. Work is the part you might generate programmatically, import from another source, or let the team manage through the Plane UI. Some teams commit both to Git. Others commit only schema and treat work items as data managed through Plane directly. Both are valid. The separation also clarifies the dependency direction: schema must exist before work can be pushed, because work items reference type names, state names, and label names that need to resolve to remote UUIDs. This is why `plane push` always applies schema before work. --- --- url: 'https://developers.plane.so/dev-tools/openapi-specification.html' description: >- Generate and access the OpenAPI 3.0 specification for the Plane public REST API using drf-spectacular. --- # OpenAPI Specification Plane uses [drf-spectacular](https://drf-spectacular.readthedocs.io/) to generate an OpenAPI 3.0 specification for the public REST API (`/api/v1/`). The feature is **disabled by default** and must be explicitly enabled. ## Enable the OpenAPI spec Add the following to your `.env` file (at the project root or `apps/api/.env`): ```ini ENABLE_DRF_SPECTACULAR=1 ``` Then restart the API server so it picks up the new variable. | Variable | Required value | Default | Description | | ------------------------ | -------------- | ------- | ------------------------------------------------------------ | | `ENABLE_DRF_SPECTACULAR` | `1` | `0` | Activates drf-spectacular and registers the schema endpoints | No other environment variables are needed — everything else (schema path prefix, tags, auth schemes, servers) is pre-configured in `apps/api/plane/settings/openapi.py`. ## Access the OpenAPI spec > Replace `{domain_name}` below with your self-hosted Plane domain (e.g. `plane.example.com`). Once the API server is running with the variable enabled, three endpoints are available: | Endpoint | URL | Description | | ----------------------------- | ---------------------------------------------- | -------------------------- | | `GET /api/schema/` | `https://{domain_name}/api/schema/` | Raw OpenAPI schema (YAML) | | `GET /api/schema/swagger-ui/` | `https://{domain_name}/api/schema/swagger-ui/` | Interactive Swagger UI | | `GET /api/schema/redoc/` | `https://{domain_name}/api/schema/redoc/` | ReDoc documentation viewer | ## Download the OpenAPI spec ### Browser Open `https://{domain_name}/api/schema/` and save the page. The default format is YAML. For JSON, append the `format` query parameter: ```text https://{domain_name}/api/schema/?format=openapi-json ``` ### curl ```bash # YAML curl -o openapi.yaml https://{domain_name}/api/schema/ # JSON curl -o openapi.json https://{domain_name}/api/schema/?format=openapi-json ``` ### Management command (offline, no running server required) ```bash # From apps/api/ ENABLE_DRF_SPECTACULAR=1 python manage.py spectacular --file openapi.yaml ENABLE_DRF_SPECTACULAR=1 python manage.py spectacular --file openapi.json --format openapi-json ``` --- --- url: 'https://developers.plane.so/api-reference/inbox-issue/add-inbox-issue.html' description: >- Create intake issue via Plane API. HTTP POST request format, required fields, and example responses. --- # Add intake issue Adds an intake issue in a project ### Path Parameters ### Body Parameters An object containing the issue details, including a `name` field (required). ### Scopes `projects.intakes:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issue": "example-issue" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/", headers={"X-API-Key": "your-api-key"}, json={ 'issue': 'example-issue' } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issue: "example-issue", }), } ); const data = await response.json(); ``` ```json { "id": "project-uuid", "name": "Project Name", "identifier": "PROJ", "description": "Project description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-relations/create-work-item-relation.html description: >- Create work item relation via Plane API. HTTP request format, parameters, scopes, and example responses for create work item relation. --- # Create work item relation Create relationships between work items. Supports various relation types including blocking, blocked\_by, duplicate, relates\_to, start\_before, start\_after, finish\_before, and finish\_after. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters Type of relationship between work items * `blocking` - Blocking * `blocked_by` - Blocked By * `duplicate` - Duplicate * `relates_to` - Relates To * `start_before` - Start Before * `start_after` - Start After * `finish_before` - Finish Before * `finish_after` - Finish After Array of work item IDs to create relations with ### Scopes `projects.work_items:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "relation_type": "blocking", "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/", headers={"X-API-Key": "your-api-key"}, json={ "relation_type": "blocking", "issues": [ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000" ] } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ relation_type: "blocking", issues: ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"], }), } ); const data = await response.json(); ``` ```json [ [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Example Name", "sequence_id": 42, "project_id": "550e8400-e29b-41d4-a716-446655440000", "relation_type": "blocked_by", "state_id": "550e8400-e29b-41d4-a716-446655440000", "priority": "high", "type_id": "550e8400-e29b-41d4-a716-446655440000", "is_epic": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "created_by": "550e8400-e29b-41d4-a716-446655440000", "updated_by": "550e8400-e29b-41d4-a716-446655440000" } ] ] ``` --- --- url: 'https://developers.plane.so/api-reference/inbox-issue/delete-inbox-issue.html' description: >- Delete an intake issue from the inbox via Plane API. Permanently removes the triaged submission. Returns 204 on success. --- # Delete intake issue Deletes an intake issue ### Path Parameters ### Scopes `projects.intakes:write` ```bash curl -X DELETE \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.delete( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", { method: "DELETE", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json // 204 No Content ``` --- --- url: >- https://developers.plane.so/api-reference/inbox-issue/get-inbox-issue-detail.html description: >- Get intake issue detail details via Plane API. Retrieve complete information for a specific resource. --- # Get intake issue detail Gets the details of an intake issue ### Path Parameters ### Scopes `projects.intakes:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "project-uuid", "name": "Project Name", "identifier": "PROJ", "description": "Project description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: 'https://developers.plane.so/api-reference/inbox-issue/list-inbox-issues.html' description: >- List intake issues via Plane API. HTTP GET request with pagination, filtering, and query parameters. --- # List intake issues Gets all the intake issue of a project ### Path Parameters ### Scopes `projects.intakes:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "id": "project-uuid", "name": "Project Name", "identifier": "PROJ", "description": "Project description", "created_at": "2024-01-01T00:00:00Z" } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-relations/list-work-item-relations.html description: >- List work item relations via Plane API. HTTP request format, parameters, scopes, and example responses for list work item relations. --- # List work item relations Retrieve all relationships for a work item including blocking, blocked\_by, duplicate, relates\_to, start\_before, start\_after, finish\_before, and finish\_after relations. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Query Parameters Pagination cursor for getting next set of results Comma-separated list of related fields to expand in response Comma-separated list of fields to include in response Field to order results by. Prefix with '-' for descending order Number of results per page (default: 20, max: 100) ### Scopes `projects.work_items:read` ```bash curl -X GET \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/?cursor=20:1:0&expand=assignees" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" ``` ```python import requests response = requests.get( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/?cursor=20:1:0&expand=assignees", headers={"X-API-Key": "your-api-key"} ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/?cursor=20:1:0&expand=assignees", { method: "GET", headers: { "X-API-Key": "your-api-key", }, } ); const data = await response.json(); ``` ```json { "blocking": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"], "blocked_by": ["550e8400-e29b-41d4-a716-446655440000"], "duplicate": [], "relates_to": ["550e8400-e29b-41d4-a716-446655440000"], "start_after": [], "start_before": ["550e8400-e29b-41d4-a716-446655440000"], "finish_after": [], "finish_before": [] } ``` --- --- url: 'https://developers.plane.so/self-hosting/methods/one-click.html' description: >- Deploy Plane instantly with one-click installers on popular cloud providers. Quick setup on AWS, DigitalOcean, Render, and Railway. --- # One-click deploy ::: infoThis feature is included in our paid plans, but for a limited time, our community users can access it for free.::: ### Requirements * Operating systems: Debian, Ubuntu, CentOS * Supported CPU architectures: AMD64, ARM64, x86\_64, AArch64 ### Download the latest stable release Run ↓ on any CLI. ``` curl -fsSL https://raw.githubusercontent.com/makeplane/plane/master/deploy/1-click/install.sh | sh - ``` ### Download the Preview release `Preview` builds do not support ARM64, AArch64 CPU architectures Run ↓ on any CLI. ``` export BRANCH=preview curl -fsSL https://raw.githubusercontent.com/makeplane/plane/preview/deploy/1-click/install.sh | sh - ``` ### Successful installation You should see ↓ if there are no hitches. That output will also list the IP address you can use to access your Plane instance. ![Install Output](/images/one-click-deploy/one-click-install.png) ### Manage your Plane instance Use `plane-app` \[OPERATOR] to manage your Plane instance easily. Get a list of all operators with `plane-app ---help`. ![Plane Help](/images/one-click-deploy/one-click-help.png) 1. Basic operators 1. `plane-app start` starts the Plane server. 2. `plane-app restart` restarts the Plane server. 3. `plane-app stop` stops the Plane server. 2. Advanced operators `plane-app --configure` will show advanced configurators. ![Advanced operators](/images/one-click-deploy/one-click-advanced.png) * Change your proxy or listening port * Change your domain name * File upload size 3. Version operators 1. `plane-app --upgrade` gets the latest stable version of `docker-compose.yaml`, `.env`, and Docker images 2. `plane-app --update-installer` updates the installer and the `plane-app` utility. 3. `plane-app --uninstall` uninstalls the Plane application and all Docker containers from the server but leaves the data stored in Postgres, Redis, and Minio alone. 4. `plane-app --install` installs the Plane app again. --- --- url: 'https://developers.plane.so/api-reference/inbox-issue/overview.html' description: >- Plane Inbox-Issue API overview. Learn about endpoints, request/response format, and how to work with inbox-issue via REST API. --- # Overview ::: warning **Deprecation notice** We are deprecating all `/api/v1/.../inbox-issues/` endpoints in favor of `/api/v1/.../intake-issues/`. **End of support**\ 31st March 2025 **What you need to do**\ To ensure uninterrupted service, replace all `/inbox-issues/` references with `/intake-issues/` in your codebase before the support end date. ::: To enable the Intake feature, the user can hit a PATCH request on the project api with the body as ```http GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/inbox-issues/ POST /api/v1/workspaces/{workspace_slug}/projects/{project_id}/inbox-issues/ GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/inbox-issues/{work_item_id}/ PATCH /api/v1/workspaces/{workspace_slug}/projects/{project_id}/inbox-issues/{work_item_id}/ DELETE /api/v1/workspaces/{workspace_slug}/projects/{project_id}/inbox-issues/{work_item_id}/ ``` ``` { inbox_view:true, } ``` To create an Intake issue, the payload should be sent in the below format ```json { "issue": { "name": "Snoozed Issue 2", "priority": "high" } } ``` ### Intake issue object **Attribute** * `created_at` *timestamp* The timestamp of the time when the project was created * `updated_at` *timestamp* The timestamp of the time when the project was last updated * `status` the status of the issue can be in above mentioned status * -2 - Pending * -1 - Rejected * 0 - Snoozed * 1 - Accepted * 2 - Duplicate * `snoozed_till` The time untill the issue is snoozed. * `source` The source describes the type intake issue from * `created_by` , `updated_by` *uuid* These values are auto saved and represent the id of the user that created or updated the module * `Project` uuid It contains projects uuid which is automatically saved. * `Workspace` uuid It contains workspace uuid which is automatically saved. * `inbox` intake id of the issue * `issue` issue id of the issue * `duplicate_to` Id of the issue of which the current issue is duplicate of. ```json { "id": "0de4d6d1-fdc7-4849-8080-dc379ab210e3", "pending_issue_count": 0, "created_at": "2023-11-21T07:32:26.072634Z", "updated_at": "2023-11-21T07:32:26.072648Z", "name": "a dummy project with Intake", "description": "", "is_default": true, "view_props": {}, "created_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "updated_by": "0649cb9d-05c8-4ef4-8e8b-d108ccddd42c", "project": "6436c4ae-fba7-45dc-ad4a-5440e17cb1b2", "workspace": "c467e125-59e3-44ec-b5ee-f9c1e138c611" } ``` --- --- url: 'https://developers.plane.so/api-reference/work-item-relations/overview.html' description: >- Plane Work Item Relations API overview. Learn how to list, create, and remove work item relationships using the Plane API. --- # Overview Work item relations let you model dependencies and planning constraints between work items, including blocking, duplicate, relates-to, and scheduling relationships. [Learn more about Work Items](https://docs.plane.so/core-concepts/issues) ## The Work Item Relations Response ### Attributes * `blocking` *array* List of issue IDs that are blocking this issue * `blocked_by` *array* List of issue IDs that this issue is blocked by * `duplicate` *array* List of issue IDs that are duplicates of this issue * `relates_to` *array* List of issue IDs that relate to this issue * `start_after` *array* List of issue IDs that start after this issue * `start_before` *array* List of issue IDs that start before this issue * `finish_after` *array* List of issue IDs that finish after this issue * `finish_before` *array* List of issue IDs that finish before this issue ```json { "blocking": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000"], "blocked_by": ["550e8400-e29b-41d4-a716-446655440000"], "duplicate": [], "relates_to": ["550e8400-e29b-41d4-a716-446655440000"], "start_after": [], "start_before": ["550e8400-e29b-41d4-a716-446655440000"], "finish_after": [], "finish_before": [] } ``` --- --- url: >- https://developers.plane.so/api-reference/work-item-relations/remove-work-item-relation.html description: >- Remove work item relation via Plane API. HTTP request format, parameters, scopes, and example responses for remove work item relation. --- # Remove work item relation Remove a relationship between work items by specifying the related work item ID. ### Path Parameters The unique identifier of the work item. The unique identifier of the project. The workspace\_slug represents the unique workspace identifier for a workspace in Plane. It can be found in the URL. For example, in the URL `https://app.plane.so/my-team/projects/`, the workspace slug is `my-team`. ### Body Parameters ID of the related work item to remove relation with ### Scopes `projects.work_items:write` ```bash curl -X POST \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/remove/" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "related_issue": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python import requests response = requests.post( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/remove/", headers={"X-API-Key": "your-api-key"}, json={ "related_issue": "550e8400-e29b-41d4-a716-446655440000" } ) print(response.status_code) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/work-items/work-item-uuid/relations/remove/", { method: "POST", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ related_issue: "550e8400-e29b-41d4-a716-446655440000", }), } ); console.log(response.status); ``` No response body. --- --- url: >- https://developers.plane.so/api-reference/inbox-issue/update-inbox-issue-detail.html description: >- Update intake issue detail via Plane API. HTTP PATCH request format, editable fields, and example responses. --- # Update intake issue detail Updates the details of an intake issue ### Path Parameters ### Body Parameters An object containing the issue details to update, including an optional `name` field. ### Scopes `projects.intakes:write` ```bash curl -X PATCH \ "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid" \ -H "X-API-Key: $PLANE_API_KEY" \ # Or use -H "Authorization: Bearer $PLANE_OAUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "issue": "example-issue" }' ``` ```python import requests response = requests.patch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", headers={"X-API-Key": "your-api-key"}, json={ 'issue': 'example-issue' } ) print(response.json()) ``` ```javascript const response = await fetch( "https://api.plane.so/api/v1/workspaces/my-workspace/projects/project-uuid/inbox-issues/issue-uuid", { method: "PATCH", headers: { "X-API-Key": "your-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ issue: "example-issue", }), } ); const data = await response.json(); ``` ```json { "id": "project-uuid", "name": "Project Name", "identifier": "PROJ", "description": "Project description", "created_at": "2024-01-01T00:00:00Z" } ```