I've been battering my head against this on-and-off for months now.

Why isn't there a simple way to deny access to a specific set of records?

We use a lot of groups, so manually assigning each group to an ACL isn't feasible. The ideal solution would be to put all restricted records in a group, give everyone access to all groups, then deny them access to the restricted records group. After that, we could use the excellent Related Permissions module to control access to individual records. But there isn't a "deny" in Civi's ACLs.

I suspect the aclGroup hook might be one I need (https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_aclGroup/) but the documentation, as for much of Civi, is minimal. In the example section of that page it has a link "Check HRD module" which is a 404. I searched github and found 1 codebase that used the hook, but in too complex a manner for me to be able to get anything from it. There are apparently NO extensions using this hook in lab.civicrm.org

Any pointers about this question would be gratefully received. And not just a link to https://civicrm.org/blog/lobo/implementing-a-custom-acl-system-in-civicrm. I've read that. It hasn't helped much.

I've never known a code base that is as difficult to get into as CiviCRM - given that I've hacked on Emacs plugins, and the first patch I ever submitted was for a Drupal Views extension. I'm only a part-time programmer, so I need to be able to work my way in, not feel like I'll have to learn how most of the CiviCRM codebase works before I can do anything. So little good documentation, so many links that are broken or out-of-date, web searches that bring up results for older versions of CiviCRM. It's very frustrating, especially as it strikes me that it shouldn't be that difficult a thing to implement. I know it's open source, and I'd be happy to help with documentation, and with code, but it's too difficult to get a first foothold.

  • Hi Gav - welcome, and I'm sorry to hear about your experience. It's true that the documentation is in sorry shape - it was generally maintained during in-person sprints, which have fallen off due to COVID. I'm going to open tickets for updating the aclGroup docs page, and also to finally get the old wiki taken offline. Meanwhile, feel free to join us at chat.civicrm.org in the ~dev channel if you have follow-up questions. Dec 22, 2022 at 18:11
  • i don't know if this comment is relevant in this scenario but we have done a lot of our 'manage who sees what' via the Drupal cms and joining the dots by using CiviCRM Relationships.
    – petednz - fuzion
    Dec 22, 2022 at 18:16
  • 1
    I just opened this MR and issue: lab.civicrm.org/documentation/docs/dev/-/issues/882 and lab.civicrm.org/documentation/docs/dev/-/merge_requests/1047. I hope this begins to address your frustration. Dec 22, 2022 at 18:24
  • Many thanks, again. Jan 19, 2023 at 15:55

1 Answer 1


Deny ACLs have been a long-standing request, but one that no one has contributed or offered to fund. You can see the open issue.

There's a bit of a tragedy of the commons that many developers, rather than develop the full feature, simply use a hook to meet their immediate need.

To help you do that - the hook_civicrm_aclGroup is probably the wrong hook, though you can view its use in the multisite extension. You probably want hook_civicrm_aclWhereClause. A simpler example of this can be found in the View My Event Participants extension, and a more complex version in the Related Permissions extension.

Edited to add: Here's an (anonymized) code example from a client-specific extension I wrote which is simpler still, but is a more limited use case. CRM_Myclientpermissions_Utils::getVipContactsIds(); returns a list of Contact IDs that only people with the Access VIP Contacts permission are allowed to see.

function myclientpermissions_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where) {
  // If the person doesn't have the "Edit All Contacts" permission, this is called, restricting access to the VIP groups.
  if (!$contactID) {
  // If the person has the "View VIP contacts" permission, skip this.
  if (CRM_Core_Permission::check('access vip contacts')) {
  // The calling function will apply a "0" for no access if the WHERE statement is empty.
  // So let's not inadvertently create a WHERE when no access is applicable.
  // That is, View Only contacts can't see anyone.
  if (!$where) {

  $vipIds = CRM_Myclientpermissions_Utils::getVipContactsIds();
  $vips = implode(",", $vipIds);
  $where .= ($where ? ' AND ' : '') . " contact_a.id NOT IN ($vips)";
  • Thank you very much! Sorry for the delay in responding to this - I thought StackExchange would notify me - I must've turned off email notifications at some point in the past. Jan 19, 2023 at 15:35
  • Looking at View My Event Participants, that looks like an approach that would work. No idea how my GitHub search missed that, and a few other results. Thanks again. Jan 19, 2023 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.