TryGhost/Ghost · warning
A view with this name already exists
Error message
A view with this name already exists
What it means
Thrown by buildViewsForSave (member-views.ts) when saving a member view whose name — case-insensitively trimmed and compared — collides with another saved view on the same 'members' route. There are two throw sites: line 91 (renaming an existing view to a name another view already has, with the target index excluded) and line 100 (creating a new view whose name duplicates an existing one). The check is normalizeSharedViewName (trim + lower-case).
Source
Thrown at apps/admin/src/members/member-views.ts:91
}
export function buildViewsForSave(allViews: SharedView[], name: string, filter: string, originalView?: MemberView): SharedView[] {
const nextView = buildMemberView(name, filter);
if (originalView) {
const matchingIndexes = findMatchingSharedViewIndexes(allViews, originalView);
if (matchingIndexes.length === 0) {
throw new Error(VIEW_UPDATE_NOT_FOUND_ERROR);
}
if (matchingIndexes.length > 1) {
throw new Error(VIEW_UPDATE_AMBIGUOUS_ERROR);
}
const targetIndex = matchingIndexes[0];
if (hasSharedViewNameConflict(allViews, nextView, targetIndex)) {
throw new Error(VIEW_EXISTS_ERROR);
}
return allViews.map((view, index) => {
return index === targetIndex ? nextView : view;
});
}
if (hasSharedViewNameConflict(allViews, nextView)) {
throw new Error(VIEW_EXISTS_ERROR);
}
return [...allViews, nextView];
}
export function buildViewsForDelete(allViews: SharedView[], view: MemberView): SharedView[] {
const matchingIndexes = findMatchingSharedViewIndexes(allViews, view);
if (matchingIndexes.length === 0) {View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Pre-check for conflict before calling buildViewsForSave using hasSharedViewNameConflict, and prompt the user to pick a distinct name.
- If the duplicate is unintended, audit the stored shared_views setting for near-duplicate names (same after trim + lower-case) and deduplicate.
- For concurrent-edit races, re-read the latest shared_views from the API immediately before saving and retry.
- Strip leading/trailing whitespace and normalize case in the UI before submitting so the user sees the canonical form.
Example fix
// before
const next = buildViewsForSave(allViews, name, filter, originalView); // throws on duplicate name
// after — detect the conflict first and surface a user-facing message
import {hasSharedViewNameConflict} from './shared-views';
const nextView = {name: name.trim(), route: 'members' as const, filter: {filter}};
if (hasSharedViewNameConflict(allViews, nextView, originalViewIndex)) {
setFieldError('name', 'A view with this name already exists');
return;
}
const next = buildViewsForSave(allViews, name, filter, originalView); Defensive patterns
Strategy: validation
Validate before calling
// Pre-check for a name conflict before saving
import {hasSharedViewNameConflict} from './shared-views';
import type {SharedView} from './shared-views';
function wouldConflict(allViews: SharedView[], name: string, excludeIndex?: number): boolean {
return hasSharedViewNameConflict(allViews, {name, route: 'members'}, excludeIndex);
} Type guard
// (No custom error class; detect by message.)
function isViewExistsError(e: unknown): boolean {
return e instanceof Error && e.message === 'A view with this name already exists';
} Try / catch
try {
const next = buildViewsForSave(allViews, name, filter, originalView);
} catch (e) {
if (e instanceof Error && e.message === 'A view with this name already exists') {
setFieldError('name', e.message);
return;
}
throw e;
} Prevention
- Trim and case-normalise the view name in the UI before submit so the user sees the canonical form.
- Re-read the latest shared_views from the API immediately before saving to avoid concurrent-edit collisions.
- Pre-check with hasSharedViewNameConflict to give a inline field error before the save attempt.
- Audit migrated/imported shared_views for near-duplicate names.
When it happens
Trigger: On create: hasSharedViewNameConflict(allViews, nextView) returns true — another view on route 'members' has the same trimmed/lowercased name. On update: hasSharedViewNameConflict(allViews, nextView, targetIndex) returns true — a view OTHER than the one being updated shares the normalized name. Examples: saving 'My View' when 'my view' exists; editing a view and renaming it to 'Drafts' while another 'drafts' view exists.
Common situations: User types a name that differs only by case or whitespace from an existing saved view; shared_views setting was imported/migrated containing near-duplicate names; two browser tabs editing views concurrently so the second save collides; copy-paste of a view name.
Related errors
- Cannot disconnect Stripe whilst you have active subscription
- Theme is not compatible or contains errors.
- A hosting plan limit was reached or exceeded.
- Preview response was incomplete
- Preview response was incomplete
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/0f359641054a0298.
Report an issue: GitHub.