This guide is for the people who deploy, operate, secure, extend, and contribute to Dify. It assumes you have read the User Guide and have a Dify instance running. If you are new to Dify, start there.
Note
Dify is classified as a "massive" complexity project: 11,069 files, 31.7 million source characters, 258 significant modules. This guide covers the full operational surface.
Table of Contents
- Executive Summary
- Architecture
- System Requirements
- Installation and Deployment
- Configuration Reference
- Security
- Operations
- Workflow Engine Internals
- RAG Pipeline Internals
- Plugin System Internals
- Vector Database Backend System
- Frontend Architecture
- Backend Architecture
- Database Schema and Migrations
- Celery Task System
- Observability and Telemetry
- Code Patterns and Conventions
- SDKs and Client Libraries
- E2E Testing
- Contributing
- Known Issues and Improvements
- CLI Reference
- Quick Reference
Executive Summary
Dify is an open-source LLM application development platform that provides a visual workflow builder, RAG pipeline, agent framework, model abstraction layer, and plugin ecosystem. It serves as the full-stack infrastructure for building and deploying AI applications.
Technology Stack
| Layer | Technology |
|---|---|
| Backend API | Python 3.12+, Flask, Gunicorn + gevent, SQLAlchemy ORM, Pydantic v2 |
| Task Queue | Celery with Redis broker |
| Frontend | Next.js 15 (App Router), React 19, Tailwind CSS 4, TypeScript |
| State Management | Zustand, TanStack Query, Jotai |
| Database | PostgreSQL (relational), Redis (cache/queue/pubsub/sessions) |
| Vector Store | 30+ pluggable backends via dify.vector_backends entry points |
| Plugin Runtime | Go-based plugin_daemon, JSON-RPC over stdio |
| Code Sandbox | Isolated execution environment for user code |
| Reverse Proxy | nginx (Docker) or any external proxy |
| Package Management | uv (Python), pnpm 10.33.0 (JavaScript) |
Key Metrics (Source: commit 13a9359)
| Metric | Value |
|---|---|
| Total files | 11,069 |
| Source characters | 31.7M |
| Extracted classes | 1,997 |
| Extracted functions | 7,321 |
| CLI commands | 129 |
| Config keys | 59 extracted (200+ total in .env.example) |
| Vector DB backends | 30+ |
| HIGH-priority modules | 258 |
Architecture
System Architecture
Internet
|
+--------v--------+
| nginx | Port 80/443, SSL termination
| (reverse proxy)| Static files, WebSocket upgrade
+---+--------+---+
| |
+---------v--+ +--v---------+
| web | | api | Flask + gevent + WebSocket
| (Next.js) | | :5001 | 4 API surfaces
| :3000 | +--+-+--+----+
+------------+ | | |
| | +----> plugin_daemon (Go) :5002
| | |-> Plugin processes (stdio JSON-RPC)
| |
+------+ +------+
| |
+-----v----+ +------v------+
| worker | | sandbox | Code execution :8194
| (Celery) | +-------------+
+--+--+-----+
| |
+------+ +------+
| |
+-----v------+ +------v-------+
| PostgreSQL | | Redis | Cache, broker, sessions,
| :5432 | | :6379 | rate limiting, pubsub
+------------+ +--------------+
|
+-----v---------+
| Vector Store | Weaviate :8080 (default)
| (pluggable) | or any of 30+ backends
+----------------+
Four API Surfaces
Dify exposes four distinct API surfaces, each serving a different audience:
| Surface | Base Path | Auth | Purpose |
|---|---|---|---|
| Console API | /console/api/* |
JWT (session) | Web UI operations (app CRUD, settings, workspace management) |
| Service API | /v1/* |
API Key (Bearer) | External integrations (chat, completion, workflow execution) |
| Web API | /api/* |
App token | End-user web app interactions |
| Inner API | /inner/api/* |
INNER_API_KEY_FOR_PLUGIN |
Plugin daemon communication |
Important
The Inner API is exclusively for plugin daemon communication. It is not intended for external use and uses a shared secret key (
INNER_API_KEY_FOR_PLUGIN) for authentication.
Process Model
The API server runs Flask under Gunicorn with the gevent worker class, providing cooperative multitasking for handling concurrent requests. The entry point is api/app.py:
# api/app.py
app = create_app()
celery = app.extensions["celery"]
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)
In production:
gunicorn --bind 0.0.0.0:5001 \
--worker-class gevent \
--workers 1 \
--timeout 200 \
app:app
Warning
The gevent worker requires monkey-patching at import time. The
app_factory.pyhandles this. Do not import standard library threading or socket modules before gevent patches are applied.
System Requirements
Hardware Requirements
| Deployment Size | CPU | RAM | Disk | Notes |
|---|---|---|---|---|
| Development | 2 cores | 4 GB | 20 GB | Minimum viable |
| Small (< 10 users) | 4 cores | 8 GB | 50 GB | Comfortable for teams |
| Medium (10-100 users) | 8 cores | 16 GB | 100 GB | Separate DB recommended |
| Large (100+ users) | 16+ cores | 32+ GB | 500+ GB | Dedicated DB, Redis, VDB |
Software Dependencies
| Component | Required Version | Notes |
|---|---|---|
| Docker Engine | >= 19.03 | Docker Compose >= 2.x |
| Python | 3.12+ | Managed via uv |
| Node.js | 22.22.1 | Strict version (enforced by engines field) |
| pnpm | 10.33.0 | Enforced by packageManager field |
| PostgreSQL | 15+ | WAL mode, foreign keys enabled |
| Redis | 6+ | Used for cache, broker, sessions, pubsub |
Installation and Deployment
Docker Compose (Production)
The docker/ directory contains the complete production deployment configuration.
Container Orchestration
| Container | Image | Role | Ports |
|---|---|---|---|
nginx |
nginx:latest | Reverse proxy, static files, SSL | 80, 443 |
web |
Custom (Next.js) | Frontend application | 3000 (internal) |
api |
Custom (Flask) | Backend API server | 5001 (internal) |
worker |
Custom (Celery) | Background task processor | -- |
db |
postgres:15 | Relational database | 5432 (internal) |
redis |
redis:7 | Cache, message broker | 6379 (internal) |
weaviate |
semitechnologies/weaviate | Default vector store | 8080, 50051 (internal) |
plugin_daemon |
Custom (Go) | Plugin runtime | 5002 (internal) |
sandbox |
Custom | Code execution sandbox | 8194 (internal) |
ssrf_proxy |
Custom (Squid) | Outbound request filtering | 3128 (internal) |
certbot |
certbot/certbot | SSL certificate automation | -- |
Deployment Steps
cd dify/docker
cp .env.example .env
# Edit .env with your production values
docker compose up -d
# Verify all containers are healthy
docker compose ps
docker compose logs --tail 50 api
nginx Configuration
The bundled nginx config (docker/nginx/) handles:
- SSL termination (with Certbot-managed Let's Encrypt certificates)
- WebSocket upgrade for real-time streaming
- Static file serving for the frontend
- Proxy pass to
api:5001for API requests - Proxy pass to
web:3000for frontend requests
Key nginx directives for Dify:
# Required for SSE streaming
proxy_buffering off;
# Required for WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Source Deployment
Backend Setup
cd api
pip install uv
uv sync --all-extras
# Configure
cp .env.example .env
# Edit .env
# Database initialization
uv run flask upgrade-db
# Start API server
uv run gunicorn --bind 0.0.0.0:5001 \
--worker-class gevent \
--workers 1 \
--timeout 200 \
app:app
# Start Celery worker (separate terminal)
uv run celery -A app.celery worker \
-Q dataset,generation,mail,ops_trace \
--loglevel INFO
Frontend Setup
cd web
pnpm install
pnpm build
pnpm start
Development mode with hot reload:
pnpm dev
# With debugging proxy:
pnpm dev:proxy
Monorepo Structure
The repository is a pnpm monorepo:
dify/
package.json # Root workspace config (pnpm 10.33.0, Node 22.22.1)
web/ # Main frontend (Next.js 15, version 1.13.3)
e2e/ # End-to-end tests (Playwright + Cucumber)
packages/
dify-ui/ # Shared UI components (@langgenius/dify-ui)
iconify-collections/ # Custom icon sets
tsconfig/ # Shared TypeScript configs
migrate-no-unchecked-indexed-access/ # TS migration tool
sdks/
nodejs-client/ # Node.js SDK (dify-client v3.1.0)
php-client/ # PHP SDK (Guzzle-based)
SSRF Proxy
The ssrf_proxy container runs Squid configured to block requests to internal/private network ranges. All outbound HTTP requests from workflow HTTP Request nodes and tool calls are routed through this proxy to prevent Server-Side Request Forgery attacks.
Configuration:
SSRF_PROXY_HTTP_URL=http://ssrf_proxy:3128
SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128
SSRF_DEFAULT_MAX_RETRIES=3
SSRF_DEFAULT_TIME_OUT=5
SSRF_DEFAULT_CONNECT_TIME_OUT=5
SSRF_DEFAULT_READ_TIME_OUT=5
SSRF_DEFAULT_WRITE_TIME_OUT=5
Sandbox (Code Execution)
The sandbox container provides isolated execution for Code nodes in workflows. It runs user-submitted Python and JavaScript in a restricted environment with resource limits.
CODE_EXECUTION_ENDPOINT=http://127.0.0.1:8194
CODE_EXECUTION_API_KEY=dify-sandbox
CODE_MAX_NUMBER=9223372036854775807
CODE_MIN_NUMBER=-9223372036854775808
CODE_MAX_STRING_LENGTH=80000
CODE_MAX_STRING_ARRAY_LENGTH=30
CODE_MAX_OBJECT_ARRAY_LENGTH=30
CODE_MAX_NUMBER_ARRAY_LENGTH=1000
TEMPLATE_TRANSFORM_MAX_LENGTH=80000
The sandbox config lives at docker/volumes/sandbox/conf/.
Configuration Reference
All configuration is via environment variables, defined in .env. The authoritative list is in docker/.env.example. Below is the complete reference organized by category.
Core Application
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
-- | Flask secret key. Required. Generate with openssl rand -base64 42 |
DEBUG |
false |
Enable Flask debug mode |
SQLALCHEMY_ECHO |
false |
Echo all SQL queries to log |
LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
LOG_FILE |
-- | Log file path. Empty = stdout only |
LOG_FILE_MAX_SIZE |
20 |
Max log file size in MB |
LOG_FILE_BACKUP_COUNT |
5 |
Number of rotated log files to keep |
LOG_DATEFORMAT |
%Y-%m-%d %H:%M:%S |
Log timestamp format |
LOG_TZ |
UTC |
Log timezone |
LOG_FORMAT |
%(asctime)s... |
Python logging format string |
INIT_PASSWORD |
-- | Required password for initial /install setup |
URLs
| Variable | Default | Description |
|---|---|---|
CONSOLE_API_URL |
http://127.0.0.1:5001 |
Console API base URL |
CONSOLE_WEB_URL |
http://127.0.0.1:3000 |
Console web base URL |
SERVICE_API_URL |
http://127.0.0.1:5001 |
Service API base URL |
APP_WEB_URL |
http://127.0.0.1:3000 |
Web app base URL |
FILES_URL |
http://127.0.0.1:5001 |
File serving base URL |
FILES_ACCESS_TIMEOUT |
300 |
File URL signature validity (seconds) |
Database (PostgreSQL)
| Variable | Default | Description |
|---|---|---|
DB_USERNAME |
postgres |
Database user |
DB_PASSWORD |
difyai123456 |
Database password |
DB_HOST |
localhost |
Database host |
DB_PORT |
5432 |
Database port |
DB_DATABASE |
dify |
Database name |
Caution
Change
DB_PASSWORDandREDIS_PASSWORDfrom their defaults before exposing Dify to any network. The defaults (difyai123456) are for local development only.
Redis
| Variable | Default | Description |
|---|---|---|
REDIS_HOST |
localhost |
Redis host |
REDIS_PORT |
6379 |
Redis port |
REDIS_USERNAME |
-- | Redis username (ACL) |
REDIS_PASSWORD |
difyai123456 |
Redis password |
REDIS_USE_SSL |
false |
Enable TLS for Redis |
REDIS_DB |
0 |
Redis database number |
REDIS_KEY_PREFIX |
-- | Prefix for all Redis keys |
CELERY_BROKER_URL |
redis://:difyai123456@localhost:6379/1 |
Celery broker URL |
Storage
| Variable | Default | Description |
|---|---|---|
STORAGE_TYPE |
opendal |
Storage backend: opendal, s3, aliyun-oss, azure-blob, baidu-obs, google-storage, huawei-obs, oci-storage, tencent-cos, volcengine-tos, supabase |
OPENDAL_SCHEME |
fs |
OpenDAL scheme (fs, s3, gcs, etc.) |
OPENDAL_FS_ROOT |
storage |
Local filesystem root for OpenDAL |
Vector Store
| Variable | Default | Description |
|---|---|---|
VECTOR_STORE |
weaviate |
Vector backend identifier |
WEAVIATE_ENDPOINT |
http://localhost:8080 |
Weaviate HTTP endpoint |
WEAVIATE_API_KEY |
-- | Weaviate API key |
WEAVIATE_BATCH_SIZE |
100 |
Weaviate batch operation size |
WEAVIATE_TOKENIZATION |
word |
Weaviate text tokenization mode |
KEYWORD_DATA_SOURCE_TYPE |
database |
Keyword search data source |
Authentication and Sessions
| Variable | Default | Description |
|---|---|---|
ACCESS_TOKEN_EXPIRE_MINUTES |
60 |
JWT access token lifetime |
REFRESH_TOKEN_EXPIRE_DAYS |
30 |
JWT refresh token lifetime |
LOGIN_LOCKOUT_DURATION |
86400 |
Account lockout after failed attempts (seconds) |
RESET_PASSWORD_TOKEN_EXPIRY_MINUTES |
5 |
Password reset link lifetime |
EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES |
5 |
Email registration token lifetime |
CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES |
5 |
Email change token lifetime |
OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES |
5 |
Ownership transfer token lifetime |
CORS
| Variable | Default | Description |
|---|---|---|
WEB_API_CORS_ALLOW_ORIGINS |
http://127.0.0.1:3000,* |
Allowed origins for Web API |
CONSOLE_CORS_ALLOW_ORIGINS |
http://127.0.0.1:3000,* |
Allowed origins for Console API |
| Variable | Default | Description |
|---|---|---|
MAIL_TYPE |
-- | Mail provider: resend or smtp |
MAIL_DEFAULT_SEND_FROM |
no-reply <[email removed]> |
Default sender address |
RESEND_API_KEY |
-- | Resend API key |
RESEND_API_URL |
https://api.resend.com |
Resend API endpoint |
SMTP_SERVER |
smtp.example.com |
SMTP server hostname |
SMTP_PORT |
465 |
SMTP port |
SMTP_USERNAME |
-- | SMTP username |
SMTP_PASSWORD |
-- | SMTP password |
SMTP_USE_TLS |
true |
Enable TLS for SMTP |
SMTP_OPPORTUNISTIC_TLS |
false |
Opportunistic TLS (STARTTLS) |
SMTP_LOCAL_HOSTNAME |
-- | Override local hostname for HELO/EHLO |
Notion Integration
| Variable | Default | Description |
|---|---|---|
NOTION_INTEGRATION_TYPE |
public |
public (OAuth) or internal (direct token) |
NOTION_CLIENT_SECRET |
-- | OAuth client secret |
NOTION_CLIENT_ID |
-- | OAuth client ID |
NOTION_INTERNAL_SECRET |
-- | Internal integration token |
Upload Limits
| Variable | Default | Description |
|---|---|---|
UPLOAD_FILE_SIZE_LIMIT |
15 |
Max file upload size (MB) |
UPLOAD_FILE_BATCH_LIMIT |
5 |
Max simultaneous file uploads |
UPLOAD_IMAGE_FILE_SIZE_LIMIT |
10 |
Max image upload size (MB) |
UPLOAD_VIDEO_FILE_SIZE_LIMIT |
100 |
Max video upload size (MB) |
UPLOAD_AUDIO_FILE_SIZE_LIMIT |
50 |
Max audio upload size (MB) |
BATCH_UPLOAD_LIMIT |
10 |
Batch upload limit |
Workflow Execution
| Variable | Default | Description |
|---|---|---|
WORKFLOW_MAX_EXECUTION_STEPS |
500 |
Max nodes per workflow run |
WORKFLOW_MAX_EXECUTION_TIME |
1200 |
Max execution time (seconds) |
WORKFLOW_CALL_MAX_DEPTH |
5 |
Max sub-workflow nesting depth |
MAX_VARIABLE_SIZE |
204800 |
Max variable size (bytes, 200 KB) |
MAX_SUBMIT_COUNT |
100 |
Max parallel thread pool submissions |
WORKFLOW_FILE_UPLOAD_LIMIT |
10 |
Max file uploads per workflow run |
Application Limits
| Variable | Default | Description |
|---|---|---|
APP_MAX_EXECUTION_TIME |
1200 |
Max app execution time (seconds) |
APP_DEFAULT_ACTIVE_REQUESTS |
0 |
Default concurrent request limit (0 = unlimited) |
APP_MAX_ACTIVE_REQUESTS |
0 |
Max concurrent request limit (0 = unlimited) |
CELERY_BEAT_SCHEDULER_TIME |
1 |
Celery beat polling interval (seconds) |
Code Execution (Sandbox)
| Variable | Default | Description |
|---|---|---|
CODE_EXECUTION_ENDPOINT |
http://127.0.0.1:8194 |
Sandbox endpoint |
CODE_EXECUTION_API_KEY |
dify-sandbox |
Sandbox API key |
CODE_MAX_NUMBER |
9223372036854775807 |
Max numeric value |
CODE_MIN_NUMBER |
-9223372036854775808 |
Min numeric value |
CODE_MAX_STRING_LENGTH |
80000 |
Max string length |
CODE_MAX_STRING_ARRAY_LENGTH |
30 |
Max string array length |
CODE_MAX_OBJECT_ARRAY_LENGTH |
30 |
Max object array length |
CODE_MAX_NUMBER_ARRAY_LENGTH |
1000 |
Max number array length |
TEMPLATE_TRANSFORM_MAX_LENGTH |
80000 |
Max Jinja2 template length |
HTTP Request Node
| Variable | Default | Description |
|---|---|---|
HTTP_REQUEST_MAX_CONNECT_TIMEOUT |
300 |
Max connect timeout (seconds) |
HTTP_REQUEST_MAX_READ_TIMEOUT |
600 |
Max read timeout (seconds) |
HTTP_REQUEST_MAX_WRITE_TIMEOUT |
600 |
Max write timeout (seconds) |
HTTP_REQUEST_NODE_MAX_BINARY_SIZE |
10485760 |
Max binary response (10 MB) |
HTTP_REQUEST_NODE_MAX_TEXT_SIZE |
1048576 |
Max text response (1 MB) |
API Tool
| Variable | Default | Description |
|---|---|---|
API_TOOL_DEFAULT_CONNECT_TIMEOUT |
10 |
Default connect timeout (seconds) |
API_TOOL_DEFAULT_READ_TIMEOUT |
60 |
Default read timeout (seconds) |
Webhook
| Variable | Default | Description |
|---|---|---|
WEBHOOK_REQUEST_BODY_MAX_SIZE |
10485760 |
Max webhook body size (10 MB) |
Plugin System
| Variable | Default | Description |
|---|---|---|
PLUGIN_DAEMON_KEY |
-- | Plugin daemon authentication key |
PLUGIN_DAEMON_URL |
http://127.0.0.1:5002 |
Plugin daemon endpoint |
PLUGIN_REMOTE_INSTALL_PORT |
5003 |
Remote plugin install port |
PLUGIN_REMOTE_INSTALL_HOST |
localhost |
Remote plugin install host |
PLUGIN_MAX_PACKAGE_SIZE |
15728640 |
Max plugin package size (15 MB) |
INNER_API_KEY_FOR_PLUGIN |
-- | Shared secret for Inner API auth |
Marketplace
| Variable | Default | Description |
|---|---|---|
MARKETPLACE_ENABLED |
true |
Enable plugin marketplace |
MARKETPLACE_API_URL |
https://marketplace.dify.ai |
Marketplace API endpoint |
Endpoint Extensions
| Variable | Default | Description |
|---|---|---|
ENDPOINT_URL_TEMPLATE |
http://localhost:5002/e/{hook_id} |
Plugin endpoint URL template |
SSRF Protection
| Variable | Default | Description |
|---|---|---|
SSRF_PROXY_HTTP_URL |
-- | HTTP proxy for outbound requests |
SSRF_PROXY_HTTPS_URL |
-- | HTTPS proxy for outbound requests |
SSRF_DEFAULT_MAX_RETRIES |
3 |
Max retries for proxied requests |
SSRF_DEFAULT_TIME_OUT |
5 |
Total timeout (seconds) |
SSRF_DEFAULT_CONNECT_TIME_OUT |
5 |
Connect timeout (seconds) |
SSRF_DEFAULT_READ_TIME_OUT |
5 |
Read timeout (seconds) |
SSRF_DEFAULT_WRITE_TIME_OUT |
5 |
Write timeout (seconds) |
Model Configuration
| Variable | Default | Description |
|---|---|---|
MULTIMODAL_SEND_FORMAT |
base64 |
Image format for multimodal models (base64 or url) |
PROMPT_GENERATION_MAX_TOKENS |
4096 |
Max tokens for prompt generation |
CODE_GENERATION_MAX_TOKENS |
1024 |
Max tokens for code generation |
Indexing
| Variable | Default | Description |
|---|---|---|
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH |
4000 |
Max tokens per chunk |
ETL_TYPE |
dify |
Document parser: dify or Unstructured |
UNSTRUCTURED_API_URL |
-- | Unstructured.io API URL |
UNSTRUCTURED_API_KEY |
-- | Unstructured.io API key |
Proxy
| Variable | Default | Description |
|---|---|---|
HTTP_PROXY |
-- | HTTP proxy for outbound connections |
HTTPS_PROXY |
-- | HTTPS proxy for outbound connections |
NO_PROXY |
localhost,127.0.0.1 |
Proxy bypass list |
Position Configuration
| Variable | Default | Description |
|---|---|---|
POSITION_TOOL_PINS |
-- | Pin tools to top of list |
POSITION_TOOL_INCLUDES |
-- | Tool whitelist |
POSITION_TOOL_EXCLUDES |
-- | Tool blacklist |
POSITION_PROVIDER_PINS |
-- | Pin providers to top |
POSITION_PROVIDER_INCLUDES |
-- | Provider whitelist |
POSITION_PROVIDER_EXCLUDES |
-- | Provider blacklist |
Billing (Enterprise)
| Variable | Default | Description |
|---|---|---|
BILLING_API_URL |
-- | Billing service URL |
BILLING_API_SECRET_KEY |
-- | Billing service secret |
Sentry
| Variable | Default | Description |
|---|---|---|
SENTRY_DSN |
-- | Sentry DSN for error tracking |
Telemetry
| Variable | Default | Description |
|---|---|---|
SCARF_NO_ANALYTICS |
false |
Disable anonymous install telemetry |
Misc
| Variable | Default | Description |
|---|---|---|
RESPECT_XFORWARD_HEADERS_ENABLED |
false |
Trust X-Forwarded-* headers |
CREATE_TIDB_SERVICE_JOB_ENABLED |
false |
Enable TiDB service job creation |
Frontend Environment Variables
All NEXT_PUBLIC_* variables are embedded at build time and available to the client:
| Variable | Description |
|---|---|
NEXT_PUBLIC_API_PREFIX |
API URL prefix |
NEXT_PUBLIC_PUBLIC_API_PREFIX |
Public API URL prefix |
NEXT_PUBLIC_WEB_PREFIX |
Web app URL prefix |
NEXT_PUBLIC_DEPLOY_ENV |
Deployment environment |
NEXT_PUBLIC_EDITION |
Edition identifier |
NEXT_PUBLIC_SOCKET_URL |
WebSocket URL |
NEXT_PUBLIC_SENTRY_DSN |
Frontend Sentry DSN |
NEXT_PUBLIC_ALLOW_EMBED |
Allow iframe embedding |
NEXT_PUBLIC_ALLOW_INLINE_STYLES |
Allow inline styles in embed |
NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME |
Allow data: URI scheme |
NEXT_PUBLIC_BATCH_CONCURRENCY |
Parallel request limit |
NEXT_PUBLIC_COOKIE_DOMAIN |
Cookie domain scope |
NEXT_PUBLIC_CSP_WHITELIST |
Content Security Policy whitelist |
NEXT_PUBLIC_TOP_K_MAX_VALUE |
Max Top-K retrieval value |
NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS |
Generation timeout (ms) |
NEXT_PUBLIC_LOOP_NODE_MAX_COUNT |
Max loop iterations |
NEXT_PUBLIC_MAX_ITERATIONS_NUM |
Max agent iterations |
NEXT_PUBLIC_MAX_TOOLS_NUM |
Max tools per agent turn |
NEXT_PUBLIC_MAX_PARALLEL_LIMIT |
Max parallel branches |
NEXT_PUBLIC_MAX_TREE_DEPTH |
Max workflow tree depth |
NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH |
Max chunk tokens (UI) |
NEXT_PUBLIC_MAINTENANCE_NOTICE |
Maintenance banner text |
NEXT_PUBLIC_IS_MARKETPLACE |
Marketplace mode flag |
NEXT_PUBLIC_MARKETPLACE_API_PREFIX |
Marketplace API URL |
NEXT_PUBLIC_MARKETPLACE_URL_PREFIX |
Marketplace URL |
NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL |
Enable Firecrawl integration |
NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER |
Enable Jina Reader |
NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL |
Enable WaterCrawl |
NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX |
Enable single-dollar LaTeX |
NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON |
Disable image-as-icon upload |
NEXT_PUBLIC_AMPLITUDE_API_KEY |
Amplitude analytics key |
NEXT_PUBLIC_ZENDESK_WIDGET_KEY |
Zendesk chat widget key |
Security
Authentication Architecture
Dify uses JWT-based authentication with access/refresh token pairs:
- Login -- User authenticates with email/password or OAuth (Google)
- Access Token -- Short-lived JWT (
ACCESS_TOKEN_EXPIRE_MINUTES, default 60 min) - Refresh Token -- Long-lived token (
REFRESH_TOKEN_EXPIRE_DAYS, default 30 days) - API Keys -- Per-app keys for Service API access (Bearer token)
Brute-Force Protection
Failed login attempts trigger account lockout after repeated failures. The lockout duration is controlled by LOGIN_LOCKOUT_DURATION (default: 86400 seconds / 24 hours).
OAuth Integration
Google OAuth is supported natively. Configure via GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env.
Encryption
LLM Credential Storage
LLM API keys and model credentials are encrypted at rest using workspace-specific asymmetric key pairs. Each workspace generates a unique key pair on creation.
Caution
Running
flask reset-encrypt-key-pairregenerates the workspace encryption keys. All stored LLM credentials become invalid and must be re-entered. Only use this command when the key pair is compromised.
Secret Key
The SECRET_KEY is used for:
- Flask session signing
- JWT token signing
- CSRF protection
Generate a strong key: openssl rand -base64 42
API Key Security
- Service API keys are scoped to individual apps
- Keys are hashed before storage; the plaintext is shown only once on creation
- Rotate keys regularly; old keys can be revoked from the app settings
SSRF Protection
The ssrf_proxy container blocks outbound requests to private network ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) to prevent Server-Side Request Forgery attacks from user-controlled workflow nodes.
Content Security
Code Sandbox Isolation
User-submitted code in workflow Code nodes runs in the sandbox container with:
- Process isolation from the main application
- Resource limits (CPU, memory, execution time)
- No network access (unless explicitly configured)
- No filesystem access outside the sandbox
Content Moderation
Three layers of content moderation are available:
- OpenAI Moderation API -- Automated content classification
- Custom Keywords -- Regex/exact-match blocklist
- External API -- Custom moderation endpoint
File Access Control
Files are served with signed URLs that expire after FILES_ACCESS_TIMEOUT seconds (default: 300). The signature prevents unauthorized access to uploaded files.
CORS Configuration
Restrict allowed origins in production:
WEB_API_CORS_ALLOW_ORIGINS=https://your-domain.com
CONSOLE_CORS_ALLOW_ORIGINS=https://your-domain.com
Warning
Never use
*for CORS origins in production. This allows any website to make API requests on behalf of authenticated users.
X-Forwarded Headers
Set RESPECT_XFORWARD_HEADERS_ENABLED=true only when Dify is behind a trusted reverse proxy. This tells Flask to trust X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers for correct URL generation and client IP detection.
Operations
Health Checks
| Endpoint | Purpose |
|---|---|
GET /health |
Full health check (DB, Redis connectivity) |
GET /ping |
Simple liveness probe |
GET /db-pool-stat |
SQLAlchemy connection pool statistics |
GET /threads |
Active thread count |
Backup and Restore
PostgreSQL
# Backup
docker compose exec db pg_dump -U postgres dify > dify_backup_$(date +%Y%m%d).sql
# Restore
docker compose exec -T db psql -U postgres dify < dify_backup_20260513.sql
Redis
Redis data is ephemeral (cache, sessions) and does not require routine backup. For persistence, configure Redis RDB/AOF snapshots in redis.conf.
File Storage
Back up the storage directory based on your STORAGE_TYPE:
opendal(fs): Back up the directory atOPENDAL_FS_ROOT- S3/cloud: Rely on provider's backup mechanisms
Complete Backup Checklist
- PostgreSQL dump
.envfile- Storage directory (uploads, keys)
- nginx configuration (if customized)
- Plugin data (if using custom plugins)
Scaling
Horizontal Scaling
- API servers: Run multiple
apicontainers behind a load balancer. Ensure all share the sameSECRET_KEY, database, and Redis. - Celery workers: Add worker containers with
--concurrencyflag. All workers share the same Redis broker. - Frontend: The
webcontainer is stateless; scale freely.
Vertical Scaling
- Database: Increase PostgreSQL
shared_buffers,work_mem,max_connections - Redis: Increase
maxmemory; configure eviction policy (allkeys-lrurecommended) - Vector store: Scale according to your backend's documentation
Queue Scaling
Celery queues and their purposes:
dataset-- Document processing, embedding, indexinggeneration-- LLM generation tasksmail-- Email deliveryops_trace-- Operational tracing/logging
Scale selectively:
# Dedicated worker for dataset processing
celery -A app.celery worker -Q dataset --concurrency 4
# Dedicated worker for generation
celery -A app.celery worker -Q generation --concurrency 2
Log Management
API Logs
# View recent API logs
docker compose logs --tail 200 api
# Follow logs in real time
docker compose logs -f api
# Filter by level
docker compose logs api 2>&1 | grep ERROR
Log Configuration
LOG_LEVEL=INFO
LOG_FILE=/var/log/dify/api.log
LOG_FILE_MAX_SIZE=20
LOG_FILE_BACKUP_COUNT=5
LOG_TZ=UTC
Aliyun SLS Integration
For cloud-based log management:
ALIYUN_SLS_ENDPOINT=<endpoint>
ALIYUN_SLS_REGION=<region>
ALIYUN_SLS_PROJECT_NAME=<project>
ALIYUN_SLS_CHECK_CONNECTIVITY_TIMEOUT=30
Database Migrations
Migrations use Flask-Migrate (Alembic). They run automatically on API container startup.
# Run migrations manually
docker compose exec api flask upgrade-db
# Check current migration version
docker compose exec api flask db current
Storage Migration
Migrate files between storage backends:
docker compose exec api flask migrate-oss \
--source opendal \
--path upload_files \
--path image_files \
--dry-run
# Execute the migration
docker compose exec api flask migrate-oss \
--source opendal \
--update-db
Supported paths: privkeys, upload_files, image_files, tools, website_files, keyword_files, ops_trace.
Data Retention
Workflow Runs
# Preview cleanup (dry run)
docker compose exec api flask clean-workflow-runs \
--before-days 30 --dry-run
# Execute cleanup
docker compose exec api flask clean-workflow-runs \
--before-days 30 --batch-size 200
# Archive to S3 before deleting
docker compose exec api flask archive-workflow-runs \
--before-days 90 --delete-after-archive --workers 4
# Restore from archive
docker compose exec api flask restore-workflow-runs \
--tenant-ids "tenant-uuid" --run-id "run-uuid"
Messages
docker compose exec api flask clean-expired-messages \
--before-days 90 --graceful-period 21 --dry-run
The --graceful-period adds extra days after subscription expiration before cleanup.
Export Data
docker compose exec api flask export-app-messages \
--app-id "app-uuid" \
--end-before "2026-01-01" \
--filename "export/messages" \
--batch-size 1000
# Upload directly to cloud storage
docker compose exec api flask export-app-messages \
--app-id "app-uuid" \
--end-before "2026-01-01" \
--use-cloud-storage
Orphaned Resources
# Find orphaned file records (DB records without storage files)
docker compose exec api flask clear-orphaned-file-records --force
# Find orphaned storage files (storage files without DB records)
docker compose exec api flask remove-orphaned-files-on-storage --force
# Query file usage
docker compose exec api flask file-usage --file-id "uuid" --json
Draft Variables
docker compose exec api flask cleanup_orphaned_draft_variables \
--dry-run --batch-size 1000 --max-apps 100
Workflow Engine Internals
Graph Execution Model
The workflow engine uses a DAG-based graph executor built on the graphon package. Key concepts:
- Graph Definition -- Nodes and edges defined in the workflow DSL (JSON/YAML)
- Topological Execution -- Nodes execute in dependency order
- Parallel Branches -- Independent branches run concurrently via a
ThreadPoolExecutor(maxMAX_SUBMIT_COUNT= 100 threads) - Variable Aggregation --
Variable Aggregatornodes act as join points, waiting for all upstream branches
Execution Lifecycle
Start Node
-> Resolve dependencies
-> Execute node
-> If parallel paths: submit to thread pool
-> If conditional: evaluate IF/ELSE
-> If iteration: loop over items
-> If sub-workflow: recursive execution (depth <= WORKFLOW_CALL_MAX_DEPTH)
-> Collect outputs
-> Route to next node(s)
-> ... repeat ...
End Node
-> Assemble final outputs
-> Persist workflow_run record
Node Execution Details
Each node type has a specific executor:
- LLM Node -- Assembles prompt from template + variables, calls model, streams response
- Knowledge Retrieval Node -- Queries vector store, optionally re-ranks, returns chunks
- Code Node -- Sends code to sandbox via HTTP, receives output
- HTTP Request Node -- Makes HTTP call through SSRF proxy, parses response
- Sub-Workflow Node -- Creates a child workflow run, shares
trace_idfor observability
Human-in-the-Loop
Workflows support human input nodes that pause execution and wait for user approval/input. The resume mechanism:
- Workflow reaches human input node
- Execution state is persisted to PostgreSQL
- API returns a
task_idfor the paused run - User submits input via API
- Workflow resumes from the paused node
Draft/Preview Mode
Single-node execution for debugging:
- Creates its own
trace_idderived fromnode_execution_id - Does not create a workflow run record
- Useful for testing LLM prompts, code, and HTTP requests in isolation
RAG Pipeline Internals
Document Processing Pipeline
Upload -> Extractor -> Cleaner -> Chunker -> Embedder -> Vector Store
Extractors
| Format | Extractor | Notes |
|---|---|---|
| Built-in or Unstructured.io | ETL_TYPE controls which |
|
| DOCX | python-docx | |
| TXT/MD | Direct read | |
| HTML | BeautifulSoup | Strips tags, preserves structure |
| CSV/XLSX | pandas | Row-per-chunk option |
| EPUB | epub parser |
Chunking Strategies
- Automatic -- Heuristic-based splitting by paragraphs and sections
- Custom -- User-defined separator (e.g.,
\n\n) + max chunk size + overlap - Parent-Child -- Hierarchical chunking for context preservation
Key parameter: INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH (default: 4000) sets the maximum tokens per chunk.
Embedding Pipeline
- Text chunks are batched and sent to the configured embedding model
- Vectors are stored in the configured vector backend
- Full-text indexes are maintained in parallel (for keyword/hybrid search)
- The
datasetCelery queue handles all processing asynchronously
Retrieval Pipeline
Query -> Query Embedding -> Vector Search (Top-K)
|
+-> Full-Text Search (BM25)
|
+-> Hybrid (merge + deduplicate)
|
+-> Reranker (optional)
|
+-> Score Threshold Filter
|
+-> Context Assembly -> LLM
Vector Store Registration System
Vector backends are registered via Python entry points under the dify.vector_backends group. The discovery mechanism:
- Backend packages live in
api/providers/vdb/<backend>/ - Each has a
pyproject.tomlwith entry point registration vector_backend_registry.pylazily loads and caches factory classes- At runtime,
get_vector_factory_class(vector_type)resolves the backend
See Vector Database Backend System for details.
Plugin System Internals
Architecture
Flask API
|
v
Inner API (/inner/api/*)
| (INNER_API_KEY_FOR_PLUGIN auth)
v
plugin_daemon (Go binary, :5002)
|
+-> Plugin Process 1 (stdio JSON-RPC)
+-> Plugin Process 2 (stdio JSON-RPC)
+-> ...
Plugin Types
| Type | Purpose | Registration |
|---|---|---|
| Model Provider | Add LLM/embedding/reranker backends | Appears in Model Providers settings |
| Tool | Callable actions for agents/workflows | Appears in Tools panel |
| Extension | Custom endpoints, triggers | Endpoint-based |
Plugin Package Format
Plugins are distributed as .difypkg files containing:
- Manifest (plugin metadata, version, dependencies)
- Implementation code
- Configuration schema
Plugin Communication
The plugin_daemon manages plugin lifecycle:
- Plugin installation (download, extract, validate)
- Plugin startup (spawn process, establish stdio JSON-RPC channel)
- Request routing (API -> Inner API -> daemon -> plugin process)
- Plugin shutdown and cleanup
Plugin CLI Commands
# Migrate existing data for plugin system
flask migrate-data-for-plugin
# Extract plugin definitions from existing configuration
flask extract-plugins --output_file plugins.jsonl --workers 10
# Bulk install plugins
flask install-plugins --input_file plugins.jsonl --output_file installed_plugins.jsonl --workers 100
# Install RAG pipeline plugins
flask install-rag-pipeline-plugins --workers 100
# Configure OAuth for tool plugins
flask setup-system-tool-oauth-client --provider github --client-params '{"client_id":"...","client_secret":"..."}'
# Configure OAuth for trigger plugins
flask setup-system-trigger-oauth-client --provider github --client-params '...'
# Configure OAuth for datasource plugins
flask setup-datasource-oauth-client --provider notion --client-params '...'
# Transform datasource credentials
flask transform-datasource-credentials --environment online
Vector Database Backend System
Architecture
The vector store system uses a plugin architecture based on Python entry points:
api/core/rag/datasource/vdb/
vector_factory.py # Main entry: Vector class
vector_backend_registry.py # Entry point discovery + caching
abstract_vector_factory.py # AbstractVectorFactory interface
base_vector.py # BaseVector interface
api/providers/vdb/
<backend>/
pyproject.toml # Package + entry point registration
src/dify_vdb_<backend>/
<backend>_vector.py # Implementation
Interfaces
| Interface | Methods | Purpose |
|---|---|---|
AbstractVectorFactory |
init_vector(dataset, attributes, embeddings) |
Create a BaseVector instance |
BaseVector |
create, add_texts, search_by_vector, delete, delete_by_ids |
Vector operations |
Complete Backend List
| Backend | Entry Point | Package | Key Dependency |
|---|---|---|---|
| Weaviate | weaviate |
dify-vdb-weaviate |
weaviate-client==4.20.5 |
| Qdrant | qdrant |
dify-vdb-qdrant |
qdrant-client==1.9.0 |
| pgvector | pgvector |
dify-vdb-pgvector |
pgvector==0.4.2 |
| pgvecto-rs | pgvecto-rs |
dify-vdb-pgvecto-rs |
pgvecto-rs[sqlalchemy]~=0.2.2 |
| Milvus | milvus |
dify-vdb-milvus |
pymilvus~=2.6.12 |
| Chroma | chroma |
dify-vdb-chroma |
chromadb==0.5.20 |
| Elasticsearch | elasticsearch |
dify-vdb-elasticsearch |
elasticsearch==8.14.0 |
| Elasticsearch (JP) | elasticsearch-ja |
dify-vdb-elasticsearch |
elasticsearch==8.14.0 |
| OpenSearch | opensearch |
dify-vdb-opensearch |
opensearch-py==3.1.0 |
| TiDB Vector | tidb_vector |
dify-vdb-tidb-vector |
tidb-vector==0.0.15 |
| TiDB on Qdrant | tidb_on_qdrant |
dify-vdb-tidb-on-qdrant |
qdrant-client==1.9.0 |
| Oracle | oracle |
dify-vdb-oracle |
oracledb==3.4.2 |
| OceanBase | oceanbase |
dify-vdb-oceanbase |
pyobvector~=0.2.17 |
| SeekDB | seekdb |
dify-vdb-oceanbase |
pyobvector~=0.2.17 |
| Couchbase | couchbase |
dify-vdb-couchbase |
couchbase~=4.6.0 |
| Tencent | tencent |
dify-vdb-tencent |
tcvectordb~=2.1.0 |
| Baidu | baidu |
dify-vdb-baidu |
pymochow==2.4.0 |
| Hologres | hologres |
dify-vdb-hologres |
holo-search-sdk>=0.4.2 |
| AnalyticDB | analyticdb |
dify-vdb-analyticdb |
alibabacloud_gpdb20160503~=5.2.0 |
| Alibaba Cloud MySQL | alibabacloud_mysql |
dify-vdb-alibabacloud-mysql |
mysql-connector-python>=9.3.0 |
| Lindorm | lindorm |
dify-vdb-lindorm |
opensearch-py==3.1.0 |
| Upstash | upstash |
dify-vdb-upstash |
upstash-vector==0.8.0 |
| VikingDB | vikingdb |
dify-vdb-vikingdb |
volcengine-compat~=1.0.0 |
| MyScale | myscale |
dify-vdb-myscale |
clickhouse-connect~=0.15.0 |
| Tablestore | tablestore |
dify-vdb-tablestore |
tablestore==6.4.4 |
| Huawei Cloud | huawei_cloud |
dify-vdb-huawei-cloud |
elasticsearch==8.14.0 |
| IRIS | iris |
dify-vdb-iris |
intersystems-irispython>=5.1.0 |
| MatrixOne | matrixone |
dify-vdb-matrixone |
mo-vector~=0.1.13 |
| Clickzetta | clickzetta |
dify-vdb-clickzetta |
clickzetta-connector-python>=0.8.102 |
| Relyt | relyt |
dify-vdb-relyt |
-- |
| OpenGauss | opengauss |
dify-vdb-opengauss |
-- |
| Vastbase | vastbase |
dify-vdb-vastbase |
pyobvector~=0.2.17 |
Adding a New Backend
- Create
api/providers/vdb/<your_backend>/ - Add
pyproject.toml:[project] name = "dify-vdb-your-backend" version = "0.0.1" dependencies = ["your-client-lib>=1.0"] [project.entry-points."dify.vector_backends"] your_backend = "dify_vdb_your_backend.your_backend_vector:YourBackendFactory" [tool.setuptools.packages.find] where = ["src"] - Implement
AbstractVectorFactoryandBaseVector - Add to
api/pyproject.toml:[tool.uv.sources]:dify-vdb-your-backend = { workspace = true }[project.optional-dependencies]: add tovdb-allgroup
- Run
uv syncinapi/
Vector Migration
flask vdb-migrate --scope all
Migrates all datasets from the current vector backend to the one configured in VECTOR_STORE.
Frontend Architecture
Technology Stack
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 15 | App Router framework |
| React | 19 | UI library |
| TypeScript | Latest | Type safety |
| Tailwind CSS | 4 | Utility-first styling |
| Zustand | Latest | Client state management |
| TanStack Query | Latest | Server state management |
| Jotai | Latest | Atomic state management |
| ReactFlow | Latest | Workflow canvas |
| socket.io-client | Latest | Real-time WebSocket communication |
| i18next | Latest | Internationalization (20+ languages) |
Project Structure
web/
app/ # Next.js App Router pages
(commonLayout)/ # Authenticated layout group
datasets/ # Knowledge base UI
apps/ # Application management
signin/ # Authentication pages
components/ # Shared components
workflow/ # Workflow editor (ReactFlow)
datasets/ # Dataset management
plugins/ # Plugin management
billing/ # Billing (enterprise)
config/ # App configuration
hooks/ # Custom React hooks (26 files)
service/ # API service layer (55 files)
models/ # TypeScript type definitions (10 files)
utils/ # Utility functions (52 files)
i18n/ # Translations (20+ locales)
i18n-config/ # i18n configuration
contract/ # API contracts / types
constants/ # App constants
public/ # Static assets
vs/ # Monaco editor (bundled)
docker/ # Frontend Dockerfile
Build and Development
# Development
pnpm dev # Next.js dev server
pnpm dev:proxy # Dev with Hono proxy
pnpm dev:vinext # Vite-based dev (experimental)
# Build
pnpm build # Next.js production build
pnpm build:vinext # Vite-based build (experimental)
# Quality
pnpm type-check # TypeScript checking
pnpm lint # ESLint
pnpm test # Vitest tests
pnpm test:coverage # Test coverage
pnpm storybook # Component development
# Utilities
pnpm gen-icons # Regenerate icon components
pnpm i18n:check # Validate translations
pnpm knip # Find unused code
pnpm analyze # Bundle analysis
Internationalization
Translations live in web/i18n/<locale>/ (30 files per locale). Supported locales:
en-US, zh-Hans, zh-Hant, ja-JP, ko-KR, de-DE, fr-FR, it-IT, es-ES, pt-BR, ru-RU, tr-TR, vi-VN, th-TH, ar-TN, hi-IN, pl-PL, ro-RO, sl-SI, uk-UA, fa-IR.
Backend Architecture
Module Organization
api/
app.py # Entry point (Flask + gevent WebSocket)
app_factory.py # Flask app factory (gevent monkey-patch)
commands/ # Flask CLI commands (7 files, 129 commands)
account.py # reset-password, reset-email, create-tenant
plugin.py # Plugin migration and OAuth setup
retention.py # Data cleanup and archival
storage.py # File management and migration
system.py # DB upgrades, encryption, app conversion
vector.py # Vector DB migration
configs/ # Pydantic v2 configuration
middleware/ # Redis, PostgreSQL, storage, etc.
constants/ # Application constants (6 files, 350KB)
context/ # Request context management (4 files)
controllers/ # API route handlers
console/ # Console API (16 files + app/ 25 files + auth/ 9 files)
service_api/ # Service API (3 files + app/ 11 files)
web/ # Web API (19 files)
inner_api/ # Inner API (app/ 2 files + plugin/ 2 files)
common/ # Shared controller utilities (6 files)
core/ # Core business logic
app/apps/ # Application runtime (pipeline, common)
moderation/ # Content moderation
external_data_tool/ # External data integration
tools/ # Tool system (12 util files)
trigger/ # Trigger system
plugin/ # Plugin integration (3 util files)
rag/datasource/vdb/ # Vector store interfaces
prompt/ # Prompt utilities (4 files)
schemas/ # Core schemas (4 files)
errors/ # Error definitions
datasource/ # Data source utilities
enums/ # Enum definitions (3 files)
extensions/ # Flask extensions
otel/ # OpenTelemetry integration
ext_logstore.py # Aliyun SLS log integration
fields/ # API response field definitions (26 files)
libs/ # Utility libraries (35 files)
migrations/ # Alembic database migrations
models/ # SQLAlchemy ORM models (22 files, 395KB)
repositories/ # Data access layer (11 files)
schedule/ # Celery beat scheduled tasks (13 files)
services/ # Business logic services (57 files, 1MB)
auth/ # Authentication services (5 files)
plugin/ # Plugin services (9 files)
providers/vdb/ # Vector database backends (30+ packages)
Key Modules by Size
| Module | Files | Size | Purpose |
|---|---|---|---|
api/services |
57 | 1 MB | Core business logic services |
api/models |
22 | 395 KB | SQLAlchemy ORM models |
api/constants |
6 | 350 KB | Application constants |
api/controllers/console/app |
25 | 329 KB | Console API handlers |
api/libs |
35 | 156 KB | Utility libraries |
api/commands |
7 | 123 KB | CLI commands |
api/repositories |
11 | 124 KB | Data access layer |
api/controllers/web |
19 | 110 KB | Web API handlers |
api/controllers/console |
16 | 92 KB | Console API (non-app) |
api/controllers/service_api/app |
11 | 81 KB | Service API handlers |
api/fields |
26 | 80 KB | Response field definitions |
Configuration System
Dify uses Pydantic v2 settings classes for configuration (api/configs/). The middleware config (api/configs/middleware/) defines typed settings for:
- PostgreSQL connection
- Redis connection
- Storage backends
- Vector store selection
- Celery configuration
All settings are loaded from environment variables at startup.
Error Handling
The codebase defines specific error classes for different domains:
- Auth errors:
ApiKeyAuthFailedError,InvalidEmailError,PasswordMismatchError,AccountLoginError,UnauthorizedError - Dataset errors:
NoFileUploadedError,UnsupportedFileTypeError,FileTooLargeError,TooManyFilesError - Workspace errors:
WorkspaceMemberNotFoundError,WorkspaceQuotaExhaustedError
Errors are caught by Flask error handlers and returned as structured JSON responses.
Database Schema and Migrations
ORM Layer
Dify uses SQLAlchemy ORM with 22 model files defining the complete schema. Key models include:
- Account -- User accounts, credentials, OAuth tokens
- Tenant -- Workspaces, member relationships, roles
- App -- Application definitions, configurations, DSL
- Conversation -- Chat conversations, message history
- Message -- Individual messages with metadata
- Dataset -- Knowledge base definitions
- Document -- Documents within datasets
- DocumentSegment -- Chunks/segments with embeddings
- WorkflowRun -- Workflow execution records
- WorkflowNodeExecution -- Per-node execution records
- InstalledPlugin -- Plugin installation records
- ApiToken -- API key records (hashed)
- UploadFile -- File upload metadata
Migration Management
Migrations use Flask-Migrate (Alembic wrapper):
# Run pending migrations
flask upgrade-db
# Check current version
flask db current
# Generate new migration (development)
flask db migrate -m "description"
Migrations run automatically on API container startup. The database uses WAL mode with foreign keys enabled.
Celery Task System
Queue Architecture
| Queue | Tasks | Purpose |
|---|---|---|
dataset |
Document processing, embedding, indexing | Knowledge base operations |
generation |
LLM inference, streaming responses | Model interactions |
mail |
Email delivery (password reset, invitations) | Notifications |
ops_trace |
Operational tracing, analytics | Observability |
Scheduled Tasks
Celery Beat runs scheduled tasks defined in api/schedule/ (13 files). The beat polling interval is CELERY_BEAT_SCHEDULER_TIME (default: 1 second).
Worker Configuration
# Single worker, all queues
celery -A app.celery worker -Q dataset,generation,mail,ops_trace --loglevel INFO
# Dedicated workers per queue
celery -A app.celery worker -Q dataset --concurrency 4 --loglevel INFO
celery -A app.celery worker -Q generation --concurrency 2 --loglevel INFO
Broker Configuration
Redis is the default and only supported broker:
CELERY_BROKER_URL=redis://:password@redis:6379/1
The broker uses Redis DB 1 (separate from the main cache on DB 0).
Observability and Telemetry
Enterprise Telemetry (OTEL)
Dify Enterprise supports OpenTelemetry integration with a "slim span + rich companion log" architecture:
| Signal | Purpose | Completeness |
|---|---|---|
| Traces (Spans) | Structure, identity, timing of operations | Per-workflow, per-node |
| Structured Logs | Deep context (inputs, outputs, metadata) | Correlated via trace_id + span_id |
| Metrics | Counters and histograms | 100% accurate (no sampling) |
Configuration
| Variable | Default | Description |
|---|---|---|
ENTERPRISE_ENABLED |
false |
Master switch for enterprise features |
ENTERPRISE_TELEMETRY_ENABLED |
false |
Master switch for telemetry |
ENTERPRISE_OTLP_ENDPOINT |
-- | OTLP collector endpoint |
ENTERPRISE_OTLP_HEADERS |
-- | Custom OTLP headers |
ENTERPRISE_OTLP_PROTOCOL |
http |
Transport protocol (http or grpc) |
ENTERPRISE_OTLP_API_KEY |
-- | Bearer token for auth |
ENTERPRISE_INCLUDE_CONTENT |
false |
Include sensitive I/O in logs |
ENTERPRISE_SERVICE_NAME |
dify |
Service name in OTEL |
ENTERPRISE_OTEL_SAMPLING_RATE |
1.0 |
Trace sampling rate (0.0-1.0) |
Trace ID Correlation
- Workflow runs:
trace_id = UUID(workflow_run_id) - Node executions:
span_id = hash(node_execution_id), child of workflow span - Sub-workflows: Shares parent's
trace_id, linked viaparent_span_id - Draft executions: Own
trace_id = UUID(node_execution_id)(not part of any workflow trace)
Content Gating
When ENTERPRISE_INCLUDE_CONTENT=false, sensitive content is replaced with reference strings:
ref:workflow_run_id=550e8400-e29b-41d4-a716-446655440000
ref:node_execution_id=660e8400-e29b-41d4-a716-446655440001
Sentry Integration
Set SENTRY_DSN for backend error tracking and NEXT_PUBLIC_SENTRY_DSN for frontend.
Amplitude Analytics
Set NEXT_PUBLIC_AMPLITUDE_API_KEY for frontend user analytics.
Code Patterns and Conventions
Backend Patterns
| Pattern | Usage |
|---|---|
| Pydantic v2 validation | Request/response models use Pydantic v2 for validation and serialization |
| SQLAlchemy ORM | All database access through ORM models, never raw SQL |
| Flask-RESTful | API routes use Flask-RESTful resource classes |
| Celery tasks | Long-running operations dispatched to background queues |
| Entry points | Plugin discovery via importlib.metadata.entry_points |
| gevent | Cooperative multitasking for concurrent request handling |
| Click | All CLI commands use the Click framework |
| Config-as-code | Pydantic settings classes with environment variable loading |
Frontend Patterns
| Pattern | Usage |
|---|---|
| App Router | Next.js 15 App Router with route groups (commonLayout) |
| Zustand | Client-side state management (UI state) |
| TanStack Query | Server state management (API data) |
| Jotai | Atomic state for fine-grained reactivity |
| ReactFlow | Workflow canvas with custom node components |
| i18next | Internationalization with server/client split |
| Tailwind CSS 4 | Utility-first styling with @langgenius/dify-ui preset |
| Zod | Runtime schema validation |
API Response Format
API responses follow a consistent structure:
{
"result": "success",
"data": { ... }
}
Error responses:
{
"code": "error_code",
"message": "Human-readable description",
"status": 400
}
Testing
# Backend unit tests
cd api
uv run pytest tests/unit_tests/
# Backend integration tests
uv run pytest tests/integration_tests/
# Specific test
uv run pytest tests/unit_tests/core/workflow/graph_engine/layers/test_llm_quota.py
# Frontend tests
cd web
pnpm test
pnpm test:coverage
SDKs and Client Libraries
Node.js SDK (dify-client v3.1.0)
| Class | Purpose |
|---|---|
DifyClient |
Base client with HTTP methods |
ChatClient |
Chat/Agent-Chat interactions |
CompletionClient |
Completion requests |
WorkflowClient |
Workflow execution |
KnowledgeBaseClient |
Dataset management |
npm install dify-client
Requires Node.js >= 18.0.0. ESM-only ("type": "module").
PHP SDK
Based on Guzzle HTTP client v7.9. Requires PHP >= 7.2.
composer require langgenius/dify-client
Python SDK
Maintained in a separate repository: github.com/langgenius/dify-sdk-python.
E2E Testing
Framework
End-to-end tests use Playwright + Cucumber (BDD):
cd e2e
pnpm e2e:install # Install Playwright + Chromium
pnpm e2e:middleware:up # Start test infrastructure
pnpm e2e # Run tests
pnpm e2e:full # Full test suite
pnpm e2e:full:headed # Full suite with browser UI
pnpm e2e:reset # Reset test state
pnpm e2e:middleware:down # Stop test infrastructure
Tests are in e2e/features/ with step definitions in e2e/features/step-definitions/.
Contributing
Development Setup
- Fork and clone the repository
- Set up the backend and frontend as described in Source Deployment
- Create a feature branch
Code Quality Tools
# Linting (root)
pnpm lint
pnpm lint:fix
# Type checking (frontend)
cd web && pnpm type-check
# Backend tests
cd api && uv run pytest
# Frontend tests
cd web && pnpm test
Commit and PR Guidelines
- PRs should target the
mainbranch - Include tests for new features
- Follow existing code patterns and naming conventions
- Update translations for UI-facing changes
Known Issues and Improvements
Open Issues (from GitHub)
Key bugs and feature requests from the issue tracker:
- Performance: Workflow execution with many parallel branches can hit thread pool limits (
MAX_SUBMIT_COUNT) - Vector Migration:
vdb-migraterequires downtime; no online migration path - Plugin Isolation: Plugin crashes can affect the daemon's ability to route to other plugins
- Large File Processing: Documents near the
UPLOAD_FILE_SIZE_LIMITcan cause worker memory spikes during extraction
Architecture Improvement Opportunities
- API server decomposition --
api/controllers/console/app/(25 files, 329 KB) handles too many concerns; could benefit from further route grouping - Service layer complexity --
api/services/(57 files, 1 MB) is the largest module; some services could be broken into domain-specific sub-modules - Workflow engine hardening -- The graph engine delegates to
graphonbut the Dify layer adds complexity with human-input resume and sub-workflow recursion - Frontend bundle size -- The bundled Monaco editor (
web/public/vs/, 377 KB compressed) loads on all pages; could be lazy-loaded - Test coverage -- Integration tests exist but are concentrated in
api/tests/test_containers_integration_tests/services(65 files, 1.7 MB); unit test coverage for controllers is lighter
CLI Reference
All CLI commands are invoked via Flask. In Docker: docker compose exec api flask <command>.
Account Commands (api/commands/account.py)
reset-password
Reset an account's password.
flask reset-password \
--email [email removed] \
--new-password "SecurePassword123" \
--password-confirm "SecurePassword123"
reset-email
Change an account's email address.
flask reset-email \
--email [email removed] \
--new-email [email removed] \
--email-confirm [email removed]
create-tenant
Create a new workspace and account.
flask create-tenant \
--email [email removed] \
--name "My Workspace" \
--language en-US
Plugin Commands (api/commands/plugin.py)
setup-system-tool-oauth-client
Configure OAuth for a tool plugin provider.
flask setup-system-tool-oauth-client \
--provider github \
--client-params '{"client_id":"...","client_secret":"..."}'
setup-system-trigger-oauth-client
Configure OAuth for a trigger plugin provider.
flask setup-system-trigger-oauth-client \
--provider github \
--client-params '{"client_id":"...","client_secret":"..."}'
setup-datasource-oauth-client
Configure OAuth for a datasource plugin provider.
flask setup-datasource-oauth-client \
--provider notion \
--client-params '{"client_id":"...","client_secret":"..."}'
transform-datasource-credentials
Transform datasource credentials for a specific environment.
flask transform-datasource-credentials --environment online
migrate-data-for-plugin
Migrate existing data for the plugin system.
flask migrate-data-for-plugin
extract-plugins
Extract plugin definitions from existing configuration.
flask extract-plugins --output_file plugins.jsonl --workers 10
extract-unique-identifiers
Extract unique plugin identifiers.
flask extract-unique-identifiers \
--input_file plugins.jsonl \
--output_file unique_identifiers.json
install-plugins
Batch install plugins from extracted definitions.
flask install-plugins \
--input_file plugins.jsonl \
--output_file installed_plugins.jsonl \
--workers 100
install-rag-pipeline-plugins
Install RAG pipeline plugins.
flask install-rag-pipeline-plugins \
--input_file plugins.jsonl \
--output_file installed_plugins.jsonl \
--workers 100
Retention Commands (api/commands/retention.py)
clear-free-plan-tenant-expired-logs
Clear expired logs for free-plan tenants.
flask clear-free-plan-tenant-expired-logs \
--days 30 \
--batch 100 \
--tenant_ids "uuid1,uuid2"
clean-workflow-runs
Delete expired workflow runs and related data.
flask clean-workflow-runs \
--before-days 30 \
--batch-size 200 \
--dry-run
# Date range mode
flask clean-workflow-runs \
--start-from "2026-01-01" \
--end-before "2026-04-01"
# Relative range mode
flask clean-workflow-runs \
--from-days-ago 90 \
--to-days-ago 30
archive-workflow-runs
Archive workflow runs to S3-compatible storage.
flask archive-workflow-runs \
--before-days 90 \
--workers 4 \
--limit 1000 \
--delete-after-archive \
--dry-run
# Grayscale rollout
flask archive-workflow-runs \
--tenant-ids "uuid1,uuid2" \
--before-days 90
restore-workflow-runs
Restore archived workflow runs.
flask restore-workflow-runs \
--tenant-ids "uuid1" \
--run-id "run-uuid" \
--workers 4 \
--limit 100
delete-archived-workflow-runs
Delete archived workflow runs from the database.
flask delete-archived-workflow-runs \
--tenant-ids "uuid1" \
--run-id "run-uuid" \
--dry-run
cleanup_orphaned_draft_variables
Remove orphaned draft variables.
flask cleanup_orphaned_draft_variables \
--dry-run \
--batch-size 1000 \
--max-apps 100 \
--force
clean-expired-messages
Clean expired messages.
flask clean-expired-messages \
--before-days 90 \
--from-days-ago 180 \
--batch-size 1000 \
--graceful-period 21 \
--dry-run
export-app-messages
Export messages for an app to JSONL.GZ.
flask export-app-messages \
--app-id "app-uuid" \
--end-before "2026-06-01" \
--filename "export/messages" \
--batch-size 1000 \
--use-cloud-storage \
--dry-run
Storage Commands (api/commands/storage.py)
clear-orphaned-file-records
Remove database records for files that no longer exist in storage.
flask clear-orphaned-file-records --force
remove-orphaned-files-on-storage
Delete storage files that have no corresponding database records.
flask remove-orphaned-files-on-storage --force
file-usage
Query where files are referenced in the system.
flask file-usage --file-id "uuid"
flask file-usage --key "upload_files/path"
flask file-usage --src "documents.%" --limit 50 --json
migrate-oss
Migrate files from local/OpenDAL to cloud storage.
flask migrate-oss \
--source opendal \
--path upload_files \
--path image_files \
--overwrite \
--update-db \
--dry-run
Default paths: privkeys, upload_files, image_files, tools, website_files, keyword_files, ops_trace.
System Commands (api/commands/system.py)
reset-encrypt-key-pair
Reset workspace asymmetric key pair for credential encryption.
flask reset-encrypt-key-pair
Caution
This invalidates ALL stored LLM credentials across ALL workspaces. Every model provider must be re-configured after running this command. Only use when the key pair is compromised. Only works in
SELF_HOSTEDmode.
convert-to-agent-apps
Convert legacy Agent Assistant apps to the new Agent App type.
flask convert-to-agent-apps
upgrade-db
Run pending database migrations.
flask upgrade-db
fix-app-site-missing
Repair apps that are missing their associated site records.
flask fix-app-site-missing
Vector Commands (api/commands/vector.py)
vdb-migrate
Migrate vectors between backends.
flask vdb-migrate --scope all
The --scope option accepts all (default) to migrate all datasets.
add-qdrant-index
Add an index to Qdrant collections.
flask add-qdrant-index --field metadata.doc_id
old-metadata-migration
Migrate legacy vector metadata format.
flask old-metadata-migration
Quick Reference
Docker Commands
# Start all services
docker compose up -d
# Stop all services
docker compose down
# View logs
docker compose logs -f api
docker compose logs --tail 100 worker
# Restart a service
docker compose restart api
# Shell into container
docker compose exec api bash
docker compose exec db psql -U postgres dify
# Check health
curl http://localhost/health
curl http://localhost/ping
Common Operations Cheat Sheet
| Task | Command |
|---|---|
| Run DB migrations | flask upgrade-db |
| Reset user password | flask reset-password --email <e> --new-password <pw> --password-confirm <pw> |
| Create workspace | flask create-tenant --email <e> --name <n> |
| Migrate vector DB | flask vdb-migrate --scope all |
| Clean old workflow data | flask clean-workflow-runs --before-days 30 |
| Export messages | flask export-app-messages --app-id <id> --end-before <date> |
| Migrate storage | flask migrate-oss --source opendal --update-db |
| Reset encryption keys | flask reset-encrypt-key-pair |
| Clean orphaned files | flask clear-orphaned-file-records -f |
| Env sync after upgrade | python docker/dify-env-sync.py |
API Surface Summary
| Surface | Base Path | Auth | Audience |
|---|---|---|---|
| Console API | /console/api/* |
JWT | Web UI |
| Service API | /v1/* |
API Key | Integrations |
| Web API | /api/* |
App token | End users |
| Inner API | /inner/api/* |
Shared secret | Plugin daemon |
System Endpoints
| Endpoint | Purpose |
|---|---|
GET /health |
Full health check |
GET /ping |
Liveness probe |
GET /version |
API version |
GET /init |
Setup status |
GET /db-pool-stat |
DB pool stats |
GET /threads |
Thread count |
Port Reference
| Port | Service | Protocol |
|---|---|---|
| 80 | nginx | HTTP |
| 443 | nginx | HTTPS |
| 3000 | web (Next.js) | HTTP |
| 5001 | api (Flask) | HTTP + WebSocket |
| 5002 | plugin_daemon | HTTP |
| 5003 | plugin remote install | HTTP |
| 5432 | PostgreSQL | TCP |
| 6379 | Redis | TCP |
| 8080 | Weaviate HTTP | HTTP |
| 50051 | Weaviate gRPC | gRPC |
| 8194 | sandbox | HTTP |
| 3128 | ssrf_proxy | HTTP |