diff --git a/api/controllers/openapi/__init__.py b/api/controllers/openapi/__init__.py
index 2f23baa11a9..c11019cf627 100644
--- a/api/controllers/openapi/__init__.py
+++ b/api/controllers/openapi/__init__.py
@@ -62,7 +62,6 @@ from controllers.openapi._models import (
SessionListQuery,
SessionListResponse,
SessionRow,
- TagItem,
TaskStopResponse,
UsageInfo,
WorkflowRunData,
@@ -96,7 +95,6 @@ register_response_schema_models(
openapi_ns,
ErrorBody,
EventStreamResponse,
- TagItem,
UsageInfo,
MessageMetadata,
AppListRow,
diff --git a/api/controllers/openapi/_models.py b/api/controllers/openapi/_models.py
index b1a432abe3c..e846db3ea75 100644
--- a/api/controllers/openapi/_models.py
+++ b/api/controllers/openapi/_models.py
@@ -38,18 +38,12 @@ class PaginationEnvelope[T](BaseModel):
return cls(page=page, limit=limit, total=total, has_more=page * limit < total, data=items)
-class TagItem(BaseModel):
- name: str
-
-
class AppListRow(BaseModel):
id: str
name: str
description: str | None = None
mode: AppMode
- tags: list[TagItem] = []
updated_at: str | None = None
- created_by_name: str | None = None
workspace_id: str | None = None
workspace_name: str | None = None
@@ -292,7 +286,6 @@ class AppListQuery(BaseModel):
limit: int = Field(20, ge=1, le=MAX_PAGE_LIMIT)
mode: AppMode | None = None
name: str | None = Field(None, max_length=200)
- tag: str | None = Field(None, max_length=100)
class AppRunRequest(BaseModel):
diff --git a/api/controllers/openapi/apps.py b/api/controllers/openapi/apps.py
index c1b9b5eed06..eadc359acce 100644
--- a/api/controllers/openapi/apps.py
+++ b/api/controllers/openapi/apps.py
@@ -20,7 +20,6 @@ from controllers.openapi._models import (
AppListQuery,
AppListResponse,
AppListRow,
- TagItem,
)
from controllers.openapi.auth.composition import auth_router
from controllers.openapi.auth.data import AuthData
@@ -32,7 +31,6 @@ from models import App
from models.model import AppMode
from services.account_service import TenantService
from services.app_service import AppListParams, AppService
-from services.tag_service import TagService
_ALLOWED_DESCRIBE_FIELDS: frozenset[str] = frozenset({"info", "parameters", "input_schema"})
@@ -164,28 +162,18 @@ class AppListApi(Resource):
name=app.name,
description=app.description,
mode=app.mode,
- tags=[TagItem(name=t.name) for t in app.tags],
updated_at=app.updated_at.isoformat() if app.updated_at else None,
- created_by_name=getattr(app, "author_name", None),
workspace_id=str(workspace_id),
workspace_name=tenant_name,
)
env = AppListResponse(page=1, limit=1, total=1, has_more=False, data=[item])
return env
- tag_ids: list[str] | None = None
- if query.tag:
- tags = TagService.get_tag_by_tag_name("app", workspace_id, query.tag, db.session)
- if not tags:
- return empty
- tag_ids = [tag.id for tag in tags]
-
params = AppListParams(
page=query.page,
limit=query.limit,
mode=query.mode.value if query.mode else "all", # type:ignore
name=query.name,
- tag_ids=tag_ids,
status="normal",
# Visibility gate pushed into the query — pagination.total stays
# consistent across pages because invisible rows never count.
@@ -206,9 +194,7 @@ class AppListApi(Resource):
name=r.name,
description=r.description,
mode=r.mode,
- tags=[TagItem(name=t.name) for t in r.tags],
updated_at=r.updated_at.isoformat() if r.updated_at else None,
- created_by_name=getattr(r, "author_name", None),
workspace_id=str(workspace_id),
workspace_name=tenant_name,
)
diff --git a/api/controllers/openapi/apps_permitted_external.py b/api/controllers/openapi/apps_permitted_external.py
index 949aa6a38d4..9bc400e5cc7 100644
--- a/api/controllers/openapi/apps_permitted_external.py
+++ b/api/controllers/openapi/apps_permitted_external.py
@@ -71,9 +71,7 @@ class PermittedExternalAppsListApi(Resource):
name=app.name,
description=app.description,
mode=app.mode,
- tags=[], # tenant-scoped; not surfaced cross-tenant
updated_at=app.updated_at.isoformat() if app.updated_at else None,
- created_by_name=None, # cross-tenant author leak prevention
workspace_id=str(app.tenant_id),
workspace_name=tenant.name if tenant else None,
)
diff --git a/api/openapi/markdown/openapi-openapi.md b/api/openapi/markdown/openapi-openapi.md
index 6841be77ff1..bd93557edcf 100644
--- a/api/openapi/markdown/openapi-openapi.md
+++ b/api/openapi/markdown/openapi-openapi.md
@@ -83,7 +83,6 @@ User-scoped operations
| mode | query | | No | string,
**Available values:** "advanced-chat", "agent", "agent-chat", "channel", "chat", "completion", "rag-pipeline", "workflow" |
| name | query | | No | string |
| page | query | | No | integer,
**Default:** 1 |
-| tag | query | | No | string |
| workspace_id | query | | Yes | string |
#### Responses
@@ -601,7 +600,6 @@ mode is a closed enum.
| mode | [AppMode](#appmode) | | No |
| name | string | | No |
| page | integer,
**Default:** 1 | | No |
-| tag | string | | No |
| workspace_id | string | | Yes |
#### AppListResponse
@@ -618,12 +616,10 @@ mode is a closed enum.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
-| created_by_name | string | | No |
| description | string | | No |
| id | string | | Yes |
| mode | [AppMode](#appmode) | | Yes |
| name | string | | Yes |
-| tags | [ [TagItem](#tagitem) ],
**Default:** | | No |
| updated_at | string | | No |
| workspace_id | string | | No |
| workspace_name | string | | No |
@@ -994,12 +990,6 @@ Pagination for GET /account/sessions. Strict (extra='forbid').
| last_used_at | string | | No |
| prefix | string | | Yes |
-#### TagItem
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| name | string | | Yes |
-
#### TaskStopResponse
200 body for POST /apps//tasks//stop. The handler always returns
diff --git a/api/tests/unit_tests/controllers/openapi/test_app_list_query.py b/api/tests/unit_tests/controllers/openapi/test_app_list_query.py
index 9d207b1930a..e0b15585323 100644
--- a/api/tests/unit_tests/controllers/openapi/test_app_list_query.py
+++ b/api/tests/unit_tests/controllers/openapi/test_app_list_query.py
@@ -5,7 +5,7 @@ Runs against the model directly, not the HTTP layer. Pins:
- workspace_id is required.
- numeric bounds enforced (page >= 1, limit in [1, MAX_PAGE_LIMIT]).
- mode validates against the AppMode enum.
-- name and tag have length caps.
+- name has a length cap.
"""
from __future__ import annotations
@@ -24,7 +24,6 @@ def test_defaults():
assert q.limit == 20
assert q.mode is None
assert q.name is None
- assert q.tag is None
def test_workspace_id_required():
@@ -80,12 +79,6 @@ def test_name_length_capped():
AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "name": "x" * 201})
-def test_tag_length_capped():
- AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "tag": "x" * 100})
- with pytest.raises(ValidationError):
- AppListQuery.model_validate({"workspace_id": "00000000-0000-0000-0000-000000000001", "tag": "x" * 101})
-
-
def test_all_fields_accept_valid_values():
"""Pin the happy-path acceptance for every field in one place."""
q = AppListQuery.model_validate(
@@ -95,7 +88,6 @@ def test_all_fields_accept_valid_values():
"limit": 50,
"mode": "workflow",
"name": "search",
- "tag": "prod",
}
)
assert q.workspace_id == "00000000-0000-0000-0000-000000000001"
@@ -104,4 +96,3 @@ def test_all_fields_accept_valid_values():
assert q.mode is not None
assert q.mode.value == "workflow"
assert q.name == "search"
- assert q.tag == "prod"
diff --git a/cli/src/api/apps.test.ts b/cli/src/api/apps.test.ts
index 68e7bcc86a3..861f60feb26 100644
--- a/cli/src/api/apps.test.ts
+++ b/cli/src/api/apps.test.ts
@@ -36,7 +36,6 @@ describe('AppsClient.list', () => {
// Optional filters are omitted entirely when not supplied.
expect(q.has('mode')).toBe(false)
expect(q.has('name')).toBe(false)
- expect(q.has('tag')).toBe(false)
})
it('forwards explicit pagination and filters', async () => {
@@ -48,7 +47,6 @@ describe('AppsClient.list', () => {
limit: 50,
mode: 'chat',
name: 'support bot',
- tag: 'prod',
})
const q = queryOf(stub.captured.url)
@@ -56,18 +54,16 @@ describe('AppsClient.list', () => {
expect(q.get('limit')).toBe('50')
expect(q.get('mode')).toBe('chat')
expect(q.get('name')).toBe('support bot')
- expect(q.get('tag')).toBe('prod')
})
it('treats empty-string filters as absent (not blank query params)', async () => {
stub = await startStubServer(cap => jsonResponder(200, LIST_BODY, cap))
- await makeClient(stub.url).list({ workspaceId: 'ws-1', mode: '', name: '', tag: '' })
+ await makeClient(stub.url).list({ workspaceId: 'ws-1', mode: '', name: '' })
const q = queryOf(stub.captured.url)
expect(q.has('mode')).toBe(false)
expect(q.has('name')).toBe(false)
- expect(q.has('tag')).toBe(false)
})
it('propagates server 403 as a classified BaseError', async () => {
diff --git a/cli/src/api/apps.ts b/cli/src/api/apps.ts
index bf672f1f45d..01b18d9a9da 100644
--- a/cli/src/api/apps.ts
+++ b/cli/src/api/apps.ts
@@ -10,7 +10,6 @@ export type ListQuery = {
readonly limit?: number
readonly mode?: AppMode | ''
readonly name?: string
- readonly tag?: string
}
// An absent or empty mode filter means "any mode" — collapse both to undefined for the query.
@@ -33,7 +32,6 @@ export class AppsClient implements AppReader {
limit: q.limit ?? 20,
mode: normalizeMode(q.mode),
name: q.name !== undefined && q.name !== '' ? q.name : undefined,
- tag: q.tag !== undefined && q.tag !== '' ? q.tag : undefined,
},
})
}
diff --git a/cli/src/commands/get/app/handlers.ts b/cli/src/commands/get/app/handlers.ts
index ac7008fa537..9c91057d26d 100644
--- a/cli/src/commands/get/app/handlers.ts
+++ b/cli/src/commands/get/app/handlers.ts
@@ -1,4 +1,4 @@
-import type { AppListResponse, AppListRow, TagItem } from '@dify/contracts/api/openapi/types.gen'
+import type { AppListResponse, AppListRow } from '@dify/contracts/api/openapi/types.gen'
import type { TableCell, TableColumn } from '@/framework/output'
export const APP_MODE_KEY = 'app'
@@ -7,9 +7,7 @@ export const APP_COLUMNS: readonly TableColumn[] = [
{ name: 'NAME', priority: 0 },
{ name: 'ID', priority: 0 },
{ name: 'MODE', priority: 0 },
- { name: 'TAGS', priority: 0 },
{ name: 'UPDATED', priority: 0 },
- { name: 'AUTHOR', priority: 1 },
{ name: 'WORKSPACE', priority: 1 },
]
@@ -25,9 +23,7 @@ export class AppRow {
this.data.name,
this.data.id,
this.data.mode,
- joinTags(this.data.tags ?? []),
this.data.updated_at ?? '',
- this.data.created_by_name ?? '',
this.data.workspace_name ?? '',
]
}
@@ -70,7 +66,3 @@ export class AppListOutput {
return this.envelope
}
}
-
-function joinTags(tags: readonly TagItem[]): string {
- return tags.map(t => t.name).join(',')
-}
diff --git a/cli/src/commands/get/app/index.ts b/cli/src/commands/get/app/index.ts
index 47594813704..ffce31b7c49 100644
--- a/cli/src/commands/get/app/index.ts
+++ b/cli/src/commands/get/app/index.ts
@@ -42,7 +42,6 @@ export default class GetApp extends DifyCommand {
'limit': Flags.string({ description: 'page size [1..200]' }),
'mode': Flags.string({ description: 'filter by app mode', options: APP_MODE_VALUES }),
'name': Flags.string({ description: 'filter by app name (server-side substring)' }),
- 'tag': Flags.string({ description: 'filter by tag name (server-side exact match)' }),
'http-retry': httpRetryFlag,
'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.WIDE], default: '' }),
}
@@ -59,7 +58,6 @@ export default class GetApp extends DifyCommand {
limitRaw: flags.limit,
mode: flags.mode as AppMode | undefined,
name: flags.name,
- tag: flags.tag,
format,
}, { active: ctx.active, http: ctx.http, io: ctx.io })
return table({
diff --git a/cli/src/commands/get/app/run.test.ts b/cli/src/commands/get/app/run.test.ts
index 094fdc69c93..99a58cdbd71 100644
--- a/cli/src/commands/get/app/run.test.ts
+++ b/cli/src/commands/get/app/run.test.ts
@@ -42,13 +42,12 @@ describe('runGetApp', () => {
}))
}
- it('list (no id, default format) renders table with NAME ID MODE TAGS UPDATED', async () => {
+ it('list (no id, default format) renders table with NAME ID MODE UPDATED', async () => {
const out = await render()
- expect(out).toMatch(/^NAME\s+ID\s+MODE\s+TAGS\s+UPDATED/)
+ expect(out).toMatch(/^NAME\s+ID\s+MODE\s+UPDATED/)
expect(out).toContain('Greeter')
expect(out).toContain('app-1')
expect(out).toContain('chat')
- expect(out).toContain('demo')
expect(out).toContain('Workflow')
expect(out).not.toContain('app-3')
})
@@ -58,9 +57,7 @@ describe('runGetApp', () => {
'NAME',
'ID',
'MODE',
- 'TAGS',
'UPDATED',
- 'AUTHOR',
'WORKSPACE',
])
})
@@ -78,12 +75,6 @@ describe('runGetApp', () => {
expect(out).not.toContain('Greeter')
})
- it('--tag filters server-side', async () => {
- const out = await render({ tag: 'demo' })
- expect(out).toContain('Greeter')
- expect(out).not.toContain('Workflow')
- })
-
it('-A all-workspaces aggregates across workspaces sorted by id', async () => {
const out = await render({ allWorkspaces: true })
expect(out).toContain('app-1')
@@ -112,10 +103,9 @@ describe('runGetApp', () => {
expect(out.trim().split('\n').sort()).toEqual(['app-1', 'app-2'])
})
- it('-o wide includes AUTHOR and WORKSPACE columns', async () => {
+ it('-o wide includes the WORKSPACE column', async () => {
const out = await render({ format: 'wide' })
- expect(out).toMatch(/^NAME\s+ID\s+MODE\s+TAGS\s+UPDATED\s+AUTHOR\s+WORKSPACE/)
- expect(out).toContain('tester')
+ expect(out).toMatch(/^NAME\s+ID\s+MODE\s+UPDATED\s+WORKSPACE/)
expect(out).toContain('Default')
})
diff --git a/cli/src/commands/get/app/run.ts b/cli/src/commands/get/app/run.ts
index 308b256bc84..c4a7911e0db 100644
--- a/cli/src/commands/get/app/run.ts
+++ b/cli/src/commands/get/app/run.ts
@@ -22,7 +22,6 @@ export type GetAppOptions = {
readonly limitRaw?: string
readonly mode?: AppMode
readonly name?: string
- readonly tag?: string
readonly format?: string
}
@@ -76,7 +75,6 @@ export async function runGetApp(opts: GetAppOptions, deps: GetAppDeps): Promise<
limit: pageSize,
mode: opts.mode,
name: opts.name,
- tag: opts.tag,
})
},
)
@@ -109,9 +107,7 @@ function describeToEnvelope(desc: AppDescribeResponse, wsId: string, wsName: str
name: desc.info.name,
description: desc.info.description,
mode: desc.info.mode as AppMode,
- tags: [],
updated_at: desc.info.updated_at,
- created_by_name: undefined,
workspace_id: wsId,
workspace_name: wsName === '' ? undefined : wsName,
}],
@@ -146,7 +142,6 @@ async function runAllWorkspaces(
limit,
mode: opts.mode,
name: opts.name,
- tag: opts.tag,
})
merged.total += env.total
merged.data = [...merged.data, ...env.data]
diff --git a/packages/contracts/generated/api/openapi/types.gen.ts b/packages/contracts/generated/api/openapi/types.gen.ts
index b3262944a90..52307ca545c 100644
--- a/packages/contracts/generated/api/openapi/types.gen.ts
+++ b/packages/contracts/generated/api/openapi/types.gen.ts
@@ -76,7 +76,6 @@ export type AppListQuery = {
mode?: AppMode | null
name?: string | null
page?: number
- tag?: string | null
workspace_id: string
}
@@ -89,12 +88,10 @@ export type AppListResponse = {
}
export type AppListRow = {
- created_by_name?: string | null
description?: string | null
id: string
mode: AppMode
name: string
- tags?: Array
updated_at?: string | null
workspace_id?: string | null
workspace_name?: string | null
@@ -406,10 +403,6 @@ export type SessionRow = {
prefix: string
}
-export type TagItem = {
- name: string
-}
-
export type TaskStopResponse = {
result: 'success'
}
@@ -605,7 +598,6 @@ export type GetAppsData = {
| 'workflow'
name?: string
page?: number
- tag?: string
workspace_id: string
}
url: '/apps'
diff --git a/packages/contracts/generated/api/openapi/zod.gen.ts b/packages/contracts/generated/api/openapi/zod.gen.ts
index b1f6b1554f1..da0a2fc04e6 100644
--- a/packages/contracts/generated/api/openapi/zod.gen.ts
+++ b/packages/contracts/generated/api/openapi/zod.gen.ts
@@ -114,10 +114,33 @@ export const zAppListQuery = z.object({
mode: zAppMode.nullish(),
name: z.string().max(200).nullish(),
page: z.int().gte(1).optional().default(1),
- tag: z.string().max(100).nullish(),
workspace_id: z.string(),
})
+/**
+ * AppListRow
+ */
+export const zAppListRow = z.object({
+ description: z.string().nullish(),
+ id: z.string(),
+ mode: zAppMode,
+ name: z.string(),
+ updated_at: z.string().nullish(),
+ workspace_id: z.string().nullish(),
+ workspace_name: z.string().nullish(),
+})
+
+/**
+ * AppListResponse
+ */
+export const zAppListResponse = z.object({
+ data: z.array(zAppListRow),
+ has_more: z.boolean(),
+ limit: z.int(),
+ page: z.int(),
+ total: z.int(),
+})
+
/**
* AppRunRequest
*/
@@ -439,6 +462,17 @@ export const zPermittedExternalAppsListQuery = z.object({
page: z.int().gte(1).optional().default(1),
})
+/**
+ * PermittedExternalAppsListResponse
+ */
+export const zPermittedExternalAppsListResponse = z.object({
+ data: z.array(zAppListRow),
+ has_more: z.boolean(),
+ limit: z.int(),
+ page: z.int(),
+ total: z.int(),
+})
+
/**
* RevokeResponse
*/
@@ -490,50 +524,6 @@ export const zSessionListResponse = z.object({
total: z.int(),
})
-/**
- * TagItem
- */
-export const zTagItem = z.object({
- name: z.string(),
-})
-
-/**
- * AppListRow
- */
-export const zAppListRow = z.object({
- created_by_name: z.string().nullish(),
- description: z.string().nullish(),
- id: z.string(),
- mode: zAppMode,
- name: z.string(),
- tags: z.array(zTagItem).optional().default([]),
- updated_at: z.string().nullish(),
- workspace_id: z.string().nullish(),
- workspace_name: z.string().nullish(),
-})
-
-/**
- * AppListResponse
- */
-export const zAppListResponse = z.object({
- data: z.array(zAppListRow),
- has_more: z.boolean(),
- limit: z.int(),
- page: z.int(),
- total: z.int(),
-})
-
-/**
- * PermittedExternalAppsListResponse
- */
-export const zPermittedExternalAppsListResponse = z.object({
- data: z.array(zAppListRow),
- has_more: z.boolean(),
- limit: z.int(),
- page: z.int(),
- total: z.int(),
-})
-
/**
* TaskStopResponse
*
@@ -720,7 +710,6 @@ export const zGetAppsQuery = z.object({
.optional(),
name: z.string().max(200).optional(),
page: z.int().gte(1).optional().default(1),
- tag: z.string().max(100).optional(),
workspace_id: z.string(),
})