Edition-Based Feature Gating#
Dify restricts or enables features based on which of three deployment editions is active: COMMUNITY (default self-hosted), ENTERPRISE (self-hosted with enterprise license), and CLOUD (hosted SaaS). The edition is the primary gating signal across backend and frontend, superseding the now-removed ENTERPRISE_ENABLED and BILLING_ENABLED flags.
Scope note: Edition gating is distinct from (a) billing/quota checks (cloud_edition_billing_* decorators, which enforce subscription limits within CLOUD only) and (b) RBAC permission decorators. A small subset of features uses edition-plus-plan gating β combining edition with Cloud subscription status β documented below. Enterprise branding (title, logos, favicon) is a sub-case of edition gating β gated on DEPLOYMENT_EDITION == ENTERPRISE in get_system_features(). See Branding Customization for details.
Edition Detection#
Backend#
DeploymentEdition is a StrEnum with three members β COMMUNITY, ENTERPRISE, CLOUD β defined in api/enums/__init__.py.
The active edition is read from the DEPLOYMENT_EDITION environment variable, configured in api/configs/deploy/__init__.py as a Pydantic Field defaulting to DeploymentEdition.COMMUNITY. All backend code accesses dify_config.DEPLOYMENT_EDITION and compares it directly against enum members.
Frontend#
SystemFeatureService.get_public_system_features() reads dify_config.DEPLOYMENT_EDITION and populates it into SystemFeatureModel, exposing it through the unauthenticated /system-features endpoint.
The frontend fetches this at app startup via systemFeaturesQueryOptions() (a TanStack Query wrapper with staleTime: Infinity), caching the value for the full session. PR #39454 centralized this: the Root Layout now prefetches and hydrates system features server-side, eliminating ambiguous client-side environment checks.
There is also a secondary deploy-time flag: NEXT_PUBLIC_ENABLE_AGENT_V2 controls Agent V2 route availability entirely, independent of the runtime edition. feature-guard.ts calls notFound() if this env var is falsy, and web/features/agent-v2/feature-flag.ts exports isAgentV2Enabled() that reads it directly. This is distinct from the runtime deployment_edition check.
Docker / Environment Configuration#
DEPLOYMENT_EDITION defaults to COMMUNITY in the shared env example file . NEXT_PUBLIC_ENABLE_AGENT_V2 is set in the web-specific env example . The web service in docker-compose.yaml loads both via layered env_file directives .
Backend API Guards#
Three route decorators in api/controllers/console/wraps.py enforce edition at the API layer β returning HTTP 404 when the condition fails:
| Decorator | Fails (404) when | Allows |
|---|---|---|
only_edition_cloud | edition β CLOUD | CLOUD only |
only_edition_enterprise | edition β ENTERPRISE | ENTERPRISE only |
only_edition_self_hosted | edition == CLOUD | COMMUNITY + ENTERPRISE |
These are pure edition guards, distinct from cloud_edition_billing_* decorators (which enforce subscription quota limits within CLOUD).
Inline checks against dify_config.DEPLOYMENT_EDITION also appear outside decorators. For example, feature_service.py enables webapp_copyright_enabled and knowledge_pipeline.publish_enabled only when DEPLOYMENT_EDITION == ENTERPRISE , and CLOUD deployments trigger the billing API fulfillment path .
Edition-plus-Plan Gating#
A small set of features applies edition-plus-plan gating β combining DEPLOYMENT_EDITION with Cloud subscription status. This pattern restricts access for unpaid CLOUD workspaces while allowing all self-hosted deployments (COMMUNITY + ENTERPRISE) unconditionally:
if (
dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD
and not FeatureService.get_workspace_plan(tenant_id).is_paid
):
abort(403, description="This feature requires a paid plan.")
Agent Version Restore (api/controllers/console/agent/roster.py) is the canonical example:
- Browsing and previewing historical agent versions is unrestricted across all editions.
- Restoring a version (the
POST /console/api/agents/:agent_id/versions/:version_id/restoreendpoint) requires a paid Cloud plan for CLOUD workspaces. - Returns HTTP
403with"This feature requires a paid plan."before making any draft changes. - COMMUNITY and ENTERPRISE deployments bypass the check entirely β the plan gate only fires when
DEPLOYMENT_EDITION == CLOUD.
This pattern is used when a feature has operational cost or strategic value in the Cloud SaaS context but should remain freely available to self-hosters.
Frontend UI Gating#
React components read deployment_edition from the cached system-features query. The canonical pattern (from configure/page.tsx):
const { data: deploymentEdition } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: (systemFeatures) => systemFeatures.deployment_edition,
})
const previewEnabled = canTestAndRun && deploymentEdition !== 'COMMUNITY'
Agent V2 Preview mode is the primary example of this gate (introduced in PR #39399):
previewEnabledistruefor CLOUD and ENTERPRISE;falsefor COMMUNITY.- Any
?mode=previewURL param is silently overridden tobuildon COMMUNITY. - The Preview tab renders as a tooltip-wrapped disabled button when
previewEnabledis false . - ENTERPRISE always has preview enabled regardless of license validity status.
A separate family of build-time feature flags in web/features/agent-v2/agent-detail/configure/feature-flags.ts controls visibility of specific Agent V2 panels (CLI tools, content moderation, knowledge retrieval, secret env variables). All default to false and are compile-time constants β not runtime edition checks. Changing them requires rebuilding the web container.
Key Source Files#
| File | Purpose |
|---|---|
api/enums/__init__.py | DeploymentEdition StrEnum (COMMUNITY, ENTERPRISE, CLOUD) |
api/configs/deploy/__init__.py | DEPLOYMENT_EDITION env var config (default: COMMUNITY) |
api/services/system_feature_service.py | get_public_system_features() β surfaces deployment_edition to clients via /system-features |
api/controllers/console/wraps.py | only_edition_cloud/enterprise/self_hosted decorators |
api/services/feature_service.py | Inline edition checks for billing, enterprise features, copyright, knowledge pipeline; get_workspace_plan() for edition-plus-plan gating |
api/controllers/console/agent/roster.py | Agent version restore β edition-plus-plan gating example |
web/features/agent-v2/agent-detail/configure/page.tsx | Preview mode edition gate β canonical frontend pattern |
web/features/agent-v2/agent-detail/configure/components/preview/header.tsx | Disabled Preview tab rendering with tooltip |
web/app/(commonLayout)/agents/feature-guard.ts | Agent V2 route guard (NEXT_PUBLIC_ENABLE_AGENT_V2) |
web/features/agent-v2/feature-flag.ts | isAgentV2Enabled() / isAgentV2InChatflowEnabled() helpers |
docker/envs/core-services/shared.env.example | DEPLOYMENT_EDITION=COMMUNITY default |
docker/envs/core-services/web.env.example | NEXT_PUBLIC_ENABLE_AGENT_V2=true default |