nocobase/nocobase · error

Failed to copy popup from template

Error message

Failed to copy popup from template

What it means

Immediately after duplicating the template's popup model in doConvert, the plugin unwraps the new uid from the duplicateModel response (duplicated?.uid, data.uid, data.data.uid). If no uid can be extracted, the conversion cannot proceed (nothing to point the new open-view at), so this localized error is thrown.

Source

Thrown at packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/menuExtensions.tsx:893

    const row = unwrap(res);
    return row && typeof row === 'object' ? (row as Record<string, any>) : null;
  };

  const inferFromTemplateRow = (tplRow: Record<string, any>): PopupTemplateContextFlags => {
    const scene = resolveActionScene((use: string) => model.flowEngine?.getModelClass?.(use), tplRow?.useModel);
    return inferPopupTemplateContextFlags(scene, tplRow?.filterByTk, tplRow?.sourceId);
  };

  const doConvert = async () => {
    const tplRow = await fetchTemplateRow();
    const targetUid = normalizeStr(tplRow?.targetUid) || normalizeStr(openViewParams?.uid);
    if (!targetUid) {
      throw new Error(tNs('Popup template not found'));
    }
    const duplicated = await model.flowEngine.duplicateModel(targetUid);
    const newUid = duplicated?.uid || duplicated?.data?.uid || duplicated?.data?.data?.uid;
    if (!newUid) {
      throw new Error(tNs('Failed to copy popup from template'));
    }
    const inferred: PopupTemplateContextFlags = tplRow
      ? inferFromTemplateRow(tplRow)
      : extractPopupTemplateContextFlagsFromParams(openViewParams);

    const nextOpenView: any = { ...(openViewParams || {}), uid: newUid };
    delete (nextOpenView as any).popupTemplateUid;
    nextOpenView.popupTemplateContext = true;
    delete (nextOpenView as any).popupTemplateHasFilterByTk;
    delete (nextOpenView as any).popupTemplateHasSourceId;
    // 同步清理 params 侧的 filterByTk/sourceId,避免 record action 复用 collection 弹窗时泄漏 filterByTk
    if (!inferred.hasFilterByTk && 'filterByTk' in nextOpenView) {
      delete nextOpenView.filterByTk;
    }
    if (!inferred.hasSourceId && 'sourceId' in nextOpenView) {
      delete nextOpenView.sourceId;
    }
    model.setStepParams('popupSettings', { [openViewStepKey]: nextOpenView });

View on GitHub (pinned to fa42722fef)

Solutions

  1. Inspect the duplicateModel request/response in devtools; fix the server-side duplication failure (permissions, resource limits).
  2. Align @nocobase/client-v2 and flow-engine versions with the server so response unwrapping matches.
  3. Retry the conversion action after any transient network/API failure.
  4. As a workaround, manually duplicate the popup block and re-link the reference to the new copy.

Example fix

// before: response envelope mismatch
const newUid = duplicated?.uid || duplicated?.data?.uid; // undefined -> throws
// after: ensure client/server versions match so envelope is { data: { uid } }
// yarn why @nocobase/client-v2  # verify single consistent version
Defensive patterns

Strategy: try-catch

Validate before calling

const duplicated = await model.flowEngine.duplicateModel(targetUid);
const newUid = duplicated?.uid || duplicated?.data?.uid || duplicated?.data?.data?.uid;
if (!newUid) {
  // duplication succeeded shape-wise but returned no uid — treat as failure, do not save template
  return message.error('Copy failed; check server duplicate endpoint response.');
}

Type guard

function extractUid(v: unknown): string | null {
  for (const o = v; o && typeof o === 'object'; ) {
    if (typeof (o as any).uid === 'string' && (o as any).uid) return (o as any).uid;
    o = (o as any).data;
  }
  return null;
}

Try / catch

try {
  const newUid = await convertFromTemplate(targetUid);
} catch (e) {
  if (e.message.includes('Failed to copy popup from template')) {
    // inspect duplicateModel response in devtools; check server logs
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the 'convert popup template reference to independent copy' action when flowEngine.duplicateModel(targetUid) resolves but its response carries no uid in any nested data layer — server-side duplication failed partially, response shape mismatch, or the request was intercepted/short-circuited.

Common situations: Client (client-v2 / flow-engine) and server version mismatch changing the duplicateModel response envelope; API middleware stripping or reshaping the response; server error returned as an empty object after a failed deep copy; rate-limit or auth expiry producing an empty 2xx-style response.

Related errors


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/d8397120e51e6b8c. Report an issue: GitHub.