{"record":{"id":"6a1ed7bd995b8efc","repo":"nocobase/nocobase","slug":"flowsurfaces-addaction-normalizeduse-is-not-a","errorCode":null,"errorMessage":"flowSurfaces addAction '${normalizedUse}' is not allowed under '${input.containerUse}'","messagePattern":"flowSurfaces addAction '(.+?)' is not allowed under '(.+?)'","errorType":"validation","errorClass":"FlowSurfaceBadRequestError","httpStatus":400,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-flow-engine/src/server/flow-surfaces/catalog.ts","lineNumber":4184,"sourceCode":"  } = {},\n) {\n  const requestedType = String(input.type || '').trim();\n  const normalizedUse = String(input.use || '').trim();\n  let item: FlowSurfaceCatalogItem | undefined;\n  if (input.containerUse) {\n    assertKnownActionContainerUse({\n      containerUse: input.containerUse,\n      context: 'resolveAction',\n    });\n  }\n\n  if (normalizedUse) {\n    const useCandidates = ACTION_CATALOG_BY_USE.get(normalizedUse) || [];\n    const availableUseCandidates = filterAvailableCatalogItems(useCandidates, options.enabledPackages);\n    const matchedAll = getContainerScopedActionCatalogItems(useCandidates, input.containerUse);\n    const matched = getContainerScopedActionCatalogItems(availableUseCandidates, input.containerUse);\n    if (useCandidates.length && input.containerUse && !matchedAll.length) {\n      throw new FlowSurfaceBadRequestError(\n        `flowSurfaces addAction '${normalizedUse}' is not allowed under '${input.containerUse}'`,\n      );\n    }\n    if ((matchedAll.length || useCandidates.length) && !matched.length && !availableUseCandidates.length) {\n      throwUnavailableCatalogItem(useCandidates[0], {\n        context: options.context || 'addAction',\n        requestedUse: normalizedUse,\n      });\n    }\n    if (matchedAll.length && !matched.length) {\n      throwUnavailableCatalogItem(matchedAll[0], {\n        context: options.context || 'addAction',\n        requestedUse: normalizedUse,\n      });\n    }\n    if (matched.length > 1 && !input.containerUse) {\n      throw new FlowSurfaceBadRequestError(\n        `flowSurfaces addAction use '${normalizedUse}' requires containerUse to resolve a public action capability`,","sourceCodeStart":4166,"sourceCodeEnd":4202,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-flow-engine/src/server/flow-surfaces/catalog.ts#L4166-L4202","documentation":"In resolveSupportedActionCatalogItem, when the request supplies `use`, the resolver looks up all catalog items registered for that use and checks which of them are allowed in the requested containerUse. If candidates exist for the use but NONE of them (regardless of package availability) is registered under that containerUse, the action/use combination is structurally invalid for that container and the resolver throws this FlowSurfaceBadRequestError before availability is even considered.","triggerScenarios":"flowSurfaces addAction with { use: '<someUse>', containerUse: '<containerUse>' } where ACTION_CATALOG_BY_USE has entries for the use but getContainerScopedActionCatalogItems finds zero items scoped to that containerUse.","commonSituations":"Typo or stale use value paired with a container that never supported it; copying an addAction payload from one container type (e.g. table row) to another (e.g. form) where the use is not registered; plugin that registers the action under a different containerUse than assumed.","solutions":["Check the action catalog for which containerUse values the given use is registered under and pass a compatible containerUse","Use `type` instead of `use` if you mean a specific public action key","If you own the action registration, add the containerUse to its allowed containers in ACTION_CATALOG","Verify the containerUse string for typos and that it is a known action container use"],"exampleFix":"// before\nawait flowSurfaces.addAction({ use: 'customize:popup', containerUse: 'filterForm' }); // not allowed under 'filterForm'\n// after\nawait flowSurfaces.addAction({ use: 'customize:popup', containerUse: 'tableRow' }); // container that registers this use","handlingStrategy":"validation","validationCode":"const candidates = getActionCatalogItemsByUse(use);\nconst allowed = candidates.filter((c) => isActionAllowedInContainer(c, containerUse));\nif (!allowed.length) throw new Error(`Use '${use}' is not registered under containerUse '${containerUse}'`);","typeGuard":"function isUseAllowedInContainer(use, containerUse) {\n  return getContainerScopedActionCatalogItems(ACTION_CATALOG_BY_USE.get(use) || [], containerUse).length > 0;\n}","tryCatchPattern":"try {\n  await flowSurfaces.addAction({ use, containerUse });\n} catch (e) {\n  if (e instanceof FlowSurfaceBadRequestError && /is not allowed under/.test(e.message)) {\n    // pick a supported containerUse from the catalog\n  } else throw e;\n}","preventionTips":["Validate use/containerUse pairs against the catalog before submitting","Do not copy addAction payloads between different container kinds","Keep action registrations and container uses in sync across plugin versions"],"tags":["validation","catalog","container-scope"],"backgroundTag":"action-not-allowed-in-container","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}