HumanSignal/label-studio · error · ValidationError
Column name cannot contain {UNQUERYABLE_COLUMN_NAME_CHARACTE
Error message
Column name cannot contain {UNQUERYABLE_COLUMN_NAME_CHARACTERS}. What it means
For a NEW column (mode 'add'), the name must pass is_queryable_column_name — it must be filterable/sortable by the Data Manager. Names containing characters in UNQUERYABLE_COLUMN_NAME_CHARACTERS (e.g. dots/dollar signs used by nested JSON paths) are rejected with ValidationError {'column_name': 'Column name cannot contain ...'}. Existing columns (e.g. imported spreadsheet headers) are exempt and stay editable.
Source
Thrown at label_studio/data_manager/actions/data_columns.py:48
)
value_type = request_data.get('value_type', 'String')
value = request_data.get('value', '')
if value_type not in {'String', 'Number', 'Expression'}:
raise ValidationError({'value_type': 'Choose a supported column type.'})
if not isinstance(value_name, str) or not value_name.strip():
raise ValidationError({'column_name': 'Select an existing column or enter a new column name.'})
value_name = value_name.strip()
column_exists = value_name in project.summary.all_data_columns
if not column_exists:
column_exists = project.tasks.filter(data__has_key=value_name).exists()
mode = 'update' if column_exists else 'add'
# Existing columns (e.g. imported spreadsheet headers) stay editable, but a new column must be
# one the Data Manager can filter and sort on.
if mode == 'add' and not is_queryable_column_name(value_name):
raise ValidationError(
{'column_name': f'Column name cannot contain {UNQUERYABLE_COLUMN_NAME_CHARACTERS}.'},
)
try:
value = {'String': str, 'Number': float, 'Expression': str}[value_type](value)
except (TypeError, ValueError) as exc:
raise ValidationError({'value': f'Enter a valid {value_type.lower()} value.'}) from exc
return mode, value_name, value_type, value
def _set_data_value_in_batches(queryset, value_name, postgres_value, sqlite_value):
if settings.DJANGO_DB == settings.DJANGO_DB_SQLITE:
updated_count = 0
task_iterator = iterate_queryset(queryset.only('id', 'data'), chunk_size=settings.UPDATE_COLUMN_BATCH_SIZE)
for task_batch in batched_iterator(task_iterator, settings.UPDATE_COLUMN_BATCH_SIZE):
for task in task_batch:
task.data = task.data or {}
task.data[value_name] = sqlite_value(task)View on GitHub (pinned to 0b49e9b539)
Solutions
- Rename the new column to exclude unqueryable characters (use '_' instead of '.').
- If the column already exists in task data, it is treated as 'update' mode and allowed — verify the exact name matches the existing key.
- Sanitize generated column names before calling the action.
Example fix
// before column_name = 'user.email' // after column_name = 'user_email'
Defensive patterns
Strategy: validation
Validate before calling
import re
FORBIDDEN = set('.$[]{}"\\ *?<>|,:;/') # mirror UNQUERYABLE_COLUMN_NAME_CHARACTERS
def assert_queryable_new_column(name, existing_columns):
if name in existing_columns:
return
bad = [c for c in name if c in FORBIDDEN]
assert not bad, f'column name contains unqueryable characters: {bad}' Type guard
def is_safe_column_name(name):
return isinstance(name, str) and bool(name.strip()) and not any(ch in name for ch in '.$[]"\\') Try / catch
from rest_framework.exceptions import ValidationError
try:
add_data_field(project, qs, request=request)
except ValidationError as e:
if 'column_name' in e.message_dict and 'cannot contain' in str(e):
sanitize_and_retry() Prevention
- Replace dots/dollar signs with underscores in generated column names.
- Check whether the column already exists (existing names are exempt).
- Unit-test column-name generation against forbidden characters.
When it happens
Trigger: Adding a column whose name contains characters like '.' or '$', e.g. column_name 'user.email' when that column does not already exist in project data.
Common situations: Trying to create nested-path style columns ('a.b') mimicking existing dotted keys; spreadsheet headers with dots when re-importing; automation generating column names with dots/dollars.
Related errors
- Choose a supported column type.
- Select an existing column or enter a new column name.
- Enter a valid {value_type.lower()} value.
- Expression must use the form command(arguments).
- Expression command is required.
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/1ddc0921d4c1c158.
Report an issue: GitHub.