---
title: Hooks and Filters
description: "The plugin's public extension surface, grouped by what you'd want to do with it. --- ## Ground Rules Before writing a callback: 1. **Filters must return their filtered value on every path**, including early bails. Returning nothing replaces the value…"
url: "https://brightleafdigital.io/docs/hooks-and-filters/"
updated: "2026-10-18"
category: Recurring Form Submissions for Gravity Forms / Developer Reference Recurring Form Submissions For Gravity Forms
---

# Hooks and Filters

The plugin's public extension surface, grouped by what you'd want to do with it.

---

## Ground Rules

Before writing a callback:

1. **Filters must return their filtered value on every path**, including early bails. Returning nothing replaces the value with `null` for every later callback.
2. **Be defensive.** Don't assume a parameter's type; validate at the top of the callback.
3. **Never log field values or full entry data.** The plugin's own records are redacted deliberately — don't undo that in an extension.

---

## Tuning Intervals

| Filter | Default | Purpose |
| --- | --- | --- |
| `rfsfgf_worker_interval` | `900` | Seconds between worker runs. One-minute floor. The health threshold (two intervals) follows it automatically. |
| `rfsfgf_failed_check_interval` | `43200` | Seconds between maintenance scans. |
| `rfsfgf_failed_check_first_run` | — | UTC timestamp anchoring the maintenance event's first run, so it lands at a preferred time of day. |
| `rfsfgf_missed_run_grace` | 2 × worker interval | How late an occurrence must be before a skipping feed treats it as missed. Never below one worker interval. |

```
// Poll every five minutes. Only worth doing if the host invokes WordPress that often.
add_filter( 'rfsfgf_worker_interval', function ( $seconds ) {
    return 300;
} );
```

Interval changes are detected during registration, and an event registered at the wrong interval is repaired — with the previous timestamp and interval recorded.

---

## Shaping Schedules

| Filter | Receives | Purpose |
| --- | --- | --- |
| `rfsfgf_schedule_created` | `$schedule, $meta, $entry` | Adjust the normalized schedule as a series is created. |
| `rfsfgf_next_occurrence` | `$target, $schedule, $after` | Adjust a calculated target before it is stored. |

`rfsfgf_next_occurrence` is the hook for calendar rules the settings don't cover — a blackout window, a business-day offset, an organisation-specific holiday list.

```
add_filter( 'rfsfgf_next_occurrence', function ( $target, $schedule, $after ) {
    if ( ! is_int( $target ) ) {
        return $target;
    }
    // Push a target landing on a company holiday to the next day.
    return my_is_holiday( $target ) ? $target + DAY_IN_SECONDS : $target;
}, 10, 3 );
```

> Return a valid future timestamp. A filter that returns something unusable will produce a schedule you can't explain from the UI.

---

## Processing Events

| Action | Receives | Fires |
| --- | --- | --- |
| `rfsfgf_attempt_started` | `$attempt, $id, $context` | As an attempt begins. |
| `rfsfgf_attempt_completed` | `$result, $context` | When an attempt resolves. The context is **redacted**. |
| `rfsfgf_lock_recovered` | `$series_id` | When a stale claim is recovered. |

Use these for structured monitoring — a metrics pipeline, an audit trail, a dashboard.

```
add_action( 'rfsfgf_attempt_completed', function ( $result, $context ) {
    if ( ! is_array( $result ) ) {
        return;
    }
    my_metrics_counter( 'rfsfgf.attempt.' . rgar( $result, 'outcome', 'unknown' ) );
}, 10, 2 );
```

---

## Lifecycle and Transition Events

| Action | Receives | Fires |
| --- | --- | --- |
| `rfsfgf_series_pause` | `$series` | A series is paused. |
| `rfsfgf_series_resume` | `$series` | A series is resumed. |
| `rfsfgf_series_cancel` | `$series` | A series is canceled. |
| `rfsfgf_series_first_failure` | `$series` | The first failure in a new sequence. |
| `rfsfgf_series_failed` | `$series` | The failure threshold was reached. |
| `rfsfgf_series_recovered` | `$series` | A success resolved the issue. |
| `rfsfgf_parent_series_canceled` | `$entry_id, $count, $reason` | A parent was trashed or deleted, cancelling its series. `$reason` distinguishes trashed from deleted. |

These are the right hooks for routing alerts somewhere other than the Admin Email — Slack, a helpdesk, a log.

```
add_action( 'rfsfgf_series_failed', function ( $series ) {
    if ( ! is_array( $series ) ) {
        return;
    }
    my_alerting_channel( sprintf(
        'Recurring series #%d stopped after %d consecutive failures.',
        (int) rgar( $series, 'id' ),
        (int) rgar( $series, 'consecutive_failures' )
    ) );
} );
```

> The historical `rfsfgf_auto_form_submissions_failed_entries` report filter is superseded by these events. Periodic full-history email generation has been removed.

---

## Schedule Propagation

| Action | Receives | Fires |
| --- | --- | --- |
| `rfsfgf_schedule_applied` | `$series_id, $schedule, $source` | A schedule was applied to one series. |
| `rfsfgf_feed_schedule_applied` | `$feed_id, $saved, $skipped` | A bulk feed application finished, with counts of applied and skipped series. |
| `rfsfgf_next_submission_changed` | `$series_id, $target, $schedule` | A series' next target was moved. |

Useful for auditing who changed what, and for reconciling a bulk application that skipped locked series.

---

## Diagnostics

| Filter | Receives | Purpose |
| --- | --- | --- |
| `rfsfgf_diagnostic_context` | `$context` | Adjust the diagnostic context before the report is produced. |

The plugin applies its **own bounded allowlist after** this filter, so you cannot use it to leak values into a report. Adding non-sensitive environment detail is the intended use.

---

## Shortcode Visibility

| Filter | Receives | Purpose |
| --- | --- | --- |
| `rfsfgf_shortcode_can_view` | `$can_view, $context, $args` | Decide whether a shortcode renders, before anything is output. |

`$context` is `overview` or `entry_widget`. For the entry widget, `$args` holds the `entry` and `form`.

```
add_filter( 'rfsfgf_shortcode_can_view', function ( $can_view, $context, $args ) {
    if ( 'entry_widget' === $context && ! $can_view ) {
        return get_current_user_id() && (int) rgar( $args['entry'], 'created_by' ) === get_current_user_id();
    }
    return $can_view;
}, 10, 3 );
```

**Widening the read does not widen the write.** The buttons and the handler behind them still require `recurring_form_submissions_manage_resubmissions`.

---

## Capability

```
recurring_form_submissions_manage_resubmissions
```

Granted to administrators automatically. Every operational surface and handler checks it; every request also verifies its nonce and that the parent, form and feed belong together.

---

## Gravity Forms Integration Points

The plugin registers ordinary Gravity Forms extension points you can use as usual:

| Point | Names |
| --- | --- |
| **Notification events** | `auto_submit_success`, `auto_submit_fail`, `auto_submits_complete` |
| **Merge tags** | `{resubmission_progress}`, `{is_auto_submission}`, `{rfsfgf_is_parent_submission}`, `{total_resubmissions}`, `{rfsfgf_feed_label}` |
| **Entry meta** | Feed Label, Is Parent Submission, Is Auto Submission, Total Resubmissions, Resubmissions Progress |

Child entries are created through normal Gravity Forms processing, so `gform_after_submission` and friends fire on them exactly as they would for a human submission. Use `{is_auto_submission}` or the entry meta to tell them apart.
