{"record":{"id":"bd81d4ea2673a857","repo":"hcengineering/platform","slug":"presenter-not-found-for-class","errorCode":null,"errorMessage":"presenter not found for ${_class}","messagePattern":"presenter not found for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/view-resources/src/utils.ts","lineNumber":712,"sourceCode":"        key: key.key,\n        sortingKey: '',\n        _class,\n        label: getEmbeddedLabel(name + ' › ' + subFieldParts.join('.')),\n        presenter,\n        props: key.props,\n        displayProps: key.displayProps,\n        collectionAttr: false,\n        isLookup: true\n      }\n    }\n  }\n\n  const presenterMixin = hierarchy.classHierarchyMixin(_class, view.mixin.CollectionPresenter)\n  if (presenterMixin?.presenter === undefined) {\n    console.error(\n      `object presenter not found for class=${_class}, mixin=${view.mixin.ObjectPresenter}, preserve key ${JSON.stringify(key)}`\n    )\n    throw new Error('presenter not found for ' + _class)\n  }\n  const presenter = await getResource(presenterMixin.presenter)\n\n  return {\n    key: key.key,\n    sortingKey: '',\n    _class,\n    label: getEmbeddedLabel(name),\n    presenter,\n    props: key.props,\n    displayProps: key.displayProps,\n    collectionAttr: false,\n    isLookup: true\n  }\n}\n\nexport async function deleteObject (client: TxOperations, object: Doc): Promise<void> {\n  const currentAcc = getCurrentAccount()","sourceCodeStart":694,"sourceCodeEnd":730,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/view-resources/src/utils.ts#L694-L730","documentation":"When resolving the presenter for an association's end class, the code looks for a classHierarchyMixin implementing view.mixin.CollectionPresenter with a defined `presenter`. If the class hierarchy provides no presenter mixin, it logs and throws.","triggerScenarios":"Building a model for an $associations relation whose target class does not register a CollectionPresenter mixin (or the mixin exists but presenter is undefined).","commonSituations":"Custom domain class added without declaring view.mixin.CollectionPresenter in its class definition; typo in mixin resource id; class hierarchy not loaded for the target class.","solutions":["Add view.mixin.CollectionPresenter with a presenter resource to the target class definition","Register the presenter resource so getResource(presenterMixin.presenter) resolves","Check the mixin is applied via classHierarchyMixin on all subclasses of the displayed class"],"exampleFix":"// before\nclass MyDoc extends Doc {} // no presenter mixin\n// after\n@Model('my::MyDoc', Doc)\nexport class MyDoc extends Doc {}\nhierarchy.addMixin(MyDoc, view.mixin.CollectionPresenter, { presenter: myPresenterResource })","handlingStrategy":"validation","validationCode":"const hierarchy = client.getHierarchy()\nconst mixin = hierarchy.classHierarchyMixin(targetClass, view.mixin.CollectionPresenter)\nif (mixin?.presenter === undefined) {\n  throw new Error(`register CollectionPresenter for ${targetClass} first`)\n}","typeGuard":"function hasCollectionPresenter(h: Hierarchy, _class: Ref<Class<Doc>>): boolean {\n  return h.classHierarchyMixin(_class, view.mixin.CollectionPresenter)?.presenter !== undefined\n}","tryCatchPattern":"try {\n  return await model(client, _class, keys)\n} catch (err) {\n  if (String(err.message).startsWith('presenter not found')) {\n    console.error('add view.mixin.CollectionPresenter to', _class)\n    return []\n  }\n  throw err\n}","preventionTips":["Register CollectionPresenter mixin whenever you introduce a new domain class","Lint/verify class definitions include presenter mixins","Test view building for every custom class in CI"],"tags":["presenter","mixin","class-hierarchy","configuration"],"backgroundTag":"presenter-not-registered","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}