> ## Documentation Index
> Fetch the complete documentation index at: https://flex-fields.bjanczak.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Examples target Filament 5. Verify optional dependencies before configuring integrations.
> Upload transfer completion, permanent storage and record acceptance are separate milestones. Native and Spatie image conversions retain the original.

# Custom lifecycle events

> Declare validated notifications and synchronous guards for your extension.

Import the public API through your package's configured SDK build adapter:

> [!TIP]
>
> **Watch this in the workshop:** [42:52 — add a synchronous review guard and copy notifications](https://www.youtube.com/watch?v=m4V_R7YxTP4\&t=2572s).

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
    createLifecycleEvents,
    createResourceScope,
} from '@bjanczak/flex-fields/sdk/v1';

const effects = createResourceScope();
const events = createLifecycleEvents({
    signal: effects.signal,
    events: {
        'acme.photo:changed': {
            kind: 'notification',
            validate: value => typeof value.id === 'string',
        },
        'acme.photo:before-save': {
            kind: 'guard',
            validate: value => typeof value.id === 'string',
        },
    },
    onError: ({ name, reason }) => reportComponentError(name, reason),
});

const unsubscribe = events.subscribe('acme.photo:changed', ({ id }) => {
    refreshPreview(id);
});

const removeGuard = events.subscribe('acme.photo:before-save', ({ id }) => {
    return isAllowedToSave(id); // Synchronous boolean.
});

if (events.allow('acme.photo:before-save', { id: photoId })) {
    savePhoto(photoId);
    events.notify('acme.photo:changed', { id: photoId });
}

// Invoke when the rendered component is destroyed.
effects.dispose();
```

Notifications can return promises; they do not delay other subscribers or asset
readiness. A guard returns `true` to allow or `false` to prevent your operation.
Exceptions, asynchronous guards and invalid decisions prevent it. Validators
must return synchronous booleans and should check all expected payload keys,
lengths and nested types. Payloads are shallow copied and frozen before validation;
the validator and subscribers receive the same snapshot. Event declarations and
payloads reject accessor properties, symbols and non-enumerable properties without
executing their getters. Use plain objects with enumerable data properties. Retain immutable
nested values. Subscribe with `{ signal: anotherOwner.signal }` to cancel just
that registration. Every subscription also has its own idempotent unsubscribe
function. Repeated callbacks registered by different owners remain independent.
Callbacks added during delivery run on the next delivery. Recursive delivery of
the same event is rejected. Error reports contain the event name and a reason,
without submitted values or exceptions from your callback.

These browser callbacks do not invoke PHP automatically and do not authorize a
server write. Keep server validation and authorization on the application's save
path. Your component decides when to call `allow()` and `notify()`.

Use owned effects instead of retaining timers or observers after removal:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const cancelRefresh = effects.timeout(refreshPreview, 200);
const cancelFrame = effects.frame(renderPreview);
const cancelPoll = effects.interval(refreshStatus, 5_000);
const disconnect = effects.trackObserver(observer);
const terminate = effects.trackWorker(worker);
```

Each method returns a release function. Completed one-shot effects release their
bookkeeping. You can also pass `{ signal: parentSignal }` to
`createResourceScope()`; aborting the parent disposes the scope. Use
`effects.signal` with cancellable asynchronous APIs and check `effects.disposed`
before publishing an async result. Disposal cannot interrupt code that is already
running. Shared workers or observers need a separate shared owner; tracking a
resource here ends that supplied resource when this component closes.

## Continue reading

[Extension guides](https://flex-fields.bjanczak.com/docs/extensions.md) · [Definition and registration reference](https://flex-fields.bjanczak.com/docs/extensions/definition-reference.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.