Project files
plane init and plane clone create a project folder like this:
myproj/
├── plane.yaml # project config
├── workspace.yaml # snapshot of workspace features, written by init
├── schema/
│ ├── features.yaml
│ ├── types.yaml
│ ├── states.yaml
│ ├── workflows.yaml
│ ├── labels.yaml
│ ├── members.yaml
│ ├── workitem_templates.yaml
│ └── page_templates.yaml
├── work/
│ ├── workitems.yaml
│ ├── cycles.yaml
│ ├── modules.yaml
│ ├── milestones.yaml
│ └── pages.yaml
├── .plane/
│ └── state.json # sync state, git-ignored
└── .gitignore # ignores .plane/Commit the YAML. Don't commit .plane/. After a fresh git clone of your config repo, run plane schema import to reconnect local names to their remote IDs before you push.
plane.yaml
type: project
workspace: myteam
connection: connection-1 # optional: which saved connection to use
project:
key: MYPROJ # max 10 characters, A-Z and 0-9
name: Myproj
description: ""
network: public # public | private
timezone: UTC # IANA name
uuid: <written after the first push>
defaults:
type: Story
api_url: https://api.plane.so # optional; set for self-hosted
schema:
extends: ../shared/schema # optional: inherit another schema folder
template: default # template used at init or upgrade| Key | Meaning |
|---|---|
workspace | Required. Workspace slug. |
connection | Optional connection ID from plane auth list-connections. This is what selects credentials for push, pull, diff and the other project commands. See Which connection a command uses. |
project.key | Required. The Plane project identifier, used as the prefix of work item IDs such as MYPROJ-12. plane init upper-cases it, strips other characters, and rejects keys longer than 10 characters. |
project.name, description, network, timezone | Project metadata, pushed by plane schema push. |
project.uuid | Filled in once the project exists in Plane. Don't set it by hand. |
api_url | Plane server base URL. Defaults to https://api.plane.so. Left at the default, the connection's server URL is used. |
defaults.type | Read but not used in v0.5.2. A work item with no type is sent as task. |
schema.extends | Relative path to a parent schema folder to inherit from. |
template | The template source, stored as typed. init records default; plane upgrade rewrites it. |
Schema files
Schema files describe the project's structure. Files can only reference names defined elsewhere in the schema: workflows list states from states.yaml, and work items use type, state and label names from the schema.
schema/features.yaml
Turns project features on or off. Changes apply on the next plane schema push.
features:
cycles: true
modules: true
pages: true
views: true
intakes: false
epics: false
work_item_types: false
workflows: false
parallel_cycles: false
manually_start_end_cycles: false
project_updates: false
time_tracking: falseThe values above are the built-in template's defaults. features.yaml also gates data: with cycles: false, modules: false or pages: false, that section isn't pushed even if its file in work/ has content.
schema/states.yaml
Each key is a state name. Every state belongs to one of five groups: backlog, unstarted, started, completed or cancelled.
states:
Backlog:
group: backlog
color: "#60646C"
allow_issue_creation: true
is_default: true
In Progress:
group: started
color: "#F59E0B"
Done:
group: completed
color: "#46A758"
Cancelled:
group: cancelled
color: "#9AA4BC"| Field | Meaning |
|---|---|
group | State group. Defaults to unstarted. |
color | Hex color. |
allow_issue_creation | Whether work items can be created directly in this state. Default true. |
is_default | The state new work items get. Set it on one state only. |
The built-in template ships Icebox, Backlog, Ready, In Progress, In Review, Testing, Done and Cancelled. Don't delete a state that a workflow uses.
schema/labels.yaml
A list of labels. id is written back after the first push.
labels:
- name: Frontend
color: "#3b82f6"
- name: Backend
color: "#10b981"The built-in template ships Frontend, Backend, Infrastructure, Documentation and Urgent.
schema/workflows.yaml
Workflows restrict which state changes are allowed for given work item types, and can require approval for a change. They need workflows: true in features.yaml. Plane creates a default workflow itself when the feature is on, so define only your own workflows here.
Each key under states is a state from states.yaml. A state's flow_type is transition (move freely) or approval (needs sign-off), and to lists the states it can move to. Use {} for a target with no extra settings, and for a state with no outgoing transitions.
workflows:
Feature Workflow:
description: Feature development with review
is_active: true
work_item_types:
- Feature
states:
Backlog:
flow_type: transition
to:
- In Progress: {}
In Progress:
flow_type: approval
to:
- Done:
approvers: [lead@example.com]
required_approvals: 1
rejection_state: Backlog
Done: {}| Field | Meaning |
|---|---|
description | Free text. |
is_active | Whether Plane enforces the workflow. Default false. |
work_item_types | Type names the workflow applies to. |
states.<State> | flow_type and to for that state, or {}. |
approvers | Emails of the members who can approve. Approval flows only. |
required_approvals | How many approvals are needed. Empty means every listed approver. |
rejection_state | Where the work item goes if the change is rejected. |
conditions.pre, .post | Rules run before and after the transition. plane pull writes these from Plane. |
This is the form plane pull writes. The older form, a list of state names plus a separate transitions: map of - to: <State> entries with a per-entry type, is still read.
schema/types.yaml
Custom work item types, and custom properties on Plane's own types (Bug, Feature, Task, Chore, Improvement). Custom types need work_item_types: true in features.yaml.
work_item_types:
Bug:
description: A defect or issue requiring a fix
properties:
- name: severity
type: option
required: true
options:
- minor
- major
- critical
- name: is_regression
type: boolean
RFC:
description: Design proposal requiring team feedback
logo_props:
icon: Activity
background_color: "#6366f1"
properties:
- name: review_deadline
type: datetime| Field | Meaning |
|---|---|
description | Free text. |
is_epic | Mark the type as an epic type. Needs epics: true in features.yaml. |
logo_props | icon and background_color, or emoji. |
properties[].name | Property name. Work items set values under this name in properties. |
properties[].type | Property type, from the list below. |
properties[].required | Whether the property is required. Default false. |
properties[].options | Choices for an option property. |
properties[].is_multi | Allow several values for an option property. Default false. |
Property types: text, number, decimal, date, datetime, option, boolean, url, email, file, member_picker, relation, release_picker and formula. string, enum and user are accepted as older names for text, option and member_picker. In v0.5.2, release_picker properties are read on pull but can't be pushed, and formula properties can't be pushed yet.
plane schema push never deletes a property that's missing from types.yaml unless you pass --prune.
schema/members.yaml
Project members, each with email and an optional role (guest, member or admin). plane init seeds it with the token owner as admin, and plane pull refreshes it.
schema/workitem_templates.yaml and page_templates.yaml
Project-level work item and page templates. They're written by plane pull and pushed by plane schema push, and need templates to be available on your Plane instance. The id field is written back by Plane; don't edit it.
Work files
work/workitems.yaml
Every *.yaml file in work/ except cycles.yaml, modules.yaml, milestones.yaml and pages.yaml is read as a work items file, in alphabetical order. That lets you split work items across files such as work/backend.yaml and work/frontend.yaml. The usual form is a workitems: list; a bare list or a single item is also accepted.
workitems:
- id: MYPROJ-12
title: Add rate limiting to the public API
type: Task
state: In Progress
priority: high
labels: [Backend]
assignees: [dev@example.com]
start_date: "2026-10-01"
due_date: "2026-10-15"
parent: MYPROJ-10
dependencies:
blocked_by: [MYPROJ-11]
relations:
relates_to: [MYPROJ-14]
properties:
severity: major| Field | Notes |
|---|---|
title | Required. |
id | Optional but strongly recommended. A stable tracking key such as MYPROJ-12. See Sync state. |
description | Free text. |
type | A type name such as Task, or {name: Task, id: <uuid>}. plane pull writes the second form. |
state | A state name from states.yaml. |
priority | urgent, high, medium, low or none, in any case. |
labels | Label names. |
start_date, due_date | YYYY-MM-DD. A due date before the start date is an error. |
parent | Parent work item ID. An item can't be its own parent, and parent loops are errors. |
assignees | Emails or user UUIDs. |
dependencies | Map of blocked_by, blocking, starts_before, starts_after, finishes_before and finishes_after to lists of work item IDs. |
relations | Map of relates_to, duplicate, implements and implemented_by, plus your workspace's custom relation names, to lists of work item IDs. |
properties | Custom property values keyed by property name, checked against the item's type. |
watchers and children are rejected by validation in v0.5.2 because push doesn't sync them. Flatten child items into top-level items with parent set.
Older files may use flat blocked_by, blocking, relates_to and duplicate_of keys. They're still read and mapped into dependencies and relations.
work/cycles.yaml and work/modules.yaml
cycles:
- name: Sprint 1
description: Core setup
start_date: "2026-10-01"
end_date: "2026-10-14"modules:
- name: Authentication
description: Login, OAuth and sessions
start_date: "2026-10-01"
end_date: "2026-12-31"
status: in-progressPlane works out a cycle's status from its dates, so a status on a cycle is ignored. Module status is one of backlog, planned, in-progress, paused, completed or cancelled.
work/milestones.yaml
milestones:
- name: Beta launch
target_date: "2026-12-31"
work_items:
- MYPROJ-1
- MYPROJ-5Milestones need the Milestones feature turned on for the project in Plane.
work/pages.yaml
pages:
- name: Architecture overview
description_html: "<p>System architecture documentation.</p>"
owner: alice@example.com
access: public # public | privatePages are create-only. A page without an id is created on push and its id is written back. A page that already has an id is skipped, because Plane has no update API for it; edit it in Plane instead. Removing a page from the file doesn't delete it.
Sync state
.plane/state.json connects your YAML to Plane. It maps names (types, states, labels, cycles, modules, milestones, pages, workflows and properties) to remote IDs, and stores each work item's tracking key with a hash of its content at the last sync.
| Concept | How it works |
|---|---|
| Tracking key | id:<your id> when the item has an id; otherwise hash:<content hash>. Editing the title of a hash-tracked item changes its key, which creates a duplicate. |
| ID write-back | When a push creates items with no id, plane writes the Plane sequence ID into your YAML and updates the tracking key. |
| Change detection | A SHA-256 hash over title, description, type, state, priority, labels, assignees, dates, parent, dependencies, relations and properties. Unchanged items are skipped. |
| Additive push | Push creates and updates. Removing an item from YAML doesn't delete it in Plane. |
| Conflict check | Before updating an item, push compares its current remote hash with the last-synced one. If someone changed it in Plane, you're asked before it's overwritten. |
| Locking | State writes are atomic and locked within one process. Run one plane push per project at a time, including in CI. |
To look inside the state or fix one entry, see plane state.

