Skip to content

Custom Membership Term plugins

You can add project-specific membership term logic by implementing a new plugin that implements MembershipTermInterface and is discovered by the Membership Term manager.

Steps

1. Create a plugin class

  • Place the class in your module under src/Plugin/MembershipTerm/ (e.g. src/Plugin/MembershipTerm/CustomTerm.php).
  • Extend Drupal\crm_membership\Plugin\MembershipTermBase.
  • Implement ContainerFactoryPluginInterface and a create() factory method for dependency injection (the base class requires EntityTypeManager, Time, DurationService, and a logger).
  • Add the #[MembershipTerm] PHP attribute so the plugin is discovered.

Attribute parameters (from Drupal\crm_membership\Attribute\MembershipTerm):

Parameter Type Description
id string Plugin ID (machine name). Required.
label TranslatableMarkup | null Human-readable label shown in the Membership Type form.
allowOverrideStartDate bool Whether the start date can be overridden when activating/renewing. Default false.
allowOverrideEndDate bool Whether the end date can be overridden. Default false.
durationConfigKey string | null Config key for duration (e.g. for duration_field). Optional.
timeGapKey string | null Config key for time gap between renewals. Optional.

2. Example (minimal)

<?php

declare(strict_types=1);

namespace Drupal\my_module\Plugin\MembershipTerm;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\crm\Entity\Contact;
use Drupal\crm_membership\Attribute\MembershipTerm;
use Drupal\crm_membership\Plugin\MembershipTermBase;

#[MembershipTerm(
  id: "my_custom_term",
  label: new TranslatableMarkup("My custom term"),
  allowOverrideStartDate: TRUE,
  allowOverrideEndDate: TRUE,
)]
final class CustomTerm extends MembershipTermBase {

  public function isActiveFor(Contact $contact): bool {
    // Your logic, or delegate to parent for period + grace handling.
    return parent::isActiveFor($contact);
  }

  public function isExpiredFor(Contact $contact): bool {
    return FALSE;
  }

  public function activate(?array $contacts = NULL): void {
    // Create initial period(s) using $this->addMembershipPeriod().
  }

  public function renew(?array $contacts = NULL): void {
    // Add new period(s) via addMembershipPeriod().
  }

  public function expire(): void {
    parent::expire();
  }

  public function cancel(): void {
    // Programmatic only — no UI calls this.
    parent::cancel();
  }

  public function allowRenewal(): bool {
    return TRUE;
  }
}

The base class implements setMembership/getMembership, getSettings, setStartDate/setEndDate, addMembershipPeriod(), and DI via create(). Override lifecycle methods and optionally buildConfigurationForm, validateConfigurationForm, and saveConfiguration.

3. Reference implementations

Built-in Fixed duration — full production example with duration, rollover, grace period, and configuration form:

  • Class: Drupal\crm_membership\Plugin\MembershipTerm\FixedDuration
  • File: src/Plugin/MembershipTerm/FixedDuration.php

Test Counter plugin — minimal plugin with custom DI (State service) used in kernel tests:

  • Class: Drupal\crm_membership_test\Plugin\MembershipTerm\Counter
  • File: tests/crm_membership_test/src/Plugin/MembershipTerm/Counter.php

Enable the crm_membership_test module in test environments to use the Counter plugin.

4. Configuration form (optional)

Override these methods to expose settings on the Membership Type form:

  • buildConfigurationForm(array $current_config, FormStateInterface $form_state): array
  • validateConfigurationForm(array $form, FormStateInterface $form_state): void
  • saveConfiguration(array $values): array

Submitted values are stored in the type’s membership_term_config and passed to the plugin as configuration. See Configuration reference.

5. Alter hook

Other modules can alter plugin definitions via hook_crm_membership_term_info() (see Alter and extend).

6. Use your plugin

  1. Clear caches so the new plugin is discovered.
  2. Edit a Membership Type (or create one) at Administration → Structure → CRM → Membership Types.
  3. Select your plugin in the “Membership term” dropdown and save. Any configuration form you added will appear there (AJAX-refreshed when the selection changes).
  4. New memberships of that type will use your plugin for activation, renewal, expiration, and status checks.

cancel() is programmatic only

There is no admin form or route that calls cancel(). Use it from custom code when you need to end a membership outside the normal expire flow.