strapi/strapi · error · Error

You can't register new conditions outside the bootstrap func

Error message

You can't register new conditions outside the bootstrap function.

What it means

Strapi freezes the permissions condition registry once the bootstrap phase completes. The condition provider's register() checks strapi.isLoaded and refuses new conditions afterwards, because conditions must be available before any request is authorized. Registering later would leave already-cached permission checks inconsistent. Conditions extend RBAC with attribute-level rules (e.g. 'author equals me').

Source

Thrown at packages/core/core/src/services/content-api/permissions/providers/condition.ts:16

import { providerFactory } from '@strapi/utils';

export interface Condition {
  name: string;
  [key: string]: unknown;
}

export default (options = {}) => {
  const provider = providerFactory(options);

  return {
    ...provider,

    async register(condition: Condition) {
      if (strapi.isLoaded) {
        throw new Error(`You can't register new conditions outside the bootstrap function.`);
      }

      return provider.register(condition.name, condition);
    },
  };
};

View on GitHub (pinned to 4a4101264d)

Solutions

  1. Move the conditionProvider.register(...) call into the bootstrap function in ./src/index.js (or the plugin's bootstrap) so it runs before strapi.isLoaded becomes true.
  2. If registering from a plugin, put it in the plugin's bootstrap({ strapi }) hook, not in register() callbacks that fire after load.
  3. In tests, register conditions before calling await strapi.start() or use a fresh Strapi instance per test.
  4. Confirm the call site runs synchronously during bootstrap, not in a deferred setTimeout/promise that resolves after load.

Example fix

// before (throws after load)
async function myService() {
  strapi.conditionProvider.register({ name: 'is-owner', handler: ... });
}

// after (runs in bootstrap)
module.exports = {
  async bootstrap({ strapi }) {
    const { conditionProvider } = strapi.service('admin::permission');
    conditionProvider.register({ name: 'is-owner', handler: ({ user }) => ({ id: user.id }) });
  },
};
Defensive patterns

Strategy: validation

Validate before calling

// Only register conditions while strapi is still loading
if (!strapi.isLoaded) {
  strapi.conditionProvider.register({ name: 'is-owner', handler });
} else {
  strapi.log.warn('Skipped condition registration: strapi already loaded');
}

Type guard

const isCondition = (c: unknown): c is { name: string; [k: string]: unknown } =>
  typeof c === 'object' && c !== null && typeof (c as any).name === 'string';

Prevention

When it happens

Trigger: Calling strapi.conditionProvider.register({ name, plugin, handler }) (or the content-api permissions condition service register) from inside a controller, a service method, or any code path that runs after strapi.start()/bootstrap has finished — i.e. when strapi.isLoaded === true.

Common situations: Moving a condition registration out of ./src/index.js bootstrap() into a service; registering conditions lazily on first request; a plugin that registers conditions in register() but the host app calls them late; upgrading and not realizing bootstrap already ran in tests.

Related errors


AI-assisted analysis of strapi/strapi@4a4101264d (2026-08-12). Data as JSON: /api/errors/4fab6e625fba45b6. Report an issue: GitHub.