Docs / Admin
Issue tracking and configurable issue types
Summary — The issues module is a generic, configurable tracker. Admins define issue types (slug, display name, status set, and custom fields) at /admin/issues/types. Staff then create and work issues of those types at /issues, updating status and adding comments.
Purpose — Understand how to configure an issue type's custom fields and allowed statuses, how staff create and manage issues day to day, and what happens when a status transition is attempted that the issue type does not allow.
Audience — Admins who configure issue types. Staff, reviewer, lead, and admin roles who create and work issues.
Prerequisites — None. Issue types must exist before staff can create issues (the create form has no fallback if none are configured).
Overview
An issue type defines the shape of a category of trackable work: its slug, display name, icon, default status, allowed statuses, and any custom fields. An issue is one record of that type — a title, description, priority, assignment, due date, tags, and the type's custom fieldData.
Key concepts:
- Issue type — Configuration record (
issue_typestable) withslug,displayName,defaultStatus,allowedStatuses(array),fields(array of custom field definitions), andisActive. - Issue — A work item (
issuestable) referencing anissueTypeId, withstatus,priority,assignedTo,dueDate,tags, andfieldData(a JSON object holding values for the type's custom fields). - Custom fields — Per-type field definitions (slug, label, type, required, options) stored on the issue type and rendered by staff when filling in
fieldData. - Status transitions — Changing an issue's
statusis validated against the owning issue type'sallowedStatusesarray; an illegal value is rejected. - Comments — Free-text notes on an issue (
issue_commentstable), with acommentTypeofcomment,status_change,assignment, orsystem. Status changes automatically create astatus_changecomment. - Linking — Issues carry optional
linkedEntityType/linkedEntityIdfields so an issue can reference another record elsewhere in the platform, and can be filtered by those fields vialistIssuesAction.
Screenshot: Issue Types admin table showing Slug, Display Name, Icon, Fields count, Active badge, and Edit link columns.
Configuring an issue type
Path: /admin/issues/types → Create Type button
This opens /admin/issues/types/new, a three-card form:
Basic Information
| Field | Notes |
|---|---|
| Display Name* | Required. E.g., "Bug Report". Typing here auto-fills the slug (via slugify()) unless the slug field has been manually edited. |
| Slug (auto-generated) | Editable text field, kept in sync with Display Name until touched directly. |
| Description | Optional free text. |
| Icon | Selected via the IconPicker component. |
Status Configuration
| Field | Notes |
|---|---|
| Default Status* | Required text field. Defaults to open in the form. This becomes the status new issues of this type start in. |
| Allowed Statuses (comma-separated)* | Required. Defaults to open,in_progress,closed. The page shows a live preview line: "Currently: open, in_progress, closed". This list is the authoritative set of statuses this issue type accepts. |
Custom Fields
Click Add Field to append a field row. Each row has:
| Sub-field | Notes |
|---|---|
| Slug | Auto-seeded as field_{timestamp}, editable. |
| Label | Free text. |
| Type | Dropdown of exactly ten field types: text, textarea, number, select, multiselect, radio, checkbox, date, rating, file. |
| Options | Only shown when Type is select, radio, or multiselect — rendered by the shared OptionsEditor component (value/label pairs). |
| Required | Checkbox. |
Click Remove on a field row to delete it before submitting.
Click Create Issue Type (disabled until Slug, Display Name, and Default Status are all filled) to submit. On success you are redirected to /admin/issues/types.
Expected result: New row in
issue_typeswithis_active: true, an audit eventissue_type.createdlogged with the slug in metadata.
Role required: admin (requireRole("admin", { module: "issues" }) on both createIssueTypeAction and updateIssueTypeAction).
Editing an issue type
The Issue Types list page renders an Edit link to /admin/issues/types/{id} for every type, but no page exists at that route in the current build — clicking it will 404. updateIssueTypeAction exists in the module and can update display name, description, icon, default status, allowed statuses, fields, config, and isActive, but there is no UI form wired to call it yet.
Creating an issue
Path: /issues → + Create Issue button → /issues/new
The form loads all active issue types and the assignable staff/admin user list, then presents:
| Field | Required | Notes |
|---|---|---|
| Issue Type | Yes (dropdown, defaults to the first type returned) | Populated from listIssueTypesAction(false) — all active types. |
| Title | Yes | Max 500 characters. |
| Description | No | Max 5000 characters. |
| Priority | No | Dropdown: Low, Medium, High, Critical. Defaults to Medium. |
| Assigned To | No | Dropdown of assignable users, or "Unassigned". |
| Due Date | No | Date input. |
| Tags (comma-separated) | No | Parsed as JSON if valid, otherwise split on commas and trimmed. |
Note: the create form does not render the issue type's custom fields — there is no dynamic field renderer on /issues/new. fieldData can only be populated by direct action calls, not through this UI form.
Click Create Issue. On success you are redirected to /issues/{id}.
Expected result: New row in
issueswithstatusset to the issue type'sdefaultStatus, an audit eventissue.createdlogged withtitleandtypeIdin metadata.
Role required: staff (requireRole("staff", { module: "issues" })).
Working an issue
Path: /issues → click a row → /issues/{id}
The detail page has three tabs: Details, Comments (n), History.
Details tab
Shows description (if present), a Custom Fields card listing every key in fieldData as key: value pairs (raw, unlabeled — the type's field definitions are not used to render labels here), and a sidebar with:
- Update Status — a dropdown fixed to five values (
Open,In Progress,Resolved,Closed,On Hold) plus an Update Status button, disabled while the selected value equals the current status. - A summary card: Status (via
StatusBadge), Assigned To (raw user ID, unformatted — no name lookup), Due Date, Tags, and Created timestamp.
Important: the status dropdown on this page always offers the same five hardcoded values, regardless of the issue type's configured allowedStatuses. If the type's allowedStatuses does not include the selected value, the server rejects the transition (see Troubleshooting below).
Comments tab
Lists all comments (oldest first) with author ID (truncated to 8 characters) and timestamp. A form at the bottom posts a new comment (content, required). Comments created through this form default to commentType: "comment".
Status transitions automatically insert a status_change comment (see below), so the Comments tab doubles as a partial history of status changes for that issue.
History tab
Placeholder only: "Issue history is tracked in the audit log." There is no in-app rendering of the audit trail on this page — an admin must query audit_events directly.
Role required: staff for viewing, transitioning, and commenting (getIssueAction, transitionIssueAction, addCommentAction, listCommentsAction all call requireRole("staff", ...)). Archiving requires lead (see below).
Status values
The issues UI (/issues filter bar and the /issues/{id} Update Status dropdown) hardcodes five status values:
| Value | Label |
|---|---|
open | Open |
in_progress | In Progress |
resolved | Resolved |
closed | Closed |
on_hold | On Hold |
These are UI defaults, not a database enum — the actual set of statuses an issue can hold is whatever the owning issue type's allowedStatuses array contains (set at issue-type creation, see above). If an admin configures a type with different statuses, the fixed dropdown values will not match, and any mismatched selection will be rejected server-side.
How a status transition is validated
- Staff submits a new status via the Update Status form.
transitionIssueActioncallsservices.transitionStatus(id, { status, notes }, userId).- The service loads the issue's owning
issueTypeand checksissueType.allowedStatuses.includes(input.status). - If not allowed, it throws:
Status '{status}' not allowed for this issue type. - If allowed, the issue's
statuscolumn is updated, astatus_changecomment is created (content defaults toStatus changed from {old} to {new}if no notes were supplied), and an audit eventissue.status_changedis logged withfromStatus/toStatus.
Archiving an issue
There is no archive button visible in the current UI pages reviewed (/issues, /issues/{id}). The underlying capability exists: archiveIssueAction(id) sets is_archived = true via a soft delete and logs an issue.archived audit event. Archived issues are excluded from listIssuesAction unless includeArchived: true is passed.
Role required: lead (requireRole("lead", { module: "issues" })).
Filtering the issues list
Path: /issues
A filter bar at the top filters by status only, using the five hardcoded values above (Open, In Progress, Resolved, Closed, On Hold). The page header shows "{filtered} of {total} issues". listIssuesAction also supports server-side filtering by typeSlug, assignedTo, linkedEntityType, linkedEntityId, and includeArchived, but only the status filter is wired to the /issues page UI.
Roles and permissions
| Action | Role required |
|---|---|
| Create issue type | admin |
| Update issue type | admin |
| List / get issue types | No role check (public within the authenticated app) |
| Create issue | staff |
| Update issue | staff |
| Get / list issues | staff |
| Transition issue status | staff |
| Archive issue | lead |
| Add comment | staff |
| List comments | staff |
Best practices
-
Set
allowedStatusesdeliberately at type creation. The Update Status dropdown in the UI is fixed toopen,in_progress,resolved,closed,on_hold— if your type's allowed set differs, staff will hit rejected transitions. Match your type's configured statuses to that fixed set unless you are prepared for mismatches. -
Keep custom field slugs stable.
fieldDatais a JSON blob keyed by field slug; renaming a slug after issues exist orphans the old data under a key the current field definitions no longer recognize. -
Use tags for cross-cutting classification. Tags are freeform comma-separated (or JSON-array) strings independent of the issue type, useful for filtering across types.
-
Add notes when transitioning status. The
notesfield becomes thestatus_changecomment content; without it, the comment defaults to a generic "Status changed from X to Y" line.
Warnings
There is no edit UI for issue types. The types list links to /admin/issues/types/{id} for editing, but no page exists at that route — the link 404s. Changes to an existing type currently require direct action calls or a database update.
The create-issue form does not expose custom fields. Custom fieldData values are not collectable through /issues/new; the form only submits title, description, priority, assignedTo, dueDate, and tags.
The status dropdown does not read the issue type's allowedStatuses. It is hardcoded to five values across the /issues filter bar and the /issues/{id} Update Status control. A type configured with a non-matching status set will reject transitions attempted from the fixed dropdown.
Archived issues have no visible restore path. Archiving is a soft delete (is_archived = true); there is no unarchive action in the reviewed code.
Troubleshooting
"Status '{status}' not allowed for this issue type"
Symptom: Clicking Update Status on /issues/{id} fails with this error message.
Cause: The selected status value is not present in the owning issue type's allowedStatuses array. This commonly happens because the Update Status dropdown always offers the same five fixed values (open, in_progress, resolved, closed, on_hold), regardless of what the type was actually configured with.
Fix: An admin must update the issue type's allowedStatuses to include the desired status (currently only possible via direct service/database update — no edit UI exists), or staff should only select statuses known to be in that type's configured set.
"Failed to create issue type" / "Failed to create issue"
Symptom: Generic error banner on /admin/issues/types/new or /issues/new after submitting.
Cause: Server-side Zod validation failed (e.g., missing required field, slug/title exceeding max length) or the role check in requireRole rejected the current user.
Fix: Confirm all required fields are filled (Display Name, Slug, Default Status for issue types; Title for issues) and that the signed-in user has the admin role (for issue types) or staff+ role (for issues).
Edit link on Issue Types page does nothing / 404s
Symptom: Clicking "Edit" next to an issue type in /admin/issues/types results in a 404 or blank page.
Cause: No page exists at /admin/issues/types/[id] in the current build. Only the "new" creation route is implemented.
Fix: Not resolvable via the UI today. Contact engineering to build the edit page, or make the change directly via updateIssueTypeAction / the database.
FAQ
Can I add custom fields when creating an issue?
No. The custom field definitions live on the issue type, but the /issues/new form does not render or submit them. fieldData can only be set through direct action calls.
What is the difference between the issues tracker and the feedback/bugs area? Not established in the issues module code reviewed — no import or reference to the feedback module was found there. Treat them as separate systems unless a dedicated guide states otherwise.
Who can delete an issue?
No one, through the UI. Issues are soft-deleted only (archiveIssue sets is_archived = true), and that action requires the lead role. There is no hard-delete path.
Can an issue link to another record in the platform (e.g., an opportunity or organization)?
The data model supports it — linkedEntityType and linkedEntityId fields exist on Issue, and listIssuesAction can filter by them — but the reviewed create and detail pages do not expose UI for setting or displaying these fields.
What happens to comments when I change an issue's status?
A status_change comment is automatically created with the old and new status, using your notes as the content if provided, or a generic message otherwise.
Related articles
- The investment pipeline lifecycle — a comparable stage/gate model for opportunities, for contrast with the issues tracker's simpler status model.
- Guides for forms and form builder, and for approval chains and workflow states, are planned (
./forms-and-form-builder.md,./approval-chains-and-workflow-states.md).