Skip to Content
Guía de NxarExtender con códigoSDK de Visual Components

SDK de Visual Components

Dentro del iframe de un Visual Component, sdk es el puente con la plataforma. Cada llamada viaja al shell, que la ejecuta con los permisos del usuario que mira la página (permission sets y sharing incluidos) y devuelve el resultado. No hay fetch ni acceso directo a la red.

Datos

await sdk.fetchRecords("accounts", { filters: [{ field: "industry", op: "eq", value: "finance" }], limit: 20, sort: "name:asc", }); // → Array<record> await sdk.getRecord("accounts", id); // → record | null await sdk.createRecord("tasks", { subject: "Llamar", account: record.id }); await sdk.updateRecord("accounts", id, { status: "active" }); await sdk.deleteRecord("tasks", id);
  • Operadores de filtro: eq, neq, contains, gt, gte, lt, lte, in, not_in, is_null, is_not_null.
  • Las escrituras pasan por el mismo pipeline que la UI (validación, automations, sharing, historial); un error de validación rechaza la promesa con el detalle.
  • Los nombres de entidad se resuelven en el ámbito del componente (un componente de CPQ puede decir "quote").
  • Para lógica con reglas, APIs externas o credenciales, no escribas desde el iframe: usá un Logic Component.

Backend: runLogic

const res = await sdk.runLogic("enrich_account", { accountId: record.id }, record.id); if (res.status === "success") setData(res.outputs); else setError(res.error?.message);

Invoca un Logic Component con inputs y devuelve { status, outputs } o { status: "error", error: { code, message } }. El tercer argumento (id del registro) se expone como ctx.record en el Logic.

sdk.openRecord("opportunities", id); // abre el registro en una pestaña del workspace const unsubscribe = sdk.onRecordChange((detail) => { /* { entity, id, patch, record } */ }); await sdk.refreshRecord(); // fuerza a la página a recargar el registro (header, detalles, highlights)

onRecordChange avisa cuando el registro de la página se edita; llamá unsubscribe() en el cleanup del efecto. refreshRecord es lo que usás después de un runLogic que modificó el registro.

Feedback: toasts y confirmaciones

sdk.toast.success("Guardado"); sdk.toast.error("Falló", { description: String(err) }); sdk.toast.warning("Se van a archivar 3 registros"); sdk.toast.info("Sincronizando…", { duration: 2000 }); const ok = await sdk.confirm("¿Eliminar este registro?", { description: "No se puede deshacer.", variant: "destructive", confirmLabel: "Eliminar", });

Los dos se dibujan en el shell (fuera del iframe): window.confirm y alert están bloqueados en el sandbox. Los callbacks no cruzan al shell (toast({ action: { onClick } }) no funciona).

Diálogos del shell

Un modal dibujado dentro del iframe queda encerrado en su rectángulo. openDialog le pide al shell un diálogo de verdad con tu mismo componente adentro:

// en la vista principal const result = await sdk.openDialog({ view: "edit-line", // qué vista de tu componente mostrar props: { lineId: line.id }, // serializable title: "Editar línea", size: "lg", // sm | md | lg | xl | fullscreen }); if (result) applyChanges(result); // undefined si cerró con Esc / afuera // el mismo componente, montado en el diálogo export default function MiComponente(props) { if (props.dialogView === "edit-line") { return <EditorDeLinea {...props.dialogProps} onSave={(patches) => sdk.closeDialog({ patches })} onCancel={() => sdk.closeDialog()} />; } return <VistaNormal {...props} />; }
  • Un diálogo a la vez: el segundo openDialog rechaza con dialog_already_open.
  • Los props tienen que ser serializables; el diálogo no ve el estado en memoria del que lo abrió.
  • El diálogo es una instancia nueva del componente; al cerrar se desmonta.

Botones del encabezado

Montado como record_header_action, tu componente recibe record y entitySlug, y dos métodos cobran sentido:

await sdk.refreshRecord(); // después de mutar el registro via runLogic await sdk.closeDialog(); // cierra el diálogo del botón (o termina una acción sin pantalla)

Con presentation.kind = "action" no hay pantalla: el componente se monta, actúa y llama closeDialog(). Guardá un useRef para no ejecutar dos veces.

Email

await sdk.openEmailComposer({ to: [record.email], subject: "…", relatedToType: "quote", relatedToId: record.id });

Abre el composer del CRM precargado; el email sale con la inbox del workspace y queda vinculado al registro.

Steppers

Montado como bloque Componente propio de una pantalla de un Stepper, el componente escribe las variables de la pantalla con:

sdk.setStepperValues({ monto: 1500, motivo: "renovación" });

Solo se aceptan las variables declaradas en el bloque (writes); cualquier otra se descarta.

Permisos del usuario

sdk.user.can("update", "quotes"); // true | false

Para mostrar u ocultar acciones según lo que el usuario puede hacer. Es orientativo: el servidor vuelve a verificar en cada llamada.

Páginas públicas

Cuando el componente se sirve como página pública, el SDK está recortado: solo sdk.publicAction(name, payload), que ejecuta una de las acciones declaradas (aprobar, rechazar) con contexto de sistema. Los datos llegan por el resolver del componente, no por fetchRecords.

Límites

  • Rate limit: 30 llamadas cada 10 segundos por instancia; el exceso rechaza con rate_limit_exceeded hasta que pase la ventana. Un useEffect con dependencias inestables es la causa típica.
  • Sin fetch, cookies ni localStorage (iframe sin allow-same-origin).
  • Sin import: React 18, sdk y Nxar son globales; el resto, inline.
  • Los overlays Radix (Nxar.Dialog, Popover, Tooltip) se renderizan dentro del iframe (que crece para mostrarlos); para un modal de pantalla completa, sdk.openDialog o sdk.confirm.
Last updated on