Skip to content

Custom Status Values

[view as markdown]

Issue status defaults to todo, in-progress, done. Teams that want richer workflows (e.g. backlog, review, blocked, qa) register status as a property with their own option list. status is a regular property key, so there is no new API to learn.

When no extension registers status, fp falls back to the built-in three. Behavior is identical to projects that never installed an extension.

Drop this file in .fp/extensions/workflow.ts. It declares a five-status workflow and uses the built-in status icons so the desktop renders them with the same look as the defaults.

import type { ExtensionInit } from "@fiberplane/extensions";
const init: ExtensionInit = async (fp) => {
await fp.issues.registerProperty("status", {
label: "Status",
display: fp.ui.properties.select(
fp.ui.properties.option("backlog", {
label: "Backlog",
icon: "no-status",
color: "neutral",
}),
fp.ui.properties.option("todo", {
label: "Todo",
icon: "to-do",
color: "neutral",
}),
fp.ui.properties.option("in-progress", {
label: "In Progress",
icon: "in-progress",
color: "blue",
}),
fp.ui.properties.option("review", {
label: "For Review",
icon: "for-review",
color: "purple",
}),
fp.ui.properties.option("done", {
label: "Done",
icon: "done",
color: "success",
}),
),
});
fp.on("issue:status:changing", ({ from, to }) => {
if (from === "backlog" && to === "done") {
return {
code: "NO_SKIP_REVIEW",
message: "Issues must pass through review before being marked done.",
};
}
return undefined;
});
};
export default init;

The order of options inside select(...) controls everything: the picker order, kanban column order, filter-bar order, sort priority, and tab-completion order. To reorder, edit the extension.

The first option is the default for new issues created without --status. The last option is treated as the completed state, so issues with it are not counted as open work.

  • deleted is reserved as a soft-delete marker. Registering an option with value: "deleted" (or a case- or separator-insensitive equivalent like Deleted or de-leted) is rejected when the extension loads.
  • All other non-empty strings are accepted as status values.

When no extension registers status, fp behaves as if these three options were registered:

ValueLabelIconColor
todoTodoto-doneutral
in-progressIn Progressin-progressblue
doneDonedonesuccess

Once an extension registers status, its option list replaces the fallback entirely. If multiple extensions register status, the last to load wins (extensions load globals first, then project, alphabetically by filename within each scope).

PropertyOption.icon accepts three families of names, and you can mix them within a single registration.

Section titled “Built-in status icons (recommended for status)”

The desktop ships six status icons. Use these for status options to match the rest of the UI:

  • to-do (empty circle)
  • in-progress (half-filled circle)
  • for-review (circle with inset arrow)
  • done (filled circle with check)
  • no-status (dashed circle)
  • cancelled (circle with diagonal slash)

Common Lucide names for the same shapes are accepted as aliases. They render the built-in variant, not the raw Lucide icon, so the look stays consistent:

InputRenders as
circle, circle-emptyto-do
loader-circle, circle-halfin-progress
circle-arrow-right, circle-arrowfor-review
circle-checkdone
circle-dashedno-status
circle-slash, circle-xcancelled

Name matching is case-, separator-, and whitespace-insensitive.

Any other name is looked up as a Lucide icon by its PascalCase equivalent, so flag, clock, git-branch, and the like render the matching Lucide icon. A name Lucide does not recognize falls back to a dashed circle.

Some surfaces (the CLI --current filter and “current work” views) need to know which statuses count as active work. fp derives this set automatically from the registered order; there is nothing to declare on individual options.

To set your own values, override them in TOML config. The override is read from three tiers, most specific first:

  1. .fp/config.local.toml: this user, this project
  2. .fp/config.toml: this project, all users
  3. ~/.fiberplane/config.toml: this user, all projects
[status]
current = ["in-progress", "review"]

Run fp guide from inside the project to see the resolved set. The project context header lists the values and labels them [auto] or [from <tier>], so you can tell whether fp derived the set or your config did.

The issue:status:changing and issue:status:changed payloads carry from and to as status strings, so hook handlers can compare against any status your workflow registers:

fp.on("issue:status:changing", ({ from, to }) => {
if (from === "backlog" && to === "done") {
return {
code: "NO_SKIP_REVIEW",
message: "Issues must pass through review before being marked done.",
};
}
return undefined;
});

The hook fires for every status transition regardless of source (CLI, desktop UI, another extension). Return a HookValidationError to block the change; return undefined to allow it.