{"record":{"id":"464fa452e537b720","repo":"makeplane/plane","slug":"invalid-expression-empty-or-null-data","errorCode":null,"errorMessage":"Invalid expression: empty or null data","messagePattern":"Invalid expression: empty or null data","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared-state/src/store/work-item-filters/adapter.ts","lineNumber":51,"sourceCode":"\n    try {\n      return this._convertExpressionToInternal(externalFilter);\n    } catch (error) {\n      console.error(\"Failed to convert external filter to internal:\", error);\n      return null;\n    }\n  }\n\n  /**\n   * Recursively converts external expression data to internal filter tree\n   * @param expression - The external expression data\n   * @returns Internal filter expression\n   */\n  private _convertExpressionToInternal(\n    expression: TWorkItemFilterExpressionData\n  ): TFilterExpression<TWorkItemFilterProperty> {\n    if (!expression || isEmpty(expression)) {\n      throw new Error(\"Invalid expression: empty or null data\");\n    }\n\n    // Check if it's a simple condition (has field property)\n    if (this._isWorkItemFilterConditionData(expression)) {\n      const conditionResult = this._extractWorkItemFilterConditionData(expression);\n      if (!conditionResult) {\n        throw new Error(\"Failed to extract condition data\");\n      }\n\n      const [property, operator, value] = conditionResult;\n      return createConditionNode({\n        property,\n        operator,\n        value,\n      });\n    }\n\n    // It's a logical group - check which type","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/packages/shared-state/src/store/work-item-filters/adapter.ts#L33-L69","documentation":"Thrown by the work-item filter adapter's private _convertExpressionToInternal when the incoming expression is null, undefined, or an empty object. This method recursively converts the external persisted filter shape into the internal TFilterExpression tree; an empty/null root has no condition or logical group to translate and is treated as malformed input rather than 'no filter'.","triggerScenarios":"toInternal({}) or toInternal(null); a persisted filter whose serialized payload lost its keys during migration/JSON round-trip; calling the adapter with a default-empty state object from a form that was never filled.","commonSituations":"Loading a work-item view whose filter payload was stored as {} after an export/import; passing a partially-initialized filter state from a MobX store before the user picks anything.","solutions":["Before calling toInternal, check the payload: if null or isEmpty, skip conversion and treat it as 'no filters' rather than passing it through.","Validate the persisted shape on load and migrate/clear malformed {} entries.","If you intentionally want 'no filter', return early with an empty filter tree instead of invoking the adapter."],"exampleFix":"// before\nconst tree = adapter.toInternal(expressionFromStore);\n\n// after\nimport { isEmpty } from 'lodash';\nconst tree = !expressionFromStore || isEmpty(expressionFromStore)\n  ? createEmptyFilter()\n  : adapter.toInternal(expressionFromStore);","handlingStrategy":"validation","validationCode":"import { isEmpty } from 'lodash';\nfunction hasContent(expr: unknown): boolean {\n  return expr != null && typeof expr === 'object' && !isEmpty(expr);\n}\nif (hasContent(expression)) adapter.toInternal(expression);","typeGuard":"function isNonEmptyExpression(e: unknown): e is Record<string, unknown> {\n  return typeof e === 'object' && e !== null && Object.keys(e as object).length > 0;\n}","tryCatchPattern":"try {\n  tree = adapter.toInternal(expr);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid expression: empty or null data') {\n    tree = createEmptyFilter();\n  } else throw e;\n}","preventionTips":["Treat null/{} as 'no filter' upstream","Sanitize persisted payloads on load","Never pass uninitialized form state to the adapter"],"tags":["filters","adapter","validation","shared-state"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}