makeplane/plane · error · Error
Invalid expression: unknown structure with keys
Error message
Invalid expression: unknown structure with keys [${expressionKeys.join(", ")}] What it means
Thrown at the end of _convertExpressionToInternal after the object is confirmed not to be a simple condition (no field key) and does not contain LOGICAL_OPERATOR.AND. The message lists the actual keys so the caller can see exactly what unrecognized shape was passed. Only AND groups and single conditions are supported by this adapter.
Solutions
- Inspect the keys printed in the message to see the unsupported operator/shape.
- If you need OR/NOT, extend the adapter to handle LOGICAL_OPERATOR.OR/NOT, or convert the payload to AND-only form upstream.
- For malformed single conditions, ensure each condition has the keys _isWorkItemFilterConditionData requires (field, operator/operator structure, value).
Example fix
// before
adapter.toInternal({ OR: [{ field: 'state', value: 'open' }] });
// after (flatten OR into an accepted shape, or extend the adapter)
adapter.toInternal({
AND: [
{ field: 'state', operator: 'is', value: 'open' },
],
}); Defensive patterns
Strategy: validation
Validate before calling
const KNOWN_TOP_KEYS = new Set(['field', 'AND']);
function isRecognizedShape(e: Record<string, unknown>): boolean {
const keys = Object.keys(e);
return keys.every(k => KNOWN_TOP_KEYS.has(k)) && (keys.includes('field') || keys.includes('AND'));
} Type guard
function isConditionOrAnd(e: any): boolean {
if (!e || typeof e !== 'object') return false;
return 'field' in e || ('AND' in e && Array.isArray(e.AND));
} Try / catch
try { adapter.toInternal(expr); } catch (e) {
if (/unknown structure with keys/.test((e as Error).message)) { logUnsupportedKeys(expr); expr = {}; } else throw e;
} Prevention
- Confirm only AND groups and supported conditions are emitted
- Extend the adapter when adding OR/NOT
- Validate payloads at the API boundary before storage
When it happens
Trigger: Passing an OR group ({ OR: [...] }) which the adapter does not implement; passing { field: 'x', value: 1 } missing the keys _isWorkItemFilterConditionData expects; passing arbitrary objects like { foo: 1 } or a NOT expression.
Common situations: Schema/version mismatch where another part of the app (or a newer backend) emits OR/NOT groups the adapter can't parse; hand-built test fixtures with wrong keys; copy-paste of a filter payload from a different filter system.
Related errors
- AND group must contain at least one condition
- Invalid expression: empty or null data
- empty_filter_object
- empty_list_value
- Filter validation errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/17e1328b19cb0924.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared-state/src/store/work-item-filters/adapter.ts:84
});
}
// It's a logical group - check which type
const expressionKeys = Object.keys(expression);
if (LOGICAL_OPERATOR.AND in expression) {
const andExpression = expression as { [LOGICAL_OPERATOR.AND]: TWorkItemFilterExpressionData[] };
const andConditions = andExpression[LOGICAL_OPERATOR.AND];
if (!Array.isArray(andConditions) || andConditions.length === 0) {
throw new Error("AND group must contain at least one condition");
}
const convertedConditions = andConditions.map((item) => this._convertExpressionToInternal(item));
return createAndGroupNode(convertedConditions);
}
throw new Error(`Invalid expression: unknown structure with keys [${expressionKeys.join(", ")}]`);
}
/**
* Converts internal filter expression to external format
* @param internalFilter - The internal filter expression
* @returns External filter expression
*/
toExternal(internalFilter: TFilterExpression<TWorkItemFilterProperty>): TWorkItemFilterExpression {
if (!internalFilter) {
return {};
}
try {
return this._convertExpressionToExternal(internalFilter);
} catch (error) {
console.error("Failed to convert internal filter to external:", error);
return {};
}View on GitHub (pinned to 1c8a60f858)