Skip to content

APIv4 Architecture

This guide is intended for anyone wishing to extend the CiviCRM API itself, either in core or in their own extension.

Example extension

As you read this, you can also follow-along with a live demo in the API Explorer by installing the APIv4 Example Extension.

API Entity Classes

Every API entity is a class which inherits from \Civi\Api4\Generic\AbstractEntity. Each class serves two purposes:

  1. Declare the API entity by its existance.

    • You can declare a new API (e.g. FooBar) simply by placing the class \Civi\Api4\FooBar in the /Civi/Api4 directory of your extension, as long as it inherits from \Civi\Api4\Generic\AbstractEntity.
  2. Provide factory functions for that entity's Actions.

API Action Classes

Every API action is a class which inherits from \Civi\Api4\Generic\AbstractAction. It has two functions:

  1. Store the parameters of the API call.

    • Every protected class var is considered a parameter (unless it starts with an underscore).
    • The $version parameter refers to the API version (4) and cannot be changed.
    • The $_entityName and $_actionName vars are set when the Action object is constructed (by the Entity class factory fn, see above). The underscore makes them hidden parameters as they cannot be changed.
    • Adding a protected var to your Action named e.g. $thing will automatically:
      • Provide a getter/setter (via __call MagicMethod) named getThing() and setThing().
      • Expose the param in the API Explorer (be sure to add a doc-block as it displays in the help panel).
      • Require a value for the param if you add the @required annotation.
  2. Define a _run() function to execute the call and return the results.

    • The _run() function is invoked by execute()
    • It gets passed a Result object into which it places an array of values to be returned.

The Result Class

An API Result is a PHP ArrayObject. It has three functions:

  1. Store the results of the API call (accessible via ArrayAccess).
  2. Store metadata like the Entity & Action names.
  3. Provide convenience methods like $result->first() and $result->indexBy($field).

If you need to use array functions such as is_array on the result you will need to call getArrayCopy() first i.e. $result->getArrayCopy() which basically copies the ArrayObject into an actual Array.

Class Inheritance


To reduce code duplication and enforce consistency, APIv4 uses PHP class inheritance extensively. Compare these (slightly simplified) examples:

APIv3 Website.php APIv4 Website.php
function civicrm_api3_website_get($params) {
  return _civicrm_api3_basic_get('Website', $params);

function civicrm_api3_website_create($params) {
  return _civicrm_api3_basic_create('Website', $params);

function civicrm_api3_website_delete($params) {
  return _civicrm_api3_basic_delete('Website', $params);
namespace Civi\Api4;

class Website extends Generic\DAOEntity {


Website is a typical CRUD API using generic functions to perform actions on the civicrm_website table. The v3 file needed a function for each action, resulting in a lot of duplicate code across API files. By taking advantage of class inheritance, APIv4 reduces this boilerplate to nothing; factory functions for the standard set of actions are inherited from Civi\Api4\Generic\DAOEntity.

DAO vs Ad-hoc Entities

Entity Inheritance Diagram

There are two categories of APIv4 entities: standard (aka DAO) entities, and ad-hoc entities.

  • Standard entities correspond to a database table and DAO class. E.g. the Contact API entity corresponds to the CRM_Conatact_DAO_Contact class and the civicrm_contact database table. They extend from Civi\Api4\Generic\DAOEntity and thus inherit the standard set of DAO actions.
  • Ad-hoc entities extend the \Civi\Api4\Generic\AbstractEntity class directly. They have the flexibility to implement any actions, using any datasource. They are not required to implement any action except GetFields. See the APIv4 example extension for a working demonstration.

Action Class Hierarchy

To standardize parameter names and reduce code duplication, each action class inherits from an abstract parent.

Action Inheritance Diagram

DAO (standard) Actions

All standard entities provide factory functions for DAOGetFieldsAction, DAOGetAction, DAOCreateAction, DAOUpdateAction, DAOSaveAction, DAODeleteAction, and BasicReplaceAction.

The Get action uses schema metadata to query the database and perform joins with other DAO entities. The Create, Delete, Save & Update actions call the core BAO methods to ensure hooks are invoked when writing to the database.

In most cases the action classes are used as-is, but it's also possible to override a DAO action to add/modify parameters or functionality.

It is also possible to add other ad-hoc actions to any entity; e.g. getChecksum is an ad-hoc action added to the Contact entity.

Building Your Own Actions

You can add arbitrary actions to any entity simply by defining a class which extends AbstractAction or one of the Basic actions.


AbstractAction is the base class for all API actions. Every action must, at minimum, extend this class. For example, the Example::random() action extends this class directly. Your custom action will define parameters as protected class properties, and implement a _run function to execute the action.

Before extending AbstractAction directly, consider if your action could benefit from the features provided by one of the Basic actions:

Basic Actions

These classes provide a framework for building custom API actions. See the Example entity for a working demonstration.

Each basic action is implemented by supplying a callback function to perform logic and return data; the basic action class handles details like validating param input and formatting & filtering the result.

Basic Actions are designed to be used in 1 of 2 ways:

  1. Your Entity's action function can construct the basic action directly, passing the callback into the constructor.
  2. You can override the basic action class (e.g. for customizing the parameters). See Example::create().

  3. BasicGetAction: Used to build actions whose purpose is fetching records. Parameters are select, where, offset, limit, and orderBy. Its built-in array-query engine automatically filters/sorts the raw data returned by your callback according to the parameters. Normally your callback can simply return an array of all existing records, but if performance is a concern, the functions _itemsToGet() and _isFieldSelected() can help you optimize your callback to only return results that are needed.

  4. BasicCreateAction: Used to create a new record. Parameters are values. Values will be passed into the callback via writeRecord, or you can override the writeRecord method in your custom create class. See Example::create().

  5. BasicUpdateAction: Used to update records based on a search query. Parameters are values, reload, where, offset, limit, and orderBy. Internally calls Get to obtain records to update, so your entity must implement a get action. Each will be passed into the callback via writeRecord, or you can override the writeRecord method in your custom update class.

  6. BasicSaveAction: Used to create or update multiple records. Parameters are records, defaults, and reload. Each will be passed into the callback via writeRecord, or you can override the writeRecord method in your custom save class.

  7. BasicBatchAction: Used to perform an action on records based on a search query. Use this class, for example, to implement Delete. Parameters are where, offset, limit, and orderBy. Internally calls Get to obtain records, so your entity must implement a get action. Each will be passed into the callback via doTask, or you can override the doTask method in your custom batch class.

  8. BasicReplaceAction: Used to replace a set of records. Parameters are records, default, reload, where, offset, limit, and orderBy. Internally calls Get to obtain records and Save to write them, so your entity must implement those actions.

  9. BasicGetFieldsAction: Metadata action returns a list of fields for your entity.