> For the complete documentation index, see [llms.txt](https://docs.hesh.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hesh.app/manuals/public-api/custom-columns.md).

# Custom Columns

## List custom columns

> \
> Returns every custom column on the tenant — across all modules and statuses (including soft-deleted) — paginated.\
> \
> \### Query\
> \
> \- \`search\` (optional) — substring match on \`name\`, case-insensitive.\
> \- \`skip\` / \`take\` (optional) — standard offset pagination; defaults to \`skip = 0\`, \`take = 20\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"PaginatedResult":{"type":"object","properties":{"meta":{"description":"Pagination metadata","allOf":[{"$ref":"#/components/schemas/PaginationMetadata"}]}},"required":["meta"]},"PaginationMetadata":{"type":"object","properties":{"total":{"type":"number","description":"Total number of items"},"lastPage":{"type":"number","description":"Last page number"},"currentPage":{"type":"number","description":"Current page number"},"perPage":{"type":"number","description":"Items per page"},"prev":{"type":"number","description":"Previous page number","nullable":true},"next":{"type":"number","description":"Next page number","nullable":true}},"required":["total","lastPage","currentPage","perPage","prev","next"]},"CustomColumnListItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"]},"is_required":{"type":"boolean"},"status":{"type":"string","enum":["active","inactive","deleted"]},"config":{"description":"Type-specific config. Shape depends on column_type.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"deleted_at":{"type":"string","nullable":true,"format":"date-time","description":"Set when status = DELETED. Anchors the 30-day permanent deletion countdown."}},"required":["id","name","column_type","is_required","status","config"]},"TextConfigDto":{"type":"object","properties":{"min_chars":{"type":"number","nullable":true,"minimum":0,"description":"Minimum character limit (inclusive). Null/undefined = no limit."},"max_chars":{"type":"number","nullable":true,"minimum":1,"description":"Maximum character limit (inclusive). Null/undefined = no limit."}}},"NumberConfigDto":{"type":"object","properties":{"number_type":{"enum":["integer","decimal"],"type":"string"}},"required":["number_type"]},"DateTimeConfigDto":{"type":"object","properties":{"time_type":{"enum":["date_time","date_only","time_only"],"type":"string"}},"required":["time_type"]},"PeopleConfigDto":{"type":"object","properties":{"assignee_type":{"enum":["user","department","position"],"type":"string"}},"required":["assignee_type"]},"BooleanConfigDto":{"type":"object","properties":{"true_label":{"type":"string","minLength":1},"false_label":{"type":"string","minLength":1}},"required":["true_label","false_label"]},"ChoiceConfigDto":{"type":"object","properties":{"choice_type":{"enum":["single","multi"],"type":"string"}},"required":["choice_type"]}}},"paths":{"/api/v1/public/custom-columns":{"get":{"description":"\nReturns every custom column on the tenant — across all modules and statuses (including soft-deleted) — paginated.\n\n### Query\n\n- `search` (optional) — substring match on `name`, case-insensitive.\n- `skip` / `take` (optional) — standard offset pagination; defaults to `skip = 0`, `take = 20`.\n","operationId":"PublicCustomColumnsController_list_v1","parameters":[{"name":"search","required":false,"in":"query","description":"Substring match on name, case-insensitive.","schema":{"maxLength":200,"type":"string"}},{"name":"skip","required":false,"in":"query","schema":{"type":"number"}},{"name":"take","required":false,"in":"query","schema":{"type":"number"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResult"},{"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomColumnListItemDto"}}}}]}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"List custom columns","tags":["Custom Columns"]}}}}
```

## Create custom column

> \
> Creates a new custom column on a given module.\
> \
> \### Notes\
> \
> \- \`column\_type\` is immutable after creation.\
> \- \`status\` on create must be \`active\` or \`inactive\`. Use the delete endpoint to soft-delete.\
> \- The per-module field limit is enforced; creation fails with 409 if reached.\
> \
> \
> \### \`config\` field — what to pass for each \`column\_type\`\
> \
> The \`config\` object is polymorphic: its required shape is determined by the sibling \`column\_type\` value. The server validates the combination — sending a config that doesn't match the column type is rejected with 400.\
> \
> \*\*\`text\`\*\*\
> \
> \`\`\`json\
> { "min\_chars": 0, "max\_chars": 500 }\
> \`\`\`\
> \
> Both \`min\_chars\` and \`max\_chars\` are optional (nullable). Bounds are inclusive. \`null\` or omitted = no limit on that side. Constraints: \`min\_chars >= 0\`, \`max\_chars >= 1\`.\
> \
> \*\*\`number\`\*\*\
> \
> \`\`\`json\
> { "number\_type": "integer" }\
> \`\`\`\
> \
> Required. Allowed values: \`"integer"\`, \`"decimal"\`. Controls the input widget and validation on stored values.\
> \
> \*\*\`date\_time\`\*\*\
> \
> \`\`\`json\
> { "time\_type": "date\_time" }\
> \`\`\`\
> \
> Required. Allowed values: \`"date\_time"\` (date + time), \`"date\_only"\`, \`"time\_only"\`.\
> \
> \*\*\`people\`\*\*\
> \
> \`\`\`json\
> { "assignee\_type": "user" }\
> \`\`\`\
> \
> Required. Allowed values: \`"user"\`, \`"department"\`, \`"position"\`. Determines the kind of entity ids that \`people\_options\` (and stored values) must reference. Immutable in practice — changing it invalidates existing options.\
> \
> \*\*\`boolean\`\*\*\
> \
> \`\`\`json\
> { "true\_label": "Yes", "false\_label": "No" }\
> \`\`\`\
> \
> Both labels required and non-empty. Shown in the UI for the two states.\
> \
> \*\*\`choice\`\*\*\
> \
> \`\`\`json\
> { "choice\_type": "single" }\
> \`\`\`\
> \
> Required. Allowed values: \`"single"\` (one choice per row) or \`"multi"\` (many). Use the \`choice\_options\` array (see below) to define the options themselves.\
> \
> \### Companion arrays\
> \
> \*\*\`choice\_options\`\*\* — required for \`choice\` columns on create; ignored for other column types. Must contain at least one entry of the form:\
> \
> \`\`\`json\
> \[{ "name": "Low", "position": 0 }, { "name": "High", "position": 1 }]\
> \`\`\`\
> \
> \`position\` is the display order (integer, \`>= 0\`). On update, use the nested management object instead:\
> \
> \`\`\`json\
> {\
> &#x20; "create": \[{ "name": "New option", "position": 2 }],\
> &#x20; "update": \[{ "id": "uuid", "name": "Renamed", "position": 0 }],\
> &#x20; "remove": \["uuid-of-option-to-delete"]\
> }\
> \`\`\`\
> \
> \*\*\`people\_options\`\*\* — only valid for \`people\` columns. On create, pass an array of entity uuids matching \`config.assignee\_type\` (user / department / position ids). Empty or omitted = all active entities of that type are allowed:\
> \
> \`\`\`json\
> \["b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e", "c91a..."]\
> \`\`\`\
> \
> On update, use the management object:\
> \
> \`\`\`json\
> {\
> &#x20; "create": \["uuid-of-entity-to-allow"],\
> &#x20; "remove": \["uuid-of-people-option-row-to-remove"]\
> }\
> \`\`\`\
> \
> Note: \`remove\` takes people-option row ids (as returned by the get-by-id endpoint), \*\*not\*\* entity ids.\
> \
> \### Concrete create-body examples\
> \
> \`\`\`json\
> // TEXT\
> { "module": "production", "column\_type": "text", "name": "Notes", "status": "active",\
> &#x20; "config": { "min\_chars": 0, "max\_chars": 500 } }\
> \
> // NUMBER\
> { "module": "order", "column\_type": "number", "name": "Weight", "status": "active",\
> &#x20; "config": { "number\_type": "decimal" } }\
> \
> // DATE\_TIME\
> { "module": "production", "column\_type": "date\_time", "name": "Delivered at", "status": "active",\
> &#x20; "config": { "time\_type": "date\_time" } }\
> \
> // PEOPLE (any active user is allowed)\
> { "module": "production", "column\_type": "people", "name": "Reviewer", "status": "active",\
> &#x20; "config": { "assignee\_type": "user" } }\
> \
> // PEOPLE (restricted to specific user ids)\
> { "module": "production", "column\_type": "people", "name": "Approvers", "status": "active",\
> &#x20; "config": { "assignee\_type": "user" },\
> &#x20; "people\_options": \["b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e", "c91…"] }\
> \
> // BOOLEAN\
> { "module": "production", "column\_type": "boolean", "name": "QA passed", "status": "active",\
> &#x20; "config": { "true\_label": "Yes", "false\_label": "No" } }\
> \
> // CHOICE (single-select)\
> { "module": "production", "column\_type": "choice", "name": "Priority", "status": "active",\
> &#x20; "config": { "choice\_type": "single" },\
> &#x20; "choice\_options": \[\
> &#x20;   { "name": "Low", "position": 0 },\
> &#x20;   { "name": "Medium", "position": 1 },\
> &#x20;   { "name": "High", "position": 2 }\
> &#x20; ] }\
> \`\`\`\ <br>

````json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"CreateCustomColumnDto":{"type":"object","properties":{"module":{"type":"string","enum":["inventory"]},"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"],"description":"Immutable after create."},"name":{"type":"string","minLength":1},"status":{"type":"string","enum":["active","inactive"],"description":"Initial status. DELETED is not allowed on create."},"is_required":{"type":"boolean","default":false},"config":{"description":"Type-specific config. Shape and required fields depend on column_type.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"choice_options":{"description":"Required when column_type = CHOICE (must have ≥ 1 option). Ignored for other types.","type":"array","items":{"$ref":"#/components/schemas/ChoiceOptionInputDto"}},"people_options":{"description":"List of allowed entity IDs (user.id / department.id / position.id depending on config.assignee_type). Only valid when column_type = PEOPLE. Empty/omitted = all active entities of the chosen assignee_type are allowed. Ignored for other types.","type":"array","items":{"type":"string","format":"uuid"}}},"required":["module","column_type","name","status","config"]},"TextConfigDto":{"type":"object","properties":{"min_chars":{"type":"number","nullable":true,"minimum":0,"description":"Minimum character limit (inclusive). Null/undefined = no limit."},"max_chars":{"type":"number","nullable":true,"minimum":1,"description":"Maximum character limit (inclusive). Null/undefined = no limit."}}},"NumberConfigDto":{"type":"object","properties":{"number_type":{"enum":["integer","decimal"],"type":"string"}},"required":["number_type"]},"DateTimeConfigDto":{"type":"object","properties":{"time_type":{"enum":["date_time","date_only","time_only"],"type":"string"}},"required":["time_type"]},"PeopleConfigDto":{"type":"object","properties":{"assignee_type":{"enum":["user","department","position"],"type":"string"}},"required":["assignee_type"]},"BooleanConfigDto":{"type":"object","properties":{"true_label":{"type":"string","minLength":1},"false_label":{"type":"string","minLength":1}},"required":["true_label","false_label"]},"ChoiceConfigDto":{"type":"object","properties":{"choice_type":{"enum":["single","multi"],"type":"string"}},"required":["choice_type"]},"ChoiceOptionInputDto":{"type":"object","properties":{"label":{"type":"string","minLength":1},"position":{"type":"number","minimum":0}},"required":["label","position"]},"CustomColumnListItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"]},"is_required":{"type":"boolean"},"status":{"type":"string","enum":["active","inactive","deleted"]},"config":{"description":"Type-specific config. Shape depends on column_type.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"deleted_at":{"type":"string","nullable":true,"format":"date-time","description":"Set when status = DELETED. Anchors the 30-day permanent deletion countdown."}},"required":["id","name","column_type","is_required","status","config"]}}},"paths":{"/api/v1/public/custom-columns":{"post":{"description":"\nCreates a new custom column on a given module.\n\n### Notes\n\n- `column_type` is immutable after creation.\n- `status` on create must be `active` or `inactive`. Use the delete endpoint to soft-delete.\n- The per-module field limit is enforced; creation fails with 409 if reached.\n\n\n### `config` field — what to pass for each `column_type`\n\nThe `config` object is polymorphic: its required shape is determined by the sibling `column_type` value. The server validates the combination — sending a config that doesn't match the column type is rejected with 400.\n\n**`text`**\n\n```json\n{ \"min_chars\": 0, \"max_chars\": 500 }\n```\n\nBoth `min_chars` and `max_chars` are optional (nullable). Bounds are inclusive. `null` or omitted = no limit on that side. Constraints: `min_chars >= 0`, `max_chars >= 1`.\n\n**`number`**\n\n```json\n{ \"number_type\": \"integer\" }\n```\n\nRequired. Allowed values: `\"integer\"`, `\"decimal\"`. Controls the input widget and validation on stored values.\n\n**`date_time`**\n\n```json\n{ \"time_type\": \"date_time\" }\n```\n\nRequired. Allowed values: `\"date_time\"` (date + time), `\"date_only\"`, `\"time_only\"`.\n\n**`people`**\n\n```json\n{ \"assignee_type\": \"user\" }\n```\n\nRequired. Allowed values: `\"user\"`, `\"department\"`, `\"position\"`. Determines the kind of entity ids that `people_options` (and stored values) must reference. Immutable in practice — changing it invalidates existing options.\n\n**`boolean`**\n\n```json\n{ \"true_label\": \"Yes\", \"false_label\": \"No\" }\n```\n\nBoth labels required and non-empty. Shown in the UI for the two states.\n\n**`choice`**\n\n```json\n{ \"choice_type\": \"single\" }\n```\n\nRequired. Allowed values: `\"single\"` (one choice per row) or `\"multi\"` (many). Use the `choice_options` array (see below) to define the options themselves.\n\n### Companion arrays\n\n**`choice_options`** — required for `choice` columns on create; ignored for other column types. Must contain at least one entry of the form:\n\n```json\n[{ \"name\": \"Low\", \"position\": 0 }, { \"name\": \"High\", \"position\": 1 }]\n```\n\n`position` is the display order (integer, `>= 0`). On update, use the nested management object instead:\n\n```json\n{\n  \"create\": [{ \"name\": \"New option\", \"position\": 2 }],\n  \"update\": [{ \"id\": \"uuid\", \"name\": \"Renamed\", \"position\": 0 }],\n  \"remove\": [\"uuid-of-option-to-delete\"]\n}\n```\n\n**`people_options`** — only valid for `people` columns. On create, pass an array of entity uuids matching `config.assignee_type` (user / department / position ids). Empty or omitted = all active entities of that type are allowed:\n\n```json\n[\"b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e\", \"c91a...\"]\n```\n\nOn update, use the management object:\n\n```json\n{\n  \"create\": [\"uuid-of-entity-to-allow\"],\n  \"remove\": [\"uuid-of-people-option-row-to-remove\"]\n}\n```\n\nNote: `remove` takes people-option row ids (as returned by the get-by-id endpoint), **not** entity ids.\n\n### Concrete create-body examples\n\n```json\n// TEXT\n{ \"module\": \"production\", \"column_type\": \"text\", \"name\": \"Notes\", \"status\": \"active\",\n  \"config\": { \"min_chars\": 0, \"max_chars\": 500 } }\n\n// NUMBER\n{ \"module\": \"order\", \"column_type\": \"number\", \"name\": \"Weight\", \"status\": \"active\",\n  \"config\": { \"number_type\": \"decimal\" } }\n\n// DATE_TIME\n{ \"module\": \"production\", \"column_type\": \"date_time\", \"name\": \"Delivered at\", \"status\": \"active\",\n  \"config\": { \"time_type\": \"date_time\" } }\n\n// PEOPLE (any active user is allowed)\n{ \"module\": \"production\", \"column_type\": \"people\", \"name\": \"Reviewer\", \"status\": \"active\",\n  \"config\": { \"assignee_type\": \"user\" } }\n\n// PEOPLE (restricted to specific user ids)\n{ \"module\": \"production\", \"column_type\": \"people\", \"name\": \"Approvers\", \"status\": \"active\",\n  \"config\": { \"assignee_type\": \"user\" },\n  \"people_options\": [\"b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e\", \"c91…\"] }\n\n// BOOLEAN\n{ \"module\": \"production\", \"column_type\": \"boolean\", \"name\": \"QA passed\", \"status\": \"active\",\n  \"config\": { \"true_label\": \"Yes\", \"false_label\": \"No\" } }\n\n// CHOICE (single-select)\n{ \"module\": \"production\", \"column_type\": \"choice\", \"name\": \"Priority\", \"status\": \"active\",\n  \"config\": { \"choice_type\": \"single\" },\n  \"choice_options\": [\n    { \"name\": \"Low\", \"position\": 0 },\n    { \"name\": \"Medium\", \"position\": 1 },\n    { \"name\": \"High\", \"position\": 2 }\n  ] }\n```\n\n","operationId":"PublicCustomColumnsController_publicCreate_v1","parameters":[{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomColumnDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomColumnListItemDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Create custom column","tags":["Custom Columns"]}}}}
````

## Get custom column by id

> \
> Returns the full custom column record, including \`choice\_options\` (for \`choice\` columns) and \`people\_options\` resolved to display entities (for \`people\` columns).\
> \
> Soft-deleted columns are returned (the response includes \`deleted\_at\` and \`status = deleted\`).<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"CustomColumnDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"module":{"type":"string","enum":["inventory"]},"name":{"type":"string"},"key":{"type":"string","description":"Stable slug used in API & integrations. Immutable after create."},"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"]},"status":{"type":"string","enum":["active","inactive","deleted"]},"is_required":{"type":"boolean"},"config":{"description":"Type-specific config. Shape depends on column_type.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"choice_options":{"description":"Present for CHOICE columns.","type":"array","items":{"$ref":"#/components/schemas/ChoiceOptionDto"}},"people_options":{"description":"Present for PEOPLE columns. Empty list = all active entities of the selected assignee_type are allowed. Options whose referenced entity has been deleted are filtered out.","type":"array","items":{"$ref":"#/components/schemas/PeopleOptionDetailDto"}},"deleted_at":{"type":"string","nullable":true,"format":"date-time","description":"Set when status = DELETED. Anchors the 30-day permanent deletion countdown."}},"required":["id","module","name","key","column_type","status","is_required","config"]},"TextConfigDto":{"type":"object","properties":{"min_chars":{"type":"number","nullable":true,"minimum":0,"description":"Minimum character limit (inclusive). Null/undefined = no limit."},"max_chars":{"type":"number","nullable":true,"minimum":1,"description":"Maximum character limit (inclusive). Null/undefined = no limit."}}},"NumberConfigDto":{"type":"object","properties":{"number_type":{"enum":["integer","decimal"],"type":"string"}},"required":["number_type"]},"DateTimeConfigDto":{"type":"object","properties":{"time_type":{"enum":["date_time","date_only","time_only"],"type":"string"}},"required":["time_type"]},"PeopleConfigDto":{"type":"object","properties":{"assignee_type":{"enum":["user","department","position"],"type":"string"}},"required":["assignee_type"]},"BooleanConfigDto":{"type":"object","properties":{"true_label":{"type":"string","minLength":1},"false_label":{"type":"string","minLength":1}},"required":["true_label","false_label"]},"ChoiceConfigDto":{"type":"object","properties":{"choice_type":{"enum":["single","multi"],"type":"string"}},"required":["choice_type"]},"ChoiceOptionDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string","minLength":1},"position":{"type":"number","minimum":0}},"required":["id","label","position"]},"PeopleOptionDetailDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"entity_id":{"type":"string","format":"uuid"},"entity":{"description":"Resolved entity. Shape depends on the parent column's config.assignee_type. Options whose referenced entity has been deleted are filtered out of the response entirely, so this is always populated when the option appears.","oneOf":[{"$ref":"#/components/schemas/PeopleOptionUserEntityDto"},{"$ref":"#/components/schemas/PeopleOptionDepartmentEntityDto"},{"$ref":"#/components/schemas/PeopleOptionPositionEntityDto"}]}},"required":["id","entity_id","entity"]},"PeopleOptionUserEntityDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"avatar_id":{"type":"string","nullable":true}},"required":["id","first_name","last_name","avatar_id"]},"PeopleOptionDepartmentEntityDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"path":{"description":"Populated materialized path, root → self (last element is the department itself).","type":"array","items":{"$ref":"#/components/schemas/PeopleOptionDepartmentPathItemDto"}}},"required":["id","name","path"]},"PeopleOptionDepartmentPathItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"PeopleOptionPositionEntityDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"paths":{"/api/v1/public/custom-columns/{id}":{"get":{"description":"\nReturns the full custom column record, including `choice_options` (for `choice` columns) and `people_options` resolved to display entities (for `people` columns).\n\nSoft-deleted columns are returned (the response includes `deleted_at` and `status = deleted`).\n","operationId":"PublicCustomColumnsController_publicGetById_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomColumnDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Get custom column by id","tags":["Custom Columns"]}}}}
```

## Update custom column

> \
> Updates a custom column. Any field not provided is left unchanged.\
> \
> \### Notes\
> \
> \- \`column\_type\` must match the persisted column type. Sending a different value is rejected with 400.\
> \- Status transitions: \`active\` ↔ \`inactive\` freely. \`deleted\` soft-deletes the column.\
> \- Deleted columns cannot be edited — restore first.\
> \
> \
> \### \`config\` field — what to pass for each \`column\_type\`\
> \
> The \`config\` object is polymorphic: its required shape is determined by the sibling \`column\_type\` value. The server validates the combination — sending a config that doesn't match the column type is rejected with 400.\
> \
> \*\*\`text\`\*\*\
> \
> \`\`\`json\
> { "min\_chars": 0, "max\_chars": 500 }\
> \`\`\`\
> \
> Both \`min\_chars\` and \`max\_chars\` are optional (nullable). Bounds are inclusive. \`null\` or omitted = no limit on that side. Constraints: \`min\_chars >= 0\`, \`max\_chars >= 1\`.\
> \
> \*\*\`number\`\*\*\
> \
> \`\`\`json\
> { "number\_type": "integer" }\
> \`\`\`\
> \
> Required. Allowed values: \`"integer"\`, \`"decimal"\`. Controls the input widget and validation on stored values.\
> \
> \*\*\`date\_time\`\*\*\
> \
> \`\`\`json\
> { "time\_type": "date\_time" }\
> \`\`\`\
> \
> Required. Allowed values: \`"date\_time"\` (date + time), \`"date\_only"\`, \`"time\_only"\`.\
> \
> \*\*\`people\`\*\*\
> \
> \`\`\`json\
> { "assignee\_type": "user" }\
> \`\`\`\
> \
> Required. Allowed values: \`"user"\`, \`"department"\`, \`"position"\`. Determines the kind of entity ids that \`people\_options\` (and stored values) must reference. Immutable in practice — changing it invalidates existing options.\
> \
> \*\*\`boolean\`\*\*\
> \
> \`\`\`json\
> { "true\_label": "Yes", "false\_label": "No" }\
> \`\`\`\
> \
> Both labels required and non-empty. Shown in the UI for the two states.\
> \
> \*\*\`choice\`\*\*\
> \
> \`\`\`json\
> { "choice\_type": "single" }\
> \`\`\`\
> \
> Required. Allowed values: \`"single"\` (one choice per row) or \`"multi"\` (many). Use the \`choice\_options\` array (see below) to define the options themselves.\
> \
> \### Companion arrays\
> \
> \*\*\`choice\_options\`\*\* — required for \`choice\` columns on create; ignored for other column types. Must contain at least one entry of the form:\
> \
> \`\`\`json\
> \[{ "name": "Low", "position": 0 }, { "name": "High", "position": 1 }]\
> \`\`\`\
> \
> \`position\` is the display order (integer, \`>= 0\`). On update, use the nested management object instead:\
> \
> \`\`\`json\
> {\
> &#x20; "create": \[{ "name": "New option", "position": 2 }],\
> &#x20; "update": \[{ "id": "uuid", "name": "Renamed", "position": 0 }],\
> &#x20; "remove": \["uuid-of-option-to-delete"]\
> }\
> \`\`\`\
> \
> \*\*\`people\_options\`\*\* — only valid for \`people\` columns. On create, pass an array of entity uuids matching \`config.assignee\_type\` (user / department / position ids). Empty or omitted = all active entities of that type are allowed:\
> \
> \`\`\`json\
> \["b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e", "c91a..."]\
> \`\`\`\
> \
> On update, use the management object:\
> \
> \`\`\`json\
> {\
> &#x20; "create": \["uuid-of-entity-to-allow"],\
> &#x20; "remove": \["uuid-of-people-option-row-to-remove"]\
> }\
> \`\`\`\
> \
> Note: \`remove\` takes people-option row ids (as returned by the get-by-id endpoint), \*\*not\*\* entity ids.\
> \
> \### Concrete create-body examples\
> \
> \`\`\`json\
> // TEXT\
> { "module": "production", "column\_type": "text", "name": "Notes", "status": "active",\
> &#x20; "config": { "min\_chars": 0, "max\_chars": 500 } }\
> \
> // NUMBER\
> { "module": "order", "column\_type": "number", "name": "Weight", "status": "active",\
> &#x20; "config": { "number\_type": "decimal" } }\
> \
> // DATE\_TIME\
> { "module": "production", "column\_type": "date\_time", "name": "Delivered at", "status": "active",\
> &#x20; "config": { "time\_type": "date\_time" } }\
> \
> // PEOPLE (any active user is allowed)\
> { "module": "production", "column\_type": "people", "name": "Reviewer", "status": "active",\
> &#x20; "config": { "assignee\_type": "user" } }\
> \
> // PEOPLE (restricted to specific user ids)\
> { "module": "production", "column\_type": "people", "name": "Approvers", "status": "active",\
> &#x20; "config": { "assignee\_type": "user" },\
> &#x20; "people\_options": \["b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e", "c91…"] }\
> \
> // BOOLEAN\
> { "module": "production", "column\_type": "boolean", "name": "QA passed", "status": "active",\
> &#x20; "config": { "true\_label": "Yes", "false\_label": "No" } }\
> \
> // CHOICE (single-select)\
> { "module": "production", "column\_type": "choice", "name": "Priority", "status": "active",\
> &#x20; "config": { "choice\_type": "single" },\
> &#x20; "choice\_options": \[\
> &#x20;   { "name": "Low", "position": 0 },\
> &#x20;   { "name": "Medium", "position": 1 },\
> &#x20;   { "name": "High", "position": 2 }\
> &#x20; ] }\
> \`\`\`\ <br>

````json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"UpdateCustomColumnDto":{"type":"object","properties":{"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"],"description":"Must match the column's persisted type. Sending a different value is rejected."},"name":{"type":"string","minLength":1},"is_required":{"type":"boolean"},"config":{"description":"Type-specific config. Validated against column_type at the pipe stage.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"status":{"type":"string","enum":["active","inactive","deleted"],"description":"ACTIVE ↔ INACTIVE freely; DELETED soft-deletes the column. Restoring a DELETED column uses the restore endpoint."},"choice_options":{"description":"Explicit create / update / remove operations on this column's choice options. Only valid for CHOICE columns.","allOf":[{"$ref":"#/components/schemas/ChoiceOptionsManagementDto"}]},"people_options":{"description":"Explicit create / remove operations on this column's people options. Only valid for PEOPLE columns.","allOf":[{"$ref":"#/components/schemas/PeopleOptionsManagementDto"}]}},"required":["column_type"]},"TextConfigDto":{"type":"object","properties":{"min_chars":{"type":"number","nullable":true,"minimum":0,"description":"Minimum character limit (inclusive). Null/undefined = no limit."},"max_chars":{"type":"number","nullable":true,"minimum":1,"description":"Maximum character limit (inclusive). Null/undefined = no limit."}}},"NumberConfigDto":{"type":"object","properties":{"number_type":{"enum":["integer","decimal"],"type":"string"}},"required":["number_type"]},"DateTimeConfigDto":{"type":"object","properties":{"time_type":{"enum":["date_time","date_only","time_only"],"type":"string"}},"required":["time_type"]},"PeopleConfigDto":{"type":"object","properties":{"assignee_type":{"enum":["user","department","position"],"type":"string"}},"required":["assignee_type"]},"BooleanConfigDto":{"type":"object","properties":{"true_label":{"type":"string","minLength":1},"false_label":{"type":"string","minLength":1}},"required":["true_label","false_label"]},"ChoiceConfigDto":{"type":"object","properties":{"choice_type":{"enum":["single","multi"],"type":"string"}},"required":["choice_type"]},"ChoiceOptionsManagementDto":{"type":"object","properties":{"create":{"type":"array","items":{"$ref":"#/components/schemas/ChoiceOptionInputDto"}},"update":{"type":"array","items":{"$ref":"#/components/schemas/UpdateChoiceOptionInputDto"}},"remove":{"description":"Option ids to delete.","type":"array","items":{"type":"string","format":"uuid"}}}},"ChoiceOptionInputDto":{"type":"object","properties":{"label":{"type":"string","minLength":1},"position":{"type":"number","minimum":0}},"required":["label","position"]},"UpdateChoiceOptionInputDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string","minLength":1},"position":{"type":"number","minimum":0}},"required":["id"]},"PeopleOptionsManagementDto":{"type":"object","properties":{"create":{"description":"Entity ids (user/department/position, per assignee_type) to add as new people options.","type":"array","items":{"type":"string","format":"uuid"}},"remove":{"description":"People option ids to delete.","type":"array","items":{"type":"string","format":"uuid"}}}},"CustomColumnListItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"column_type":{"type":"string","enum":["text","number","date_time","people","boolean","choice"]},"is_required":{"type":"boolean"},"status":{"type":"string","enum":["active","inactive","deleted"]},"config":{"description":"Type-specific config. Shape depends on column_type.","oneOf":[{"$ref":"#/components/schemas/TextConfigDto"},{"$ref":"#/components/schemas/NumberConfigDto"},{"$ref":"#/components/schemas/DateTimeConfigDto"},{"$ref":"#/components/schemas/PeopleConfigDto"},{"$ref":"#/components/schemas/BooleanConfigDto"},{"$ref":"#/components/schemas/ChoiceConfigDto"}]},"deleted_at":{"type":"string","nullable":true,"format":"date-time","description":"Set when status = DELETED. Anchors the 30-day permanent deletion countdown."}},"required":["id","name","column_type","is_required","status","config"]}}},"paths":{"/api/v1/public/custom-columns/{id}":{"put":{"description":"\nUpdates a custom column. Any field not provided is left unchanged.\n\n### Notes\n\n- `column_type` must match the persisted column type. Sending a different value is rejected with 400.\n- Status transitions: `active` ↔ `inactive` freely. `deleted` soft-deletes the column.\n- Deleted columns cannot be edited — restore first.\n\n\n### `config` field — what to pass for each `column_type`\n\nThe `config` object is polymorphic: its required shape is determined by the sibling `column_type` value. The server validates the combination — sending a config that doesn't match the column type is rejected with 400.\n\n**`text`**\n\n```json\n{ \"min_chars\": 0, \"max_chars\": 500 }\n```\n\nBoth `min_chars` and `max_chars` are optional (nullable). Bounds are inclusive. `null` or omitted = no limit on that side. Constraints: `min_chars >= 0`, `max_chars >= 1`.\n\n**`number`**\n\n```json\n{ \"number_type\": \"integer\" }\n```\n\nRequired. Allowed values: `\"integer\"`, `\"decimal\"`. Controls the input widget and validation on stored values.\n\n**`date_time`**\n\n```json\n{ \"time_type\": \"date_time\" }\n```\n\nRequired. Allowed values: `\"date_time\"` (date + time), `\"date_only\"`, `\"time_only\"`.\n\n**`people`**\n\n```json\n{ \"assignee_type\": \"user\" }\n```\n\nRequired. Allowed values: `\"user\"`, `\"department\"`, `\"position\"`. Determines the kind of entity ids that `people_options` (and stored values) must reference. Immutable in practice — changing it invalidates existing options.\n\n**`boolean`**\n\n```json\n{ \"true_label\": \"Yes\", \"false_label\": \"No\" }\n```\n\nBoth labels required and non-empty. Shown in the UI for the two states.\n\n**`choice`**\n\n```json\n{ \"choice_type\": \"single\" }\n```\n\nRequired. Allowed values: `\"single\"` (one choice per row) or `\"multi\"` (many). Use the `choice_options` array (see below) to define the options themselves.\n\n### Companion arrays\n\n**`choice_options`** — required for `choice` columns on create; ignored for other column types. Must contain at least one entry of the form:\n\n```json\n[{ \"name\": \"Low\", \"position\": 0 }, { \"name\": \"High\", \"position\": 1 }]\n```\n\n`position` is the display order (integer, `>= 0`). On update, use the nested management object instead:\n\n```json\n{\n  \"create\": [{ \"name\": \"New option\", \"position\": 2 }],\n  \"update\": [{ \"id\": \"uuid\", \"name\": \"Renamed\", \"position\": 0 }],\n  \"remove\": [\"uuid-of-option-to-delete\"]\n}\n```\n\n**`people_options`** — only valid for `people` columns. On create, pass an array of entity uuids matching `config.assignee_type` (user / department / position ids). Empty or omitted = all active entities of that type are allowed:\n\n```json\n[\"b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e\", \"c91a...\"]\n```\n\nOn update, use the management object:\n\n```json\n{\n  \"create\": [\"uuid-of-entity-to-allow\"],\n  \"remove\": [\"uuid-of-people-option-row-to-remove\"]\n}\n```\n\nNote: `remove` takes people-option row ids (as returned by the get-by-id endpoint), **not** entity ids.\n\n### Concrete create-body examples\n\n```json\n// TEXT\n{ \"module\": \"production\", \"column_type\": \"text\", \"name\": \"Notes\", \"status\": \"active\",\n  \"config\": { \"min_chars\": 0, \"max_chars\": 500 } }\n\n// NUMBER\n{ \"module\": \"order\", \"column_type\": \"number\", \"name\": \"Weight\", \"status\": \"active\",\n  \"config\": { \"number_type\": \"decimal\" } }\n\n// DATE_TIME\n{ \"module\": \"production\", \"column_type\": \"date_time\", \"name\": \"Delivered at\", \"status\": \"active\",\n  \"config\": { \"time_type\": \"date_time\" } }\n\n// PEOPLE (any active user is allowed)\n{ \"module\": \"production\", \"column_type\": \"people\", \"name\": \"Reviewer\", \"status\": \"active\",\n  \"config\": { \"assignee_type\": \"user\" } }\n\n// PEOPLE (restricted to specific user ids)\n{ \"module\": \"production\", \"column_type\": \"people\", \"name\": \"Approvers\", \"status\": \"active\",\n  \"config\": { \"assignee_type\": \"user\" },\n  \"people_options\": [\"b86fa12a-76fc-46f5-8a3e-bf39e7be4c4e\", \"c91…\"] }\n\n// BOOLEAN\n{ \"module\": \"production\", \"column_type\": \"boolean\", \"name\": \"QA passed\", \"status\": \"active\",\n  \"config\": { \"true_label\": \"Yes\", \"false_label\": \"No\" } }\n\n// CHOICE (single-select)\n{ \"module\": \"production\", \"column_type\": \"choice\", \"name\": \"Priority\", \"status\": \"active\",\n  \"config\": { \"choice_type\": \"single\" },\n  \"choice_options\": [\n    { \"name\": \"Low\", \"position\": 0 },\n    { \"name\": \"Medium\", \"position\": 1 },\n    { \"name\": \"High\", \"position\": 2 }\n  ] }\n```\n\n","operationId":"PublicCustomColumnsController_publicUpdate_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomColumnDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomColumnListItemDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Update custom column","tags":["Custom Columns"]}}}}
````

## Soft-delete custom column

> \
> Soft-deletes a custom column. Permanent deletion runs after 30 days.\
> \
> Calling this endpoint sets \`status = deleted\` and stamps \`deleted\_at\`. Use the restore endpoint to bring it back within the retention window.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"MessageDto":{"type":"object","properties":{"message":{"type":"string","description":"Message returned from API confirming the operation"}},"required":["message"]}}},"paths":{"/api/v1/public/custom-columns/{id}":{"delete":{"description":"\nSoft-deletes a custom column. Permanent deletion runs after 30 days.\n\nCalling this endpoint sets `status = deleted` and stamps `deleted_at`. Use the restore endpoint to bring it back within the retention window.\n","operationId":"PublicCustomColumnsController_delete_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Soft-delete custom column","tags":["Custom Columns"]}}}}
```

## Restore custom column

> \
> Restores a soft-deleted custom column to \`status = active\` and clears \`deleted\_at\`.\
> \
> Fails with 409 if the per-module field limit is already reached — delete another column first.<br>

```json
{"openapi":"3.0.0","info":{"title":"Public API","version":"1.0"},"tags":[{"name":"Custom Columns"}],"servers":[{"url":"https://api-stage.hesh.tech"}],"security":[{"PublicApiKey":[]}],"components":{"securitySchemes":{"PublicApiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"MessageDto":{"type":"object","properties":{"message":{"type":"string","description":"Message returned from API confirming the operation"}},"required":["message"]}}},"paths":{"/api/v1/public/custom-columns/{id}/restore":{"post":{"description":"\nRestores a soft-deleted custom column to `status = active` and clears `deleted_at`.\n\nFails with 409 if the per-module field limit is already reached — delete another column first.\n","operationId":"PublicCustomColumnsController_publicRestore_v1","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"x-tenant-id","in":"header","description":"Tenant id (uuid v4)","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDto"}}}},"429":{"description":"Returned when the rate limit is exceeded","headers":{"X-RateLimit-Limit":{"description":"Maximum number of allowed requests during the current window","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining number of requests before throttling occurs","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Number of seconds until the rate limit window resets","schema":{"type":"integer"}}}}},"summary":"Restore custom column","tags":["Custom Columns"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hesh.app/manuals/public-api/custom-columns.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
