Civix¶
Introduction¶
The civix
command-line tool is the community-endorsed method for building your CiviCRM extensions.
Follow the installation instructions in the GitHub repository.
After fulfilling the Pre-Requisites, you can verify that all your configuration is correct by running the following command from within your extensions directory with:
civix civicrm:ping
This command should reply with "Ping successful". If the ping is unsuccessful, re-read the civix README.md and do the post-installation configuration.
It is also useful to examine the output of the civix help
command to read about what civix
can do for you.
Help is available on individual commands, e.g.,:
civix help civicrm:ping
Generate a skeletal extension¶
To generate a skeletal extension module, we will use civix generate:module
and pass in the name for our extension. See here for details of naming conventions.
Start with:
cd $YOUR_EXTENSIONS_DIR
civix generate:module --help
Then use a command like this:
civix generate:module myextension
This command will report that it has created three files, following the standard extension structure.
The command attempts to auto-detect authorship information (your name and email address) by reading your git
configuration. If this fails or is otherwise incorrect, then you may pass explicit values with --author
and --email
.
You can now update your info.xml
. This file initially contains some examples and placeholders which you need to fix. You can edit most of these fields intuitively. If you need detailed specifications, see Extension Reference.
Now that you have created your extension, you need to activate it by navigating to:
Administer » System Settings » Extensions
For more detailed instructions, see Extensions.
Test your knowledge
A small quiz to test your understanding:
Add features¶
There are many different features that you can add to a module-extension at your discretion. A few possibilities:
Add a unit-test class¶
Unit-testing is essential to maintain quality-control over your extension. Ideally you write unit tests as you write your main code - this also helps you to writer cleaner, testable code. When developing a test case for a CiviCRM extension, it is useful to run the test case within an active, clean CiviCRM environment. The combined CiviCRM civix
testing tools will automate this as long as you follow a few basic conventions.
The following steps will create and run a test in your extension.
Explore the full options with:
civix help generate:test
To create a skeletal test-class choose a class name in your extension's namespace (\Civi\Myextension) that ends with the word Test:
civix generate:test \\Civi\\Myextension\\Test
This creates a new directory and a new PHP file:
tests/phpunit
is the base directory for all test classes.tests/phpunit/Civi/Myextension/Test.php
is the actual test class. It should be written according to the conventions of PHPUnit.
To make sure you can run the test civix needs to know where the CiviCRM base install is located.
The skeletal test class does not do anything useful. For more details on how to write a test class:
- Read PHP Unit Manual: Writing Tests for PHPUnit..
- For more about testing in the CiviCRM environment see our testing documentation
To run the tests see the instructions for running PHPUnit tests.
Add a new entity¶
If you want your extension to store data in the database, then you will need to create a new entity.
-
Pick a name for your entity.
-
In some places, CiviCRM expects a
CamelCaseName
, in others, asnake_case_name
. Be absolutely consistent in your naming, because CiviCRM needs to translate between those two naming conventions. -
Also consider that all entity names (including yours) should be unique across all core entities as well as all extension entities (for all installed extensions). Thus in many cases it's best to prefix your entity name with the short name of your extension.
-
For the remainder of this tutorial, we will use
MyEntity
as the name of the entity.
-
-
Generate the skeletal files
civix generate:entity MyEntity
Make sure to use CamelCase here.
This creates a skeletal file for your XML schema, your BAO, and your API.
-
Edit the XML schema definitions that you just generated (in the
xml
folder). Define your desired fields. -
Generate your DAO and SQL files.
civix generate:entity-boilerplate
You can safely re-run this command after you make changes to your XML schema definition. But if your schema changes require database migrations for existing installations, then you'll need to write a migration manually in addition to re-generating your boilerplate.
-
Generate a database upgrader.
civix generate:upgrader
Even though you're not yet creating any upgrades for your extension, you need to do this step now so that CiviCRM will pick up
auto_install.sql
andauto_uninstall.sql
later on. -
Re-install your extension.
cv ext:uninstall myextension cv ext:enable myextension
Now your entity should be ready to use. Try it out with cv api4 MyEntity.create
and cv api4 MyEntity.get
. Then add some tests.
By default when you generate an entity you will be generating an APIv4 entity. To generate an APIv3 (or both) interface you need to specify --api-version 3,4
or just --api-version 3
. To use an APIv3 interface with cv, use cv api3 MyEntity.create
and cv api3 MyEntity.get
.
Test your knowledge
A little quiz to test your knowledge:
Troubleshooting
If you've generated an entity within an extension that you created with civix
v18.01.0 or earlier, then you'll need to add this hook to your myextension.php
file (changing myextension
to your extension's short name).
/**
* Implements hook_civicrm_entityTypes().
*
* Declare entity types provided by this module.
*
* @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_entityTypes
*/
function myextension_civicrm_entityTypes(&$entityTypes) {
_myextension_civix_civicrm_entityTypes($entityTypes);
}
(Starting from civix
v18.02.0, this hook is automatically added when you generate a new extension.)
Add an API function¶
The CiviCRM API provides a way to expose functions for use by other developers. API functions allow implementing AJAX interfaces (using the CRM.$().crmAPI() helper), and they can also be called via REST, PHP, Smarty, Drush CLI, and more. Each API requires a two-part name: an entity name (such as "Contact", "Event", or "MyEntity") and an action name (such as "create" or "myaction").
Unfortunately Civix can only create v3 apis at the moment. Generally v4 CRUD apis are more useful since they allow your entities to be accessed from search-kit and form-builder. relevant issue & how to manually add
Get started by accessing the civix
help:
civix help generate:api
Note
Action names must be lowercase. The javascript helpers CRM.api()
and CRM.api3()
force actions to be lowercase. This issue does not present itself in the API Explorer or when the api action is called via PHP, REST, or SMARTY.
Note
The API loader is very sensitive to case on some platforms. Be careful with capitalization, underscores and multiword action names. API calls can work in one context (Mac OSX filesystem, case-insensitive) and fail in another (Linux filesystem, case-sensitive).
You can make your API code with a command in this pattern:
civix generate:api NewEntity newaction
This creates one file:
api/v3/NewEntity/Newaction.php
provides the API functioncivicrm_api3_new_entity_newaction()
and the specification function_civicrm_api3_new_entity_newaction_spec()
. Note that the parameters and return values must be processed in a particular way (as demonstrated by the auto-generated file).
For use with CiviCRM 4.3 and later, you can also add the --schedule
option (e.g., --schedule Hourly
). This will create another file:
api/v3/NewEntity/Newaction.mgd.php
provides the scheduling record that will appear in the CiviCRM's job-manager.
When calling the API, follow these rules:
- Entity-names are UpperCamelCase
- Action-names are lowersmashedcase
- Other variations may work when you call them. Some docs/explorers may show these in cases which work. However, if you try to do the same in new code, you may get a headache.
For example: cv api NewEntity.newaction
civicrm_api3('NewEntity', 'newaction')
Tip
Read more about APIv4 architecture for help writing custom APIv4 implementations.
Add a database upgrader, installer and uninstaller¶
If your module requires creating or maintaining SQL tables, then you should create a class for managing database upgrades. The upgrader adds a class for managing installs and upgrades but you then need to edit the file comment out the various upgrade and uninstall functions to make it work. Generally your install script belongs in an sql/
folder in the root of your extension with a name like 'install'.
This civix
command does not require arguments:
civix generate:upgrader
This creates two files and one directory:
CRM/Myextension/Upgrader.php
stores a series of upgrade functions based on a function naming pattern similar to Drupal's hook_update_N. After examining the file's comments for example upgrade functions you can then write your own.CRM/Myextension/Upgrader/Base.php
contains helper functions and adapters which make it easier to write the upgrader. This file may be overwritten from time-to-time to provide new helpers or adapters.- sql
After reviewing the examples and creating your own upgrade functions, you can execute the upgrades through the web interface by visiting the "Manage Extensions" screen. This screen will display an alert with an action-link to perform the upgrades.
Note
The "upgrader" class is a wrapper for hook_civicrm_upgrade which aims to be easy-to-use for developers with Drupal experience. If you need to organize the upgrade logic differently, then consider providing your own implementation of hook_civicrm_upgrade.
Caution
Only use the upgrade system to manage new SQL tables. Do not manipulate core schema.
If you need to create triggers on core SQL tables, use hook_civicrm_triggerInfo. This allows your triggers to coexist with triggers from other modules.
Add a basic quickform web page¶
CiviCRM uses a typical web-MVC architecture. To implement a basic web page, you must create a PHP controller class, create a Smarty template file, and create a routing rule. You can create the appropriate files by calling civix generate:page
.
Once again you can review the output of this command to see the available options:
civix generate:page --help
Generally you will only need to supply the PHP class name and web-path, for example:
civix generate:page MyPage civicrm/my-page
This creates three files:
xml/Menu/myextension.xml
defines request-routing rules and associates the controller ("CRM_Myextension_Page_Greeter") with the web pathcivicrm/my-page
.CRM/Myextension/Page/MyPage.php
is the controller which coordinates any parsing, validation, business-logic, or database operations.templates/CRM/Myextension/Page/MyPage.tpl
is loaded automatically after the controller executes. It defines the markup that is eventually displayed. For more information on the syntax of this file, see the smarty guide.
The auto-generated code for the controller and view demonstrate a few basic operations, such as passing data from the controller to the view.
Note
After adding or modifying a route in the XML file, you must reset CiviCRMs "menu cache". Do this in the CiviCRM user interface (Administer>System Settings>Cleanup Caches and Update Paths), in a web browser by visiting /civicrm/menu/rebuild?reset=1
or, if using Drupal & Drush, by running drush cc civicrm
.
Test your knowledge
A little quiz to test your knowledge:
Edit In Place
If the data on the page is read and updated through the API, then you may want to consider using the in-place editing API.
Add a basic web form¶
Caution
The form system (based on QuickForm) is not well documented and the CiviCRM 4.x/5.x series have seen a growing number of angular forms, as well as the first steps with Afform. In general, migrating basic pages will be easier than migrating basic forms, so you may want to consider building your data-input interface using basic pages, the AJAX API, and the in-place editing API.
CiviCRM uses a typical web-MVC architecture. To implement a basic web form, you must create a PHP controller class, create a Smarty template file, and create a routing rule. You can create the appropriate files by calling civix generate:form
.
The form generation command has similar arguments to civix generate:page
, requiring a class name and a web route:
civix generate:form FavoriteColor civicrm/favcolor
This creates three files:
xml/Menu/myextension.xml
defines request-routing rules and associates the controllerCRM_Myextension_Form_FavoriteColor
with the web pathcivicrm/favcolor
.CRM/Myextension/Form/FavoriteColor.php
is the controller which coordinates any parsing, validation, business-logic, or database operations. For more details on how this class works, see QuickForm Reference.templates/CRM/Myextension/Form/FavoriteColor.tpl
is loaded automatically after the controller executes. It defines the markup that is eventually displayed. For more information on the syntax of this file, see the smarty guide.
The auto-generated code for the controller and view demonstrate a few basic operations, such as adding a <select>
element to the form.
Note
After adding or modifying a route in the XML file, you must reset CiviCRMs "menu cache". Do this in the CiviCRM user interface (Administer>System Settings>Cleanup Caches and Update Paths), in a web browser by visiting /civicrm/menu/rebuild?reset=1
or, if using Drupal & Drush, by running drush cc civicrm
..
Test your knowledge
A little quiz to test your knowledge:
Add a case type¶
If you want to develop a custom case-type for CiviCase, then you can generate a skeletal CiviCase XML file.
Once again civix
will give the details of options and arguments with this command:
civix help generate:case-type
This reports that civix expects a label argument and an optional name:
civix generate:case-type "Volunteer Training" Training
This creates two files:
xml/case/Training.xml
defines the roles, activity types, and timelines associated with the new case type. For more in depth discussion of CiviCase XML, see CiviCase Configuration.alltypes.civix.php
(which may already exist) defines implementations of various hooks (notably hook_civicrm_caseTypes).
Add custom fields¶
If you wish to create custom fields for an entity that does not support custom fields out of the box you will need to add a new option value to the cg_group_extends option group. It will then be possible to create custom fields for this entity per above or via the UI. However, for most fields you will only be able to interact with these fields via the UI. Exceptions are those like RelationshipType that use an Entity Form in core.
Extending a base entity¶
This is the simplest scenario. Start by creating a custom fields using the web interface and then export them for use with the extension, saving them in an .mgd file. In recent versions of CiviCRM you can do this export in the APIv4 explorer.
Accessing Custom Fields¶
For setting and getting custom field values in hooks, APIv4 explorer is your friend. Try doing get
, and create
and update
within the explorer and you will see a code example generated.
Caution
Note that custom field values may not always be available when you might expect. For instance, you can't retrieve custom field values in the 'create' operation in the _pre and _post hooks, because the custom field values haven't been stored yet. However, you can retrieve values in the 'edit' operation.
Add a hook function¶
CiviCRM hook functions allow extensions to run extra logic as part of the normal CiviCRM processing. For example, hook_civicrm_buildForm()
allows a module to run logic whenever a web-form is displayed, and hook_civicrm_post()
allows a module to run logic after any entity is saved.
Hook function names follow the Drupal convention of being the module's short-name concatenated to the hook name. This strict but simple naming convention is what allows the CiviCRM core to locate your hook functions and call them at the appropriate times. For example, if our module's main file is myextension.php
and we want to use hook_civicrm_post()
to write to a log file every time a contribution is saved, then our function must be called myextension_civicrm_post()
.
To implement a hook, add a function to the module's main .php
file created earlier with civix generate:module
:
/**
* Implementation of hook_civicrm_post
*/
function myextension_civicrm_post(string $op, string $objectName, int $objectId, &$objectRef) {
switch ($objectName) {
case 'Contribution':
$file = '/tmp/contributions.log';
$message = strtr("Performed \"@op\" at @time on contribution #@id\n", array(
'@op' => $op,
'@time' => date('Y-m-d H:i:s'),
'@id' => $objectId,
));
file_put_contents($file, $message, FILE_APPEND);
break;
default:
// nothing to do
}
}
Note
When you first created the skeletal project, several hook functions were auto-generated in myextension.php
. These functions are usually about one line long – they simply delegate the work to another function. For example myextension_civicrm_config()
delegates work to _myextension_civix_civicrm_config()
. You should feel free to add more code to myextension_civicrm_config()
, but you should preserve the call to _myextension_civix_civicrm_config()
.
Add a resource file¶
To include static resources such as stylesheets, Javascript files, or images place them in your extension directory. To load the files at runtime, see the examples in the Resource Reference.
Add a report¶
CiviReport enables developers to define new business reports using customizable SQL logic and form layouts. This command is available if you want to create a new report. It also provides an option, if another existing report is close to your needs, to easily copy and modify it.
You can often achieve what you want to achieve using SearchKit and you should only generate CiviReports if you have determined SearchKit can't meet your use case.
In some cases you can take advantage of the alterReportVar hook to adjust the columns, sql, or event rows of an existing report to modify it to suit your needs instead of creating a new report. However, this is generally discouraged as changes to the report could break your extension.
To see the available report generation options activate the civix
help:
civix generate:report --help
To create a new report specify the report PHP class name and the CiviCRM component, for example:
civix generate:report MyReport CiviContribute
This creates three files:
CRM/Myextension/Form/Report/MyReport.mgd.php
stores metadata about the report in a format based on hook_civicrm_managed and the API.CRM/Myextension/Form/Report/MyReport.php
contains the form-builder and query-builder for the report. For details about its structure, see the CiviReport Reference.templates/CRM/Myextension/Form/Report/MyReport.tpl
contains the report's HTML template. This template usually delegates responsibility to a core template and does not need to be edited.
If one of the existing reports is close to meeting your needs, but requires further PHP or SQL customization, you may simply make a new report based on that report. To copy a report, find the class-name of the original report within the civicrm/CRM/Report/Form/
directory in the CiviCRM repository. Then run the civix generate:report
command using the copy option from within your extension directory.
For example, this command will copy the activity report in the class CRM_Report_Form_Activity
to a new report within your extension:
civix generate:report --copy CRM_Report_Form_Activity MyActivity Contact
Note
Copying a report like this and modifying it is likely to lead to maintenance issues similar to those related to overriding core files in an extension. In particular, bug fixes or other changes to code that you have copied will not automatically be applied to the copied code your new report. Often a better approach is to extend the class of the core report in your extension, then selectively override its functions. In the functions that you override, if possible run the original code and then just tweak the behaviour afterwards, i.e: at the beginning of thisFn()
, call parent::thisFn()
then add your code. For example:
class CRM_myExtension_Form_Report_ExtendContributionDetails extends CRM_Report_Form_Contribute_Detail {
public function from() {
parent::from();
// your code
}
...
}
Note
To have your extension create an instance of your report template with configurations for columns, groups, filtering, etc. and then put it into the menu system, you need to make an additional entry in the CRM/Myextension/Form/Report/MyReport.mgd.php
file. The additional entry needs some serialized data that is not easy to code but is easy to lookup for a configured report instance. So manually configure a report instance appropriately on a CiviCRM install running your extension (i.e. go to Administer > CiviReport > Create New Report from Template, then configure and save the instance). Then use APIv3 to get the ReportInstance values you need for your .mgd.php entry. Note that you need to specify the return fields as navigation_id is not returned by default. If you are not emailing the report, then the following should be sufficient, substituting the id for your report for 8 below:
$result = civicrm_api3('ReportInstance', 'get', [
'sequential' => 1,
'return' => ["title", "report_id", "name", "description", "permission", "grouprole", "is_active", "navigation_id", "is_reserved", "form_values"],
'id' => 8,
]);
[
'module' => 'com.example.myextension',
'name' => 'My Enhanced Contribution Detail',
...
'is_reserved' => 0,
],
Upgrade civix¶
What happens when a new version of civix
comes out? Your extension should continue to work as-is -- after all, civix
is just a code-generator.
However, if you generate an extension with one version of civix
(e.g. v16.03), and if you upgrade (e.g. v18.03), and if you run a new generator,
then you could encounter problems. This is because generators and templates evolve over time -- in particular, new templates may rely on new
helpers and stubs in <mymodule>.php
and <mymodule>.civix.php
.
Fortunately, these changes are rare; they tend to have limited impact; and there's documentation for them. From time-to-time, you should check UPGRADE.md for suggestions on updating your code to match the current templates.
Add a custom search¶
You are strongly discouraged from adding new custom searches building on the legacy custom search framework. Going forwards the focus is very much on SearchKit. If you do need to do so for any reason
- the old documentation is here
- you should add the following to your
info.xml
<requires>
<ext>legacycustomsearches</ext>
</requires>