Skip to content

Status and periods

Membership lifecycle state is tracked through the membership_status field on the Membership entity and the current_periods reference field. Term plugins create and update both when activating, renewing, or expiring.

The status field is a boolean published flag (EntityPublishedInterface). Unpublished memberships are excluded from MembershipService queries and from view access unless the user has administer crm_membership.

membership_status values

Value Meaning
active Membership has at least one current period that is active now, or was set active by the term plugin after the last period change.
future A period exists but has not started yet (start date in the future).
expired Membership has been expired (typically via cron/queue calling expire()).

The membership_status field is a simple list string — there is no state machine workflow yet. See Roadmap.

current_periods field

current_periods holds references to crm_membership_period entities that represent the membership’s current coverage. Term plugins maintain this field via addMembershipPeriod() and updateCurrentPeriods() in MembershipTermBase:

  1. When adding a period, expired periods are removed from current_periods.
  2. The new period is appended.
  3. If the new period isActive(), membership_status is set to active; otherwise, if membership_status was not already active, it is set to future.

Historical periods may remain in the database but are no longer referenced in current_periods once expired.

Membership period applicability

Each period has optional applicable contacts. When empty, getApplicableContacts() falls back to all contacts on the parent membership. This supports household-style memberships where one membership covers multiple people but individual periods may apply to subsets.

Membership::getCurrentPeriodsForContact() and MembershipPeriod::isApplicableForContact() filter periods for a given contact.

isActiveFor() vs isMember()

Check What it does
Membership::isActiveFor($contact) Delegates to the term plugin. Checks current periods for the contact, period active dates, and grace period (days after end date from plugin config).
MembershipService::isMember($contact, $target) Queries published memberships with membership_status = active, then calls isActiveFor() on candidates. Also checks indirect memberships and dispatches IS_MEMBER event.

A membership can have membership_status = active but isActiveFor() returns false if all periods (including grace) have ended. Conversely, cron may queue a membership for expiration while membership_status is still active.

Grace period

Fixed and Rolling duration plugins support a grace_period config key (integer days). After a period’s end date, isActiveFor() still returns true until grace period elapses. Grace does not change the stored membership_status field or extend the period entity’s end date.

Expiration flow

  1. Cron finds memberships to expire (see Cron and queue).
  2. Queue worker calls $membership->getMembershipTerm()->expire().
  3. Base expire() sets membership_status to expired and clears current_periods.