{"record":{"id":"89a69128cf4d4c83","repo":"HumanSignal/label-studio","slug":"cannot-filter-on-column-filter-filter-removepre","errorCode":null,"errorMessage":"Cannot filter on column \"{_filter.filter.removeprefix('filter:tasks:')}\": column names cannot contain {UNQUERYABLE_COLUMN_NAME_CHARACTERS}.","messagePattern":"Cannot filter on column \"(.+?)\": column names cannot contain (.+?)\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/data_manager/managers.py","lineNumber":901,"sourceCode":"                for value in _filter.value:\n                    q &= ~Q(predictions__model_version__contains=value)\n                filter_expressions.append(q)\n                continue\n            elif field_name == 'predictions_model_versions' and _filter.operator == Operator.EMPTY:\n                value = cast_bool_from_str(_filter.value)\n                filter_expressions.append(Q(predictions__model_version__isnull=value))\n                continue\n\n            # use other name because of model names conflict\n            if field_name == 'file_upload':\n                field_name = 'file_upload_field'\n\n            # From here on the column name becomes an ORM path and query alias, which Django\n            # rejects with a bare ValueError for names holding whitespace, quotes, control\n            # characters and the like — for instance a task.data key imported from a spreadsheet\n            # header. Report it as a client error instead of failing the request (UTC-1221).\n            if not is_queryable_column_name(field_name):\n                raise ValidationError(\n                    f'Cannot filter on column \"{_filter.filter.removeprefix(\"filter:tasks:\")}\": '\n                    f'column names cannot contain {UNQUERYABLE_COLUMN_NAME_CHARACTERS}.'\n                )\n\n            # annotate with cast to number if need\n            if _filter.type == 'Number' and field_name.startswith('data__'):\n                json_field = field_name.replace('data__', '')\n                queryset = queryset.annotate(\n                    **{\n                        f'filter_{json_field.replace(\"$undefined$\", \"undefined\")}': Cast(\n                            KeyTextTransform(json_field, 'data'), output_field=FloatField()\n                        )\n                    }\n                )\n                clean_field_name = f'filter_{json_field.replace(\"$undefined$\", \"undefined\")}'\n            else:\n                clean_field_name = field_name\n","sourceCodeStart":883,"sourceCodeEnd":919,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/data_manager/managers.py#L883-L919","documentation":"apply_filters turns each filter's column name into a Django ORM path and query alias. Names containing whitespace, quotes, or control characters (UNQUERYABLE_COLUMN_NAME_CHARACTERS) make Django raise a bare ValueError, so the code proactively raises ValidationError telling the caller the column name is not queryable (UTC-1221 fix).","triggerScenarios":"Filtering on task.data.<key> where <key> comes from a spreadsheet header or imported JSON with spaces/quotes/newlines, e.g. data__Customer Name or data__\"total\".","commonSituations":"CSV/Excel imports creating column keys with spaces, slashes, or quotes; keys copied from column display labels instead of raw keys; control characters from copy-pasted headers.","solutions":["Rename the task.data key to a queryable identifier (letters, digits, underscores) and re-import","Sanitize data keys at import time (e.g. slugify headers: 'Customer Name' -> 'customer_name')","Filter using the sanitized key: filter:tasks:data.customer_name","Inspect UNQUERYABLE_COLUMN_NAME_CHARACTERS in managers.py to see exactly which characters are rejected"],"exampleFix":"// before\n{\"filter\": \"filter:tasks:data.Customer Name\", \"operator\": \"equal\", \"value\": \"Acme\"}\n// after\n{\"filter\": \"filter:tasks:data.customer_name\", \"operator\": \"equal\", \"value\": \"Acme\"}","handlingStrategy":"validation","validationCode":"import re\nUNQUERYABLE = re.compile(r'[\\s\\'\"\\x00-\\x1f]')\nkey = col_name\nif UNQUERYABLE.search(key): key = re.sub(r'\\W+', '_', key.strip())\nfilter_name = f'filter:tasks:data.{key}'","typeGuard":"const isQueryableKey = (k) => typeof k === 'string' && k.length > 0 && !/[\\s'\"\\x00-\\x1f]/.test(k);","tryCatchPattern":"try:\n    apply_filters(...)\nexcept ValidationError as e:\n    if 'cannot contain' in str(e): slugify_data_keys_and_retry(e)\n    else: raise","preventionTips":["Slugify data keys at import time (headers -> snake_case)","Never build filter names from display labels; use raw storage keys","Add an import-time lint that rejects whitespace/quotes in keys","Keep key sanitization in one shared utility used by both importer and filter builder"],"tags":["validation","filters","django","orm","column-names"],"backgroundTag":"invalid-column-name","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}