npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sensinum/strapi-plugin-multi-domain

v5.2.2

Published

Allow to provide multi domain support

Readme


This plugin brings multi-domain support to your Strapi application. It allows you to isolate data between different domains, providing a robust solution for SaaS applications and other multi-user platforms where data separation is crucial.

GET THE LICENSE: To obtain the license we encourage you to visit the plugin commercial website, select the appropriate plan, and reach out to your preferred reseller or directly contact the plugin authors via this form

⚠️ This plugin is licensed by VirtusLab Ltd. under the End User License Agreement (EULA). Unauthorized use, including usage without a valid license key or modification of the code in a way that breaches the integrity of the software, is strictly prohibited and will be treated as a violation of the EULA, with all associated legal consequences.

Table of Contents

  1. ✨ Features
  2. 🎨 Admin Panel Features
  3. ⚙️ How it works
  4. ⚠️ Important Considerations
  5. 🔧 Configuration
  6. 🧪 Testing
  7. 👨‍💻 For Developers: Advanced Customization
  8. 🔌 Supported Third-Party Plugins
  9. 📝 License

✨ Features

  • Domain-based Data Isolation: Automatically scopes content types to the current user's domain.
  • Media Library Segregation: Each domain has its own isolated section within the media library.
  • User-Domain Association: Users can be associated with one or more domains through Strapi roles.
  • Domain Selector: A convenient domain selector is added to the admin UI for users who belong to multiple domains.
  • Super Admin Access: Super admins have the ability to view data across all domains.
  • Configurable: Easily configure which content types should be domain-aware.

🎨 Admin Panel Features

  • Domain Management UI: A dedicated settings page in the admin panel (Settings > Multi-Domain Settings) allows administrators to create, view, update, and delete domains.
  • Domain Selector: Users belonging to multiple domains will see a "Select domain" option in the user menu (top right). This opens a modal allowing them to easily switch between their assigned domains, reloading the app context for the selected domain.
  • "Unique by Domain" Custom Field: The plugin registers a new custom field type named "Unique by domain". When applied to a text field on a content type, it ensures that the value of that field is unique among all entries of that type within the same domain.
  • Content Manager Integration: To prevent content from being created without a domain, the "Create new entry" button in the Content Manager is hidden when the "Super Admin" domain is selected.

⚙️ How it works

The plugin introduces a Domain collection type and associates other content types with it. When a user logs in, they are associated with a domain. All their actions within the Content Manager and Media Library are then scoped to that domain. This is achieved by decorating Strapi's core services and controllers to automatically add domain filters to database queries.

⚠️ Important Considerations

Users & Permissions Plugin Roles

When using the Users & Permissions plugin with multi-domain functionality, there's an important limitation to be aware of:

The public role does not work with multi-domain by default because the plugin cannot recognize which domain the request belongs to without proper authentication context.

Solutions:

  1. Add auth: false to routes: For routes that need to be publicly accessible across domains, you can disable authentication by adding auth: false to the route configuration.

  2. Use API tokens assigned to domains: Ensure all requests include an API token that is properly assigned to a specific domain. This allows the plugin to identify the correct domain context for the request.

Example:

// In your route configuration
module.exports = {
  routes: [
    {
      method: 'GET',
      path: '/my-public-endpoint',
      handler: 'my-controller.myPublicMethod',
      config: {
        auth: false, // Disables authentication for this route
      },
    },
  ],
};

Without these configurations, public requests may fail or return unexpected results because the multi-domain plugin cannot determine the appropriate domain context.

🔧 Configuration

To configure the plugin, create a plugins.js (or plugins.ts) file in your Strapi project's config directory.

File: config/plugins.js

module.exports = {
  'multi-domain': {
    enabled: true,
    config: {
      /**
       * The code of default domain is used to create a domain for super admins.
       * By default, it uses the super admin role code from Strapi.
       */
      defaultDomain: {
        name: 'Super Admin',
        // code is an unique identifier for the domain
        // by default it's 'strapi-super-admin'
        customFields: {},
      },
      /**
       * A set of content-type uids that should not be scoped by domain.
       * By default, all content-types are scoped except for some internal Strapi models.
       * @returns {Set<string>}
       */
      getOmitContentTypes: () => new Set(['api::my-global-content-type.my-global-content-type']),
      /**
       * Optional async hook to validate domain customFields before create/update.
       * Throw an error to reject the operation.
       */
      customFieldValidation: async (customFields) => {
        if (!customFields?.myRequiredField) {
          throw new Error('myRequiredField is required');
        }
      },
      /**
       * A list of content-api endpoints to exclude from domain logic.
       * e.g., ['/api/my-public-endpoint']
       * @returns {string[]}
       */
      getExcludedContentAPIEndpoints: () => [],
      getExtraProxyDecorators: () => [
        {
          condition: (modelName) => modelName === 'api::article.article',
          handler(target, targetElement, ctx, prop, modelName) {
            // This handler will apply only to the 'article' content type
            return async (queryParams, ...restParams) => {
              const newQueryParams = {
                ...queryParams,
                where: {
                  ...(queryParams?.where || {}),
                  // Add some custom filter based on domain or user logic
                  customFilter: true,
                },
              };
              // Call the original database method with modified params
              return targetElement(newQueryParams, ...restParams);
            };
          },
        },
      ],
    },
  },
};

Configuration Options

  • defaultDomain (object): Configures the domain that is automatically created for super admins.
    • name (string): The display name for the default domain.
    • customFields (object): A JSON object for any custom data you want to associate with the domain.
  • getOmitContentTypes (function): Returns a Set of content-type UIDs that should be excluded from multi-domain. Use this for global, non-domain-specific content.
  • getExcludedContentAPIEndpoints (function): Returns an array of URL paths for Content API endpoints that should be publicly accessible and not scoped by domain.
  • customFieldValidation (async function): An optional async validation hook called before a domain is created or updated. It receives the domain's customFields object and should throw an error (or return a rejected promise) if validation fails. By default it is a no-op (() => Promise.resolve()).
  • getExtraProxyDecorators (function): An advanced feature that allows you to add custom logic to the database query proxy. This is useful for implementing complex, domain-specific business rules. It should return an array of objects, each with a condition and a handler function.
    • condition (function): Takes a modelName and returns true if the handler should be applied.
    • handler (function): A function that wraps the original database method, allowing you to modify its behavior. It receives the target repository, the original targetElement method, the Koa ctx, the method prop name, and the modelName.

Here's an example of how you might use getExtraProxyDecorators to add an additional filter to a specific content type:

// config/plugins.js
module.exports = {
  'multi-domain': {
    enabled: true,
    config: {
      /**
       * The code of default domain is used to create a domain for super admins.
       * By default, it uses the super admin role code from Strapi.
       */
      defaultDomain: {
        name: 'Super Admin',
        // code is an unique identifier for the domain
        // by default it's 'strapi-super-admin'
        customFields: {},
      },
      /**
       * A set of content-type uids that should not be scoped by domain.
       * By default, all content-types are scoped except for some internal Strapi models.
       * @returns {Set<string>}
       */
      getOmitContentTypes: () => new Set(['api::my-global-content-type.my-global-content-type']),
      /**
       * A list of content-api endpoints to exclude from domain logic.
       * e.g., ['/api/my-public-endpoint']
       * @returns {string[]}
       */
      getExcludedContentAPIEndpoints: () => [],
      getExtraProxyDecorators: () => [
        {
          condition: (modelName) => modelName === 'api::article.article',
          handler(target, targetElement, ctx, prop, modelName) {
            // This handler will apply only to the 'article' content type
            return async (queryParams, ...restParams) => {
              const newQueryParams = {
                ...queryParams,
                where: {
                  ...(queryParams?.where || {}),
                  // Add some custom filter based on domain or user logic
                  customFilter: true,
                },
              };
              // Call the original database method with modified params
              return targetElement(newQueryParams, ...restParams);
            };
          },
        },
      ],
    },
  },
};

🧪 Testing

To run the tests for this plugin, navigate to the plugin's directory and run the following command:

This will execute the Jest test suite. To run tests in watch mode, you can use yarn test:watch.

👨‍💻 For Developers: Advanced Customization via shared/pluginIds.ts

The shared/pluginIds.ts file is a centralized module for constants that are used across both the server-side and client-side parts of the plugin. While it's not part of the standard configuration, it offers a powerful way for developers to perform advanced customizations or re-brand the plugin's core concepts.

Warning: Modifying these values is an advanced procedure. It can have widespread effects and potentially break the plugin's functionality if not done carefully and consistently.

What can be achieved by changing this file?

By changing the values in this file, you can fundamentally alter the plugin's identifiers. For example, if your business logic uses the term "Brand" instead of "Domain," you could update modelId to 'brand' to make the content type, API routes, and database relationships align with your terminology.

Here's a breakdown of the constants and what you can control by changing them:

  • pluginId: The official ID of the plugin (multi-domain). Changing this will alter the plugin's root path in Strapi, including settings URLs and permission domains.

Note: This value is derived from the strapi.name property in package.json and should always be kept in sync with it.

  • modelId: The singular ID for the domain content type (default: 'domain'). Changing this renames the content type itself, which affects the database table, API endpoints, and the name of the relationship field added to other content types.
  • modelServiceId: The identifier for the domain service (default: 'domain'). This should typically be kept in sync with modelId.
  • userField: The property name on the user object where associated domain information is stored (default: 'domain').
  • modelRoute: The base route for the domain management API (default: 'domains'). This is derived from modelId, so changing modelId will automatically update this.
  • COOKIE_NAME: The name of the cookie used to store the user's currently selected domain (default: 'domain'). You might change this to avoid naming conflicts with other cookies in your application.

🔌 Supported Third-Party Plugins

This plugin provides domain-scoped data isolation for the following third-party plugins:

strapi-plugin-navigation

Navigation items and navigations are automatically scoped to the current domain.

strapi-plugin-comments

Comments and comment reports are automatically scoped to the current domain. Since the Authorization header is reserved for the domain API token, author identification for comments is handled via the X-Author-Authorization header:

X-Author-Authorization: Bearer <users-permissions-jwt>

This header is processed on comment endpoints that require author context: creating, updating, and deleting comments. If the token is valid, the resolved Strapi user is set as the comment author. Without this header, the comment must include an author object in the request body with id, name, and email fields.

strapi-plugin-reactions

Reactions and reaction types are automatically scoped to the current domain. Since the Authorization header is reserved for the domain API token, author identification for reactions is handled via the X-Author-Authorization header:

X-Author-Authorization: Bearer <users-permissions-jwt>

This header is processed on reaction endpoints: listing, creating, deleting, and toggling reactions. If the token is valid, the resolved Strapi user is set as the reaction author. The reactions plugin also supports its own X-Reactions-Author header for custom (non-Strapi) users. When X-Reactions-Author is present, X-Author-Authorization is ignored — custom author identification takes precedence.

📝 License

All rights reserved. Copyright (c) VirtusLab Ltd.