Plugin Database Integrity#
Plugin database integrity covers a class of state inconsistencies in Dify's two-database architecture — the main dify PostgreSQL database (managed by Flask-Migrate/Alembic) and the plugin-daemon's separate dify_plugin database (managed by the Go-based plugin daemon). Upgrade sequences between Dify API and the plugin daemon versions are the most common trigger.
Four distinct failure patterns are documented in the wild:
| Failure | Symptom | Trigger |
|---|---|---|
| Version reconciliation | record not found / Plugin table missing in daemon logs | Old plugin version directories remain on disk after upgrade |
model_type enum mismatch | "Credentials unavailable"; models visible but uneditable | 1.14.x changed enum values without migrating existing rows |
Duplicate plugin_unique_identifier | SQLSTATE 23505 — daemon crashes in a restart loop | Duplicate rows block unique index creation on upgrade |
| Daemon schema init sequence | Tables not created in dify_plugin on fresh install | Daemon version skipped a required baseline migration |
Pattern 1 — Version Reconciliation (1.15.0+)#
After upgrading to 1.15.0, the plugin daemon scans PLUGIN_INSTALLED_PATH on startup and finds stale directories from pre-upgrade plugin versions. Because the upgrade created new database records with new version identifiers, the old directories no longer have matching rows.
Symptom (INFO-level, not errors):
record not found — SELECT * FROM "plugins" WHERE plugin_unique_identifier = 'langgenius/siliconflow:0.0.12@...'
Plugin table missing for existing PLUGIN_INSTALLED_PATH
Resolution:
- Confirm the API migration completed:
docker compose exec api flask upgrade-db - Run the backfill:
docker compose exec api flask backfill-plugin-auto-upgrade - Verify the plugin daemon is version
0.6.3-local(shipped with 1.15.0) - Remove stale version directories from
PLUGIN_INSTALLED_PATHonce plugins are confirmed working in the UI
The backfill-plugin-auto-upgrade command (api/commands/plugin.py) backfills category-scoped auto-upgrade strategies and normalizes plugin lists — it must be run after the schema migration completes.
Pattern 2 — model_type Enum Mismatch (1.14.x)#
Dify 1.14.x renamed model_type column values (text-generation → llm, embeddings → text-embedding, reranking → rerank) without running a data migration on existing rows. Pre-upgrade records retain legacy values, breaking credential lookups in five tables. The fix is tracked in PR #34488 and closed in PR #36520.
Symptom: Credentials show as unavailable; HTTP 400 "Credential with id ... not found" in browser console; plugin daemon logs record not found for the plugin's plugin_unique_identifier. Models still execute inference (the call path is unaffected) but cannot be viewed, edited, or deleted in the UI.
Manual SQL fix — run against the dify database (back up tables first):
-- Repeat for each of: provider_models, provider_model_credentials,
-- tenant_default_models, provider_model_settings, load_balancing_model_configs
UPDATE <table> SET model_type = 'llm' WHERE model_type = 'text-generation';
UPDATE <table> SET model_type = 'text-embedding' WHERE model_type = 'embeddings';
UPDATE <table> SET model_type = 'rerank' WHERE model_type = 'reranking';
tenant_default_models edge case: The unique constraint on (tenant_id, model_type) may cause SQLSTATE 23505 during the UPDATE if both old and new values already exist. Delete the legacy-valued row first:
DELETE FROM tenant_default_models
WHERE tenant_id = '<id>' AND model_type IN ('text-generation','embeddings','reranking');
After the SQL fix, uninstall and reinstall affected plugins to ensure plugin state is consistent.
Pattern 3 — Duplicate plugin_unique_identifier (1.13.x → 1.14.x)#
When upgrading from 1.13.x to 1.14.x, the plugin daemon's Go migration attempts to create a unique index on plugins.plugin_unique_identifier. If the table contains duplicate values, PostgreSQL rejects the operation with SQLSTATE 23505 and the daemon restarts in a loop.
Diagnosis:
SELECT plugin_unique_identifier, COUNT(*) AS cnt
FROM plugins
GROUP BY plugin_unique_identifier
HAVING COUNT(*) > 1
ORDER BY cnt DESC, plugin_unique_identifier;
Remove the extra rows (keep the most recent), then restart the daemon. The migration will succeed once no duplicates exist.
Note: The
pluginstable is in thedify_plugindatabase, not the maindifydatabase. Plugin daemon issues should be reported tolanggenius/dify-plugins.
Pattern 4 — Plugin Daemon Schema Init Sequence#
Installing directly with plugin-daemon:0.5.6-local fails to create tables in dify_plugin. The workaround is to install with 0.5.3-local first (which correctly initializes the schema), then upgrade to 0.5.6-local. This indicates migration path dependencies in the Go-managed schema.
Key Files and References#
| Resource | Role |
|---|---|
api/commands/plugin.py | backfill-plugin-auto-upgrade CLI command |
api/commands/system.py | flask upgrade-db — runs Alembic migrations |
| Issue #38268 / comment | Version reconciliation after 1.15.0 upgrade |
| Issue #36129 | model_type enum mismatch; full SQL fix |
| Issue #38276 | Duplicate plugin_unique_identifier; diagnostic query |
| Discussion #37917 | Plugin daemon table init sequence dependency |
| PR #34488 / PR #36520 | Application-level fix for model_type enum |