{"record":{"id":"886db918a23b50ba","repo":"phacility/phabricator","slug":"call-s-when-rendering-an-action-as-a-form","errorCode":null,"errorMessage":"Call %s when rendering an action as a form.","messagePattern":"Call (.+?) when rendering an action as a form\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/view/layout/PhabricatorActionView.php","lineNumber":234,"sourceCode":"\n    if ($this->download) {\n      $sigils[] = 'download';\n    }\n\n    if ($this->submenu) {\n      $sigils[] = 'keep-open';\n    }\n\n    if ($this->sigils) {\n      $sigils = array_merge($sigils, $this->sigils);\n    }\n\n    $sigils = $sigils ? implode(' ', $sigils) : null;\n\n    if ($this->href) {\n      if ($this->renderAsForm) {\n        if (!$this->hasViewer()) {\n          throw new Exception(\n            pht(\n              'Call %s when rendering an action as a form.',\n              'setViewer()'));\n        }\n\n        $item = javelin_tag(\n          'button',\n          array(\n            'class' => 'phabricator-action-view-item',\n          ),\n          array($icon, $this->name));\n\n        $item = phabricator_form(\n          $this->getViewer(),\n          array(\n            'action'    => $this->getHref(),\n            'method'    => 'POST',\n            'sigil'     => $sigils,","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/view/layout/PhabricatorActionView.php#L216-L252","documentation":"When a PhabricatorActionView renders as a form (setRenderAsForm(true) with an href), it emits a real form whose CSRF/session token derives from the viewer, so render() requires one and throws if none is set. PhabricatorActionListView propagates its own viewer to every action at render time (it calls $action->setViewer($viewer)), so actions inside a viewer-configured list never hit this; standalone actions or viewer-less lists do.","triggerScenarios":"Creating a standalone PhabricatorActionView with setRenderAsForm(true) (and an href) and rendering it without setViewer(); or rendering an action list on which setViewer() was never called.","commonSituations":"Custom action lists built in controllers where $list->setViewer($viewer) was forgotten; embedding a single form-action in a hovercard or curtain; refactors that pull an action out of a list into standalone rendering.","solutions":["Call $action_view->setViewer($viewer) directly on the action.","Or attach actions to a PhabricatorActionListView and call $list->setViewer($viewer) once; the list propagates it to each action at render time.","In controllers, always source the viewer from $this->getViewer()."],"exampleFix":"// before\n$action = id(new PhabricatorActionView())\n  ->setHref('/obj/subscribe/')\n  ->setRenderAsForm(true)\n  ->setName(pht('Subscribe'));\n// no viewer: render() throws\n\n// after\n$action = id(new PhabricatorActionView())\n  ->setViewer($viewer)\n  ->setHref('/obj/subscribe/')\n  ->setRenderAsForm(true)\n  ->setName(pht('Subscribe'));","handlingStrategy":"validation","validationCode":"if (!$action->hasViewer()) {\n  $action->setViewer($viewer);\n}\n// and for lists (propagates to every action on render):\n$list->setViewer($viewer);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call setViewer() on the PhabricatorActionListView once instead of per action.","Any action with setRenderAsForm(true) implies a viewer is required.","hasViewer() is public — assert it before rendering standalone actions."],"tags":["php","phabricator","action-view","viewer","csrf"],"backgroundTag":"missing-viewer-context","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}