Skip to main content

CnObjectSidebar

Right sidebar for entity detail pages. Provides standardized tabs — Files, Notes, Tags, Tasks, and Audit Trail — that integrate with OpenRegister API endpoints bridging to Nextcloud-native APIs. Each tab is optional and independently overridable via slots.

Wraps: NcAppSidebar, NcAppSidebarTab

Try it​

Loading CnObjectSidebar playground…

Tabs​

Tab IDLabelContent
filesFilesFile attachments via CnFilesTab
notesNotesNotes list and add form via CnNotesTab
tagsTagsTag management via CnTagsTab
tasksTasksTask list via CnTasksTab
auditTrailAudit TrailChange history via CnAuditTrailTab

Usage​

<!-- Basic usage -->
<CnObjectSidebar
object-type="pipelinq_lead"
:object-id="lead.id"
:register="registerConfig.register"
:schema="registerConfig.schema" />

<!-- Hide specific tabs -->
<CnObjectSidebar
object-type="pipelinq_lead"
:object-id="lead.id"
:hidden-tabs="['tasks', 'tags']" />

<!-- Override a tab with custom content -->
<CnObjectSidebar object-type="pipelinq_lead" :object-id="lead.id">
<template #tab-notes="{ objectId }">
<MyCustomNotesComponent :id="objectId" />
</template>
</CnObjectSidebar>

<!-- Add an extra custom tab -->
<CnObjectSidebar object-type="pipelinq_lead" :object-id="lead.id">
<template #extra-tabs>
<NcAppSidebarTab id="relations" name="Relations" :order="6">
<template #icon><LinkVariant :size="20" /></template>
<RelationsList :object-id="lead.id" />
</NcAppSidebarTab>
</template>
</CnObjectSidebar>

Props​

PropTypeRequiredDefaultDescription
objectTypeString✓—Entity type identifier (e.g. 'pipelinq_lead') — used as the sidebar title fallback
objectIdString✓—Object UUID passed to all tab components
registerString''OpenRegister register ID
schemaString''OpenRegister schema ID
objectDataObjectnullThe loaded object, forwarded to prop-driven tab widgets (the data / metadata built-ins) as objectData. The sidebar is otherwise coordinate-based; hosts like CnDetailPage (via CnAppRoot) publish the loaded object here.
objectSchemaObjectnullThe resolved JSON Schema object, forwarded to the data built-in tab widget (which needs the schema definition, not the schema slug). Published by hosts like CnDetailPage (via CnAppRoot).
hiddenTabsArray[]Tab IDs to hide: 'files', 'notes', 'tags', 'tasks', 'auditTrail'
openBooleantrueWhether the sidebar is visible
titleString''Sidebar title (defaults to objectType)
subtitleString''Sidebar subtitle
subtitlePropString''Deprecated — use subtitle instead
apiBaseString'/apps/openregister/api'Base URL for OpenRegister API calls
filesLabelString'Files'Files tab label
notesLabelString'Notes'Notes tab label
tagsLabelString'Tags'Tags tab label
tasksLabelString'Tasks'Tasks tab label
auditTrailLabelString'Audit Trail'Audit Trail tab label
tabsArraynullOpen-enum tab definitions [\{ id, label, icon?, widgets?, component?, order? \}]. When set with at least one entry, REPLACES the hard-coded built-in tab set. See Custom tabs below.
customComponentsObjectnullCustom-component registry for tab component names and unknown widget type values. Falls back to the injected cnCustomComponents from a CnAppRoot ancestor.
requested-tabStringnullExternally-requested active tab id — lets a host deep-link into a specific leaf, e.g. a 'Linked apps' row opening the Mails tab.

A tab's component name is resolved against the v2 component registry (cnRegistry inject from CnAppRoot, ADR-036) first — any kind-tagged entry with a component field resolves, including kind: "page" tab components — then falls back to the legacy customComponents map. This lets apps that migrated their sidebar-tab components into registry.js (the procest pattern) keep rendering tabs without duplicating them in customComponents.

Events​

EventPayloadDescription
update:openbooleanEmitted when the sidebar is closed; use with .sync
mention{ objectId, register, schema, noteId, mentionedUserIds }Forwarded unchanged from the built-in Notes tab after a note containing at least one @mention was created or edited. mentionedUserIds is the unique list of mentioned Nextcloud user ids. nc-vue is a frontend library and never dispatches notifications itself — the consuming app listens to this event and creates Nextcloud notifications from its own backend (e.g. INotificationManager in the controller that persists the note). Not emitted when the saved note contains no mentions, nor when the Notes tab is overridden via the tab-notes slot.

The Notes tab's composer supports @mention autocomplete (backed by the core core/autocomplete/get OCS endpoint) and stores mentions inline in the note text as @userId / @"user id" — the same convention as Nextcloud Comments/Talk. Stored mentions render as highlighted chips with the user's display name, degrading to the raw id for unknown/deleted users.

Slots​

SlotScopeDescription
tab-files{ objectId, objectType }Override the Files tab content
tab-notes{ objectId, objectType }Override the Notes tab content
tab-tags{ objectId, objectType }Override the Tags tab content
tab-tasks{ objectId, objectType }Override the Tasks tab content
tab-audit-trail{ objectId, objectType }Override the Audit Trail tab content
extra-tabs—Additional NcAppSidebarTab elements appended after the built-in tabs

Custom tabs​

The tabs prop opens up the closed-enum tab set so apps can drive CnObjectSidebar directly from manifest.json (pages[].config.sidebarProps.tabs). When tabs is set with at least one entry, the built-in tabs (Files / Notes / Tags / Tasks / Audit Trail) do NOT render — the consumer-supplied array drives the UI.

<CnObjectSidebar
object-type="decision"
:object-id="decisionId"
:tabs="[
{ id: 'overview', label: 'Overview', icon: 'eye',
widgets: [
{ type: 'data', props: { schema, objectData } },
{ type: 'metadata', props: { objectData } },
] },
{ id: 'related', label: 'Related', icon: 'link',
component: 'MyRelatedTab' },
]"
:custom-components="{ MyRelatedTab }" />

Tab definition shape​

FieldTypeNotes
idStringRequired. Unique within the array; used for active-tab tracking.
labelStringRequired. Display label (i18n key already resolved by the consumer).
iconStringOptional MDI icon name; rendered via CnIcon.
widgetsArrayOptional. List of \{ type, props? \} widget specs (see below).
componentStringOptional. Registry name resolved against customComponents. Mutually exclusive with widgets — when both are set, component wins and a console.warn is logged.
orderNumberOptional. Defaults to array index + 1.

Built-in widget types​

Widget typeResolved componentRequired props
dataCnObjectDataWidgetschema, objectData (forward via per-widget props)
metadataCnObjectMetadataWidgetobjectData
audit / audit-trailCnAuditTrailTab— (register / schema / objectId flow from the shared context)
object-tableCnWidgetObjectTablesource or endpointSource, columns (forward via per-widget props)

Any other type value resolves against the customComponents registry — the explicit customComponents prop wins over the injected cnCustomComponents (mirroring CnPageRenderer's pattern).

The object-table type (#89) renders a declarative list scoped to the sidebar's parent object — its source.filter resolves @objectId / @object.<field> tokens against the object context the sidebar provides (see Shared object context). This is how a detail page's ZGW-style relation tab (e.g. a zaak's besluiten, filtered by { zaak: "@objectId" }) renders with no bespoke component:

tabs: [{
id: 'besluiten',
label: 'Besluiten',
widgets: [{
type: 'object-table',
props: {
source: { register: 'ztc', schema: 'besluit', filter: { zaak: '@objectId' } },
columns: [{ key: 'identificatie', label: 'Besluit' }, { key: 'datum', label: 'Datum' }],
},
}],
}]

Shared object context​

Every widget and component mounted inside a custom tab receives the parent CnObjectSidebar's objectId / objectType / register / schema / apiBase as default props (matching the context the built-in tabs receive). Per-widget props win on conflict, so a tab can override objectData, apiBase, etc. without losing the rest of the context.

For widgets that resolve @objectId / @object.<field> tokens through injection rather than props (the object-table built-in), CnObjectSidebar also provides a reactive cnObjectContext ({ objectId, object, register, schema }) seeded from its own props — mirroring CnDetailPage. When the sidebar is nested inside a CnDetailPage that already provides a richer context (with the loaded object), the sidebar defers to the ancestor so @object.<field> keeps resolving; standalone, it seeds @objectId + register/schema from its props.

Backwards compatibility​

Apps satisfied with the default tab set make NO changes — leave tabs unset and the hard-coded built-in tabs render exactly as today, including the #tab-files / #tab-notes / #tab-tags / #tab-tasks / #tab-audit-trail / #extra-tabs slot overrides. The tabs prop is purely additive.

Live updates (collaborative editing)​

CnObjectSidebar auto-subscribes to live updates for the active object when both objectStore and (objectType + objectId) are provided. This wires useObjectSubscription into the sidebar lifecycle so the cached object stays fresh as remote users edit, and downstream tabs (CnObjectDataWidget, CnAuditTrailTab, etc.) re-render reactively without polling.

PropDefaultBehaviour
subscribetrueWhen false, skips the auto-subscribe (useful for read-only / archive surfaces).
objectStorenullPinia store instance. When omitted, the auto-subscribe is skipped.

The locked-banner UX lives on CnDetailPage for v1 — sidebars host so many editor surfaces (each tab) that the banner would compete with tab content. Consumers needing lock UX inside a sidebar tab should consume useObjectLock directly inside the tab component.

Integration registry props (AD-19)​

PropTypeDefaultNotes
useRegistry (use-registry)BooleantrueUse the pluggable integration registry (ADR-019) to drive the tabs — one tab per provider registered on window.OCA.OpenRegister.integrations. The canonical five built-ins (files / notes / tags / tasks / audit-trail) ship as providers in builtinIntegrations and are registered by OpenRegister's bootstrap, so the default surface is unchanged for apps that register them. Set false to opt back into the legacy hardcoded-tabs path (renders the five built-in tabs directly and supports #tab-<id> slot overrides) — for consumers that don't call registerBuiltinIntegrations(). hiddenTabs / excludeIntegrations apply in both modes. Mutually exclusive with the open-enum tabs prop — tabs wins when both are set.
excludeIntegrations (exclude-integrations)String[][]Integration ids to exclude when rendering registry-driven tabs. Mirrors hiddenTabs for the legacy mode.

Reference (auto-generated)​

The tables below are generated from the SFC source via vue-docgen-cli. They reflect what's actually in CnObjectSidebar.vue and update automatically whenever the component changes.

Props​

NameTypeRequiredDefaultDescription
objectTypestring✓—The entity type (e.g., "pipelinq_lead", "procest_case")
objectIdstring✓—The object UUID
registerstring''OpenRegister register ID
schemastring''OpenRegister schema ID
objectDataunionnullThe loaded object, forwarded to prop-driven sidebar-tab widgets (the data / metadata built-ins) as objectData. The sidebar is otherwise coordinate-based — most tabs self-fetch from objectId/register/schema — so this is null unless a host (e.g. CnDetailPage via CnAppRoot) publishes the loaded object.
objectSchemaunionnullThe resolved JSON Schema OBJECT (with a properties field), forwarded to the data built-in tab widget (CnObjectDataWidget), which needs the schema definition rather than the schema slug string. Null unless a host (e.g. CnDetailPage via CnAppRoot) publishes it.
hiddenTabsarray[]Array of tab IDs to hide: 'files', 'notes', 'tags', 'tasks', 'auditTrail'
useRegistrybooleantrueUse the pluggable integration registry to drive the sidebar tabs. Defaults to true (ADR-019): tabs are rendered one per provider registered on window.OCA.OpenRegister.integrations (and via useIntegrationRegistry()). The canonical five built-ins — files / notes / tags / tasks / audit-trail — are shipped as providers in builtinIntegrations and registered by OpenRegister's bootstrap (registerBuiltinIntegrations()), so the default surface is unchanged for apps that register them. Set false to opt back into the legacy hardcoded-tabs path, which renders the five built-in tabs directly from this component and supports the #tab-&lt;id&gt; slot overrides. Use this for consumers that do not call registerBuiltinIntegrations() and want the built-in tabs without standing up the registry. hiddenTabs / excludeIntegrations and the #extra-tabs slot apply in both modes. Mutually exclusive with the open-enum tabs prop — when both are set, tabs wins and a console.warn is logged.
excludeIntegrationsstring[][]Integration ids to exclude when rendering registry-driven tabs. Mirrors hiddenTabs for the legacy mode.
openbooleantrueWhether the sidebar is open
titlestring''Sidebar title (defaults to objectType)
subtitlestring''Sidebar subtitle
subtitlePropstring''
apiBasestring'/apps/openregister/api'Base API URL for OpenRegister
subscribebooleantrueWhether to auto-subscribe to live updates for the current object. Defaults to true. The sidebar calls objectStore.subscribe(objectType, objectId) on mount and unsubscribes on unmount via tryOnScopeDispose.
objectStoreunionnullOptional explicit Pinia store instance. When omitted, the sidebar skips auto-subscribe (Pinia not yet active in the consumer context).
filesLabelstring() =&gt; t('nextcloud-vue', 'Files')Label for the Files tab
notesLabelstring() =&gt; t('nextcloud-vue', 'Notes')Label for the Notes tab
tagsLabelstring() =&gt; t('nextcloud-vue', 'Tags')Label for the Tags tab
tasksLabelstring() =&gt; t('nextcloud-vue', 'Tasks')Label for the Tasks tab
auditTrailLabelstring() =&gt; t('nextcloud-vue', 'Audit trail')Label for the Audit Trail tab
tabsArray<{ id: string, label: string, icon?: string, widgets?: Array<{ type: string, props?: object }>, component?: string, order?: number }>&#124;nullnullOpen-enum tab definitions. When provided with at least one entry, REPLACES the hard-coded built-in tabs (Files, Notes, Tags, Tasks, Audit Trail). When unset (the default), the built-in tabs render as today. Each entry shape: - id (string, required) — unique tab id, used for active-tab tracking. - label (string, required) — tab display label (caller-resolved i18n). - icon (string, optional) — MDI icon name resolved via CnIcon. - widgets (array, optional) — list of widget specs { type, props? } to render inside the tab. Built-in types: data → CnObjectDataWidget, metadata → CnObjectMetadataWidget, audit / audit-trail → CnAuditTrailTab, object-table → CnWidgetObjectTable (a declarative list scoped to the parent object via @objectId / @object.&lt;field&gt; filter tokens). Any other type resolves against the customComponents registry. - component (string, optional) — name resolved against the customComponents registry. Mutually exclusive with widgets (when both are set, component wins and a console.warn is logged). - order (number, optional) — explicit order; defaults to array index + 1.
customComponentsunionnullCustom-component registry. Keys are names referenced by tabs[].component and unknown tabs[].widgets[].type values. Falls back to the injected cnCustomComponents from a CnAppRoot ancestor when omitted.
requestedTabstringnullExternally-requested active tab id. When set to a non-null id, the sidebar switches to that tab — lets a host deep-link into a specific leaf (e.g. a "Linked apps" row on the detail page that opens the Mails tab). Leave null for normal internal tracking.

Events​

NamePayloadDescription
update:open—
mention—Forwarded unchanged from the built-in CnNotesTab after a note with at least one @mention was saved. Payload: { objectId, register, schema, noteId, mentionedUserIds }.

Slots​

NameBindingsDescription
extra-tabs—
tab-filesobject-id, object-type
tab-notesobject-id, object-type
tab-tagsobject-id, object-type
tab-tasksobject-id, object-type
tab-audit-trailobject-id, object-type