Plugins

The plugin system lets you hook into every access check, feature evaluation, policy evaluation, and experiment assignment. Use plugins for audit logging, analytics, custom condition operators, and more.

Plugin Interface

interface AccessPlugin {
  name: string;
  onAccessCheck?: (event: AccessCheckEvent) => void;
  onFeatureEvaluate?: (event: FeatureEvaluateEvent) => void;
  onPolicyEvaluate?: (event: PolicyEvaluateEvent) => void;
  onExperimentAssign?: (event: ExperimentAssignEvent) => void;
  onConfigLoad?: (event: ConfigLoadEvent) => void;
  onRenderDenied?: (event: RenderDeniedEvent) => void;
  operators?: readonly CustomOperator[];
}

All hooks are optional. Plugin errors are silently caught — they never break your application.

Built-In Plugins

Audit Logger

Logs all access events for audit trails:

import { createAuditLoggerPlugin } from "react-access-engine";

const auditPlugin = createAuditLoggerPlugin({
  log: (event) => {
    // Send to your logging service
    console.log(`[AUDIT] ${event.type}:`, event);
  },
  deniedOnly: false, // Set true to only log denied events
});

Analytics

Track access events in your analytics provider:

import { createAnalyticsPlugin } from "react-access-engine";

const analyticsPlugin = createAnalyticsPlugin({
  adapter: {
    track: (eventName, properties) => {
      mixpanel.track(eventName, properties);
    },
  },
  prefix: "rac", // Event prefix (default: 'rac')
  trackFeatures: true, // Track feature evaluations
  trackExperiments: true, // Track experiment assignments
  trackDenied: true, // Track denied access attempts
});

Custom Operators

Register custom condition operators for the ABAC engine:

import { createOperatorPlugin } from "react-access-engine";

const geoPlugin = createOperatorPlugin([
  {
    name: "withinRegion",
    evaluate: (fieldValue, targetValue) => {
      return Array.isArray(targetValue)
        ? targetValue.includes(fieldValue)
        : fieldValue === targetValue;
    },
  },
  {
    name: "matchesPattern",
    evaluate: (fieldValue, pattern) => {
      return new RegExp(String(pattern)).test(String(fieldValue));
    },
  },
]);

Then use them in conditions:

policies: [
  {
    id: 'geo-restricted',
    effect: 'deny',
    permissions: ['data:export'],
    condition: (ctx) => {
      // Custom operators are available through the condition engine
      return ctx.user.attributes?.region !== 'EU';
    },
  },
],

Writing a Custom Plugin

const myPlugin: AccessPlugin = {
  name: "my-custom-plugin",

  onAccessCheck: ({ permission, user, allowed, resource }) => {
    // Called after every permission check
    console.log(`${user.id} ${allowed ? "can" : "cannot"} ${permission}`);
  },

  onFeatureEvaluate: ({ feature, enabled, reason, user }) => {
    // Called after every feature evaluation
    if (!enabled) {
      console.log(`Feature ${feature} disabled for ${user.id}: ${reason}`);
    }
  },

  onPolicyEvaluate: ({ permission, effect, matchedRule, user }) => {
    // Called after every policy evaluation
    console.log(`Policy: ${permission} → ${effect} (rule: ${matchedRule})`);
  },

  onExperimentAssign: ({ experimentId, variant, user }) => {
    // Called when a user is assigned to an experiment variant
    console.log(`Experiment ${experimentId}: ${user.id} → ${variant}`);
  },

  onRenderDenied: ({ component, permission, user }) => {
    // Called when a component denies rendering
    console.log(`Render denied: ${component} for ${user.id}`);
  },
};

Registering Plugins

Add plugins to your config:

const config = defineAccess({
  roles: ["admin", "user"] as const,
  permissions: {
    /* ... */
  },
  plugins: [auditPlugin, analyticsPlugin, geoPlugin, myPlugin],
});

Plugin Engine (Internal)

The PluginEngine class is used internally by the library to manage plugin lifecycle. It is not exported as part of the public API — plugins are automatically initialized when passed via the plugins array in defineAccess().