feat: custom fields per project

Text, number, date or option-list fields defined in the project settings
(archived, never removed; their type is fixed once saved). Tickets hold
them, edited in the side panel, listed under the title and filtered by
option in the list view; also over GraphQL, REST (?field=ID:value) and MCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 09:31:12 +02:00
parent 233c46e9fa
commit f7e850068b
44 changed files with 1909 additions and 96 deletions

View File

@@ -98,5 +98,12 @@ export function toSettingsInput(
name: a.name,
archived: a.archived,
})),
customFields: (settings.customFields ?? []).map((f) => ({
id: f.id,
name: f.name,
type: f.type,
options: f.options,
archived: f.archived,
})),
};
}

View File

@@ -51,6 +51,13 @@ fragment ProjectFields on Project {
name
archived
}
customFields {
id
name
type
options
archived
}
}
}
@@ -95,6 +102,10 @@ fragment IssueSummaryFields on Issue {
storyPoints
startDate
dueDate
customFields {
fieldId
value
}
trashedAt
}

View File

@@ -89,6 +89,8 @@ export type CreateIssueInput = {
/** An area id */
area?: string | null | undefined;
assigneeId?: string | null | undefined;
/** Only the fields to change */
customFields?: Array<CustomFieldValueInput> | null | undefined;
description?: string | null | undefined;
dueDate?: string | null | undefined;
/** Figma frame for UI changes */
@@ -141,6 +143,24 @@ export type CreateWebhookInput = {
url: string;
};
export type CustomFieldDefinitionInput = {
archived?: boolean;
id: string;
name: string;
/** The choices of a SELECT field */
options?: Array<string>;
type: CustomFieldType;
};
/** What a custom field holds (DATE: YYYY-MM-DD) */
export type CustomFieldType = 'DATE' | 'NUMBER' | 'SELECT' | 'TEXT';
/** A custom field's value; null or '' clears it */
export type CustomFieldValueInput = {
fieldId: string;
value?: string | null | undefined;
};
export type DevLinkKind = 'BRANCH' | 'COMMIT' | 'PULL_REQUEST';
export type DevLinkState = 'CLOSED' | 'MERGED' | 'OPEN';
@@ -149,6 +169,8 @@ export type IssueFilterInput = {
areas?: Array<string> | null | undefined;
assigneeId?: string | null | undefined;
categories?: Array<StatusCategory> | null | undefined;
/** Only tickets holding these custom values */
customFields?: Array<CustomFieldValueInput> | null | undefined;
/** Due on or after this day (YYYY-MM-DD) */
dueFrom?: string | null | undefined;
/** Due on or before this day (YYYY-MM-DD) */
@@ -231,6 +253,8 @@ export type ProjectRole = 'ADMIN' | 'MEMBER' | 'OWNER' | 'VIEWER';
/** The whole configuration (replaces the previous one). Statuses go in board order. */
export type ProjectSettingsInput = {
areas: Array<AreaOptionInput>;
/** Absent: unchanged. Fields cannot be removed: archive them. */
customFields?: Array<CustomFieldDefinitionInput> | null | undefined;
issueTypes: Array<IssueTypeOptionInput>;
statuses: Array<WorkflowStatusInput>;
transitions: Array<StatusTransitionsInput>;
@@ -298,6 +322,8 @@ export type UpdateChecklistItemInput = {
export type UpdateIssueInput = {
/** An area id */
area?: string | null | undefined;
/** Only the fields to change */
customFields?: Array<CustomFieldValueInput> | null | undefined;
description?: string | null | undefined;
dueDate?: string | null | undefined;
/** Figma frame for UI changes */
@@ -498,6 +524,13 @@ export type ProjectFieldsFragment = {
template: { description: string | null; checklist: Array<string> } | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
};
@@ -527,6 +560,7 @@ export type IssueSummaryFieldsFragment = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
};
export type IssueFieldsFragment = {
@@ -588,6 +622,7 @@ export type IssueFieldsFragment = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
};
export type CommentFieldsFragment = {
@@ -736,6 +771,7 @@ export type BoardQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
}>;
};
@@ -805,6 +841,7 @@ export type IssueQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
};
};
@@ -840,6 +877,7 @@ export type SearchIssuesQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1008,6 +1046,7 @@ export type MyIssuesQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1044,6 +1083,7 @@ export type IssueChildrenQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1080,6 +1120,7 @@ export type EpicsQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
};
progress: {
total: number;
@@ -1158,6 +1199,7 @@ export type BacklogQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1228,6 +1270,7 @@ export type TrashQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
@@ -1263,6 +1306,7 @@ export type DueIssuesQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1301,6 +1345,7 @@ export type IssueListQuery = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
};
};
@@ -1466,6 +1511,13 @@ export type MyProjectsQuery = {
} | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
}>;
};
@@ -1509,6 +1561,13 @@ export type ProjectQuery = {
} | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
};
};
@@ -1662,6 +1721,13 @@ export type PublicProjectsQuery = {
} | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
}>;
};
@@ -2010,6 +2076,7 @@ export type IssueChangedSubscription = {
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
};
};
};
@@ -2115,6 +2182,13 @@ export type ProjectChangedSubscription = {
} | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
};
};
@@ -2161,6 +2235,13 @@ export type MyProjectsChangedSubscription = {
} | null;
}>;
areas: Array<{ id: string; name: string; archived: boolean }>;
customFields: Array<{
id: string;
name: string;
type: CustomFieldType;
options: Array<string>;
archived: boolean;
}>;
};
};
};
@@ -2524,6 +2605,26 @@ export const ProjectFieldsFragmentDoc = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},
@@ -2638,6 +2739,17 @@ export const IssueSummaryFieldsFragmentDoc = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -2886,6 +2998,17 @@ export const IssueFieldsFragmentDoc = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -4600,6 +4723,17 @@ export const BoardDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -4761,6 +4895,17 @@ export const IssueDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -5057,6 +5202,17 @@ export const SearchIssuesDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -6375,6 +6531,17 @@ export const MyIssuesDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -6550,6 +6717,17 @@ export const IssueChildrenDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -6743,6 +6921,17 @@ export const EpicsDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -7240,6 +7429,17 @@ export const BacklogDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -7674,6 +7874,17 @@ export const TrashDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -7849,6 +8060,17 @@ export const DueIssuesDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -8039,6 +8261,17 @@ export const IssueListDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -8957,6 +9190,26 @@ export const MyProjectsDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},
@@ -9180,6 +9433,26 @@ export const ProjectDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},
@@ -10254,6 +10527,26 @@ export const PublicProjectsDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},
@@ -12224,6 +12517,17 @@ export const IssueChangedDocument = {
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
@@ -12776,6 +13080,26 @@ export const ProjectChangedDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},
@@ -12986,6 +13310,26 @@ export const MyProjectsChangedDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'options' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'archived' },
},
],
},
},
],
},
},