TryGhost/Ghost · error · Error

Failed to update member

Error message

Failed to update member

What it means

Thrown in apps/portal/src/actions.js updateMemberData when a name change is requested but api.member.update({name}) resolves falsy. The action catches it and returns {success:false, error:err} so the UI shows a profile-update failure rather than crashing.

Source

Thrown at apps/portal/src/actions.js:670

        } catch (err) {
            return {
                success: false,
                error: err
            };
        }
    }
    return null;
}

async function updateMemberData({data, state, api}) {
    const name = data?.name?.trim();
    const originalName = getMemberName({member: state.member});

    if (originalName !== name) {
        try {
            const member = await api.member.update({name});
            if (!member) {
                throw new Error('Failed to update member');
            }
            return {
                member,
                success: true
            };
        } catch (err) {
            return {
                success: false,
                error: err
            };
        }
    }
    return null;
}

async function refreshMemberData({state, api}) {
    if (state.member) {
        try {

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Verify the member session is valid — call api.member.identity() or reload; re-authenticate if expired.
  2. Inspect the PUT /members/api/member/ response in Network tab; confirm a non-empty member object.
  3. Trim and length-check the name client-side before sending (Ghost limits names server-side).
  4. Align Portal bundle version with the Ghost server version.

Example fix

// before
const member = await api.member.update({name});
if (!member) {
    throw new Error('Failed to update member');
}

// after: validate input then throw a more useful signal
const trimmed = name?.slice(0, 200);
if (!trimmed) {
    return {success: false, error: new Error('Name is empty')};
}
const member = await api.member.update({name: trimmed});
if (!member) {
    throw new Error('Server returned no member — session may have expired');
}
Defensive patterns

Strategy: validation

Validate before calling

// validate + bound the name before sending
function normalizeName(name) {
    const trimmed = (name || '').trim();
    if (!trimmed) { return null; } // nothing to change
    if (trimmed.length > 200) { throw new Error('Name is too long (200 chars max)'); }
    return trimmed;
}

Type guard

function isMemberResponse(value) {
    return value != null && typeof value === 'object' && typeof value.uuid === 'string';
}

Try / catch

try {
    const member = await api.member.update({name});
    if (!member) { throw new Error('Server returned no member — session may have expired'); }
    return {member, success: true};
} catch (err) {
    return {success: false, error: err};
}

Prevention

When it happens

Trigger: Member edits their display name in Portal account page; originalName !== name so the update fires; api.member.update resolves but the returned member is null/undefined.

Common situations: Expired member session after long idle; backend rejecting the new name silently (e.g. server-side length limit); response-shape drift between Portal and Ghost server versions; network hiccup returning an empty 200.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/763342e494e2a2a2. Report an issue: GitHub.