Build a Plane App
Step-by-step development guide to building a Plane App
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
- Register your Plane App
- Implement OAuth
- Obtain access tokens
- Make authenticated API requests to Plane
- Handle token refresh
Register Your Plane App
- Go to
https://app.plane.so/<workspace_slug>/settings/applications/
. - Click Build your own.
- Fill out the required details:
- Name and Short Description
- Redirect URIs
- Contact Details
- Setup URL (Optional)
- Webhook URL Endpoint (Optional)
- Organization Details (Optional)
- If you're building an agent, enable the Is Mentionable checkbox.
- Once the app is created, securely store the generated Client ID and Client Secret.
Implement OAuth Flow
Generating Consent URL (Optional)
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.
- Python
- TypeScript
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)}"
import { URLSearchParams } from 'url';
const params = new URLSearchParams({
client_id: process.env.PLANE_CLIENT_ID!,
response_type: "code",
redirect_uri: process.env.PLANE_REDIRECT_URI!,
});
const consentUrl = `https://api.plane.so/auth/o/authorize-app/?${params.toString()}`;
There are two types of authenticated actions your app can perform:
- User-authorized actions: Actions performed on behalf of a user after they grant permission via OAuth.
- 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.
Parameter | Description |
---|---|
app_installation_id | The unique identifier for the app installation in the workspace |
Example
- Python
- TypeScript
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"]
import axios from 'axios';
const clientId = "your_client_id";
const clientSecret = "your_client_secret";
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const payload = {
grant_type: "client_credentials",
app_installation_id: appInstallationId
};
const response = await axios.post(
"https://api.plane.so/auth/o/token/",
payload,
{
headers: {
Authorization: `Basic ${basicAuth}`,
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const responseData = response.data;
const botToken = responseData.access_token;
const expiresIn = responseData.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:
Parameter | Description | Required |
---|---|---|
code | The authorization code to exchange for an access token | Yes |
state | The state parameter passed in the authorization request | No |
Example
- Python
- TypeScript
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"]
import axios from 'axios';
const code = "authorization_code_from_callback";
const clientId = "your_client_id";
const clientSecret = "your_client_secret";
const redirectUri = "your_redirect_uri";
const payload = {
grant_type: "authorization_code",
code: code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri
};
const response = await axios.post(
"https://api.plane.so/auth/o/token/",
payload,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const responseData = response.data;
const accessToken = responseData.access_token;
const refreshToken = responseData.refresh_token;
const expiresIn = responseData.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.
- Python
- TypeScript
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]
import axios from 'axios';
const headers = { Authorization: `Bearer ${token}` };
const response = await axios.get(
`https://api.plane.so/auth/o/app-installation/?id=${app_installation_id}`,
{ headers }
);
const workspaceDetails = 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.
- Python
- TypeScript
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"]
const refreshPayload = {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret
};
const refreshResponse = await axios.post(
"https://api.plane.so/auth/o/token/",
refreshPayload,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const refreshResponseData = refreshResponse.data;
const accessToken = refreshResponseData.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.