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 ID | Label | Content |
|---|
files | Files | File attachments via CnFilesTab |
notes | Notes | Notes list and add form via CnNotesTab |
tags | Tags | Tag management via CnTagsTab |
tasks | Tasks | Task list via CnTasksTab |
auditTrail | Audit Trail | Change 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
| Prop | Type | Required | Default | Description |
|---|
objectType | String | ✓ | — | Entity type identifier (e.g. 'pipelinq_lead') — used as the sidebar title fallback |
objectId | String | ✓ | — | Object UUID passed to all tab components |
register | String | | '' | OpenRegister register ID |
schema | String | | '' | OpenRegister schema ID |
hiddenTabs | Array | | [] | Tab IDs to hide: 'files', 'notes', 'tags', 'tasks', 'auditTrail' |
open | Boolean | | true | Whether the sidebar is visible |
title | String | | '' | Sidebar title (defaults to objectType) |
subtitle | String | | '' | Sidebar subtitle |
subtitleProp | String | | '' | Deprecated — use subtitle instead |
apiBase | String | | '/apps/openregister/api' | Base URL for OpenRegister API calls |
filesLabel | String | | 'Files' | Files tab label |
notesLabel | String | | 'Notes' | Notes tab label |
tagsLabel | String | | 'Tags' | Tags tab label |
tasksLabel | String | | 'Tasks' | Tasks tab label |
auditTrailLabel | String | | 'Audit Trail' | Audit Trail tab label |
tabs | Array | | null | Open-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. |
customComponents | Object | | null | Custom-component registry for tab component names and unknown widget type values. Falls back to the injected cnCustomComponents from a CnAppRoot ancestor. |
Events
| Event | Payload | Description |
|---|
update:open | boolean | Emitted when the sidebar is closed; use with .sync |
Slots
| Slot | Scope | Description |
|---|
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
| Field | Type | Notes |
|---|
id | String | Required. Unique within the array; used for active-tab tracking. |
label | String | Required. Display label (i18n key already resolved by the consumer). |
icon | String | Optional MDI icon name; rendered via CnIcon. |
widgets | Array | Optional. List of \{ type, props? \} widget specs (see below). |
component | String | Optional. Registry name resolved against customComponents. Mutually exclusive with widgets — when both are set, component wins and a console.warn is logged. |
order | Number | Optional. Defaults to array index + 1. |
Any other type value resolves against the customComponents registry — the explicit customComponents prop wins over the injected cnCustomComponents (mirroring CnPageRenderer's pattern).
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.
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.
| Prop | Default | Behaviour |
|---|
subscribe | true | When false, skips the auto-subscribe (useful for read-only / archive surfaces). |
objectStore | null | Pinia 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)
| Prop | Type | Default | Notes |
|---|
useRegistry (use-registry) | Boolean | true | Use 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
| Name | Type | Required | Default | Description |
|---|
objectType | string | ✓ | — | The entity type (e.g., "pipelinq_lead", "procest_case") |
objectId | string | ✓ | — | The object UUID |
register | string | | '' | OpenRegister register ID |
schema | string | | '' | OpenRegister schema ID |
hiddenTabs | array | | [] | Array of tab IDs to hide: 'files', 'notes', 'tags', 'tasks', 'auditTrail' |
useRegistry | boolean | | true | Use 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-<id> 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. |
excludeIntegrations | string[] | | [] | Integration ids to exclude when rendering registry-driven tabs. Mirrors hiddenTabs for the legacy mode. |
open | boolean | | true | Whether the sidebar is open |
title | string | | '' | Sidebar title (defaults to objectType) |
subtitle | string | | '' | Sidebar subtitle |
subtitleProp | string | | '' | |
apiBase | string | | '/apps/openregister/api' | Base API URL for OpenRegister |
subscribe | boolean | | true | Whether 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. |
objectStore | union | | null | Optional explicit Pinia store instance. When omitted, the sidebar skips auto-subscribe (Pinia not yet active in the consumer context). |
filesLabel | string | | () => t('nextcloud-vue', 'Files') | Label for the Files tab |
notesLabel | string | | () => t('nextcloud-vue', 'Notes') | Label for the Notes tab |
tagsLabel | string | | () => t('nextcloud-vue', 'Tags') | Label for the Tags tab |
tasksLabel | string | | () => t('nextcloud-vue', 'Tasks') | Label for the Tasks tab |
auditTrailLabel | string | | () => t('nextcloud-vue', 'Audit trail') | Label for the Audit Trail tab |
tabs | Array<{ id: string, label: string, icon?: string, widgets?: Array<{ type: string, props?: object }>, component?: string, order?: number }>|null | | null | Open-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. 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. |
customComponents | union | | null | Custom-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. |
Events
| Name | Payload | Description |
|---|
update:open | — | |
Slots
| Name | Bindings | Description |
|---|
extra-tabs | — | |
tab-files | object-id, object-type | |
tab-notes | object-id, object-type | |
tab-tags | object-id, object-type | |
tab-tasks | object-id, object-type | |
tab-audit-trail | object-id, object-type | |