Skip to main content

Build a Plane App

Step-by-step development guide to building a Plane App

Beta

Plane Apps are currently in Beta. Some aspects of the API may change. Please send feedback to support@plane.so.

Prerequisites

  • A Plane workspace
  • Admin access to your Plane workspace settings
  • Familiarity with OAuth 2.0 concepts
  • A backend server to handle OAuth token exchange

High-Level Workflow

  1. Register your Plane App
  2. Implement OAuth
  3. Obtain access tokens
  4. Make authenticated API requests to Plane
  5. Handle token refresh

Register Your Plane App

  1. Go to https://app.plane.so/<workspace_slug>/settings/applications/.
  2. Click Build your own.
  3. Fill out the required details:
    • Name and Short Description
    • Redirect URIs
    • Contact Details
    • Setup URL (Optional)
    • Webhook URL Endpoint (Optional)
    • Organization Details (Optional)
  4. If you're building an agent, enable the Is Mentionable checkbox.
  5. Once the app is created, securely store the generated Client ID and Client Secret.

Implement OAuth Flow

If your app should be installed from outside Plane, generate the consent URL using the client ID from app creation. Provide this URL in the "Setup URL" field if you want the flow triggered from the marketplace.

import os
from urllib.parse import urlencode
params = {
"client_id": os.getenv("PLANE_CLIENT_ID"),
"response_type": "code",
"redirect_uri": os.getenv("PLANE_REDIRECT_URI"),
}
consent_url = f"https://api.plane.so/auth/o/authorize-app/?{urlencode(params)}"

There are two types of authenticated actions your app can perform:

  1. User-authorized actions: Actions performed on behalf of a user after they grant permission via OAuth.
  2. App-authorized actions: Actions the app can perform independently within the workspace where it is installed.

For both flows, Plane will make a GET request to the Redirect URI with parameters as described below.

App-Authorized Actions (Client Credentials Flow)

When the app is installed, Plane will send an app_installation_id as part of the callback. Use this to request a bot token for your app.

ParameterDescription
app_installation_idThe unique identifier for the app installation in the workspace

Example

import base64
import requests
client_id = "your_client_id"
client_secret = "your_client_secret"
basic_auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
payload = {
"grant_type": "client_credentials",
"app_installation_id": app_installation_id
}
response = requests.post(
url="https://api.plane.so/auth/o/token/",
headers={"Authorization": f"Basic {basic_auth}", "Content-Type": "application/x-www-form-urlencoded"},
data=payload
)
response_data = response.json()
bot_token = response_data['access_token']
expires_in = response_data["expires_in"]

User-Authorized Actions (Authorization Code Flow)

In this flow, your app exchanges the code received as a query parameter on the callback for an access token and refresh token. The access token is short-lived and must be refreshed using the refresh token when it expires. Store both tokens securely.

Plane will make a GET request to the Redirect URI with:

ParameterDescriptionRequired
codeThe authorization code to exchange for an access tokenYes
stateThe state parameter passed in the authorization requestNo

Example

import requests
code = "authorization_code_from_callback"
client_id = "your_client_id"
client_secret = "your_client_secret"
redirect_uri = "your_redirect_uri"
payload = {
"grant_type": "authorization_code",
"code": code,
"client_id": client_id,
"client_secret": client_secret,
"redirect_uri": redirect_uri
}
response = requests.post(
url="https://api.plane.so/auth/o/token/",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data=payload
)
response_data = response.json()
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
expires_in = response_data["expires_in"]

Fetching App Installation Details

In both flows, the app_installation_id identifies the app's installation within a workspace. Fetch workspace details after OAuth is completed. Use the app-installation endpoint with either type of token.

import requests
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
url=f"https://api.plane.so/auth/o/app-installation/?id={app_installation_id}",
headers=headers
)
workspace_details = response.data[0]

Sample Response

[
{
"id": "34b97361-8636-43dc-953e-90deedc8498f",
"workspace_detail": {
"name": "sandbox",
"slug": "sandbox",
"id": "7a2e5944-c117-4a7d-b5f4-058fe705d7d1",
"logo_url": null
},
"created_at": "2025-05-16T13:50:27.865821Z",
"updated_at": "2025-06-23T08:57:26.976742Z",
"deleted_at": null,
"status": "installed",
"workspace": "7a2e5944-c117-4a7d-b5f4-058fe705d7d1",
"application": "ab235529-388a-4f51-a55a-78272251f5f1",
"installed_by": "63333ab1-c605-42fc-82f7-5cd86799eca1",
"app_bot": "7286aaa7-9250-4851-a520-29c904fd7654",
"webhook": "b1f4b7f1-51e8-4919-a84c-0b1143b51d2c"
}
]

Obtain and store access tokens securely

Store the access and refresh tokens securely in your database or secrets manager.

Make authenticated API requests to Plane

Use the access token to make authenticated requests to Plane via the Plane API or official SDKs.

Handle Token Refresh

When the access token expires, use the refresh token to obtain a new access token.

refresh_payload = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": client_id,
"client_secret": client_secret
}
refresh_response = requests.post(
url="https://api.plane.so/auth/o/token/",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data=refresh_payload
)
refresh_response_data = refresh_response.json()
access_token = refresh_response_data["access_token"]

Listing Your App on Plane Marketplace

Apps built using the OAuth flow can be listed on the Plane Marketplace: https://plane.so/marketplace/integrations

To list your app, contact the Plane team at support@plane.so.